/*
Copyright (C) 1997-2001 Id Software, Inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
// sv_main.c -- server main program

#include "server.h"

// shared message buffer to be used for occasional messages
msg_t		tmpMessage;
qbyte		tmpMessageData[MAX_MSGLEN];


/*
=============================================================================

Com_Printf redirection

=============================================================================
*/

char sv_outputbuf[SV_OUTPUTBUF_LENGTH];

void SV_FlushRedirect( int sv_redirected, char *outputbuf )
{
	if( sv_client->edict && (sv_client->edict->r.svflags & SVF_FAKECLIENT) )
		return;

	if( sv_redirected == RD_PACKET )
	{
		Netchan_OutOfBandPrint( NS_SERVER, net_from, "print\n%s", outputbuf );
	}
	else if( sv_redirected == RD_CLIENT )
	{
		SV_ClientPrintf( sv_client, outputbuf );
	}
}


/*
=============================================================================

EVENT MESSAGES

=============================================================================
*/

/*
===============
SV_ExpandNewlines

Converts newlines to "\n" so a line prints nicer
===============
*/
char *SV_ExpandNewlines( char *in ) {
	static	char	string[1024];
	int		l;

	l = 0;
	while ( *in && l < sizeof(string) - 3 ) {
		if ( *in == '\n' ) {
			string[l++] = '\\';
			string[l++] = 'n';
		} else {
			string[l++] = *in;
		}
		in++;
	}
	string[l] = 0;

	return string;
}

/*
======================
SV_ReplacePendingServerCommands

  This is ugly
======================
*//*
int SV_ReplacePendingServerCommands( client_t *client, const char *cmd ) {
	int i, index, csnum1, csnum2;

	for ( i = client->reliableSent+1; i <= client->reliableSequence; i++ ) {
		index = i & ( MAX_RELIABLE_COMMANDS - 1 );
		//
		if ( !Q_strncmp(cmd, client->reliableCommands[ index ], strlen("cs")) ) {
			sscanf(cmd, "cs %i", &csnum1);
			sscanf(client->reliableCommands[ index ], "cs %i", &csnum2);
			if ( csnum1 == csnum2 ) {
				Q_strncpyz( client->reliableCommands[ index ], cmd, sizeof( client->reliableCommands[ index ] ) );
				
				//if ( client->netchan.remoteAddress.type != NA_BOT ) {
				//	Com_Printf( "WARNING: client %i removed double pending config string %i: %s\n", client-svs.clients, csnum1, cmd );
				//}
				
				return qtrue;
			}
		}
	}
	return qfalse;
}
*/
/*
======================
SV_AddServerCommand

The given command will be transmitted to the client, and is guaranteed to
not have future snapshot_t executed before it is executed
======================
*/
void SV_AddServerCommand( client_t *client, const char *cmd ) {
	int		index, i;

	if( !client )
		return;

	if( client->edict && (client->edict->r.svflags & SVF_FAKECLIENT) )
		return;

	if( !cmd || !cmd[0] || !strlen(cmd) )
		return;

	// this is very ugly but it's also a waste to for instance send multiple config string updates
	// for the same config string index in one snapshot
//	if ( SV_ReplacePendingServerCommands( client, cmd ) ) {
//		return;
//	}

	client->reliableSequence++;
	// if we would be losing an old command that hasn't been acknowledged,
	// we must drop the connection
	// we check == instead of >= so a broadcast print added by SV_DropClient()
	// doesn't cause a recursive drop client
	if ( client->reliableSequence - client->reliableAcknowledge == MAX_RELIABLE_COMMANDS + 1 ) {
		//Com_Printf( "===== pending server commands =====\n" );
		for ( i = client->reliableAcknowledge + 1 ; i <= client->reliableSequence ; i++ ) {
			Com_Printf( "cmd %5d: %s\n", i, client->reliableCommands[ i & (MAX_RELIABLE_COMMANDS-1) ] );
		}
		Com_Printf( "cmd %5d: %s\n", i, cmd );
		SV_DropClient( client, DROP_TYPE_GENERAL, "Server command overflow" );
		return;
	}
	index = client->reliableSequence & ( MAX_RELIABLE_COMMANDS - 1 );
	Q_strncpyz( client->reliableCommands[ index ], cmd, sizeof( client->reliableCommands[ index ] ) );
}


