/*
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_send.c

#include "server.h"
#include "mvd.h"

static int		demo_msglen;
static byte		demo_data[MAX_PACKETLEN];

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

MISC

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

char sv_outputbuf[SV_OUTPUTBUF_LENGTH];

void SV_FlushRedirect( int sv_redirected, char *outputbuf ) {
	if( sv_redirected == RD_PACKET ) {
		Netchan_OutOfBandPrint( NS_SERVER, &net_from, "print\n%s", outputbuf );
	} else if( sv_redirected == RD_CLIENT ) {
		MSG_WriteByte( svc_print );
		MSG_WriteByte( PRINT_HIGH );
		MSG_WriteString( outputbuf );
		SV_ClientAddMessage( sv_client, MSG_RELIABLE|MSG_CLEAR );
	}
}

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

Returns qtrue if the client is over its current
bandwidth estimation and should not be sent another packet
=======================
*/
static qboolean SV_RateDrop( client_t *client ) {
	int		total;
	int		i;

	// never drop over the loopback
	if( NET_IsLocalAddress( &client->netchan->remote_address ) ) {
		return qfalse;
	}
	if( sv_lan_force_rate->integer && NET_IsLANAddress(
                &client->netchan->remote_address ) )
    {
		return qfalse;
	}

	total = 0;
	for( i = 0; i < RATE_MESSAGES; i++ ) {
		total += client->message_size[i];
	}

	if( total > client->rate ) {
		client->surpressCount++;
		client->message_size[sv.framenum % RATE_MESSAGES] = 0;
		return qtrue;
	}

	return qfalse;
}

void SV_CalcSendTime( client_t *client, int messageSize ) {
	int delta;

	if( messageSize == -1 ) {
		return;
	}

	// never drop over the loopback
	if( NET_IsLocalAddress( &client->netchan->remote_address ) ) {
		client->sendTime = 0;
		return;
	}

	if( sv_lan_force_rate->integer && NET_IsLANAddress(
                &client->netchan->remote_address ) )
    {
		client->sendTime = 0;
		return;
	}

	client->message_size[sv.framenum % RATE_MESSAGES] = messageSize;

	delta = messageSize * 1000 / client->rate;
	client->sendTime = svs.realtime + delta;
}

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

EVENT MESSAGES

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


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

Sends text across to be displayed if the level passes.
NOT archived in MVD stream.
=================
*/
void SV_ClientPrintf( client_t *client, int level, const char *fmt, ... ) {
	va_list		argptr;
	char		string[MAX_STRING_CHARS];
	
	if( level < client->messagelevel )
		return;
	
	va_start( argptr, fmt );
	Q_vsnprintf( string, sizeof( string ), fmt, argptr );
	va_end( argptr );

	MSG_WriteByte( svc_print );
	MSG_WriteByte( level );
	MSG_WriteString( string );

	SV_ClientAddMessage( client, MSG_RELIABLE|MSG_CLEAR );

}

/*
=================
SV_BroadcastPrintf

Sends text to all active clients, including MVD clients.
NOT archived in MVD stream.
=================
*/
void SV_BroadcastPrintf( int level, const char *fmt, ... ) {
	va_list		argptr;
	char		string[MAX_STRING_CHARS];
	client_t	*client;
	int			i;

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

	MSG_WriteByte( svc_print );
	MSG_WriteByte( level );
	MSG_WriteString( string );

	// echo to console
	if( dedicated->integer ) {
		// mask off high bits
		for( i = 0; string[i]; i++ )
			string[i] &= 127;
		Com_Printf( "%s", string );
	}

    FOR_EACH_CLIENT( client ) {
		if( client->state != cs_spawned )
			continue;
		if( level < client->messagelevel )
			continue;
		SV_ClientAddMessage( client, MSG_RELIABLE );
	}

	SZ_Clear( &msg_write );
}

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

Sends command to all active clients, including MVD clients.
NOT archived in MVD stream.
=================
*/
void SV_BroadcastCommand( const char *fmt, ... ) {
	va_list		argptr;
	char		string[MAX_STRING_CHARS];
	client_t	*client;
	
	if( !sv.state )
		return;
	va_start( argptr, fmt );
	Q_vsnprintf( string, sizeof( string ), fmt, argptr );
	va_end( argptr );

	MSG_WriteByte( svc_stufftext );
	MSG_WriteString( string );

    FOR_EACH_CLIENT( client ) {
		if( client->state < cs_connected )
			continue;
		SV_ClientAddMessage( client, MSG_RELIABLE );
	}

	SZ_Clear( &msg_write );
}


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

