diff options
Diffstat (limited to 'source/sv_send.c')
-rw-r--r-- | source/sv_send.c | 748 |
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 ); + } + +} + |