/*
=================
SV_SendServerCommand

Sends a reliable command string to be interpreted by 
the client game module: "cp", "print", "chat", etc
A NULL client will broadcast to all clients
=================
*/
void SV_SendServerCommand( client_t *cl, const char *fmt, ... ) {
	va_list		argptr;
	char		message[MAX_MSGLEN];
	client_t	*client;
	int			j;
	
	va_start( argptr,fmt );
	vsnprintf( message, sizeof(message), fmt, argptr );
	va_end( argptr );

	if ( cl != NULL ) {
		if( cl->state < CS_CONNECTED )
			return;
		SV_AddServerCommand( cl, message );
		return;
	}

	// hack to echo broadcast prints to console
	if( dedicated->integer ) {
		if( !strncmp( message, "pr", 2 ) )
			Com_DPrintf( "broadcast: %s\n", SV_ExpandNewlines(message) );
	}

	// send the data to all relevant clients
	for( j = 0, client = svs.clients; j < sv_maxclients->integer; j++, client++ ) {
		if( client->state < CS_SPAWNED ) { // jal
			continue;
		}
		SV_AddServerCommand( client, message );
	}
}

/*
==================
SV_AddReliableCommandsToMessage

(re)send all server commands the client hasn't acknowledged yet
==================
*/
void SV_AddReliableCommandsToMessage( client_t *client, msg_t *msg ) {
	int		i;

	if( client->edict && (client->edict->r.svflags & SVF_FAKECLIENT) )
		return;

	if( sv_debug_serverCmd->integer )
		Com_Printf( "sv_cl->reliableAcknowledge: %i sv_cl->reliableSequence:%i\n", client->reliableAcknowledge, client->reliableSequence );

	// write any unacknowledged serverCommands
	for( i = client->reliableAcknowledge + 1 ; i <= client->reliableSequence ; i++ ) {
		if( !strlen( client->reliableCommands[ i & (MAX_RELIABLE_COMMANDS-1) ] ) ) 
			continue;
		MSG_WriteByte( msg, svc_servercmd );
		MSG_WriteLong( msg, i );
		MSG_WriteString( msg, client->reliableCommands[ i & (MAX_RELIABLE_COMMANDS-1) ] );
		if( sv_debug_serverCmd->integer )
			Com_Printf( "SV_AddServerCommandsToMessage(%i):%s\n", i, client->reliableCommands[ i & (MAX_RELIABLE_COMMANDS-1) ] );
	}
	client->reliableSent = client->reliableSequence;
}

#ifdef BATTLEYE
void SV_AddBattleyeCommandToMessage( client_t *client, msg_t *msg ) {
	qbyte	*bepacket = NULL;
	int		 bepacketlen = 0;

	if( !client || !client->battleye )
		return;

	if( sv_battleye->integer ) {
		// the client is not acknowledging packets
		if( client->BE.acknowledgedPacket + BE_UPDATE_BACKUP <= client->BE.headPacket ) {
			SV_DropClient( client, DROP_TYPE_GENERAL, "Too many unacknowledged BattlEye packets" );
			return;
		}

		if( msg ) {
			if( client->BE.lastSentTime > svs.realtime ) // clamp 
				client->BE.lastSentTime = svs.realtime;

			if( client->BE.lastSentTime + BE_MIN_RESEND < svs.realtime ) {
				// send the next buffered unacknowledged packet (if any)
				if( client->BE.headPacket > client->BE.acknowledgedPacket ) {
					bepacketlen = client->BE.packetLens[client->BE.acknowledgedPacket & BE_UPDATE_MASK];
					if( bepacketlen ) {
						bepacket = client->BE.packets[client->BE.acknowledgedPacket & BE_UPDATE_MASK];
					}
				}
			}

			MSG_WriteByte( msg, svc_battleye );
			//write clc_battleye acknowledge
			MSG_WriteLong( msg, client->BE.commandReceived );
			if( bepacket ) {
				MSG_WriteShort( msg, bepacketlen );
				MSG_Write( msg, bepacket, bepacketlen );
				MSG_WriteLong( msg, client->BE.acknowledgedPacket+1 ); // id of this BE packet
				client->BE.lastSentTime = svs.realtime;
			} else {
				MSG_WriteShort( msg, 0 ); // no data, just acknowledge of clc_battleye
			}
		}
	}
}
#endif

/*
=============================================================================

EVENT MESSAGES

=============================================================================
*/

