#include <stdarg.h>

#include "doomtype.h"
#include "doomstat.h"
#include "d_player.h"
#include "s_sound.h"
#include "gi.h"
#include "d_net.h"
#include "g_game.h"
#include "p_local.h"
#include "sv_main.h"
#include "i_system.h"
#include "c_console.h"
#include "c_dispatch.h"
#include "m_argv.h"
#include "m_random.h"
#include "s_sndseq.h"
#include "v_video.h"

int     lastreliablenum;
short   parse_cl;

client_t clients[MAXPLAYERS];

int       maxclients;
client_t  tempclient;

#define CLIENT_TIMEOUT 65 // 65 seconds

// Different welcome strings
char welcomestring[] = "Version 0.7 Server\n";
char welcomestring_teamplay[] = "Version 0.7 Teamplay Server\n";

int  gametime;

void SV_UpdateConsolePlayer(client_t* cl);
void SV_Fraglogfile (void);
int SV_ChooseTeamForPlayer(player_t *player);
void P_SpawnPlayer (mapthing2_t* mthing);

CVAR (rcon_password, "", CVAR_SERVERINFO)

BEGIN_COMMAND (fraglog)
{
	SV_Fraglogfile();
}
END_COMMAND (fraglog)


//
// SV_InitNetwork
//
void SV_InitNetwork (void)
{
    char      *v;

	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 = SERVERPORT;
	
	// set up a socket and net_message buffer
	InitNetCommon();

	// determine my name & address
	// NET_GetLocalAddress ();

	Printf(PRINT_HIGH, "UDP Initialized\n");

	v = Args.CheckValue ("-maxclients");
    if (v)
    {
		maxclients = atoi(v);
		if (maxclients < 0)
			maxclients = 0;
		if (maxclients > MAXPLAYERS)
			maxclients = MAXPLAYERS;
	}
	else
		maxclients = MAXPLAYERS;

	// initialize clients
	for (int i=0; i<maxclients; i++)
    {
		clients[i].netbuf.data = new byte[MAX_UDP_PACKET];
		clients[i].netbuf.maxsize = MAX_UDP_PACKET;
		SZ_Clear(&clients[i].netbuf);
	   
		clients[i].reliablebuf.data = new byte[MAX_UDP_PACKET];
		clients[i].reliablebuf.maxsize = MAX_UDP_PACKET;
		SZ_Clear(&clients[i].reliablebuf);

		clients[i].relpackets.data = new byte[1024*50];
		clients[i].relpackets.maxsize = 1024*50; // 50 KB

		clients[i].cls = cs_free;
	}

	tempclient.reliablebuf.data = new byte[MAX_UDP_PACKET];
    tempclient.reliablebuf.maxsize = MAX_UDP_PACKET;

	gametime = I_GetTime ();
	
	SV_InitMaster();	
}


int SV_GetFreeClient(void)
{
	int i;

	for (i=0; i < maxclients; i++)
	{
		if (clients[i].cls == cs_free)
			return i;
	}
	
	return -1;
}

short SV_FindClientByAddr(void)
{
    int i;

	for (i=0; i<MAXPLAYERS; i++)
	{
	   if (clients[i].cls == cs_free)
	       continue;
		
	   if (NET_CompareAdr(clients[i].address, net_from) )
	       return i;
	}
	
	return -1;
}

//
// SV_CheckTimeouts
// If a packet has not been received from a client in CLIENT_TIMEOUT
// seconds, drop the conneciton.
//
void SV_CheckTimeouts (void)
{
	for (int i=0; i<MAXPLAYERS; i++)
	{
		client_t *cl = &clients[i];

		if (cl->cls < cs_spawned)
			continue;

		if (gametic - cl->last_received == CLIENT_TIMEOUT*35)
		{
			parse_cl = i;
		    SV_DropClient();
		}
	}
}

//
// SV_GetPackets
//
void SV_GetPackets (void)
{
    while (NET_GetPacket() )
    {
        // packet loss emulation :)
        //if (P_Random(pr_misc) > 150)
		//	continue;

        parse_cl = SV_FindClientByAddr();

        if (parse_cl == -1) // no client with net_from address
		{
    	    // apparently, someone is trying to connect
    	    if (gamestate == GS_LEVEL)
				SV_GetChallenge();
            continue;
        }

		clients[parse_cl].last_received = gametic;

		if (clients[parse_cl].cls == cs_challenge)
		{
			SV_ConnectClient();
			continue;
		}

		if (clients[parse_cl].cls == cs_connected)
		{
			SV_SpawnClient();
			continue;
		}

		SV_ParseCommands();
    }
}

//
// SV_Sound
//
void SV_Sound (AActor *mo, byte channel, char *name, byte attenuation)
{
	int        sfx_id;
	client_t  *cl;
	int        i;

	if (*name == '*')
	{
		// Sexed sound
		char nametemp[128];
		const char templat[] = "player/%s/%s";
		const char *genders[] = { "male", "female", "cyborg" };
		player_t *player;

		sfx_id = -1;
		if ( (player = mo->player) ) 
		{
			sprintf (nametemp, templat, skins[player->userinfo.skin].name, name + 1);
			sfx_id = S_FindSound (nametemp);
			if (sfx_id == -1) 
			{
				sprintf (nametemp, templat, genders[player->userinfo.gender], name + 1);
				sfx_id = S_FindSound (nametemp);
			}
		}
		if (sfx_id == -1) 
		{
			sprintf (nametemp, templat, "male", name + 1);
			sfx_id = S_FindSound (nametemp);
		}
	} 
	else
		sfx_id = S_FindSound (name);


	if (sfx_id > 255 || sfx_id < 0)
	{
		Printf (PRINT_HIGH, "SV_Sound: range error. Sfx_id = %d\n", sfx_id);
		return;
	}

	for (i=0; i<MAXPLAYERS; i++)
	{
		cl = &clients[i];

		if (cl->cls < cs_spawned)
			continue;

		MSG_WriteByte (&cl->netbuf, svc_startsound);
		MSG_WriteShort (&cl->netbuf, mo->netid);
		MSG_WriteByte (&cl->netbuf, channel);
		MSG_WriteByte (&cl->netbuf, sfx_id);
		MSG_WriteByte (&cl->netbuf, attenuation);
	}

}