Sends the contents of the write buffer to a subset of the clients,
then clears the write buffer.

Archived in MVD stream.

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_Multicast( vec3_t origin, multicast_t to ) {
	client_t	*client;
	byte		*mask;
	cleaf_t		*leaf;
	int			cluster;
	int			area1, area2;
	int			flags;
	vec3_t		org;
	player_state_t	*ps;

	flags = 0;

	switch( to ) {
	case MULTICAST_ALL_R:
		flags |= MSG_RELIABLE;	// intentional fallthrough
	case MULTICAST_ALL:
		area1 = 0;
		cluster = 0;
		mask = NULL;
		break;

	case MULTICAST_PHS_R:
		flags |= MSG_RELIABLE;	// intentional fallthrough
	case MULTICAST_PHS:
		leaf = CM_PointLeaf( &sv.cm, origin );
		area1 = CM_LeafArea( leaf );
		cluster = CM_LeafCluster( leaf );
		mask = CM_ClusterPHS( &sv.cm, cluster );
		break;

	case MULTICAST_PVS_R:
		flags |= MSG_RELIABLE;	// intentional fallthrough
	case MULTICAST_PVS:
		leaf = CM_PointLeaf( &sv.cm, origin );
		area1 = CM_LeafArea( leaf );
		cluster = CM_LeafCluster( leaf );
		mask = CM_ClusterPVS( &sv.cm, cluster );
		break;

	default:
		mask = NULL;
		area1 = 0;
		Com_Error( ERR_DROP, "SV_Multicast: bad to: %i", to );
	}

	// send the data to all relevent clients
    FOR_EACH_CLIENT( client ) {
		if( client->state < cs_connected ) {
			continue;
		}
        if( client->protocol == PROTOCOL_VERSION_MVD ) {
            client->AddMulticast( client, origin, to );
            continue;
        }
		if( !( flags & MSG_RELIABLE ) ) {
			/* do not send unreliables to connecting clients */
			if( client->state != cs_spawned || client->download ||
                    client->nodata )
            {
				continue; 
			}
		}

		if( mask ) {
			// find the client's PVS
			ps = &client->edict->client->ps;
			VectorMA( ps->viewoffset, 0.125f, ps->pmove.origin, org );
			leaf = CM_PointLeaf( &sv.cm, org );
			area2 = CM_LeafArea( leaf );
			if( !CM_AreasConnected( &sv.cm, area1, area2 ) )
				continue;
			cluster = CM_LeafCluster( leaf );
			if( !Q_IsBitSet( mask, cluster ) ) {
				continue;
			}
		}

		SV_ClientAddMessage( client, flags );

	}

	// if doing a serverrecord, store everything
	MVD_RecMulticast( origin, to );

	/* clear the buffer */
	SZ_Clear( &msg_write );

}