/*
=================
SV_ClientChatf

Sends text across to be displayed in chat window
=================
*/
void SV_ClientChatf( client_t *cl, char *fmt, ... )
{
	va_list		argptr;
	char		string[1024], *p;
	client_t	*client;
	int			i;

	va_start( argptr, fmt );
	vsnprintf( string, sizeof(string), fmt, argptr );
	va_end( argptr );

	// double quotes are bad
	while( (p = strchr(string, '\"')) != NULL )
		*p = '\'';

	if( cl != NULL ) {
		if( cl->state == CS_SPAWNED )
			SV_SendServerCommand( cl, "ch \"%s\"", string );
		return;
	}

	for( i = 0, client = svs.clients; i < sv_maxclients->integer; i++, client++ )
	{
		if( client->state == CS_SPAWNED )
			SV_SendServerCommand( client, "ch \"%s\"", string );
	}

	// echo to console
	if( dedicated->integer )
	{
		char	copy[MAX_PRINTMSG];
		int		i;

		// mask off high bits and colored strings
		for( i = 0 ; i < sizeof(copy)-1 && string[i] ; i++ )
			copy[i] = string[i]&127;
		copy[i] = 0;
		Com_Printf( "%s", copy );
	}
}

/*
=================
SV_ClientPrintf

Sends text across to be displayed as print
=================
*/
void SV_ClientPrintf( client_t *cl, char *fmt, ... )
{
	va_list		argptr;
	char		string[1024], *p;
	client_t	*client;
	int			i;

	va_start( argptr, fmt );
	vsnprintf( string, sizeof(string), fmt, argptr );
	va_end( argptr );

	// double quotes are bad
	while( (p = strchr(string, '\"')) != NULL )
		*p = '\'';

	if( cl != NULL ) {
		SV_SendServerCommand( cl, "pr \"%s\"", string );
		return;
	}

	for( i = 0, client = svs.clients; i < sv_maxclients->integer; i++, client++ )
	{
		if( client->state < CS_SPAWNED )
			continue;
		SV_SendServerCommand( client, "pr \"%s\"", string );
	}

	// echo to console
	if( dedicated->integer )
	{
		char	copy[MAX_PRINTMSG];
		int		i;

		// mask off high bits and colored strings
		for( i = 0 ; i < sizeof(copy)-1 && string[i] ; i++ )
			copy[i] = string[i]&127;
		copy[i] = 0;
		Com_Printf( "%s", copy );
	}
}

/*
=================
SV_BroadcastCommand

Sends a command to all connected clients. Ignores client->state < CS_SPAWNED check
=================
*/
void SV_BroadcastCommand( char *fmt, ... )
{
	client_t	*client;
	int			i;
	va_list		argptr;
	char		string[1024];

	if( !sv.state )
		return;

	va_start( argptr, fmt );
	vsnprintf( string, sizeof(string), fmt, argptr );
	va_end( argptr );
	
	for( i = 0, client = svs.clients; i < sv_maxclients->integer; i++, client++ ) {
		if( client->state < CS_CONNECTED )
			continue;
		SV_SendServerCommand( client, string );
	}
}