void SV_Sound (client_t *cl, AActor *mo, byte channel, char *name, byte attenuation)
{
	int sfx_id;

	if (*name == '*')
	{
		// Sexed sound
		char nametemp[128];
		const char templat[] = "player/%s/%s";
		const char *genders[] = { "male", "female", "cyborg" };
		player_t *player;

		sfx_id = -1;
		if ( (player = mo->player) ) 
		{
			sprintf (nametemp, templat, skins[player->userinfo.skin].name, name + 1);
			sfx_id = S_FindSound (nametemp);
			if (sfx_id == -1) 
			{
				sprintf (nametemp, templat, genders[player->userinfo.gender], name + 1);
				sfx_id = S_FindSound (nametemp);
			}
		}
		if (sfx_id == -1) 
		{
			sprintf (nametemp, templat, "male", name + 1);
			sfx_id = S_FindSound (nametemp);
		}
	} 
	else
		sfx_id = S_FindSound (name);

	if (sfx_id > 255 || sfx_id < 0)
	{
		Printf (PRINT_HIGH, "SV_Sound: range error. Sfx_id = %d\n", sfx_id);
		return;
	}

	MSG_WriteByte (&cl->netbuf, svc_startsound);
	if (mo == NULL)
		MSG_WriteShort (&cl->netbuf, 0);
	else
		MSG_WriteShort (&cl->netbuf, mo->netid);
	MSG_WriteByte (&cl->netbuf, channel);
	MSG_WriteByte (&cl->netbuf, sfx_id);
	MSG_WriteByte (&cl->netbuf, attenuation);
}

//
// SV_SoundAvoidCl
// Sends a sound to clients, but doesn't send it to client 'player'.
//
void SV_SoundAvoidCl (player_t *player, AActor *mo, byte channel, char *name, byte attenuation)
{
	int        i;
	int        sfx_id;
	client_t  *cl;

	if (*name == '*')
	{
		// Sexed sound
		char nametemp[128];
		const char templat[] = "player/%s/%s";
		const char *genders[] = { "male", "female", "cyborg" };
		player_t *player;

		sfx_id = -1;
		if ( (player = mo->player) ) 
		{
			sprintf (nametemp, templat, skins[player->userinfo.skin].name, name + 1);
			sfx_id = S_FindSound (nametemp);
			if (sfx_id == -1) 
			{
				sprintf (nametemp, templat, genders[player->userinfo.gender], name + 1);
				sfx_id = S_FindSound (nametemp);
			}
		}
		if (sfx_id == -1) 
		{
			sprintf (nametemp, templat, "male", name + 1);
			sfx_id = S_FindSound (nametemp);
		}
	} 
	else
		sfx_id = S_FindSound (name);

	if (sfx_id > 255 || sfx_id < 0)
	{
		Printf (PRINT_HIGH, "SV_Sound: range error. Sfx_id = %d\n", sfx_id);
		return;
	}

    for (i=0; i<MAXPLAYERS; i++)
    {
		cl = &clients[i];

		if (cl->cls < cs_spawned || player == &players[i])
			continue;

		MSG_WriteByte (&cl->netbuf, svc_startsound);
		MSG_WriteShort (&cl->netbuf, mo->netid);
		MSG_WriteByte (&cl->netbuf, channel);
		MSG_WriteByte (&cl->netbuf, sfx_id);
		MSG_WriteByte (&cl->netbuf, attenuation);
    }
}


void SV_Sound (fixed_t x, fixed_t y, byte channel, char *name, byte attenuation)
{
	int        sfx_id;
	client_t  *cl;
	int        i;

	sfx_id = S_FindSound (name);

	if (sfx_id > 255 || sfx_id < 0)
	{
		Printf (PRINT_HIGH, "SV_Sound: range error. Sfx_id = %d\n", sfx_id);
		return;
	}

	for (i=0; i<MAXPLAYERS; i++)
	{
		cl = &clients[i];

		if (cl->cls < cs_spawned)
			continue;

		MSG_WriteByte (&cl->netbuf, svc_soundorigin);
		MSG_WriteLong (&cl->netbuf, x);
		MSG_WriteLong (&cl->netbuf, y);
		MSG_WriteByte (&cl->netbuf, channel);
		MSG_WriteByte (&cl->netbuf, sfx_id);
		MSG_WriteByte (&cl->netbuf, attenuation);
	}

}


//
// SV_StartSequence
//
void SV_StartSequence (sector_t *sector, int sequence, seqtype_t type)
{
	int sec, i;

	for (sec=0; sec<numsectors; sec++)
		if (sector == &sectors[sec])
			break;

	for (i=0; i<MAXPLAYERS; i++)
	{
		client_t *cl = &clients[i];

		if (cl->cls < cs_spawned)
			continue;

		MSG_WriteByte (&cl->reliablebuf, svc_startsectorsequence);
		MSG_WriteShort (&cl->reliablebuf, sec);
		MSG_WriteShort (&cl->reliablebuf, sequence);
		MSG_WriteByte (&cl->reliablebuf, (byte)type);
	}
}

//
// SV_StopSequence
//
void SV_StopSequence(sector_t *sector)
{
	int sec, i;

	for (sec=0; sec<numsectors; sec++)
		if (sector == &sectors[sec])
			break;

	for (i=0; i<MAXPLAYERS; i++)
	{
		client_t *cl = &clients[i];

		if (cl->cls < cs_spawned)
			continue;

		MSG_WriteByte (&cl->reliablebuf, svc_stopsectorsequence);
		MSG_WriteShort (&cl->reliablebuf, sec);
	}
}