/*  
==================
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, float timeofs )
{       
	int			sendchan;
    int			flags;
	int			ent;
	vec3_t		origin_v;
    vec3_t      mid;
	qboolean	use_phs;

	if( volume < 0 || volume > 1.0 )
		Com_Error( ERR_DROP, "SV_StartSound: volume = %f", volume );

	if( attenuation < 0 || attenuation > 4 )
		Com_Error( ERR_DROP, "SV_StartSound: attenuation = %f", attenuation );

	if( timeofs < 0 || timeofs > 0.255 )
		Com_Error( ERR_DROP, "SV_StartSound: timeofs = %f", timeofs );

	ent = NUM_FOR_EDICT( entity );

	if( channel & 8 ) {	// 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;

	// always send the entity number for channel overrides
	flags |= SND_ENT;

	if( timeofs )
		flags |= SND_OFFSET;

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

	// the client doesn't know that bmodels have weird origins
	// the origin can also be explicitly set
	if( ent == 0 || ( entity->svflags & SVF_NOCLIENT ) ||
            entity->solid == SOLID_BSP || origin )
		flags |= SND_POS;

	// use the entity origin unless it is a bmodel or explicitly specified
	if( !origin ) {
		origin = origin_v;
		if( ent == 0 ) {
			VectorClear( origin_v );
		} else if( entity->solid == SOLID_BSP ) {
            VectorAdd( entity->mins, entity->maxs, mid );
            VectorMA( entity->s.origin, 0.5f, mid, origin_v );
		} else {
			VectorCopy( entity->s.origin, origin_v );
		}
	}

	MSG_WriteByte( svc_sound );
	MSG_WriteByte( flags );
	MSG_WriteByte( soundindex );

	if( flags & SND_VOLUME )
		MSG_WriteByte( volume * 255 );
	if( flags & SND_ATTENUATION )
		MSG_WriteByte( attenuation * 64 );
	if( flags & SND_OFFSET )
		MSG_WriteByte( timeofs * 1000 );

	if( flags & SND_ENT )
		MSG_WriteShort( sendchan );

	if( flags & SND_POS )
		MSG_WritePos( origin );

	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 );
	}
}

/*
=======================
SV_ClientAddMessage

Adds contents of the current write buffer to client's message list.
Does NOT clean the buffer for multicast delivery purpose,
unless told otherwise.
=======================
*/
void SV_ClientAddMessage( client_t *client, int flags ) {
	if( !msg_write.cursize ) {
		return;
	}

	client->AddMessage( client, msg_write.data, msg_write.cursize,
		( flags & MSG_RELIABLE ) ? qtrue : qfalse );

	if( sv_debug_send->integer > 1 ) {
		if( flags & MSG_RELIABLE ) {
			Com_Printf( "AddRel( %s ): %i bytes\n",
				client->name, msg_write.cursize );
		} else {
			Com_Printf( "AddMsg( %s ): %i bytes\n",
				client->name, msg_write.cursize );
		}
	}

	if( flags & MSG_CLEAR ) {
		SZ_Clear( &msg_write );
	}
}

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

FRAME UPDATES - OLD CLIENTS

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

void SV_OldClientAddMessage( client_t *client, byte *data,
							  int length, qboolean reliable )
{
	clientMessage_t	*msg;

	if( length > client->netchan->maxpacketlen ) {
		if( reliable ) {
			SV_DropClient( client, "oversize reliable message" );
		} else {
            Com_DPrintf( "Dumped oversize unreliable for %s\n", client->name );
        }
		return;
	}

    msg = ( clientMessage_t * )client->freeMsgList.first;
	if( !msg ) {
		Com_WPrintf( "Out of message slots for %s!\n", client->name );
		if( reliable ) {
            SV_OldClientClearMessages( client );
			SV_DropClient( client, "no slot for reliable message" );
		}
		return;
	}

	if( length > MSGQUEUE_MALLOC_TRESHOLD ) {
		msg->data = SV_Malloc( length );
	} else {
		msg->data = msg->localBuffer;
	}

	memcpy( msg->data, data, length );
	msg->cursize = length;
	msg->reliable = reliable;

	List_DeleteElem( msg );
	List_Append( &client->usedMsgList, msg );
}

static void SV_OldClientRemoveMessage( client_t *client, clientMessage_t *msg )
{
	if( msg->cursize > MSGQUEUE_MALLOC_TRESHOLD ) {
		Z_Free( msg->data );
	}

	msg->data = NULL;
	msg->cursize = 0;

	List_DeleteElem( msg );
    List_Append( &client->freeMsgList, msg );
}

void SV_OldClientClearMessages( client_t *client ) {
	listElem_t	*elem, *next;

	for( elem = client->usedMsgList.first; elem; elem = next ) {	
        next = elem->next;
		SV_OldClientRemoveMessage( client, ( clientMessage_t * )elem );
	}

}


/*
=======================
SV_OldClient_SendReliableMessages

This should be the only place data is
ever written to client->netchan.message
=======================
*/
void SV_OldClientWriteReliableMessages( client_t *client, int maxSize ) {
	listElem_t	*elem, *next;
	clientMessage_t *msg;
	int count;

	if( client->netchan->reliable_length ) {
		if( sv_debug_send->integer > 1 ) {
			Com_Printf( "SndRel( %s ): unacked reliable\n", client->name );
		}
		return;	// there is still outgoing reliable message pending
	}

	// find at least one reliable message to send
	count = 0;
	for( elem = client->usedMsgList.first; elem; elem = next ) {
        next = elem->next;
		msg = ( clientMessage_t * )elem;
		if( !msg->reliable ) {
			continue;
		}

		// stop if this msg doesn't fit (reliables must be delivered in order)
		if( client->netchan->message.cursize + msg->cursize > maxSize ) {
			if( !count ) {
				Com_WPrintf( "Overflow on the first reliable message "
					"for %s (should not happen).\n", client->name );
			}
			break;
		}

		if( sv_debug_send->integer > 1 ) {
			Com_Printf( "SndRel( %s ): %i bytes\n", client->name,
                    msg->cursize );
		}

		SZ_Write( &client->netchan->message, msg->data, msg->cursize );
		SV_OldClientRemoveMessage( client, msg );
		count++;
	}

}