/*
=================
SV_Multicast

Sends the contents of sv.multicast to a subset of the clients,
then clears sv.multicast.

MULTICAST_ALL	same as broadcast (origin can be NULL)
MULTICAST_PVS	send to clients potentially visible from org
MULTICAST_PHS	send to clients potentially hearable from org
=================
*/
void SV_MulticastMessage( qbyte *messageData, int length, vec3_t origin, multicast_t to )
{
	client_t	*client;
	qbyte		*mask;
	int			leafnum, cluster;
	int			j;
	int			area1, area2;

	// check we have data to send before adding it
	if( !length || !messageData )
		return;

	if( to != MULTICAST_ALL )
	{
		assert( origin != NULL );

		leafnum = CM_PointLeafnum( origin );
		area1 = CM_LeafArea( leafnum );
	}
	else
	{
		leafnum = 0;	// just to avoid compiler warnings
		area1 = 0;
	}

#ifdef SERVERSIDE_DEMOS
	// if doing a serverrecord, store everything
	if (svs.demofile)
		MSG_Write (&svs.demo_multicast, messageData, length);
#endif

	switch( to )
	{
	case MULTICAST_ALL:
		leafnum = 0;
		mask = NULL;
		break;

	case MULTICAST_PHS:
		leafnum = CM_PointLeafnum( origin );
		cluster = CM_LeafCluster( leafnum );
		mask = CM_ClusterPHS( cluster );
		break;

	case MULTICAST_PVS:
		leafnum = CM_PointLeafnum( origin );
		cluster = CM_LeafCluster( leafnum );
		mask = CM_ClusterPVS( cluster );
		break;

	default:
		{
			mask = NULL;
			// wsw : jal : no need to crash here
			Com_DPrintf( "ERROR: SV_MulticastMessage: bad to : %i", to );
			return;
		}
	}

	// send the data to all relevant clients
	for( j = 0, client = svs.clients; j < sv_maxclients->integer; j++, client++ )
	{
		if( client->state < CS_SPAWNED )
			continue;
		if( client->edict && (client->edict->r.svflags & SVF_FAKECLIENT) )
			continue;

		if(mask)
		{
			leafnum = CM_PointLeafnum( client->edict->s.origin );
			cluster = CM_LeafCluster( leafnum );
			if( mask && ( !(mask[cluster>>3] & (1<<(cluster&7)) ) ) )
				continue;
			area2 = CM_LeafArea( leafnum );
			if( !CM_AreasConnected(area1, area2) )
				continue;
		}

		if( messageData[0] != svc_sound ) {
			Com_Error( ERR_DROP, "SV_MulticasMessage: Message was not a sound\n" );
		} else {
			// wsw : jal : multicast only for sounds and is not for reliable
			MSG_Write( &client->soundsmsg, messageData, length );
		}
	}
}