//
// SV_UpdateFrags
//
void SV_UpdateFrags (player_t *player)
{
    int  playernum;

    for (playernum=0; playernum<MAXPLAYERS; playernum++)
    {
        if (player == &players[playernum])
            break;
    }

    for (int i=0; i<MAXPLAYERS; i++)
    {
		client_t *cl = &clients[i];

		if (cl->cls < cs_spawned)
			continue;

        MSG_WriteByte (&cl->reliablebuf, svc_updatefrags);
		MSG_WriteByte (&cl->reliablebuf, playernum);
        MSG_WriteShort (&cl->reliablebuf, player->fragcount);
    }
}

//
// SV_SendUserInfo
//
void SV_SendUserInfo (int player, client_t* cl)
{
	player_t *p = &players[player];

	MSG_WriteByte (&cl->reliablebuf, svc_userinfo);
	MSG_WriteByte (&cl->reliablebuf, player);
    MSG_WriteString (&cl->reliablebuf, p->userinfo.netname);
    MSG_WriteString (&cl->reliablebuf, p->userinfo.team);
    MSG_WriteLong (&cl->reliablebuf, p->userinfo.gender);
    MSG_WriteLong (&cl->reliablebuf, p->userinfo.color);
}


void SV_SetupUserInfo(void)
{
    player_t *p;
   
    p = &players[parse_cl];

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

	int team = SV_ChooseTeamForPlayer(p);

	if (team == 0 && teamplay.value)
		p->userinfo.color = V_GetColorFromString (NULL, "ff 00 00");
	
	if (team == 1 && teamplay.value)
		p->userinfo.color = V_GetColorFromString (NULL, "00 00 ff");

	// send to other players
	for (int i=0; i<MAXPLAYERS; i++)
	{
		client_t *cl = &clients[i];

		if (cl->cls < cs_spawned || i==parse_cl)
			continue;

		SV_SendUserInfo(parse_cl, cl);
	}
}


//
// SV_ClientFullUpdate
//
void SV_ClientFullUpdate (client_t *cl)
{
	AActor *mo;
	int     i;

	// send players info to the client
	for (i=0; i<MAXPLAYERS; i++)
	{
		if (clients[i].cls < cs_spawned || cl == &clients[i])
			continue;

		MSG_WriteByte(&cl->reliablebuf, svc_spawnplayer);
		MSG_WriteByte(&cl->reliablebuf, i);
		MSG_WriteByte (&cl->reliablebuf, PST_REBORN);
		MSG_WriteShort(&cl->reliablebuf, players[i].mo->netid);
		MSG_WriteLong(&cl->reliablebuf, players[i].mo->angle);
		MSG_WriteLong(&cl->reliablebuf, players[i].mo->x);
		MSG_WriteLong(&cl->reliablebuf, players[i].mo->y);
		MSG_WriteLong(&cl->reliablebuf, players[i].mo->z);

		SV_SendUserInfo(i, cl);
	}

	// update frags
	for (i=0; i<MAXPLAYERS; i++)
	{
		if (!playeringame[i])
			continue;

		MSG_WriteByte(&cl->reliablebuf, svc_updatefrags);
		MSG_WriteByte(&cl->reliablebuf, i);
		MSG_WriteShort(&cl->reliablebuf, players[i].fragcount);
	}

	// update things
	TThinkerIterator<AActor> iterator;
    while ( (mo = iterator.Next() ) )
    {
		if (!mo->netid)
			continue;

        if (mo->type != MT_PLAYER 
			|| (mo->type == MT_PLAYER && !mo->player) )
		{
			MSG_WriteByte(&cl->reliablebuf, svc_spawnmobj);
			MSG_WriteLong(&cl->reliablebuf, mo->x);
			MSG_WriteLong(&cl->reliablebuf, mo->y);
			MSG_WriteLong(&cl->reliablebuf, mo->z);
			MSG_WriteShort(&cl->reliablebuf, mo->type);
			MSG_WriteShort(&cl->reliablebuf, mo->netid);

			// a monster
			if (mo->type == MT_SKULL || (mo->flags & MF_COUNTKILL) )
			{
				MSG_WriteByte(&cl->reliablebuf, svc_mobjangle);
				MSG_WriteShort(&cl->reliablebuf, mo->netid);
				MSG_WriteLong(&cl->reliablebuf, mo->angle);
			}
		}

		// corpses
		if ( mo->health <= 0 && (mo->type == MT_PLAYER 
			|| (mo->flags & MF_COUNTKILL) ) )
		{
			MSG_WriteByte(&cl->reliablebuf, svc_corpse);
			MSG_WriteShort(&cl->reliablebuf, mo->netid);
			MSG_WriteByte (&cl->reliablebuf, mo->frame);
		}

		if (cl->reliablebuf.cursize >= 4096)
			SV_SendPacket(cl);
    }

	// update sectors
	for (int s=0; s<numsectors; s++)
	{ 
		sector_t* sec = &sectors[s];

	    if (sec->moveable)
		{
			MSG_WriteByte (&cl->reliablebuf, svc_sector);
			MSG_WriteShort (&cl->reliablebuf, s);
			MSG_WriteShort (&cl->reliablebuf, sec->floorheight>>FRACBITS);
			MSG_WriteShort (&cl->reliablebuf, sec->ceilingheight>>FRACBITS);
		}

		if (cl->reliablebuf.cursize >= 4096)
			SV_SendPacket(cl);

	}

	SV_SendPacket(cl);
}