/*
=======================
OldClient_SendDatagram
=======================
*/
void SV_OldClientWriteDatagram( client_t *client ) {
	listElem_t	*elem, *next;
	clientMessage_t *msg;
	int cursize, maxsize;
	sizebuf_t	compressed;
	byte		compressed_data[MAX_PACKETLEN_WRITABLE];

	maxsize = client->netchan->maxpacketlen;
	if( client->netchan->reliable_length ) {
		// there is still unacked reliable message pending
		maxsize -= client->netchan->reliable_length;
	} else {
		// find at least one reliable message to send
		// and make sure to reserve space for it
		for( elem = client->usedMsgList.first; elem; elem = elem->next ) {
			msg = ( clientMessage_t * )elem;
			if( msg->reliable ) {
				maxsize -= msg->cursize; 
				break;
			}
		}
	}

	// send over all the relevant entity_state_t
	// and the player_state_t
	client->WriteFrame( client );
	if( msg_write.cursize > maxsize ) {
		if( sv_debug_send->integer ) {
			Com_Printf( S_COLOR_BLUE"Frame overflowed for %s, ", client->name );
		}
		if( client->protocol != PROTOCOL_VERSION_DEFAULT ) {
			SZ_Init( &compressed, compressed_data, sizeof( compressed_data ) );

			if( sv_debug_send->integer ) {
				Com_Printf( "deflating data... " );
			}

			// compress the frame
			if( Com_ZLibDeflateBuffer( &msg_write, &compressed ) ) {
				cursize = msg_write.cursize;
				SZ_Clear( &msg_write );

				if( compressed.cursize <= maxsize ) {
					if( sv_debug_send->integer ) {
						Com_Printf( "succeeded: %i --> %i\n",
                                cursize, compressed.cursize );
					}

					MSG_WriteByte( svc_zpacket );
					MSG_WriteShort( compressed.cursize );
					MSG_WriteShort( cursize );
					MSG_WriteData( compressed.data, compressed.cursize );
				} else {
					if( sv_debug_send->integer ) {
						Com_Printf( "failed: %i --> %i\n",
                                cursize, compressed.cursize );
					}
				}
			} else {
				if( sv_debug_send->integer ) {
					Com_Printf( "deflate failed\n" );
				}
				SZ_Clear( &msg_write );
			}
		} else {
			if( sv_debug_send->integer ) {
				Com_Printf( "clearing frame\n" );
			}
			SZ_Clear( &msg_write );
		}
	}

	// now try to write unreliable messages
	// it is necessary for this to be after the WriteEntities
	// so that entity references will be current
	if( msg_write.cursize + 4 <= maxsize ) {
		// temp entities first
		for( elem = client->usedMsgList.first; elem; elem = next ) {
            next = elem->next;
			msg = ( clientMessage_t * )elem;
			if( msg->reliable ) {
				continue;
			}
			if( msg->data[0] != svc_temp_entity ) {
				continue;
			}
			// ignore some low-priority effects, these checks come from r1q2
			if( msg->data[1] == TE_BLOOD || msg->data[1] == TE_SPLASH ) {
				continue;
			}
			if( msg->data[1] == TE_GUNSHOT || msg->data[1] == TE_BULLET_SPARKS
                    || msg->data[1] == TE_SHOTGUN )
            {
				continue;
			}

			// if this msg fits, write it
			if( msg_write.cursize + msg->cursize <= maxsize ) {
				MSG_WriteData( msg->data, msg->cursize );
			}

			SV_OldClientRemoveMessage( client, msg );
		}

		if( msg_write.cursize + 4 <= maxsize ) {
			// then sounds
            for( elem = client->usedMsgList.first; elem; elem = next ) {
                next = elem->next;
				msg = ( clientMessage_t * )elem;
				if( msg->reliable ) {
					continue;
				}
				if( msg->data[0] != svc_sound ) {
					continue;
				}

				// if this msg fits, write it
				if( msg_write.cursize + msg->cursize <= maxsize ) {
					MSG_WriteData( msg->data, msg->cursize );
				}

				SV_OldClientRemoveMessage( client, msg );
			}

			if( msg_write.cursize + 4 <= maxsize ) {
				// then everything else left
				for( elem = client->usedMsgList.first; elem; elem = next ) {
                    next = elem->next;
					msg = ( clientMessage_t * )elem;
					if( msg->reliable ) {
						continue;
					}

					// if this msg fits, write it
					if( msg_write.cursize + msg->cursize <= maxsize ) {
						MSG_WriteData( msg->data, msg->cursize );
					}

					SV_OldClientRemoveMessage( client, msg );
				}
			}
		}
	}
	
	// clear all unreliable messages still left
	for( elem = client->usedMsgList.first; elem; elem = next ) {
        next = elem->next;
		msg = ( clientMessage_t * )elem;
		if( !msg->reliable ) {
			SV_OldClientRemoveMessage( client, msg );
		}

	}

	SV_OldClientWriteReliableMessages( client, client->netchan->maxpacketlen -
            msg_write.cursize );

	// send the datagram
	cursize = client->netchan->Transmit( client->netchan, msg_write.cursize,
            msg_write.data );

	// record the size for rate estimation
	SV_CalcSendTime( client, cursize );

	SZ_Clear( &msg_write );

}