/*  
==================
SV_StartSound

Each entity can have eight independant sound sources, like voice,
weapon, feet, etc.

If cahnnel & 8, the sound will be sent to everyone, not just
things in the PHS.

FIXME: if entity isn't in PHS, they must be forced to be sent or
have the origin explicitly sent.

Channel 0 is an auto-allocate channel, the others override anything
already running on that entity/channel pair.

An attenuation of 0 will play full volume everywhere in the level.
Larger attenuations will drop off.  (max 4 attenuation)

Timeofs can range from 0.0 to 0.1 to cause sounds to be started
later in the frame than they normally would.

If origin is NULL, the origin is determined from the entity origin
or the midpoint of the entity box for bmodels.
==================
*/  
void SV_StartSound( vec3_t origin, edict_t *entity, int channel,
					int soundindex, float volume,
					float attenuation )
{       
	int			sendchan;
    int			flags;
    int			i, v;
	int			ent;
	vec3_t		origin_v;
	qboolean	use_phs;
	msg_t		message;
	qbyte		messageData[MAX_MSGLEN];

	MSG_Init( &message, messageData, sizeof(messageData) );
	MSG_Clear( &message );

	if( soundindex < 0 || soundindex >= MAX_SOUNDS )
		Com_Error( ERR_FATAL, "SV_StartSound: soundindex = %i", soundindex );
	if( volume < 0 || volume > 1.0 )
		Com_Error( ERR_FATAL, "SV_StartSound: volume = %f", volume);
	if( attenuation < 0 || attenuation > 4 )
		Com_Error( ERR_FATAL, "SV_StartSound: attenuation = %f", attenuation );
//	if( channel < 0 || channel > 15 )
//		Com_Error( ERR_FATAL, "SV_StartSound: channel = %i", channel );

	ent = NUM_FOR_EDICT( entity );

	if( channel & CHAN_NO_PHS_ADD )	// no PHS flag
	{
		use_phs = qfalse;
		channel &= 7;
	}
	else
		use_phs = qtrue;

	sendchan = (ent<<3) | (channel&7);

	flags = 0;
	if( volume != DEFAULT_SOUND_PACKET_VOLUME )
		flags |= SND_VOLUME;
	if( attenuation != DEFAULT_SOUND_PACKET_ATTENUATION )
		flags |= SND_ATTENUATION;

	// use the entity origin unless it is a bmodel or explicitly specified
	if( !origin )
	{
		origin = origin_v;

		// the client doesn't know that bmodels have weird origins
		// the origin can also be explicitly set
		if( entity->r.solid == SOLID_BSP )
		{
			for( i=0 ; i<3 ; i++ )
				origin_v[i] = entity->s.origin[i]+0.5 * (entity->r.mins[i]+entity->r.maxs[i]);
		}
		else
		{
			VectorCopy( entity->s.origin, origin_v );
		}
	}

	v = Q_rint( origin[0] ); flags |= ( SND_POS0_8|SND_POS0_16 );
	if( v+256/2 >= 0 && v+256/2 < 256 ) flags &= ~SND_POS0_16; else if( v+65536/2 >= 0 && v+65536/2 < 65536 ) flags &= ~SND_POS0_8;

	v = Q_rint( origin[1] ); flags |= ( SND_POS1_8|SND_POS1_16 );
	if( v+256/2 >= 0 && v+256/2 < 256 ) flags &= ~SND_POS1_16; else if( v+65536/2 >= 0 && v+65536/2 < 65536 ) flags &= ~SND_POS1_8;

	v = Q_rint( origin[2] ); flags |= ( SND_POS2_8|SND_POS2_16 );
	if( v+256/2 >= 0 && v+256/2 < 256 ) flags &= ~SND_POS2_16; else if( v+65536/2 >= 0 && v+65536/2 < 65536 ) flags &= ~SND_POS2_8;

	MSG_WriteByte( &message, svc_sound );
	MSG_WriteByte( &message, flags );
	MSG_WriteByte( &message, soundindex );

	// always send the entity number for channel overrides
	MSG_WriteShort( &message, sendchan );

	if( (flags & (SND_POS0_8|SND_POS0_16)) == SND_POS0_8 )
		MSG_WriteChar( &message, Q_rint(origin[0]) );
	else if( (flags & (SND_POS0_8|SND_POS0_16)) == SND_POS0_16 )
		MSG_WriteShort( &message, Q_rint(origin[0]) );
	else
		MSG_WriteInt3( &message, Q_rint(origin[0]) );

	if( (flags & (SND_POS1_8|SND_POS1_16)) == SND_POS1_8 )
		MSG_WriteChar( &message, Q_rint(origin[1]) );
	else if( (flags & (SND_POS1_8|SND_POS1_16)) == SND_POS1_16 )
		MSG_WriteShort( &message, Q_rint(origin[1]) );
	else
		MSG_WriteInt3( &message, Q_rint(origin[1]) );

	if( (flags & (SND_POS2_8|SND_POS2_16)) == SND_POS2_8 )
		MSG_WriteChar( &message, Q_rint(origin[2]) );
	else if( (flags & (SND_POS2_8|SND_POS2_16)) == SND_POS2_16 )
		MSG_WriteShort( &message, Q_rint(origin[2]) );
	else
		MSG_WriteInt3( &message, Q_rint(origin[2]) );

	if( flags & SND_VOLUME )
		MSG_WriteByte( &message, volume*255 );
	if( flags & SND_ATTENUATION )
		MSG_WriteByte( &message, attenuation*64 );

	// if the sound doesn't attenuate,send it to everyone
	// (global radio chatter, voiceovers, etc)
	if( attenuation == ATTN_NONE )
		use_phs = qfalse;

	// wsw : jal : I never send sounds are reliable (at least by now)
	SV_MulticastMessage( messageData, message.cursize, origin, use_phs ? MULTICAST_PHS : MULTICAST_ALL );
	
	/*if( channel & CHAN_RELIABLE ) {
		if( use_phs )
			SV_Multicast( origin, MULTICAST_PHS_R );
		else
			SV_Multicast( origin, MULTICAST_ALL_R );
	}
	else {
		if( use_phs )
			SV_Multicast( origin, MULTICAST_PHS );
		else
			SV_Multicast( origin, MULTICAST_ALL );
	}*/
}


/*
===============================================================================

FRAME UPDATES

===============================================================================
*/

/*
=======================
SV_RateDrop

Returns true if the client is over its current
bandwidth estimation and should not be sent another packet
=======================
*/
qboolean SV_RateDrop( client_t *c )
{
	return qfalse;
}

/*
=======================
SV_SendClientsFragments
=======================
*/
qboolean SV_SendClientsFragments( void )
{
	int			i;
	client_t	*client;
	qboolean	remaining = qfalse;

	// send a message to each connected client
	for( i=0, client = svs.clients ; i<sv_maxclients->integer; i++, client++ )
	{
		if( !client->state )
			continue;
		if( client->edict && (client->edict->r.svflags & SVF_FAKECLIENT) )
			continue;

		if( client->netchan.unsentFragments )
			Netchan_TransmitNextFragment( &client->netchan );

		if( client->netchan.unsentFragments )
			remaining = qtrue;
	}

	return remaining;
}