//
// SV_LoadLevel
//
void SV_LoadLevel()
{
	int	i;

	for (i=0; i<MAXPLAYERS; i++)
	{
		client_t *cl = &clients[i];

		if (cl->cls < cs_spawned)
			continue;

		// send dmflags
		MSG_WriteByte(&cl->reliablebuf, svc_dmflags);
		MSG_WriteLong(&cl->reliablebuf, dmflags);

		// send skill
		MSG_WriteByte(&cl->reliablebuf, svc_gameskill);
		MSG_WriteByte(&cl->reliablebuf, (byte)gameskill.value);

		int team = SV_ChooseTeamForPlayer(&players[parse_cl]);
   
		// player must choose red or blue team
		if (teamplay.value && team == -1)
		{
			MSG_WriteByte (&cl->reliablebuf, svc_print);
			MSG_WriteByte (&cl->reliablebuf, PRINT_HIGH);
			MSG_WriteString (&cl->reliablebuf, 
				"Choose red or blue team.\n"
				"Use \"team <teamname>\" command to change your team.\n");

			MSG_WriteByte(&cl->reliablebuf, svc_disconnect);
			SV_SendPacket(cl);
	   
			playeringame[parse_cl] = false;
			cl->cls = cs_free;

			return;
		}

		// Send special settings like teamplay and deathmatch.
		MSG_WriteByte(&cl->reliablebuf, svc_specialsettings);
		MSG_WriteByte(&cl->reliablebuf, (byte)teamplay.value);
		MSG_WriteByte(&cl->reliablebuf, team);
		MSG_WriteByte(&cl->reliablebuf, (byte)deathmatch.value);

		// Force the client to load a new map.
		MSG_WriteByte(&cl->reliablebuf, svc_loadmap);
		MSG_WriteString(&cl->reliablebuf, level.mapname);		
	}
}


void SV_NewLevelClientUpdate()
{
	AActor *mo;
	int		i,j;

	for (i=0; i<MAXPLAYERS; i++)
	{
		client_t *cl = &clients[i];

		if (cl->cls < cs_spawned)
			continue;
	
		if (deathmatch.value)
			for (j=0; j<MAXPLAYERS; j++)
			{
				MSG_WriteByte(&cl->reliablebuf, svc_updatefrags);
				MSG_WriteByte(&cl->reliablebuf, j);
				MSG_WriteShort(&cl->reliablebuf, players[j].fragcount);
			}

		// update things
		TThinkerIterator<AActor> iterator;
		while ( (mo = iterator.Next() ) )
		{
			if (!mo->netid)
				continue;

			// a monster
			if (mo->type == MT_SKULL || (mo->flags & MF_COUNTKILL) )
			{
				MSG_WriteByte(&cl->reliablebuf, svc_mobjangle);
				MSG_WriteShort(&cl->reliablebuf, mo->netid);
				MSG_WriteLong(&cl->reliablebuf, mo->angle);
			}
		}

		SV_SendPacket(cl);
	}
}


//
// SV_ChooseTeamForPlayer
//
int SV_ChooseTeamForPlayer(player_t *player)
{
	if (   !strcmp(player->userinfo.team, "red") 
		|| !strcmp(player->userinfo.team, "RED") 
		|| !strcmp(player->userinfo.team, "Red") ) // Red team
	{
		return 0;
	}

	if (   !strcmp(player->userinfo.team, "blue") 
		|| !strcmp(player->userinfo.team, "BLUE") 
		|| !strcmp(player->userinfo.team, "Blue") ) // Blue team
	{
		return 1;
	}
	
	return -1;
}


void SV_GetChallenge(void)
{
	int challenge = MSG_ReadLong();

	if (challenge == LAUNCHER_CHALLENGE)
	{
		SV_SendServerInfo();  // for csDoom Launcher
		return;
	}

	if (challenge != CHALLENGE)
		return;

	int n = SV_GetFreeClient();
	if (n==-1)  // a server is full
	{
		SZ_Clear(&tempclient.reliablebuf);
		MSG_WriteByte(&tempclient.reliablebuf, S2C_FULL);
		NET_SendPacket(tempclient.reliablebuf.cursize, tempclient.reliablebuf.data, net_from);
		return;
	}

	client_t *cl = &clients[n];
	cl->address = net_from;
	cl->last_received = gametic;
	cl->cls = cs_challenge;

	SZ_Clear(&cl->reliablebuf);
	MSG_WriteByte(&cl->reliablebuf, S2C_CHALLENGE);
	NET_SendPacket(cl->reliablebuf.cursize, cl->reliablebuf.data, cl->address);
}


void SV_ConnectClient(void)
{
	client_t  *cl;

	cl = &clients[parse_cl];

	SZ_Clear(&cl->reliablebuf);

	short version = MSG_ReadShort();
	if (version != VERSION)
	{
		MSG_WriteByte(&cl->reliablebuf, svc_disconnect);
		NET_SendPacket(cl->reliablebuf.cursize, cl->reliablebuf.data, cl->address);
		cl->cls = cs_free;
		return;
	}

	MSG_WriteByte(&cl->reliablebuf, S2C_CONNECTION);
	NET_SendPacket(cl->reliablebuf.cursize, cl->reliablebuf.data, cl->address);
	cl->cls = cs_connected;

	Printf(PRINT_HIGH, "%s : connect\n", NET_AdrToString(net_from) );
   
	cl->reliable_bps = 0;
	cl->unreliable_bps = 0;
	cl->lastcmdtic = 0;
	cl->allow_rcon = false;

	SZ_Clear(&cl->netbuf);
	SZ_Clear(&cl->reliablebuf);
	SZ_Clear(&cl->relpackets);

	memset(cl->packetseq, -1, sizeof(cl->packetseq) );
	memset(cl->packetbegin, 0, sizeof(cl->packetbegin) );
	memset(cl->packetsize, 0, sizeof(cl->packetsize) );
	cl->sequence = 0;
	cl->last_sequence = -1;
	cl->packetnum = 0;
}