void SV_OldClientAddUnicast( client_t *client, int clientNum ) {
    qboolean reliable = ( clientNum >> 7 ) & 1;
    byte data[MAX_PACKETLEN];
    sizebuf_t buf;

    SZ_Init( &buf, data, sizeof( data ) );

    SZ_WriteByte( &buf, svc_unicast );
    SZ_WriteByte( &buf, clientNum );
    SZ_WriteShort( &buf, msg_write.cursize );
    SZ_Write( &buf, msg_write.data, msg_write.cursize );

	SV_OldClientAddMessage( client, buf.data, buf.cursize, reliable );

}

void SV_OldClientAddMulticast( client_t *client, const vec3_t origin,
        multicast_t to )
{
    qboolean reliable = to >= MULTICAST_ALL_R ? qtrue : qfalse;
    byte data[MAX_PACKETLEN];
    sizebuf_t buf;
    int size;

    SZ_Init( &buf, data, sizeof( data ) );

	size = ( to << 12 ) | ( msg_write.cursize & 0xFFF );

    SZ_WriteByte( &buf, svc_multicast );
    SZ_WriteShort( &buf, size );

    /* write exact position for PVS/PHS checks */
    if( to != MULTICAST_ALL && to != MULTICAST_ALL_R ) {
        SZ_WriteExactPos( &buf, origin );
    }
    
    SZ_Write( &buf, msg_write.data, msg_write.cursize );

	SV_OldClientAddMessage( client, buf.data, buf.cursize, reliable );
}


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

FRAME UPDATES - Q2PRO CLIENTS

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

void SV_NewClientAddMessage( client_t *client, byte *data,
							  int length, qboolean reliable )
{
	sizebuf_t *buf = reliable ? &client->netchan->message : &client->datagram;
	SZ_Write( buf, data, length );
}

void SV_NewClientWriteDatagram( client_t *client ) {
	int cursize;
	int i;

	// send over all the relevant entity_state_t
	// and the player_state_t
	client->WriteFrame( client );

	if( msg_write.overflowed ) {
		// should never happen
		Com_WPrintf( "Frame overflowed for %s\n", client->name );
		SZ_Clear( &msg_write );
	}

	// copy the accumulated multicast datagram
	// for this client out to the message
	// it is necessary for this to be after the WriteEntities
	// so that entity references will be current
	if( client->datagram.overflowed ) {
		Com_WPrintf( "Datagram overflowed for %s\n", client->name );
	} else if( msg_write.cursize + client->datagram.cursize >
            msg_write.maxsize )
    {
		Com_WPrintf( "Dumping datagram for %s\n", client->name );
	} else {
		MSG_WriteData( client->datagram.data, client->datagram.cursize );
	}

	SZ_Clear( &client->datagram );

	if( sv_pad_packets->integer ) {
        int pad = msg_write.cursize + sv_pad_packets->integer;

        if( pad > msg_write.maxsize ) {
            pad = msg_write.maxsize;
        }
	    for( i = msg_write.cursize; i < pad; i++ ) {
		    MSG_WriteByte( svc_nop );
	    }
    }

	// send the datagram
	cursize = client->netchan->Transmit( client->netchan, msg_write.cursize,
            msg_write.data );

	// record the size for rate estimation
	SV_CalcSendTime( client, cursize );

	// clear the write buffer
	SZ_Clear( &msg_write );
}