//==================
//SV_Netchan_Transmit
//==================
void SV_Netchan_Transmit( client_t *client, msg_t *msg ) {
	size_t		length = 0;	
	int			zerror;

	// if we got here with unsent fragments, fire them all now
	Netchan_PushAllFragments( &client->netchan );

#ifdef BATTLEYE
	SV_AddBattleyeCommandToMessage( client, msg );
#endif

	zerror = Netchan_CompressMessage( msg );
	if( zerror < 0 ) { // it's compression error, just send uncompressed
		Com_DPrintf( "SV_Netchan_Transmit (ignoring compression): Compression error %i\n", zerror );
	}

	//jalfixme: not as precise as it could be
	length = msg->cursize + PACKET_HEADER;
	Netchan_Transmit( &client->netchan, msg );
	client->lastPacketSentTime = svs.realtime;
}

/*
=======================
SV_SendMessageToClient
=======================
*/
void SV_SendMessageToClient( client_t *client, msg_t *msg ) 
{
	static msg_t		message;
	static qbyte		messageData[MAX_MSGLEN];

	if( !client )
		return;

	if( client->edict && (client->edict->r.svflags & SVF_FAKECLIENT) )
		return;

	MSG_Init( &message, messageData, sizeof(messageData) );
	MSG_Clear( &message );

	// write the last client-command we received so it's acknowledged
	MSG_WriteByte( &message, svc_clcack );
	MSG_WriteLong( &message, (unsigned long)client->clientCommandExecuted );

	// write the message data
	if( msg != NULL && msg->cursize ) {
		MSG_Write( &message, msg->data, msg->cursize );
	}
	
	SV_Netchan_Transmit( client, &message );
}

//=======================
//SV_ResetClientFrameCounters
// This is used for a temporary sanity check I'm doing.
//=======================
void SV_ResetClientFrameCounters( void ) {
	int			i;
	client_t	*client;
	for( i=0, client = svs.clients ; i<sv_maxclients->integer; i++, client++ )
	{
		if( !client->state )
			continue;
		if( client->edict && (client->edict->r.svflags & SVF_FAKECLIENT) )
			continue;

			client->lastSentFrameNum = 0;
	}
}

/*
=======================
SV_SendClientDatagram
=======================
*/
void SV_SendClientDatagram( client_t *client )
{
	qbyte		msg_buf[MAX_MSGLEN];
	msg_t		msg;

	if( client->edict && (client->edict->r.svflags & SVF_FAKECLIENT) )
		return;

	// temp warning for me
	if( client->lastSentFrameNum >= sv.framenum ) {
		Com_Printf( "WARNING: Attempting to send a frame snap twice (tell this to jal: lastsent:%i current:%i)\n", client->lastSentFrameNum, sv.framenum );
		//return;
	}

	MSG_Init( &msg, msg_buf, sizeof(msg_buf) );
	MSG_Clear( &msg );
	msg.allowoverflow = qtrue;

	SV_AddReliableCommandsToMessage( client, &msg );

	// send over all the relevant entity_state_t
	// and the player_state_t
	SV_BuildClientFrameSnap( client );
	SV_WriteFrameSnapToClient( client, &msg );
	SV_SendMessageToClient( client, &msg );

	return;
}

/*
=======================
SV_SendClientMessages
=======================
*/
void SV_SendClientMessages( void )
{
	int			i;
	client_t	*client;

	// send a message to each connected client
	for( i=0, client = svs.clients ; i<sv_maxclients->integer; i++, client++ )
	{
		if( !client->state )
			continue;
		if( client->edict && (client->edict->r.svflags & SVF_FAKECLIENT) )
			continue;

		if( client->state == CS_SPAWNED ) 
		{
			SV_SendClientDatagram( client );
		}
		else
		{
			// send pending reliable commands, or send heartbeats for not timing out
			if( client->reliableSequence > client->reliableAcknowledge ||
				svs.realtime - client->lastPacketSentTime > 1000 ) 
			{ 
				MSG_Clear( &tmpMessage );
				SV_AddReliableCommandsToMessage( client, &tmpMessage );
				SV_SendMessageToClient( client, &tmpMessage );
			}
		}
	}
}