void SV_SpawnClient(void)
{
	client_t *cl = &clients[parse_cl];

	cl->cls = cs_spawned;
	playeringame[parse_cl] = true;

	// send welcome message
	MSG_WriteByte (&cl->reliablebuf, svc_print);
	MSG_WriteByte (&cl->reliablebuf, PRINT_HIGH);
  
	// [NightFang] - Tell the player which type of server this is
	if (teamplay.value)
		MSG_WriteString (&cl->reliablebuf, welcomestring_teamplay);
	else
		MSG_WriteString (&cl->reliablebuf, welcomestring);

	// send dmflags
	MSG_WriteByte(&cl->reliablebuf, svc_dmflags);
	MSG_WriteLong(&cl->reliablebuf, dmflags);

	// send consoleplayer number
	MSG_WriteByte(&cl->reliablebuf, svc_consoleplayer);
	MSG_WriteByte(&cl->reliablebuf, parse_cl);

	// send skill
	MSG_WriteByte(&cl->reliablebuf, svc_gameskill);
	MSG_WriteByte(&cl->reliablebuf, (byte)gameskill.value);

	players[parse_cl].playerstate = PST_REBORN;
	memset (players[parse_cl].frags,0,sizeof(players[parse_cl].frags)); 
	players[parse_cl].fragcount = 0;

	// get client userinfo
	MSG_ReadByte();  // clc_userinfo
	SV_SetupUserInfo(); // send it to other players
						// if it's a team game, player's color
						// will be set to red or blue
	cl->rate = MSG_ReadLong(); // get rate value

	int team = SV_ChooseTeamForPlayer(&players[parse_cl]);
   
	// player must choose red or blue team
	if (teamplay.value && team == -1)
	{
		MSG_WriteByte (&cl->reliablebuf, svc_print);
		MSG_WriteByte (&cl->reliablebuf, PRINT_HIGH);
		MSG_WriteString (&cl->reliablebuf, 
			"Choose red or blue team.\n"
			"Use \"team <teamname>\" command to change your team.\n");

		MSG_WriteByte(&cl->reliablebuf, svc_disconnect);
		SV_SendPacket(cl);
	   
		playeringame[parse_cl] = false;
		cl->cls = cs_free;

		return;
	}

	// Send special settings like teamplay and deathmatch.
	MSG_WriteByte(&cl->reliablebuf, svc_specialsettings);
	MSG_WriteByte(&cl->reliablebuf, (byte)teamplay.value);
	MSG_WriteByte(&cl->reliablebuf, team);
	MSG_WriteByte(&cl->reliablebuf, (byte)deathmatch.value);

	// send a map name
	MSG_WriteByte(&cl->reliablebuf, svc_loadmap);
	MSG_WriteString(&cl->reliablebuf, level.mapname);

	if (deathmatch.value)
		G_DeathMatchSpawnPlayer(parse_cl);
	else
		P_SpawnPlayer(&playerstarts[parse_cl]);
	 
	SV_ClientFullUpdate(cl);

	if (!teamplay.value)
	{
		SV_BroadcastPrintf (PRINT_HIGH, "%s entered the game\n", players[parse_cl].userinfo.netname);
	}
	else
	{ 
		if (team == 0)
			SV_BroadcastPrintf (PRINT_HIGH, "%s entered the game and joined the RED team.\n", players[parse_cl].userinfo.netname);
		if (team == 1)
			SV_BroadcastPrintf (PRINT_HIGH, "%s entered the game and joined the BLUE team.\n", players[parse_cl].userinfo.netname);
	}

	SV_UpdateFrags (&players[parse_cl]);
}


//
// SV_DisconnectClient
//
void SV_DisconnectClient(void)
{
	client_t *cl = &clients[parse_cl];

	playeringame[parse_cl] = false;
	cl->cls = cs_free;
    
	if (gametic - cl->last_received == CLIENT_TIMEOUT*35)
		SV_BroadcastPrintf (PRINT_HIGH, "%s timed out\n", players[parse_cl].userinfo.netname);
	else
        SV_BroadcastPrintf (PRINT_HIGH, "client %s disconnected\n", players[parse_cl].userinfo.netname);

	// tell others clients about it		
	for (int i=0; i<MAXPLAYERS; i++)
	{
		client_t *cl = &clients[i];

		if (cl->cls < cs_spawned)
			continue;

		MSG_WriteByte(&cl->reliablebuf, svc_disconnectclient);
		MSG_WriteByte(&cl->reliablebuf, parse_cl);
	}

	players[parse_cl].mo->Destroy();
	players[parse_cl].mo = NULL;
}


//
// SV_DropClient
// Called when the player is leaving the server unwillingly. 
//
void SV_DropClient(void)
{
	client_t *cl = &clients[parse_cl];

	MSG_WriteByte(&cl->reliablebuf, svc_disconnect);
	SV_SendPacket(cl);

	SV_DisconnectClient();
}

//
// SV_SendReconnectSignal
// All clients will reconnect. Called when the server changes a map
//
void SV_SendReconnectSignal()
{
	client_t *cl;

	for (int i=0; i<MAXPLAYERS; i++)
	{
		cl = &clients[i];
		
		if (cl->cls < cs_spawned)
			continue;

		MSG_WriteByte(&cl->reliablebuf, svc_reconnect);
		SV_SendPacket(cl);
	   
		playeringame[i] = false;
		cl->cls = cs_free;
	   
		players[i].mo->Destroy();
		players[i].mo = NULL;
	}

}


void SV_SendDisconnectSignal()
{
	client_t *cl;

	for (int i=0; i<MAXPLAYERS; i++)
	{
		cl = &clients[i];
		
		if (cl->cls < cs_spawned)
			continue;
		
		MSG_WriteByte(&cl->reliablebuf, svc_disconnect);
		SV_SendPacket(cl);
	   
		playeringame[i] = false;
		cl->cls = cs_free;
	   
		players[i].mo->Destroy();
		players[i].mo = NULL;
	}

}

//
// SV_ExitLevel
// Called when timelimit or fraglimit hit.
//
void SV_ExitLevel()
{
	for (int i=0; i<MAXPLAYERS; i++)
	{
	   client_t *cl = &clients[i];

	   if (cl->cls < cs_spawned)
		   continue;

	   MSG_WriteByte(&cl->reliablebuf, svc_exitlevel);
	}
}