void SV_NewClientAddUnicast( client_t *client, int clientNum ) {
    sizebuf_t *buf;

    if( ( clientNum >> 7 ) & 1 ) {
        buf = &client->netchan->message;
    } else {
        buf = &client->datagram;
    }

    SZ_WriteByte( buf, svc_unicast );
    SZ_WriteByte( buf, clientNum );
    SZ_WriteShort( buf, msg_write.cursize );
    SZ_Write( buf, msg_write.data, msg_write.cursize );
}

void SV_NewClientAddMulticast( client_t *client, const vec3_t origin,
        multicast_t to )
{
    int size;
    sizebuf_t *buf;

    if( to >= MULTICAST_ALL_R ) {
        buf = &client->netchan->message;
    } else {
        buf = &client->datagram;
    }

	size = ( to << 12 ) | ( msg_write.cursize & 0xFFF );

    SZ_WriteByte( buf, svc_multicast );
    SZ_WriteShort( buf, size );

    /* write exact position for PVS/PHS checks */
    if( to != MULTICAST_ALL && to != MULTICAST_ALL_R ) {
        SZ_WriteExactPos( buf, origin );
    }
    
    SZ_Write( buf, msg_write.data, msg_write.cursize );
}

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

COMMON STUFF

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

/*
==================
SV_DemoCompleted
==================
*/
void SV_DemoCompleted( void ) {
	Com_DPrintf( "SV_DemoCompleted()\n" );
	if( sv.demofile ) {
		FS_FCloseFile( sv.demofile );
		sv.demofile = 0;
	}
	SV_Nextserver();
}


/*
=======================
SV_SendClientMessages

Called each game frame, sends svc_frame messages to spawned clients only.
Clients in earlier connection state are handled in SV_SendAsyncPackets.
=======================
*/
void SV_SendClientMessages( void ) {
	client_t	*client;
	int			msglen;
	int			r;

	demo_msglen = 0;

	// read the next demo message if needed
	if( sv.state == ss_demo && sv.demofile ) {
		// get the next message
		r = FS_Read( &msglen, 4, sv.demofile );
		if( r != 4 ) {
			SV_DemoCompleted();
			return;
		}
		msglen = LittleLong( msglen );
		if( msglen == -1 ) {
			SV_DemoCompleted();
			return;
		}
		if( msglen < 0 || msglen > MAX_PACKETLEN )
			Com_Error( ERR_DROP, "SV_SendClientMessages: bad demo msglen" );
		r = FS_Read( demo_data, msglen, sv.demofile );
		if( r != msglen ) {
			SV_DemoCompleted();
			return;
		}
		demo_msglen = msglen;
		
	}

	if( sv.state != ss_game && sv.state != ss_broadcast ) {
		// send a demo message to each connected client
        FOR_EACH_CLIENT( client ) {
			if( client->netchan->type == NETCHAN_OLD ) {
				SV_OldClientWriteReliableMessages( client,
                        client->netchan->maxpacketlen );
			}
			client->netchan->Transmit( client->netchan,
                    demo_msglen, demo_data );
		}
		return;
	}

	// send a message to each connected client
    FOR_EACH_CLIENT( client ) {
		if( client->state != cs_spawned || client->download || client->nodata )
			continue;

		// if the reliable message overflowed,
		// drop the client (should never happen)
		if( client->netchan->message.overflowed ) {
			SZ_Clear( &client->netchan->message );
			SV_DropClient( client, "overflowed" );
		}

		// don't overrun bandwidth
		if( SV_RateDrop( client ) ) {
			if( sv_debug_send->integer ) {
				Com_Printf( "Frame %d surpressed for %s\n", sv.framenum,
                        client->name );
			}
			client->surpressCount++;
			continue;
		}

		// don't write any frame data until all fragments are sent
		if( client->netchan->fragment_pending ) {
			msglen = client->netchan->TransmitNextFragment( client->netchan );
			SV_CalcSendTime( client, msglen );
			continue;
		}

        client->BuildFrame( client );
	    client->WriteDatagram( client );
	}
}

