summaryrefslogtreecommitdiff
path: root/source/sv_user.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_user.c
Initial import of the new Q2PRO tree.
Diffstat (limited to 'source/sv_user.c')
-rw-r--r--source/sv_user.c1207
1 files changed, 1207 insertions, 0 deletions
diff --git a/source/sv_user.c b/source/sv_user.c
new file mode 100644
index 0000000..75c20eb
--- /dev/null
+++ b/source/sv_user.c
@@ -0,0 +1,1207 @@
+/*
+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_user.c -- server code for moving users
+
+#include "sv_local.h"
+#include "mvd_local.h"
+
+edict_t *sv_player;
+
+/*
+============================================================
+
+USER STRINGCMD EXECUTION
+
+sv_client and sv_player will be valid.
+============================================================
+*/
+
+/*
+==================
+SV_BeginDemoServer
+==================
+*/
+void SV_BeginDemoserver (void)
+{
+ char name[MAX_QPATH];
+
+ Com_sprintf (name, sizeof(name), "demos/%s", sv.name);
+ FS_FOpenFile (name, &sv.demofile, FS_MODE_READ);
+ if (!sv.demofile)
+ Com_Error (ERR_DROP, "Couldn't open %s\n", name);
+}
+
+// ============================================================
+
+/*
+================
+SV_CreateBaselines
+
+Entity baselines are used to compress the update messages
+to the clients -- only the fields that differ from the
+baseline will be transmitted
+================
+*/
+void SV_CreateBaselines( void **baselines ) {
+ int i;
+ edict_t *ent;
+ entity_state_t *base, **chunk;
+
+ /* clear baselines from previous level */
+ for( i = 0; i < SV_BASELINES_CHUNKS; i++ ) {
+ base = ( entity_state_t * )baselines[i];
+ if( !base ) {
+ continue;
+ }
+ memset( base, 0, sizeof( *base ) * SV_BASELINES_PER_CHUNK );
+ }
+
+ for( i = 1; i < MAX_EDICTS; i++ ) {
+ ent = EDICT_NUM( i );
+
+ if( !ent->s.modelindex && !ent->s.sound && !ent->s.effects ) {
+ continue;
+ }
+
+ ent->s.number = i;
+
+ chunk = ( entity_state_t ** )&baselines[i >> SV_BASELINES_SHIFT];
+ if( *chunk == NULL ) {
+ *chunk = SV_Mallocz( sizeof( *base ) * SV_BASELINES_PER_CHUNK );
+ }
+
+ base = *chunk + ( i & SV_BASELINES_MASK );
+
+ *base = ent->s;
+ }
+
+}
+
+static void SV_WritePlainConfigstrings( char *configstrings, int start ) {
+ int i;
+ char *string;
+ int length;
+
+ // write a packet full of data
+ string = configstrings + start * MAX_QPATH;
+ for( i = start; i < MAX_CONFIGSTRINGS; i++, string += MAX_QPATH ) {
+ if( !string[0] ) {
+ continue;
+ }
+ length = strlen( string );
+ if( length > MAX_QPATH ) {
+ length = MAX_QPATH;
+ }
+ // check if this configstring will overflow
+ if( msg_write.cursize + length + 64 > sv_client->netchan->maxpacketlen )
+ {
+ SV_ClientAddMessage( sv_client, MSG_RELIABLE|MSG_CLEAR );
+ }
+
+ MSG_WriteByte( svc_configstring );
+ MSG_WriteShort( i );
+ MSG_WriteData( string, length );
+ MSG_WriteByte( 0 );
+ }
+
+ SV_ClientAddMessage( sv_client, MSG_RELIABLE|MSG_CLEAR );
+}
+
+static void SV_WritePlainBaselines( int start ) {
+ int i, j;
+ byte *p;
+ entity_state_t *base;
+
+ // write a packet full of data
+ for( i = start >> SV_BASELINES_SHIFT; i < SV_BASELINES_CHUNKS; i++ ) {
+ p = sv_client->param.baselines[i];
+ if( !p ) {
+ continue;
+ }
+ for( j = 0; j < SV_BASELINES_PER_CHUNK; j++ ) {
+ base = ( entity_state_t * )p;
+ if( base->number ) {
+ // check if this baseline will overflow
+ if( msg_write.cursize + 64 > sv_client->netchan->maxpacketlen )
+ {
+ SV_ClientAddMessage( sv_client, MSG_RELIABLE|MSG_CLEAR );
+ }
+
+ MSG_WriteByte( svc_spawnbaseline );
+ MSG_WriteDeltaEntity( NULL, base, MSG_ES_FORCE );
+ }
+ p += sv_client->param.basesize;
+ }
+ }
+
+ SV_ClientAddMessage( sv_client, MSG_RELIABLE|MSG_CLEAR );
+}
+
+#if USE_ZLIB
+
+static void SV_WriteCompressedGamestate( char *string ) {
+ sizebuf_t *buf = &sv_client->netchan->message;
+ entity_state_t *base;
+ int i, j, length;
+ byte *p;
+ uint16 *patch;
+
+ MSG_WriteByte( svc_gamestate );
+
+ // write configstrings
+ for( i = 0; i < MAX_CONFIGSTRINGS; i++, string += MAX_QPATH ) {
+ if( !string[0] ) {
+ continue;
+ }
+ length = strlen( string );
+ if( length > MAX_QPATH ) {
+ length = MAX_QPATH;
+ }
+
+ MSG_WriteShort( i );
+ MSG_WriteData( string, length );
+ MSG_WriteByte( 0 );
+ }
+ MSG_WriteShort( MAX_CONFIGSTRINGS ); // end of configstrings
+
+ // write baselines
+ for( i = 0; i < SV_BASELINES_CHUNKS; i++ ) {
+ p = sv_client->param.baselines[i];
+ if( !p ) {
+ continue;
+ }
+ for( j = 0; j < SV_BASELINES_PER_CHUNK; j++ ) {
+ base = ( entity_state_t * )p;
+ if( base->number ) {
+ MSG_WriteDeltaEntity( NULL, base, MSG_ES_FORCE );
+ }
+ p += sv_client->param.basesize;
+ }
+ }
+ MSG_WriteShort( 0 ); // end of baselines
+
+ SZ_WriteByte( buf, svc_zpacket );
+ patch = SZ_GetSpace( buf, 2 );
+ SZ_WriteShort( buf, msg_write.cursize );
+
+ deflateReset( &svs.z );
+ svs.z.next_in = msg_write.data;
+ svs.z.avail_in = msg_write.cursize;
+ svs.z.next_out = buf->data + buf->cursize;
+ svs.z.avail_out = buf->maxsize - buf->cursize;
+ SZ_Clear( &msg_write );
+
+ if( deflate( &svs.z, Z_FINISH ) != Z_STREAM_END ) {
+ SV_DropClient( sv_client, "deflate() failed on gamestate" );
+ return;
+ }
+
+ if( sv_debug_send->integer ) {
+ Com_Printf( S_COLOR_BLUE"%s: comp: %lu into %lu\n",
+ sv_client->name, svs.z.total_in, svs.z.total_out );
+ }
+
+ *patch = LittleShort( svs.z.total_out );
+ buf->cursize += svs.z.total_out;
+}
+
+static inline qboolean SV_ZFlush( byte *buffer ) {
+ if( deflate( &svs.z, Z_FINISH ) != Z_STREAM_END ) {
+ return qfalse;
+ }
+
+ if( sv_debug_send->integer ) {
+ Com_Printf( S_COLOR_BLUE"%s: comp: %lu into %lu\n",
+ sv_client->name, svs.z.total_in, svs.z.total_out );
+ }
+
+ MSG_WriteByte( svc_zpacket );
+ MSG_WriteShort( svs.z.total_out );
+ MSG_WriteShort( svs.z.total_in );
+ MSG_WriteData( buffer, svs.z.total_out );
+
+ SV_ClientAddMessage( sv_client, MSG_RELIABLE|MSG_CLEAR );
+
+ return qtrue;
+}
+
+static inline void SV_ZReset( byte *buffer ) {
+ deflateReset( &svs.z );
+ svs.z.next_out = buffer;
+ svs.z.avail_out = sv_client->netchan->maxpacketlen - 5;
+}
+
+static void SV_WriteCompressedConfigstrings( char *string ) {
+ int i, length;
+ byte buffer[MAX_PACKETLEN_WRITABLE];
+
+ SV_ZReset( buffer );
+
+ // write a packet full of data
+ for( i = 0; i < MAX_CONFIGSTRINGS; i++, string += MAX_QPATH ) {
+ if( !string[0] ) {
+ continue;
+ }
+ length = strlen( string );
+ if( length > MAX_QPATH ) {
+ length = MAX_QPATH;
+ }
+
+ // check if this configstring will overflow
+ if( svs.z.avail_out < length + 32 ) {
+ // then flush compressed data
+ if( !SV_ZFlush( buffer ) ) {
+ goto fail;
+ }
+ SV_ZReset( buffer );
+ }
+
+ MSG_WriteByte( svc_configstring );
+ MSG_WriteShort( i );
+ MSG_WriteData( string, length );
+ MSG_WriteByte( 0 );
+
+ svs.z.next_in = msg_write.data;
+ svs.z.avail_in = msg_write.cursize;
+ SZ_Clear( &msg_write );
+
+ if( deflate( &svs.z, Z_SYNC_FLUSH ) != Z_OK ) {
+ goto fail;
+ }
+ }
+
+ // finally flush all remaining compressed data
+ if( !SV_ZFlush( buffer ) ) {
+fail:
+ SV_DropClient( sv_client, "deflate() failed on configstrings" );
+ }
+}
+
+#if 0
+static void SV_WriteCompressedBaselines( void ) {
+ int i, j;
+ entity_state_t *base;
+ byte buffer[MAX_PACKETLEN_WRITABLE];
+ byte *p;
+
+ SV_ZReset( buffer );
+
+ // write a packet full of data
+ for( i = 0; i < SV_BASELINES_CHUNKS; i++ ) {
+ p = sv_client->param.baselines[i];
+ if( !p ) {
+ continue;
+ }
+ for( j = 0; j < SV_BASELINES_PER_CHUNK; j++ ) {
+ base = ( entity_state_t * )p;
+ if( base->number ) {
+ // check if this baseline will overflow
+ if( svs.z.avail_out < 64 ) {
+ // then flush compressed data
+ if( !SV_ZFlush( buffer ) ) {
+ goto fail;
+ }
+ SV_ZReset( buffer );
+ }
+
+ MSG_WriteByte( svc_spawnbaseline );
+ MSG_WriteDeltaEntity( NULL, base, MSG_ES_FORCE );
+
+ svs.z.next_in = msg_write.data;
+ svs.z.avail_in = msg_write.cursize;
+ SZ_Clear( &msg_write );
+
+ if( deflate( &svs.z, Z_SYNC_FLUSH ) != Z_OK ) {
+ goto fail;
+ }
+ }
+ p += sv_client->param.basesize;
+ }
+ }
+
+ // finally flush all remaining compressed data
+ if( !SV_ZFlush( buffer ) ) {
+fail:
+ SV_DropClient( sv_client, "deflate() failed on baselines" );
+ }
+}
+#endif
+
+#endif // USE_ZLIB
+
+/*
+================
+SV_New_f
+
+Sends the first message from the server to a connected client.
+This will be sent on the initial connection and upon each server load.
+================
+*/
+static void SV_New_f( void ) {
+ edict_t *ent;
+ char *gamedir, *mapname, *strings;
+ int i, entnum, clientNum;
+
+ Com_DPrintf( "New() from %s\n", sv_client->name );
+
+ if( sv_client->state < cs_connected ) {
+ Com_DPrintf( "Going from cs_assigned to cs_connected for %s\n",
+ sv_client->name );
+ sv_client->state = cs_connected;
+ sv_client->lastmessage = svs.realtime; // don't timeout
+ } else if( sv_client->state > cs_connected ) {
+ Com_DPrintf( "New not valid -- already primed\n" );
+ return;
+ }
+
+ // demo servers just dump the file message
+ if( sv.state == ss_demo ) {
+ SV_BeginDemoserver ();
+ return;
+ }
+
+ //
+ // serverdata needs to go over for all types of servers
+ // to make sure the protocol is right, and to set the gamedir
+ //
+
+ if( sv.state == ss_broadcast ) {
+ mvd_t *mvd = mvd_clients[sv_client->number].mvd;
+
+ gamedir = mvd->gamedir;
+ mapname = mvd->configstrings[CS_NAME];
+ strings = ( char * )mvd->configstrings;
+ clientNum = mvd->clientNum;
+ for( i = 0; i < SV_BASELINES_CHUNKS; i++ ) {
+ sv_client->param.baselines[i] = mvd->baselines[i];
+ }
+ sv_client->param.basesize = sizeof( entityStateEx_t );
+ sv_client->param.maxplayers = mvd->maxclients;
+ } else {
+ gamedir = fs_game->string;
+ mapname = sv.configstrings[CS_NAME];
+ strings = ( char * )sv.configstrings;
+ if( sv.state >= ss_cinematic ) {
+ clientNum = -1;
+ } else {
+ clientNum = sv_client->number;
+ SV_CreateBaselines( sv_client->param.baselines );
+ sv_client->param.basesize = sizeof( entity_state_t );
+ sv_client->param.maxplayers = sv_maxclients->integer;
+ }
+ }
+
+ // send the serverdata
+ MSG_WriteByte( svc_serverdata );
+ MSG_WriteLong( sv_client->protocol );
+ MSG_WriteLong( sv.spawncount );
+ MSG_WriteByte( sv.attractloop );
+ MSG_WriteString( gamedir );
+ MSG_WriteShort( clientNum );
+ MSG_WriteString( mapname );
+
+ // write protocol-specific stuff
+ switch( sv_client->protocol ) {
+ case PROTOCOL_VERSION_R1Q2:
+ MSG_WriteByte( 0 ); // enhanced
+ MSG_WriteShort( PROTOCOL_VERSION_R1Q2_MINOR );
+ MSG_WriteByte( 0 ); // advancedDeltas
+ MSG_WriteByte( sv_strafejump_hack->integer ? 1 : 0 );
+ break;
+ case PROTOCOL_VERSION_Q2PRO:
+ MSG_WriteShort( PROTOCOL_VERSION_Q2PRO_MINOR );
+ MSG_WriteByte( svs.gametype );
+ MSG_WriteByte( sv_strafejump_hack->integer ? 1 : 0 );
+ MSG_WriteByte( sv_qwmod->integer );
+ break;
+ case PROTOCOL_VERSION_MVD:
+ MSG_WriteShort( PROTOCOL_VERSION_MVD_MINOR );
+ break;
+ default:
+ break;
+ }
+
+ SV_ClientAddMessage( sv_client, MSG_RELIABLE|MSG_CLEAR );
+
+ // send version string request
+ if( !sv_client->versionString ) {
+ MSG_WriteByte( svc_stufftext );
+ MSG_WriteString( "cmd \177c version $version\n" );
+ SV_ClientAddMessage( sv_client, MSG_RELIABLE|MSG_CLEAR );
+ }
+
+ Com_DPrintf( "Going from cs_connected to cs_primed for %s\n",
+ sv_client->name );
+ sv_client->state = cs_primed;
+
+ if( sv.state >= ss_cinematic ) {
+ return; // no real map
+ }
+
+ // set up the entity for the client
+ entnum = sv_client->number + 1;
+ ent = EDICT_NUM( entnum );
+ ent->s.number = entnum;
+ sv_client->edict = ent;
+ sv_player = ent;
+
+ memset( &sv_client->lastcmd, 0, sizeof( sv_client->lastcmd ) );
+
+#if USE_ZLIB
+ if( !sv_client->zlib ) {
+ SV_WritePlainConfigstrings( strings, 0 );
+ SV_WritePlainBaselines( 0 );
+ } else {
+ if( sv_client->netchan->type == NETCHAN_NEW ) {
+ SV_WriteCompressedGamestate( strings );
+ } else {
+#if 0
+ SV_WriteCompressedConfigstrings( strings );
+ SV_WriteCompressedBaselines();
+#else
+ // FIXME: Z_SYNC_FLUSH is not efficient for baselines
+ SV_WriteCompressedConfigstrings( strings );
+ SV_WritePlainBaselines( 0 );
+#endif
+ }
+ }
+#else // USE_ZLIB
+ SV_WritePlainConfigstrings( strings, 0 );
+ SV_WritePlainBaselines( 0 );
+#endif // !USE_ZLIB
+
+ // send next command
+ MSG_WriteByte( svc_stufftext );
+ MSG_WriteString( va( "precache %i\n", sv.spawncount ) );
+ SV_ClientAddMessage( sv_client, MSG_RELIABLE|MSG_CLEAR );
+}
+
+/*
+==================
+SV_Begin_f
+==================
+*/
+static void SV_Begin_f( void ) {
+ Com_DPrintf( "Begin() from %s\n", sv_client->name );
+
+ // handle the case of a level changing while a client was connecting
+ if( sv_client->state < cs_primed ) {
+ Com_DPrintf( "Begin not valid -- not yet primed\n" );
+ SV_New_f();
+ return;
+ }
+ if( sv_client->state > cs_primed ) {
+ Com_DPrintf( "Begin not valid -- already spawned\n" );
+ return;
+ }
+
+ Com_DPrintf( "Going from cs_primed to cs_spawned for %s\n",
+ sv_client->name );
+ sv_client->state = cs_spawned;
+ sv_client->sendTime = 0;
+ sv_client->commandMsec = 1800;
+ sv_client->surpressCount = 0;
+
+ // call the game begin function
+ ge->ClientBegin( sv_player );
+}
+
+//=============================================================================
+
+#define MAX_DOWNLOAD_CHUNK 1024
+
+/*
+==================
+SV_NextDownload_f
+==================
+*/
+static void SV_NextDownload_f( void ) {
+ int r;
+ int percent;
+ int size;
+
+ if ( !sv_client->download )
+ return;
+
+ r = sv_client->downloadsize - sv_client->downloadcount;
+ if ( r > MAX_DOWNLOAD_CHUNK )
+ r = MAX_DOWNLOAD_CHUNK;
+
+ MSG_WriteByte( svc_download );
+ MSG_WriteShort( r );
+
+ sv_client->downloadcount += r;
+ size = sv_client->downloadsize;
+ if( !size )
+ size = 1;
+ percent = sv_client->downloadcount*100/size;
+ MSG_WriteByte( percent );
+ MSG_WriteData( sv_client->download + sv_client->downloadcount - r, r );
+
+ if( sv_client->downloadcount == sv_client->downloadsize ) {
+ FS_FreeFile( sv_client->download );
+ sv_client->download = NULL;
+ }
+
+ SV_ClientAddMessage( sv_client, MSG_RELIABLE|MSG_CLEAR );
+
+}
+
+static void SV_DownloadFailed( void ) {
+ MSG_WriteByte( svc_download );
+ MSG_WriteShort( -1 );
+ MSG_WriteByte( 0 );
+ SV_ClientAddMessage( sv_client, MSG_RELIABLE|MSG_CLEAR );
+}
+
+/*
+==================
+SV_BeginDownload_f
+==================
+*/
+static void SV_BeginDownload_f( void ) {
+ char name[MAX_QPATH];
+ int downloadsize;
+ int offset = 0;
+ cvar_t *allow;
+ int length;
+ char *filename;
+
+ length = Q_ClearStr( name, Cmd_Argv( 1 ), sizeof( name ) );
+ Q_strlwr( name );
+
+ if( Cmd_Argc() > 2 )
+ offset = atoi( Cmd_Argv( 2 ) ); // downloaded offset
+
+ // hacked by zoid to allow more conrol over download
+ // first off, no .. or global allow check
+ if( !allow_download->integer
+ // check for empty paths
+ || !length
+ // check for illegal negative offsets
+ || offset < 0
+ // don't allow anything with .. path
+ || strstr( name, ".." )
+ // leading dots, slashes, etc are no good
+ || !Q_ispath( name[0] )
+ // trailing dots, slashes, etc are no good
+ || !Q_ispath( name[ length - 1 ] )
+ // back slashes should be never sent
+ || strchr( name, '\\' )
+ // colons are bad also
+ || strchr( name, ':' )
+ // MUST be in a subdirectory
+ || !strchr( name, '/' ) )
+ {
+ SV_DownloadFailed();
+ return;
+ }
+
+ if( strncmp( name, "players/", 8 ) == 0 ) {
+ allow = allow_download_players;
+ } else if( strncmp( name, "models/", 7 ) == 0 ) {
+ allow = allow_download_models;
+ } else if( strncmp( name, "sound/", 6 ) == 0 ) {
+ allow = allow_download_sounds;
+ } else if( strncmp( name, "maps/", 5 ) == 0 ) {
+ allow = allow_download_maps;
+ } else {
+ allow = allow_download_other;
+ }
+
+ if( !allow->integer ) {
+ Com_DPrintf( "Refusing download of %s to %s\n", name, sv_client->name );
+ SV_DownloadFailed();
+ return;
+ }
+
+ if( sv_client->download ) {
+ Com_DPrintf( "Closing existing download for %s (should not happen)\n", sv_client->name );
+ FS_FreeFile( sv_client->download );
+ sv_client->download = NULL;
+ }
+
+ filename = name;
+
+ downloadsize = FS_LoadFileEx( filename, NULL, FS_FLAG_RAW );
+
+ if( downloadsize == -1 || downloadsize == 0
+ // special check for maps, if it came from a pak file, don't allow
+ // download ZOID
+ || ( allow == allow_download_maps
+ && allow_download_maps->integer < 2
+ && FS_LastFileFromPak() ) )
+ {
+ Com_DPrintf( "Couldn't download %s to %s\n", name, sv_client->name );
+ SV_DownloadFailed();
+ return;
+ }
+
+ if( offset > downloadsize ) {
+ Com_DPrintf( "Refusing download, %s has wrong version of %s (%d > %d)\n",
+ sv_client->name, name, offset, downloadsize );
+ SV_ClientPrintf( sv_client, PRINT_HIGH, "File size differs from server.\n"
+ "Please delete the corresponding .tmp file from your system.\n" );
+ SV_DownloadFailed();
+ return;
+ }
+
+ if( offset == downloadsize ) {
+ Com_DPrintf( "Refusing download, %s already has %s (%d bytes)\n",
+ sv_client->name, name, offset );
+ MSG_WriteByte( svc_download );
+ MSG_WriteShort( 0 );
+ MSG_WriteByte( 100 );
+ SV_ClientAddMessage( sv_client, MSG_RELIABLE|MSG_CLEAR );
+ return;
+ }
+
+ sv_client->downloadsize = FS_LoadFileEx( filename, ( void ** )&sv_client->download, FS_FLAG_RAW );
+ sv_client->downloadcount = offset;
+
+ Com_DPrintf( "Downloading %s to %s\n", name, sv_client->name );
+
+ SV_NextDownload_f();
+
+}
+
+static void SV_StopDownload_f( void ) {
+ int size, percent;
+
+ if( !sv_client->download ) {
+ return;
+ }
+
+ size = sv_client->downloadsize;
+ if( !size ) {
+ percent = 0;
+ } else {
+ percent = sv_client->downloadcount*100/size;
+ }
+
+ MSG_WriteByte( svc_download );
+ MSG_WriteShort( -1 );
+ MSG_WriteByte( percent );
+ SV_ClientAddMessage( sv_client, MSG_RELIABLE|MSG_CLEAR );
+
+ Com_DPrintf( "Download for %s stopped by user request\n", sv_client->name );
+ FS_FreeFile( sv_client->download );
+ sv_client->download = NULL;
+}
+
+//============================================================================
+
+
+/*
+=================
+SV_Disconnect_f
+
+The client is going to disconnect, so remove the connection immediately
+=================
+*/
+static void SV_Disconnect_f( void ) {
+ SV_DropClient( sv_client, NULL );
+}
+
+
+/*
+==================
+SV_ShowServerinfo_f
+
+Dumps the serverinfo info string
+==================
+*/
+static void SV_ShowServerinfo_f( void ) {
+ Com_BeginRedirect( RD_CLIENT, sv_outputbuf, SV_OUTPUTBUF_LENGTH, SV_FlushRedirect );
+ Info_Print( Cvar_Serverinfo() );
+ Com_EndRedirect();
+}
+
+
+void SV_Nextserver( void ) {
+ client_t *client;
+ char *v;
+
+ //ZOID, ss_pic can be nextserver'd in coop mode
+ if (sv.state == ss_game || (sv.state == ss_pic && !Cvar_VariableValue("coop")))
+ return; // can't nextserver while playing a normal game
+
+ FOR_EACH_CLIENT( client ) {
+ SV_ClientReset( client );
+ }
+
+ v = sv_nextserver->string;
+ if (!v[0])
+ Cbuf_AddText ("killserver\n");
+ else
+ {
+ Cbuf_AddText (v);
+ Cbuf_AddText ("\n");
+ }
+ Cvar_Set ("nextserver","");
+}
+
+/*
+==================
+SV_Nextserver_f
+
+A cinematic has completed or been aborted by a client, so move
+to the next server.
+==================
+*/
+static void SV_Nextserver_f( void ) {
+ if( !NET_IsLocalAddress( &sv_client->netchan->remote_address ) ) {
+ Com_DPrintf( "Nextserver() from remote client, from %s\n",
+ sv_client->name );
+ return;
+ }
+ if ( atoi( Cmd_Argv( 1 ) ) != sv.spawncount ) {
+ Com_DPrintf( "Nextserver() from wrong level, from %s\n",
+ sv_client->name );
+ return; // leftover from last server
+ }
+
+ Com_DPrintf( "Nextserver() from %s\n", sv_client->name );
+
+ SV_Nextserver ();
+}
+
+static void SV_NoGameData_f( void ) {
+ sv_client->nodata ^= 1;
+}
+
+static void SV_CvarResult_f( void ) {
+ char *c, *v;
+
+ c = Cmd_Argv( 1 );
+ v = Cmd_RawArgsFrom( 2 );
+ if( !strcmp( c, "version" ) ) {
+ if( sv_client->versionString ) {
+ goto unrequested;
+ }
+ if( dedicated->integer ) {
+ Com_Printf( "%s@%s: %s\n", sv_client->name,
+ NET_AdrToString( &sv_client->netchan->remote_address ), v );
+ }
+ sv_client->versionString = SV_CopyString( v );
+ return;
+ }
+
+unrequested:
+ Com_DPrintf( "Unrequested SV_CvarResult_f() from %s: %s %s\n",
+ sv_client->name, c, v );
+}
+
+static ucmd_t ucmds[] = {
+ // auto issued
+ { "new", SV_New_f },
+ { "begin", SV_Begin_f },
+ { "baselines", NULL },
+ { "configstrings", NULL },
+
+ { "nextserver", SV_Nextserver_f },
+
+ { "disconnect", SV_Disconnect_f },
+
+ // issued by hand at client consoles
+ { "info", SV_ShowServerinfo_f },
+
+ { "download", SV_BeginDownload_f },
+ { "nextdl", SV_NextDownload_f },
+ { "stopdl", SV_StopDownload_f },
+
+ { "\177c", SV_CvarResult_f },
+ { "nogamedata", SV_NoGameData_f },
+
+ { NULL, NULL }
+};
+
+/*
+==================
+SV_ExecuteUserCommand
+==================
+*/
+static void SV_ExecuteUserCommand( const char *s ) {
+ const ucmd_t *u;
+ char *c;
+
+ Cmd_TokenizeString( s, qfalse );
+ sv_player = sv_client->edict;
+
+ c = Cmd_Argv( 0 );
+ if( !c[0] ) {
+ return;
+ }
+
+ if( ( u = Com_Find( ucmds, c ) ) != NULL ) {
+ if( u->func ) {
+ u->func();
+ }
+ return;
+ }
+ if( sv.state == ss_game || sv.state == ss_broadcast ) {
+ ge->ClientCommand( sv_player );
+ }
+}
+
+/*
+===========================================================================
+
+USER CMD EXECUTION
+
+===========================================================================
+*/
+
+static int net_drop;
+
+
+/*
+==================
+SV_ClientThink
+==================
+*/
+void SV_ClientThink( client_t *cl, usercmd_t *cmd ) {
+ cl->commandMsec -= cmd->msec;
+
+ if( cl->commandMsec < 0 && sv_enforcetime->integer ) {
+ Com_DPrintf( "commandMsec underflow from %s: %d\n",
+ cl->name, cl->commandMsec );
+ return;
+ }
+
+ ge->ClientThink( cl->edict, cmd );
+}
+
+/*
+==================
+SV_OldClientExecuteMove
+==================
+*/
+static void SV_OldClientExecuteMove( void ) {
+ usercmd_t oldest, oldcmd, newcmd;
+ int lastframe;
+
+ if( sv_client->protocol == PROTOCOL_VERSION_DEFAULT ) {
+ MSG_ReadByte(); /* skip over checksum */
+ }
+
+ lastframe = MSG_ReadLong();
+ if( sv_client->protocol == PROTOCOL_VERSION_MVD ) {
+ sv_client->lastframe = lastframe;
+ return;
+ }
+ if( lastframe != sv_client->lastframe ) {
+ sv_client->lastframe = lastframe;
+ if( lastframe > 0 ) {
+ sv_client->frame_latency[lastframe & LATENCY_MASK] =
+ svs.realtime - sv_client->frames[lastframe & UPDATE_MASK].senttime;
+ }
+ }
+
+
+ MSG_ReadDeltaUsercmd( NULL, &oldest );
+ MSG_ReadDeltaUsercmd( &oldest, &oldcmd );
+ MSG_ReadDeltaUsercmd( &oldcmd, &newcmd );
+
+ if( sv_client->state != cs_spawned ) {
+ sv_client->lastframe = -1;
+ return;
+ }
+
+ if( net_drop > 2 ) {
+ sv_client->frameflags |= FF_CLIENTPRED;
+// Com_DPrintf( "%s: net_drop %i\n", sv_client->name, net_drop );
+ }
+
+ if( net_drop < 20 ) {
+ while( net_drop > 2 ) {
+ SV_ClientThink( sv_client, &sv_client->lastcmd );
+ net_drop--;
+ }
+ if( net_drop > 1 )
+ SV_ClientThink( sv_client, &oldest );
+
+ if( net_drop > 0 )
+ SV_ClientThink( sv_client, &oldcmd );
+
+ }
+ SV_ClientThink( sv_client, &newcmd );
+
+ sv_client->lastcmd = newcmd;
+}
+
+
+
+/*
+==================
+SV_NewClientExecuteMove
+==================
+*/
+static void SV_NewClientExecuteMove( int c ) {
+ usercmd_t cmds[MAX_PACKET_FRAMES][MAX_PACKET_USERCMDS];
+ usercmd_t *lastcmd, *cmd;
+ int lastframe;
+ int numCmds[MAX_PACKET_FRAMES], numDups;
+ int i, j, lightlevel;
+
+ numDups = c >> SVCMD_BITS;
+ c &= SVCMD_MASK;
+ if( c == clc_move_nodelta ) {
+ lastframe = -1;
+ } else {
+ lastframe = MSG_ReadLong();
+ }
+
+ if( lastframe != sv_client->lastframe ) {
+ sv_client->lastframe = lastframe;
+ if( lastframe != -1 ) {
+ sv_client->frame_latency[lastframe & LATENCY_MASK] =
+ svs.realtime - sv_client->frames[lastframe & UPDATE_MASK].senttime;
+ }
+ }
+
+ if( numDups > MAX_PACKET_FRAMES - 1 ) {
+ SV_DropClient( sv_client, "too many frames in packet" );
+ return;
+ }
+
+ lightlevel = MSG_ReadByte();
+
+ /* read them all */
+ lastcmd = NULL;
+ for( i = 0; i <= numDups; i++ ) {
+ numCmds[i] = MSG_ReadBits( 5 );
+ if( numCmds[i] == -1 ) {
+ SV_DropClient( sv_client, "read past end of message" );
+ return;
+ }
+ if( numCmds[i] >= MAX_PACKET_USERCMDS ) {
+ SV_DropClient( sv_client, "too many usercmds in frame" );
+ return;
+ }
+ for( j = 0; j < numCmds[i]; j++ ) {
+ if( msg_read.readcount > msg_read.cursize ) {
+ SV_DropClient( sv_client, "read past end of message" );
+ return;
+ }
+ cmd = &cmds[i][j];
+ MSG_ReadDeltaUsercmd_Enhanced( lastcmd, cmd );
+ cmd->lightlevel = lightlevel;
+ lastcmd = cmd;
+ }
+ }
+ if( sv_client->state != cs_spawned ) {
+ sv_client->lastframe = -1;
+ return;
+ }
+
+ if( q_unlikely( !lastcmd ) ) {
+ return; // should never happen
+ }
+
+ if( net_drop > numDups ) {
+ sv_client->frameflags |= FF_CLIENTPRED;
+// Com_DPrintf( "%s: net_drop %i\n", sv_client->name, net_drop );
+ }
+
+ if( net_drop < 20 ) {
+ /* run lastcmd multiple times if no backups available */
+ while( net_drop > numDups ) {
+ SV_ClientThink( sv_client, &sv_client->lastcmd );
+ net_drop--;
+ }
+
+ /* run backup cmds, if any */
+ while( net_drop > 0 ) {
+ i = numDups - net_drop;
+ for( j = 0; j < numCmds[i]; j++ ) {
+ SV_ClientThink( sv_client, &cmds[i][j] );
+ }
+ net_drop--;
+ }
+
+ }
+
+ /* run new cmds */
+ for( j = 0; j < numCmds[numDups]; j++ ) {
+ SV_ClientThink( sv_client, &cmds[numDups][j] );
+ }
+
+ sv_client->lastcmd = *lastcmd;
+}
+
+/*
+===================
+SV_ExecuteClientMessage
+
+The current net_message is parsed for the given client
+===================
+*/
+void SV_ExecuteClientMessage( client_t *client ) {
+ int c;
+ char *s;
+ qboolean move_issued;
+ int stringCmdCount;
+ int userinfoUpdateCount;
+
+ sv_client = client;
+ sv_player = sv_client->edict;
+
+ // only allow one move command
+ move_issued = qfalse;
+ stringCmdCount = 0;
+ userinfoUpdateCount = 0;
+
+ net_drop = client->netchan->dropped;
+ if( net_drop ) {
+ client->frameflags |= FF_CLIENTDROP;
+ }
+
+ while( 1 ) {
+ if( msg_read.readcount > msg_read.cursize ) {
+ SV_DropClient( client, "read past end of message" );
+ break;
+ }
+
+ c = MSG_ReadByte();
+ if( c == -1 )
+ break;
+
+ switch( c & SVCMD_MASK ) {
+ default:
+ badbyte:
+ SV_DropClient( client, "unknown command byte" );
+ break;
+
+ case clc_nop:
+ break;
+
+ case clc_userinfo:
+ s = MSG_ReadString();
+
+ // malicious users may try sending too many userinfo updates
+ if( userinfoUpdateCount == MAX_PACKET_USERINFOS ) {
+ Com_DPrintf( "Too many userinfos from %s\n", client->name );
+ break;
+ }
+
+ SV_UpdateUserinfo( s );
+ userinfoUpdateCount++;
+ break;
+
+ case clc_move:
+ if( move_issued ) {
+ SV_DropClient( client, "multiple clc_move commands in packet" );
+ break; // someone is trying to cheat...
+ }
+
+ move_issued = qtrue;
+
+ SV_OldClientExecuteMove();
+ break;
+
+ case clc_stringcmd:
+ s = MSG_ReadString();
+
+ Com_DPrintf( "ClientCommand( %s ): %s\n", client->name, s );
+
+ // malicious users may try using too many string commands
+ if( stringCmdCount == MAX_PACKET_STRINGCMDS ) {
+ Com_DPrintf( "Too many stringcmds from %s\n", client->name );
+ break;
+ }
+ SV_ExecuteUserCommand( s );
+ stringCmdCount++;
+ break;
+
+ // r1q2 specific operations
+ case clc_setting: {
+ uint16 idx, value;
+
+ if( client->protocol < PROTOCOL_VERSION_R1Q2 ) {
+ goto badbyte;
+ }
+
+ idx = MSG_ReadShort();
+ value = MSG_ReadShort();
+ if( idx < CLS_MAX ) {
+ client->settings[idx] = value;
+ }
+ }
+ break;
+
+
+ // q2pro specific operations
+ case clc_move_nodelta:
+ case clc_move_batched:
+ if( client->protocol != PROTOCOL_VERSION_Q2PRO ) {
+ goto badbyte;
+ }
+
+ if( move_issued ) {
+ SV_DropClient( client, "multiple clc_move commands in packet" );
+ break; // someone is trying to cheat...
+ }
+
+ move_issued = qtrue;
+ SV_NewClientExecuteMove( c );
+ break;
+
+ case clc_userinfo_delta: {
+ char *key, *value;
+ char buffer[MAX_INFO_STRING];
+
+ if( client->protocol != PROTOCOL_VERSION_Q2PRO ) {
+ goto badbyte;
+ }
+
+ key = MSG_ReadString();
+ value = MSG_ReadString();
+
+ // malicious users may try sending too many userinfo updates
+ if( userinfoUpdateCount == MAX_PACKET_USERINFOS ) {
+ Com_DPrintf( "Too many userinfos from %s\n", client->name );
+ break;
+ }
+ userinfoUpdateCount++;
+
+ strcpy( buffer, client->userinfo );
+ if( !Info_AttemptSetValueForKey( buffer, key, value ) ) {
+ SV_ClientPrintf( client, PRINT_HIGH,
+ "Malformed userinfo update supplied. Ignored.\n" );
+ break;
+ }
+
+ SV_UpdateUserinfo( buffer );
+ }
+ break;
+ }
+
+ if( client->state < cs_assigned ) {
+ break; // disconnect command
+ }
+ }
+
+ sv_client = NULL;
+ sv_player = NULL;
+}
+
+
+