//
// SV_BroadcastPrintf
// Sends text to all active clients.
//
void STACK_ARGS SV_BroadcastPrintf (int level, const char *fmt, ...)
{
    va_list       argptr;
    char          string[1024];
    client_t     *cl;

    va_start (argptr,fmt);
    vsprintf (string, fmt,argptr);
    va_end (argptr);

    Printf (level, "%s", string);  // print to the console

    for (int i=0; i<MAXPLAYERS; i++)
    {
		cl = &clients[i];

		if (cl->cls < cs_spawned)
			continue;        

		MSG_WriteByte (&cl->reliablebuf, svc_print);
		MSG_WriteByte (&cl->reliablebuf, level);
		MSG_WriteString (&cl->reliablebuf, string);
    }
}


//
// SV_Say
// Show a chat string and send it to others clients.
//
void SV_Say(void)
{
	char     *s;

    s = MSG_ReadString();

	SV_BroadcastPrintf (PRINT_CHAT, "%s: %s\n", players[parse_cl].userinfo.netname, s);
}

//
// SV_UpdateMissiles
// Updates missiles position sometimes.
//
void SV_UpdateMissiles(client_t* cl)
{
    AActor *mo;

    TThinkerIterator<AActor> iterator;
    while ( (mo = iterator.Next() ) )
    {
        if (!(mo->flags & MF_MISSILE) )
			continue;

		if (mo->type == MT_PLASMA)
			continue;
            
		// update missile position every 50 tics
		if ( ((gametic + mo->netid) % 50) )
			continue;

		MSG_WriteByte (&cl->netbuf, svc_movemobj);
		MSG_WriteShort (&cl->netbuf, mo->netid);
		MSG_WriteLong (&cl->netbuf, mo->x);
		MSG_WriteLong (&cl->netbuf, mo->y);
		MSG_WriteLong (&cl->netbuf, mo->z);
    }


}


#define MAXCORPSES 15

//
// SV_RemoveCorpses
// Removes some corpses
//
void SV_RemoveCorpses (void)
{
	AActor *mo;
	int     corpses = 0;

	if (gametic%35)
	{
		return;
	}
	else
	{
		TThinkerIterator<AActor> iterator;
		while ( (mo = iterator.Next() ) )
		{
			if (mo->type == MT_PLAYER && (!mo->player || mo->health <=0) )
				corpses++;
		}
	}

	if (corpses > MAXCORPSES)
	{
		TThinkerIterator<AActor> iterator;
		while (corpses > MAXCORPSES && (mo = iterator.Next() ) )
		{
			if (mo->type == MT_PLAYER && !mo->player)
			{
				mo->Destroy();
				corpses--;
			}
		}
	}
}


//
// SV_SendGametic
// Sends gametic to calculate ping
//
void SV_SendGametic(client_t* cl)
{
	if ((gametic%100) != 0)
		return;

	MSG_WriteByte (&cl->reliablebuf, svc_svgametic);
	MSG_WriteLong (&cl->reliablebuf, gametic);
}

// calculates ping using gametic which was sent by SV_SendGametic and
// current gametic
void SV_CalcPing(void)
{	
	int oldtic = MSG_ReadLong();

	int ping = gametic - oldtic;
	if (ping > 255) ping = 255;
	players[parse_cl].ping = ping;
}

//
// SV_UpdatePing
// send pings to a client
//
void SV_UpdatePing(client_t* cl)
{
	if ((gametic%101) != 0)
		return;

	for (int j=0; j<MAXPLAYERS; j++)
	{
		if (cl->cls < cs_spawned)
			continue;

	    MSG_WriteByte(&cl->reliablebuf, svc_updateping);
	    MSG_WriteByte(&cl->reliablebuf, j);  // player
	    MSG_WriteByte(&cl->reliablebuf, players[j].ping);
	}
}


//
// SV_UpdateDeadPlayers
// Update player's frame while he's dying.
//
void SV_UpdateDeadPlayers()
{
    AActor *mo;

    TThinkerIterator<AActor> iterator;
    while ( (mo = iterator.Next() ) )
    {
        if (mo->type != MT_PLAYER || mo->player)
			continue;
		
		if (mo->oldframe != mo->frame)
			for (int i=0; i<MAXPLAYERS; i++)
			{
				client_t *cl = &clients[i];

				if (cl->cls < cs_spawned)
					continue;

				MSG_WriteByte (&cl->reliablebuf, svc_mobjframe);
				MSG_WriteShort (&cl->reliablebuf, mo->netid);
				MSG_WriteByte (&cl->reliablebuf, mo->frame);
			}
		
		mo->oldframe = mo->frame;
    }

}


//
// SV_UpdateSectors
// Update doors, moving floors, ceilings etc...
//
void SV_UpdateSectors(client_t* cl)
{
	for (int s=0; s<numsectors; s++)
	{ 
		sector_t* sec = &sectors[s];

		if (sec->floordata && sec->floordata->IsKindOf (RUNTIME_CLASS(DMover)))
		{
			MSG_WriteByte (&cl->netbuf, svc_floorheight);
			MSG_WriteShort (&cl->netbuf, s);
			MSG_WriteShort (&cl->netbuf, sec->floorheight>>FRACBITS);
		}
		
		if (sec->ceilingdata && sec->ceilingdata->IsKindOf (RUNTIME_CLASS(DMover)))
		{
			MSG_WriteByte (&cl->netbuf, svc_ceilingheight);
			MSG_WriteShort (&cl->netbuf, s);
			MSG_WriteShort (&cl->netbuf, sec->ceilingheight>>FRACBITS);
		}
	}
}

//
// SV_ClearClientsBPS
//
void SV_ClearClientsBPS(void)
{
	if (gametic % 35)
		return;

	for (int i=0; i<MAXPLAYERS; i++)
	{
		client_t *cl = &clients[i];

		if (cl->cls < cs_spawned)
			continue;

		cl->reliable_bps = 0;
		cl->unreliable_bps = 0;
	}
}


