summaryrefslogtreecommitdiff
path: root/source/sv_send.c
diff options
context:
space:
mode:
authorAndrey Nazarov <skuller@skuller.net>2007-08-14 20:18:08 +0000
committerAndrey Nazarov <skuller@skuller.net>2007-08-14 20:18:08 +0000
commitf294db4ccf45f6274e65260dd6f9a2c5faa94313 (patch)
treee8cf1ba2bfe9c8417eec17faf912442f52fc4ef2 /source/sv_send.c
Initial import of the new Q2PRO tree.
Diffstat (limited to 'source/sv_send.c')
-rw-r--r--source/sv_send.c748
1 files changed, 748 insertions, 0 deletions
diff --git a/source/sv_send.c b/source/sv_send.c
new file mode 100644
index 0000000..8d7d13e
--- /dev/null
+++ b/source/sv_send.c
@@ -0,0 +1,748 @@
+/*
+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 "sv_local.h"
+#include "mvd_local.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( !client->rate ) {
+ 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( !client->rate ) {
+ 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.
+NOT archived in MVD stream.
+=================
+*/
+void SV_BroadcastCommand( const char *fmt, ... ) {
+ va_list argptr;
+ char string[MAX_STRING_CHARS];
+ client_t *client;
+
+ 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 ) {
+ 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 leafnum;
+ 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;
+ leafnum = 0;
+ cluster = 0;
+ mask = NULL;
+ break;
+ case MULTICAST_PHS_R:
+ flags |= MSG_RELIABLE; // intentional fallthrough
+ case MULTICAST_PHS:
+ leaf = CM_PointLeaf( &sv.cm, origin );
+ leafnum = leaf - sv.cm.cache->leafs;
+ 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 );
+ leafnum = leaf - sv.cm.cache->leafs;
+ area1 = CM_LeafArea( leaf );
+ cluster = CM_LeafCluster( leaf );
+ mask = CM_ClusterPVS( &sv.cm, cluster );
+ break;
+ default:
+ 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_primed ) {
+ continue;
+ }
+ // do not send unreliables to connecting clients
+ if( !( flags & MSG_RELIABLE ) && ( 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( svs.mvdummy ) {
+ SV_MvdMulticast( leafnum, mvd_multicast_all + to );
+ }
+
+ // clear the buffer
+ SZ_Clear( &msg_write );
+}
+
+
+
+/*
+=======================
+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( sv_debug_send->integer > 1 ) {
+ Com_Printf( S_COLOR_BLUE"%s: add%c: %d\n", client->name,
+ ( flags & MSG_RELIABLE ) ? 'r' : 'u', msg_write.cursize );
+ }
+
+ if( !msg_write.cursize ) {
+ return;
+ }
+
+ if( client->state > cs_zombie ) {
+ client->AddMessage( client, msg_write.data, msg_write.cursize,
+ ( flags & MSG_RELIABLE ) ? qtrue : qfalse );
+ }
+
+ if( flags & MSG_CLEAR ) {
+ SZ_Clear( &msg_write );
+ }
+}
+
+static inline void SV_PacketizedRemove( client_t *client, pmsg_t *msg ) {
+ List_Delete( &msg->entry );
+
+ if( msg->cursize > MSG_TRESHOLD ) {
+ Z_Free( msg );
+ } else {
+ List_Append( &client->freemsg, &msg->entry );
+ }
+}
+
+void SV_PacketizedClear( client_t *client ) {
+ pmsg_t *msg, *next;
+
+ LIST_FOR_EACH_SAFE( pmsg_t, msg, next, &client->usedmsg, entry ) {
+ SV_PacketizedRemove( client, msg );
+ }
+
+ LIST_FOR_EACH_SAFE( pmsg_t, msg, next, &client->relmsg, entry ) {
+ SV_PacketizedRemove( client, msg );
+ }
+}
+
+
+pmsg_t *SV_PacketizedAdd( client_t *client, byte *data,
+ int length, qboolean reliable )
+{
+ pmsg_t *msg;
+
+ if( length > MSG_TRESHOLD ) {
+ msg = SV_Malloc( sizeof( *msg ) + length - MSG_TRESHOLD );
+ } else {
+ if( LIST_EMPTY( &client->freemsg ) ) {
+ Com_WPrintf( "Out of message slots for %s!\n", client->name );
+ if( reliable ) {
+ SV_PacketizedClear( client );
+ SV_DropClient( client, "no slot for reliable message" );
+ }
+ return NULL;
+ }
+ msg = LIST_FIRST( pmsg_t, &client->freemsg, entry );
+ List_Remove( &msg->entry );
+ }
+
+ memcpy( msg->data, data, length );
+ msg->cursize = length;
+
+ if( reliable ) {
+ List_Append( &client->relmsg, &msg->entry );
+ } else {
+ List_Append( &client->usedmsg, &msg->entry );
+ }
+
+ return msg;
+}
+
+/*
+===============================================================================
+
+FRAME UPDATES - DEFAULT, R1Q2 AND Q2PRO CLIENTS (OLD NETCHAN)
+
+===============================================================================
+*/
+
+
+void SV_OldClientAddMessage( client_t *client, byte *data,
+ int length, qboolean reliable )
+{
+ 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;
+ }
+
+ SV_PacketizedAdd( client, data, length, reliable );
+}
+
+/*
+=======================
+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 ) {
+ pmsg_t *msg, *next;
+ int count;
+
+ if( client->netchan->reliable_length ) {
+ if( sv_debug_send->integer > 1 ) {
+ Com_Printf( S_COLOR_BLUE"%s: unacked\n", client->name );
+ }
+ return; // there is still outgoing reliable message pending
+ }
+
+ // find at least one reliable message to send
+ count = 0;
+ LIST_FOR_EACH_SAFE( pmsg_t, msg, next, &client->relmsg, entry ) {
+ // 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( S_COLOR_BLUE"%s: wrt%d: %d\n",
+ client->name, count, msg->cursize );
+ }
+
+ SZ_Write( &client->netchan->message, msg->data, msg->cursize );
+ SV_PacketizedRemove( client, msg );
+ count++;
+ }
+}
+
+
+/*
+=======================
+OldClient_SendDatagram
+=======================
+*/
+void SV_OldClientWriteDatagram( client_t *client ) {
+ pmsg_t *msg, *next;
+ int cursize, maxsize;
+
+ 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
+ if( !LIST_EMPTY( &client->relmsg ) ) {
+ msg = LIST_FIRST( pmsg_t, &client->relmsg, entry );
+ maxsize -= msg->cursize;
+ }
+ }
+
+ // 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: %d > %d\n",
+ client->name, msg_write.cursize, maxsize );
+ }
+ 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
+ LIST_FOR_EACH_SAFE( pmsg_t, msg, next, &client->usedmsg, entry ) {
+ 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_PacketizedRemove( client, msg );
+ }
+
+ if( msg_write.cursize + 4 <= maxsize ) {
+ // then sounds
+ LIST_FOR_EACH_SAFE( pmsg_t, msg, next, &client->usedmsg, entry ) {
+ 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_PacketizedRemove( client, msg );
+ }
+
+ if( msg_write.cursize + 4 <= maxsize ) {
+ // then everything else left
+ LIST_FOR_EACH_SAFE( pmsg_t, msg, next, &client->usedmsg, entry ) {
+ // if this msg fits, write it
+ if( msg_write.cursize + msg->cursize <= maxsize ) {
+ MSG_WriteData( msg->data, msg->cursize );
+ }
+
+ SV_PacketizedRemove( client, msg );
+ }
+ }
+ }
+ }
+
+ // write at least one reliable message
+ 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_OldClientFinishFrame( client_t *client ) {
+ pmsg_t *msg, *next;
+
+ // clear all unreliable messages still left
+ LIST_FOR_EACH_SAFE( pmsg_t, msg, next, &client->usedmsg, entry ) {
+ SV_PacketizedRemove( client, msg );
+ }
+}
+
+/*
+===============================================================================
+
+FRAME UPDATES - Q2PRO CLIENTS (NEW NETCHAN)
+
+===============================================================================
+*/
+
+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 really 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 );
+ }
+
+ 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_NewClientFinishFrame( client_t *client ) {
+ SZ_Clear( &client->datagram );
+}
+
+/*
+===============================================================================
+
+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 )
+ goto finish;
+
+ // 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++;
+ goto finish;
+ }
+
+ // 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 );
+ goto finish;
+ }
+
+ // build the new frame
+ client->BuildFrame( client );
+
+ // write it
+ client->WriteDatagram( client );
+
+finish:
+ // clear the unreliable datagram
+ client->FinishFrame( client );
+ }
+
+}
+