//
// SV_WriteCommands
//
void SV_WriteCommands(void)
{
	int        i, j;
	client_t  *cl;

	
	SV_UpdateDeadPlayers(); // Update dying players.

	for (i=0; i<MAXPLAYERS; i++)
	{
		cl = &clients[i];

		if (cl->cls < cs_spawned)
			continue;
		
		// Don't need to update origin every tic.
		// The server sends origin and velocity of a 
		// player and the client always knows origin
		// on the next tic.
		if (gametic % 2)
			for (j=0; j<MAXPLAYERS; j++)
			{
				if (!playeringame[j] || j == i)
					continue;

				AActor *mobj = players[j].mo;

				byte bits = 0;

				MSG_WriteByte(&cl->netbuf, svc_moveplayer);
				MSG_WriteByte(&cl->netbuf, j);     // player number
				
				if (mobj->x != mobj->oldx)
					bits |= CM_X;
				if (mobj->y != mobj->oldy)
					bits |= CM_Y;
				if (mobj->z != mobj->oldz)
					bits |= CM_Z;
				if (mobj->angle != mobj->oldangle)
					bits |= CM_ANGLE;
				if (mobj->frame != mobj->oldframe)
					bits |= CM_FRAME;
				if (mobj->momx != mobj->oldmomx)
					bits |= CM_MOMX;
				if (mobj->momy != mobj->oldmomy)
					bits |= CM_MOMY;
				if (mobj->momz != mobj->oldmomz)
					bits |= CM_MOMZ;

				MSG_WriteByte(&cl->netbuf, bits);

				if (mobj->x != mobj->oldx)
					MSG_WriteLong(&cl->netbuf, mobj->x);				
				if (mobj->y != mobj->oldy)
					MSG_WriteLong(&cl->netbuf, mobj->y);
				if (mobj->z != mobj->oldz)
					MSG_WriteLong(&cl->netbuf, mobj->z);
				
				if (mobj->angle != mobj->oldangle)
					MSG_WriteLong(&cl->netbuf, mobj->angle);

				if (mobj->frame != mobj->oldframe)
				{
					if (mobj->frame == 32773)
						MSG_WriteByte(&cl->netbuf, PLAYER_FULLBRIGHTFRAME);
					else
						MSG_WriteByte(&cl->netbuf, mobj->frame);
				}

				// write velocity
				if (mobj->momx != mobj->oldmomx)
					MSG_WriteLong(&cl->netbuf, mobj->momx);
				if (mobj->momy != mobj->oldmomy)
					MSG_WriteLong(&cl->netbuf, mobj->momy);
				if (mobj->momz != mobj->oldmomz)
					MSG_WriteLong(&cl->netbuf, mobj->momz);
			}


		SV_UpdateConsolePlayer(cl);

		SV_UpdateMissiles(cl);

        SV_UpdateSectors(cl); // update doors, moving floors, ceilings etc		                        

	 	SV_SendGametic(cl); // used to calculate a ping value,
		                    // when a client returns it
     	SV_UpdatePing(cl);
	}

	// save players' origin, velocity, frame and angle
	if (gametic % 2)
		for (i=0; i<MAXPLAYERS; i++)
		{
			if (!playeringame[i])
				continue;

			AActor *mobj = players[i].mo;
		
			mobj->oldx = mobj->x;
			mobj->oldy = mobj->y;
			mobj->oldz = mobj->z;
			mobj->oldangle = mobj->angle;
			mobj->oldframe = mobj->frame;
			mobj->oldmomx = mobj->momx;
			mobj->oldmomy = mobj->momy;
			mobj->oldmomz = mobj->momz;
		}
}

void SV_PlayerTriedToCheat()
{
	SV_BroadcastPrintf(PRINT_HIGH, "%s tried to cheat!\n", players[parse_cl].userinfo.netname);
	SV_DropClient();
}

/*
struct usercmd_s {
	byte	msec;			// not sure how to use this yet...
	byte	buttons;
	short	pitch;			// up/down. currently just a y-sheering amount
	short	yaw;			// left/right	// If you haven't guessed, I just
	short	roll;			// tilt			// ripped these from Quake2's usercmd.
	short	forwardmove;
	short	sidemove;
	short	upmove;
	byte	impulse;
	byte	use;
};
*/

void SV_GetPlayerCmd(void)
{
	client_t *cl = &clients[parse_cl];
	ticcmd_t *cmd = &players[parse_cl].cmd;
	int       tic;

    tic = MSG_ReadLong(); // get client gametic

	// get the previous cmds and angle
	if (  cl->lastcmdtic && (tic - cl->lastcmdtic >= 2) 
		&& gamestate != GS_INTERMISSION )
	{
		cmd->ucmd.buttons = MSG_ReadByte();
	    players[parse_cl].mo->angle = MSG_ReadShort() << 16;
		
		if (dmflags & DF_NO_FREELOOK)
		{
			players[parse_cl].mo->pitch = 0;
			MSG_ReadShort();
		}
		else
			players[parse_cl].mo->pitch = MSG_ReadShort() << 16;

	    cmd->ucmd.forwardmove = MSG_ReadShort();
	    cmd->ucmd.sidemove = MSG_ReadShort();

		if ( abs(cmd->ucmd.forwardmove) > 12800 
			|| abs(cmd->ucmd.sidemove) > 12800)
		{
			SV_PlayerTriedToCheat();
			return;
		}

		cmd->ucmd.impulse = MSG_ReadByte();

		P_PlayerThink (&players[parse_cl]);
	    players[parse_cl].mo->RunThink();
	}
	else
	{
		// get 10 bytes
		MSG_ReadLong();
		MSG_ReadLong();
		MSG_ReadShort();
	}

	// get current cmds and angle
	cmd->ucmd.buttons = MSG_ReadByte();
	if (gamestate != GS_INTERMISSION)
	{
		players[parse_cl].mo->angle = MSG_ReadShort() << 16;
		
		if (dmflags & DF_NO_FREELOOK)
		{
			players[parse_cl].mo->pitch = 0;
			MSG_ReadShort();
		}
		else
			players[parse_cl].mo->pitch = MSG_ReadShort() << 16;
	}
	else
		MSG_ReadLong();

	cmd->ucmd.forwardmove = MSG_ReadShort();
	cmd->ucmd.sidemove = MSG_ReadShort();
	
	if ( abs(cmd->ucmd.forwardmove) > 12800 
		|| abs(cmd->ucmd.sidemove) > 12800)
	{
		SV_PlayerTriedToCheat();
		return;
	}
	
	cmd->ucmd.impulse = MSG_ReadByte();

	if (gamestate != GS_INTERMISSION)
	{
		P_PlayerThink (&players[parse_cl]);
		players[parse_cl].mo->RunThink();
	}

	cl->lastcmdtic = tic;
}


void SV_UpdateConsolePlayer(client_t* cl)
{
	int        playernum;
	player_t  *player;

	// It's not a good idea to send 33 bytes every tic.
	if (gametic % 3)
		return;

	for (playernum=0; playernum<MAXPLAYERS; playernum++)
	{
		if (cl->cls < cs_spawned)
			continue;

		if (cl == &clients[playernum])
		{
			player = &players[playernum];
			break;
		}
	}
	
	if (!player)
	    return;
	
	// haven't got any cmds yet
	if (!cl->lastcmdtic)
		return;

	// client player will update his position if packets were missed
	MSG_WriteByte (&cl->netbuf, svc_updatelocalplayer);
	MSG_WriteLong (&cl->netbuf, cl->lastcmdtic);

	MSG_WriteLong (&cl->netbuf, player->mo->x);
	MSG_WriteLong (&cl->netbuf, player->mo->y);
	MSG_WriteLong (&cl->netbuf, player->mo->z);

	MSG_WriteLong (&cl->netbuf, player->mo->momx);
	MSG_WriteLong (&cl->netbuf, player->mo->momy);
	MSG_WriteLong (&cl->netbuf, player->mo->momz);

//	MSG_WriteShort (&cl->netbuf, player->mo->momx >> FRACBITS);
//	MSG_WriteShort (&cl->netbuf, player->mo->momy >> FRACBITS);
//	MSG_WriteShort (&cl->netbuf, player->mo->momz >> FRACBITS);
}


//
// SV_ParseCommands
//
void SV_ParseCommands(void)
{		
	 int  cmd;
	 char *str;
	 
	 while(1)	 
	 {  
 		if (msg_badread)
		{
            Printf (PRINT_HIGH, "SV_ParseCommands: badread\n");
          	SV_DropClient();
            break;
		}

		cmd = MSG_ReadByte();

		if (cmd == -1) // EOM
			break;

		if (clients[parse_cl].cls < cs_spawned)
			break;

	    
		switch(cmd)
		{
		case clc_disconnect:
			SV_DisconnectClient();
			return;

        case clc_userinfo:
			SV_SetupUserInfo();
			break;

		case clc_say:
			SV_Say();
			break;

		case clc_move:
			SV_GetPlayerCmd();
			break;

		case clc_svgametic:
			SV_CalcPing();
			break;

		case clc_rate:
			clients[parse_cl].rate = MSG_ReadLong();	
			break;

		case clc_ack:
			SV_AcknowledgePacket();
			break;
	
		case clc_rcon:
			str = MSG_ReadString();
			if (clients[parse_cl].allow_rcon)
				AddCommandString (str);
			break;

		case clc_rcon_password:
			str = MSG_ReadString();
			if (strlen(rcon_password.string) >= 5 && !strcmp(rcon_password.string, str) )
				clients[parse_cl].allow_rcon = true;
			break;
		
		default:			
			Printf(PRINT_HIGH, "SV_ParseCommands: Unknown client message.\n");
          	SV_DropClient();
            return;
		}	 
	}
}


//
// SV_RunTics
//
void SV_RunTics (void)
{
	int              nowtime;
	int              newtics;

	I_DoSelect();

	nowtime = I_GetTime ();
        newtics = nowtime - gametime;

	// console input
	char *cmd = I_ConsoleInput();
	if (cmd)
		AddCommandString (cmd);

	SV_GetPackets(); // get packets

	if (newtics > 0)
	{
		DObject::BeginFrame ();

		// run the newtime tics
		while (newtics--)
		{
			C_Ticker ();
			G_Ticker ();
			SV_UpdateMaster();			
			gametic++;
		}

		DObject::EndFrame ();
	
		gametime = nowtime;
	}
}



//////// FRAGS LOGGING ///////////

FILE  *sv_fraglogfile;

//
// SV_Fraglogfile
// from quake world
//
void SV_Fraglogfile (void)
{
	char	name[32];
	int		i;

	if (sv_fraglogfile)
	{
		Printf (PRINT_HIGH, "Frag file logging off.\n");
		fclose (sv_fraglogfile);
		sv_fraglogfile = NULL;
		return;
	}

	// find an unused name
	for (i=0 ; i<1000 ; i++)
	{
		sprintf (name, "frag_%i.log", i);
		sv_fraglogfile = fopen (name, "r");
		if (!sv_fraglogfile)
		{	// can't read it, so create this one
			sv_fraglogfile = fopen (name, "w");
			if (!sv_fraglogfile)
				i=1000;	// give error
			break;
		}
		fclose (sv_fraglogfile);
	}

	if (i==1000)
	{
		Printf (PRINT_HIGH, "Can't open any logfiles.\n");
		sv_fraglogfile = NULL;
		return;
	}

	Printf (PRINT_HIGH, "Logging frags to %s.\n", name);
}


//
// SV_logfrag
//
void SV_Logfrag (player_t* source, player_t* target)
{
	if (!sv_fraglogfile) 
		return;

	fprintf (sv_fraglogfile, "\\%s\\%s\\\n",
		source->userinfo.netname, target->userinfo.netname);
	fflush (sv_fraglogfile);
}