diff options
Diffstat (limited to 'src/client/cl_main.c')
-rw-r--r-- | src/client/cl_main.c | 4418 |
1 files changed, 4418 insertions, 0 deletions
diff --git a/src/client/cl_main.c b/src/client/cl_main.c new file mode 100644 index 0000000..d545fc9 --- /dev/null +++ b/src/client/cl_main.c @@ -0,0 +1,4418 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous 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. + +Tremulous 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 Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// cl_main.c -- client main loop + +#include "client.h" +#include <limits.h> + +#ifdef USE_MUMBLE +#include "libmumblelink.h" +#endif + +#ifdef USE_MUMBLE +cvar_t *cl_useMumble; +cvar_t *cl_mumbleScale; +#endif + +#ifdef USE_VOIP +cvar_t *cl_voipUseVAD; +cvar_t *cl_voipVADThreshold; +cvar_t *cl_voipSend; +cvar_t *cl_voipSendTarget; +cvar_t *cl_voipGainDuringCapture; +cvar_t *cl_voipCaptureMult; +cvar_t *cl_voipShowMeter; +cvar_t *cl_voip; +#endif + +cvar_t *cl_nodelta; +cvar_t *cl_debugMove; + +cvar_t *cl_noprint; +cvar_t *cl_motd; + +cvar_t *rcon_client_password; +cvar_t *rconAddress; + +cvar_t *cl_timeout; +cvar_t *cl_maxpackets; +cvar_t *cl_packetdup; +cvar_t *cl_timeNudge; +cvar_t *cl_showTimeDelta; +cvar_t *cl_freezeDemo; + +cvar_t *cl_shownet; +cvar_t *cl_showSend; +cvar_t *cl_timedemo; +cvar_t *cl_timedemoLog; +cvar_t *cl_autoRecordDemo; +cvar_t *cl_aviFrameRate; +cvar_t *cl_aviMotionJpeg; +cvar_t *cl_forceavidemo; + +cvar_t *cl_freelook; +cvar_t *cl_sensitivity; + +cvar_t *cl_mouseAccel; +cvar_t *cl_mouseAccelOffset; +cvar_t *cl_mouseAccelStyle; +cvar_t *cl_showMouseRate; + +cvar_t *m_pitch; +cvar_t *m_yaw; +cvar_t *m_forward; +cvar_t *m_side; +cvar_t *m_filter; + +cvar_t *j_pitch; +cvar_t *j_yaw; +cvar_t *j_forward; +cvar_t *j_side; +cvar_t *j_pitch_axis; +cvar_t *j_yaw_axis; +cvar_t *j_forward_axis; +cvar_t *j_side_axis; + +cvar_t *cl_activeAction; + +cvar_t *cl_motdString; + +cvar_t *cl_allowDownload; +cvar_t *com_downloadPrompt; +cvar_t *cl_conXOffset; +cvar_t *cl_inGameVideo; + +cvar_t *cl_serverStatusResendTime; +cvar_t *cl_trn; + +cvar_t *cl_lanForcePackets; + +cvar_t *cl_guidServerUniq; + +cvar_t *cl_consoleKeys; + +cvar_t *cl_gamename; + +clientActive_t cl; +clientConnection_t clc; +clientStatic_t cls; +vm_t *cgvm; + +// Structure containing functions exported from refresh DLL +refexport_t re; + +ping_t cl_pinglist[MAX_PINGREQUESTS]; + +typedef struct serverStatus_s +{ + char string[BIG_INFO_STRING]; + netadr_t address; + int time, startTime; + qboolean pending; + qboolean print; + qboolean retrieved; +} serverStatus_t; + +serverStatus_t cl_serverStatusList[MAX_SERVERSTATUSREQUESTS]; +int serverStatusCount; + +#if defined __USEA3D && defined __A3D_GEOM + void hA3Dg_ExportRenderGeom (refexport_t *incoming_re); +#endif + +extern void GLimp_Minimize(void); +extern void SV_BotFrame( int time ); +void CL_CheckForResend( void ); +void CL_ShowIP_f(void); +void CL_ServerStatus_f(void); +void CL_ServerStatusResponse( netadr_t from, msg_t *msg ); + +/* +=============== +CL_CDDialog + +Called by Com_Error when a cd is needed +=============== +*/ +void CL_CDDialog( void ) { + cls.cddialog = qtrue; // start it next frame +} + +#ifdef USE_MUMBLE +static +void CL_UpdateMumble(void) +{ + vec3_t pos, forward, up; + float scale = cl_mumbleScale->value; + float tmp; + + if(!cl_useMumble->integer) + return; + + // !!! FIXME: not sure if this is even close to correct. + AngleVectors( cl.snap.ps.viewangles, forward, NULL, up); + + pos[0] = cl.snap.ps.origin[0] * scale; + pos[1] = cl.snap.ps.origin[2] * scale; + pos[2] = cl.snap.ps.origin[1] * scale; + + tmp = forward[1]; + forward[1] = forward[2]; + forward[2] = tmp; + + tmp = up[1]; + up[1] = up[2]; + up[2] = tmp; + + if(cl_useMumble->integer > 1) { + fprintf(stderr, "%f %f %f, %f %f %f, %f %f %f\n", + pos[0], pos[1], pos[2], + forward[0], forward[1], forward[2], + up[0], up[1], up[2]); + } + + mumble_update_coordinates(pos, forward, up); +} +#endif + + +#ifdef USE_VOIP +static +void CL_UpdateVoipIgnore(const char *idstr, qboolean ignore) +{ + if ((*idstr >= '0') && (*idstr <= '9')) { + const int id = atoi(idstr); + if ((id >= 0) && (id < MAX_CLIENTS)) { + clc.voipIgnore[id] = ignore; + CL_AddReliableCommand(va("voip %s %d", + ignore ? "ignore" : "unignore", id), qfalse); + Com_Printf("VoIP: %s ignoring player #%d\n", + ignore ? "Now" : "No longer", id); + return; + } + } + Com_Printf("VoIP: invalid player ID#\n"); +} + +static +void CL_UpdateVoipGain(const char *idstr, float gain) +{ + if ((*idstr >= '0') && (*idstr <= '9')) { + const int id = atoi(idstr); + if (gain < 0.0f) + gain = 0.0f; + if ((id >= 0) && (id < MAX_CLIENTS)) { + clc.voipGain[id] = gain; + Com_Printf("VoIP: player #%d gain now set to %f\n", id, gain); + } + } +} + +/* +================ +CL_VoipParseTargets + +Sets clc.voipTarget{1,2,3} by asking the cgame to produce a string and then +parsing it as a series of client numbers +Perhaps it would be better to allow the cgame to set the three integers +directly, but this way we can change the net protocol without changing the +vmcall +================ +*/ +static void CL_VoipParseTargets( void ) +{ + const char *target = cl_voipSendTarget->string; + intptr_t p = VM_Call( cgvm, CG_VOIP_STRING ); + + if( p ) + target = VM_ExplicitArgPtr( cgvm, p ); + + if( !target[ 0 ] || Q_stricmp( target, "all" ) == 0 ) + clc.voipTarget1 = clc.voipTarget2 = clc.voipTarget3 = 0x7FFFFFFF; + else if( Q_stricmp( target, "none" ) == 0 ) + clc.voipTarget1 = clc.voipTarget2 = clc.voipTarget3 = 0; + else + { + char *end; + int val; + clc.voipTarget1 = clc.voipTarget2 = clc.voipTarget3 = 0; + + while( 1 ) + { + while( *target && !isdigit( *target ) ) + target++; + if( !*target ) + break; + + val = strtol( target, &end, 10 ); + assert( target != end ); + if( val < 0 || val >= MAX_CLIENTS ) + Com_Printf( S_COLOR_YELLOW "WARNING: VoIP target %d is not a valid " + "client number\n", val ); + else if( val < 31 ) + clc.voipTarget1 |= 1 << val; + else if( ( val -= 31 ) < 31 ) + clc.voipTarget2 |= 1 << val; + else if( ( val -= 31 ) < 31 ) + clc.voipTarget3 |= 1 << val; + target = end; + } + } +} + +void CL_Voip_f( void ) +{ + const char *cmd = Cmd_Argv(1); + const char *reason = NULL; + + if (cls.state != CA_ACTIVE) + reason = "Not connected to a server"; + else if (!clc.speexInitialized) + reason = "Speex not initialized"; + else if (!cl_connectedToVoipServer) + reason = "Server doesn't support VoIP"; + + if (reason != NULL) { + Com_Printf("VoIP: command ignored: %s\n", reason); + return; + } + + if (strcmp(cmd, "ignore") == 0) { + CL_UpdateVoipIgnore(Cmd_Argv(2), qtrue); + } else if (strcmp(cmd, "unignore") == 0) { + CL_UpdateVoipIgnore(Cmd_Argv(2), qfalse); + } else if (strcmp(cmd, "gain") == 0) { + if (Cmd_Argc() > 3) { + CL_UpdateVoipGain(Cmd_Argv(2), atof(Cmd_Argv(3))); + } else if (Q_isanumber(Cmd_Argv(2))) { + int id = atoi(Cmd_Argv(2)); + if (id >= 0 && id < MAX_CLIENTS) { + Com_Printf("VoIP: current gain for player #%d " + "is %f\n", id, clc.voipGain[id]); + } else { + Com_Printf("VoIP: invalid player ID#\n"); + } + } else { + Com_Printf("usage: voip gain <playerID#> [value]\n"); + } + } else if (strcmp(cmd, "muteall") == 0) { + Com_Printf("VoIP: muting incoming voice\n"); + CL_AddReliableCommand("voip muteall", qfalse); + clc.voipMuteAll = qtrue; + } else if (strcmp(cmd, "unmuteall") == 0) { + Com_Printf("VoIP: unmuting incoming voice\n"); + CL_AddReliableCommand("voip unmuteall", qfalse); + clc.voipMuteAll = qfalse; + } else { + Com_Printf("usage: voip [un]ignore <playerID#>\n" + " voip [un]muteall\n" + " voip gain <playerID#> [value]\n"); + } +} + + +static +void CL_VoipNewGeneration(void) +{ + // don't have a zero generation so new clients won't match, and don't + // wrap to negative so MSG_ReadLong() doesn't "fail." + clc.voipOutgoingGeneration++; + if (clc.voipOutgoingGeneration <= 0) + clc.voipOutgoingGeneration = 1; + clc.voipPower = 0.0f; + clc.voipOutgoingSequence = 0; +} + +/* +=============== +CL_CaptureVoip + +Record more audio from the hardware if required and encode it into Speex + data for later transmission. +=============== +*/ +static +void CL_CaptureVoip(void) +{ + const float audioMult = cl_voipCaptureMult->value; + const qboolean useVad = (cl_voipUseVAD->integer != 0); + qboolean initialFrame = qfalse; + qboolean finalFrame = qfalse; + +#if USE_MUMBLE + // if we're using Mumble, don't try to handle VoIP transmission ourselves. + if (cl_useMumble->integer) + return; +#endif + + if (!clc.speexInitialized) + return; // just in case this gets called at a bad time. + + if (clc.voipOutgoingDataSize > 0) + return; // packet is pending transmission, don't record more yet. + + if (cl_voipUseVAD->modified) { + Cvar_Set("cl_voipSend", (useVad) ? "1" : "0"); + cl_voipUseVAD->modified = qfalse; + } + + if ((useVad) && (!cl_voipSend->integer)) + Cvar_Set("cl_voipSend", "1"); // lots of things reset this. + + if (cl_voipSend->modified) { + qboolean dontCapture = qfalse; + if (cls.state != CA_ACTIVE) + dontCapture = qtrue; // not connected to a server. + else if (!cl_connectedToVoipServer) + dontCapture = qtrue; // server doesn't support VoIP. + else if (clc.demoplaying) + dontCapture = qtrue; // playing back a demo. + else if ( cl_voip->integer == 0 ) + dontCapture = qtrue; // client has VoIP support disabled. + else if ( audioMult == 0.0f ) + dontCapture = qtrue; // basically silenced incoming audio. + + cl_voipSend->modified = qfalse; + + if (dontCapture) { + cl_voipSend->integer = 0; + return; + } + + if (cl_voipSend->integer) { + initialFrame = qtrue; + } else { + finalFrame = qtrue; + } + } + + // try to get more audio data from the sound card... + + if (initialFrame) { + float gain = cl_voipGainDuringCapture->value; + if (gain < 0.0f) gain = 0.0f; else if (gain >= 1.0f) gain = 1.0f; + S_MasterGain(cl_voipGainDuringCapture->value); + S_StartCapture(); + CL_VoipNewGeneration(); + CL_VoipParseTargets(); + } + + if ((cl_voipSend->integer) || (finalFrame)) { // user wants to capture audio? + int samples = S_AvailableCaptureSamples(); + const int mult = (finalFrame) ? 1 : 12; // 12 == 240ms of audio. + + // enough data buffered in audio hardware to process yet? + if (samples >= (clc.speexFrameSize * mult)) { + // audio capture is always MONO16 (and that's what speex wants!). + // 2048 will cover 12 uncompressed frames in narrowband mode. + static int16_t sampbuffer[2048]; + float voipPower = 0.0f; + int speexFrames = 0; + int wpos = 0; + int pos = 0; + + if (samples > (clc.speexFrameSize * 12)) + samples = (clc.speexFrameSize * 12); + + // !!! FIXME: maybe separate recording from encoding, so voipPower + // !!! FIXME: updates faster than 4Hz? + + samples -= samples % clc.speexFrameSize; + S_Capture(samples, (byte *) sampbuffer); // grab from audio card. + + // this will probably generate multiple speex packets each time. + while (samples > 0) { + int16_t *sampptr = &sampbuffer[pos]; + int i, bytes; + + // preprocess samples to remove noise... + speex_preprocess_run(clc.speexPreprocessor, sampptr); + + // check the "power" of this packet... + for (i = 0; i < clc.speexFrameSize; i++) { + const float flsamp = (float) sampptr[i]; + const float s = fabs(flsamp); + voipPower += s * s; + sampptr[i] = (int16_t) ((flsamp) * audioMult); + } + + // encode raw audio samples into Speex data... + speex_bits_reset(&clc.speexEncoderBits); + speex_encode_int(clc.speexEncoder, sampptr, + &clc.speexEncoderBits); + bytes = speex_bits_write(&clc.speexEncoderBits, + (char *) &clc.voipOutgoingData[wpos+1], + sizeof (clc.voipOutgoingData) - (wpos+1)); + assert((bytes > 0) && (bytes < 256)); + clc.voipOutgoingData[wpos] = (byte) bytes; + wpos += bytes + 1; + + // look at the data for the next packet... + pos += clc.speexFrameSize; + samples -= clc.speexFrameSize; + speexFrames++; + } + + clc.voipPower = (voipPower / (32768.0f * 32768.0f * + ((float) (clc.speexFrameSize * speexFrames)))) * + 100.0f; + + if ((useVad) && (clc.voipPower < cl_voipVADThreshold->value)) { + CL_VoipNewGeneration(); // no "talk" for at least 1/4 second. + } else { + clc.voipOutgoingDataSize = wpos; + clc.voipOutgoingDataFrames = speexFrames; + + Com_DPrintf("VoIP: Send %d frames, %d bytes, %f power\n", + speexFrames, wpos, clc.voipPower); + + #if 0 + static FILE *encio = NULL; + if (encio == NULL) encio = fopen("voip-outgoing-encoded.bin", "wb"); + if (encio != NULL) { fwrite(clc.voipOutgoingData, wpos, 1, encio); fflush(encio); } + static FILE *decio = NULL; + if (decio == NULL) decio = fopen("voip-outgoing-decoded.bin", "wb"); + if (decio != NULL) { fwrite(sampbuffer, speexFrames * clc.speexFrameSize * 2, 1, decio); fflush(decio); } + #endif + } + } + } + + // User requested we stop recording, and we've now processed the last of + // any previously-buffered data. Pause the capture device, etc. + if (finalFrame) { + S_StopCapture(); + S_MasterGain(1.0f); + clc.voipPower = 0.0f; // force this value so it doesn't linger. + } +} +#endif + +/* +======================================================================= + +CLIENT RELIABLE COMMAND COMMUNICATION + +======================================================================= +*/ + +/* +====================== +CL_AddReliableCommand + +The given command will be transmitted to the server, and is gauranteed to +not have future usercmd_t executed before it is executed +====================== +*/ +void CL_AddReliableCommand(const char *cmd, qboolean isDisconnectCmd) +{ + int unacknowledged = clc.reliableSequence - clc.reliableAcknowledge; + + // if we would be losing an old command that hasn't been acknowledged, + // we must drop the connection + // also leave one slot open for the disconnect command in this case. + + if ((isDisconnectCmd && unacknowledged > MAX_RELIABLE_COMMANDS) || + (!isDisconnectCmd && unacknowledged >= MAX_RELIABLE_COMMANDS)) + { + if(com_errorEntered) + return; + else + Com_Error(ERR_DROP, "Client command overflow"); + } + + Q_strncpyz(clc.reliableCommands[++clc.reliableSequence & (MAX_RELIABLE_COMMANDS - 1)], + cmd, sizeof(*clc.reliableCommands)); +} + +/* +====================== +CL_ChangeReliableCommand +====================== +*/ +void CL_ChangeReliableCommand( void ) { + int r, index, l; + + r = clc.reliableSequence - (random() * 5); + index = clc.reliableSequence & ( MAX_RELIABLE_COMMANDS - 1 ); + l = strlen(clc.reliableCommands[ index ]); + if ( l >= MAX_STRING_CHARS - 1 ) { + l = MAX_STRING_CHARS - 2; + } + clc.reliableCommands[ index ][ l ] = '\n'; + clc.reliableCommands[ index ][ l+1 ] = '\0'; +} + +/* +======================================================================= + +CLIENT SIDE DEMO RECORDING + +======================================================================= +*/ + +/* +==================== +CL_WriteDemoMessage + +Dumps the current net message, prefixed by the length +==================== +*/ +void CL_WriteDemoMessage ( msg_t *msg, int headerBytes ) { + int len, swlen; + + // write the packet sequence + len = clc.serverMessageSequence; + swlen = LittleLong( len ); + FS_Write (&swlen, 4, clc.demofile); + + // skip the packet sequencing information + len = msg->cursize - headerBytes; + swlen = LittleLong(len); + FS_Write (&swlen, 4, clc.demofile); + FS_Write ( msg->data + headerBytes, len, clc.demofile ); +} + + +/* +==================== +CL_StopRecording_f + +stop recording a demo +==================== +*/ +void CL_StopRecord_f( void ) { + int len; + + if ( !clc.demorecording ) { + Com_Printf ("Not recording a demo.\n"); + return; + } + + // finish up + len = -1; + FS_Write (&len, 4, clc.demofile); + FS_Write (&len, 4, clc.demofile); + FS_FCloseFile (clc.demofile); + clc.demofile = 0; + clc.demorecording = qfalse; + clc.spDemoRecording = qfalse; + Com_Printf ("Stopped demo.\n"); +} + +/* +================== +CL_DemoFilename +================== +*/ +void CL_DemoFilename( int number, char *fileName ) { + int a,b,c,d; + + if(number < 0 || number > 9999) + number = 9999; + + a = number / 1000; + number -= a*1000; + b = number / 100; + number -= b*100; + c = number / 10; + number -= c*10; + d = number; + + Com_sprintf( fileName, MAX_OSPATH, "demo%i%i%i%i" + , a, b, c, d ); +} + +/* +==================== +CL_Record_f + +record <demoname> + +Begins recording a demo from the current position +==================== +*/ +static char demoName[MAX_QPATH]; // compiler bug workaround +void CL_Record_f( void ) { + char name[MAX_OSPATH]; + byte bufData[MAX_MSGLEN]; + msg_t buf; + int i; + int len; + entityState_t *ent; + entityState_t nullstate; + char *s; + + if ( Cmd_Argc() > 2 ) { + Com_Printf ("record <demoname>\n"); + return; + } + + if ( clc.demorecording ) { + if (!clc.spDemoRecording) { + Com_Printf ("Already recording.\n"); + } + return; + } + + if ( cls.state != CA_ACTIVE ) { + Com_Printf ("You must be in a level to record.\n"); + return; + } + + // sync 0 doesn't prevent recording, so not forcing it off .. everyone does g_sync 1 ; record ; g_sync 0 .. + if ( NET_IsLocalAddress( clc.serverAddress ) && !Cvar_VariableValue( "g_synchronousClients" ) ) { + Com_Printf (S_COLOR_YELLOW "WARNING: You should set 'g_synchronousClients 1' for smoother demo recording\n"); + } + + if ( Cmd_Argc() == 2 ) { + s = Cmd_Argv(1); + Q_strncpyz( demoName, s, sizeof( demoName ) ); + Com_sprintf (name, sizeof(name), "demos/%s.%s%d", demoName, DEMOEXT, PROTOCOL_VERSION ); + } else { + int number; + + // scan for a free demo name + for ( number = 0 ; number <= 9999 ; number++ ) { + CL_DemoFilename( number, demoName ); + Com_sprintf (name, sizeof(name), "demos/%s.%s%d", demoName, DEMOEXT, PROTOCOL_VERSION ); + + if (!FS_FileExists(name)) + break; // file doesn't exist + } + } + + // open the demo file + + Com_Printf ("recording to %s.\n", name); + clc.demofile = FS_FOpenFileWrite( name ); + if ( !clc.demofile ) { + Com_Printf ("ERROR: couldn't open.\n"); + return; + } + clc.demorecording = qtrue; + if (Cvar_VariableValue("ui_recordSPDemo")) { + clc.spDemoRecording = qtrue; + } else { + clc.spDemoRecording = qfalse; + } + + + Q_strncpyz( clc.demoName, demoName, sizeof( clc.demoName ) ); + + // don't start saving messages until a non-delta compressed message is received + clc.demowaiting = qtrue; + + // write out the gamestate message + MSG_Init (&buf, bufData, sizeof(bufData)); + MSG_Bitstream(&buf); + + // NOTE, MRE: all server->client messages now acknowledge + MSG_WriteLong( &buf, clc.reliableSequence ); + + MSG_WriteByte (&buf, svc_gamestate); + MSG_WriteLong (&buf, clc.serverCommandSequence ); + + // configstrings + for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) { + if ( !cl.gameState.stringOffsets[i] ) { + continue; + } + s = cl.gameState.stringData + cl.gameState.stringOffsets[i]; + MSG_WriteByte (&buf, svc_configstring); + MSG_WriteShort (&buf, i); + MSG_WriteBigString (&buf, s); + } + + // baselines + Com_Memset (&nullstate, 0, sizeof(nullstate)); + for ( i = 0; i < MAX_GENTITIES ; i++ ) { + ent = &cl.entityBaselines[i]; + if ( !ent->number ) { + continue; + } + MSG_WriteByte (&buf, svc_baseline); + MSG_WriteDeltaEntity (&buf, &nullstate, ent, qtrue ); + } + + MSG_WriteByte( &buf, svc_EOF ); + + // finished writing the gamestate stuff + + // write the client num + MSG_WriteLong(&buf, clc.clientNum); + // write the checksum feed + MSG_WriteLong(&buf, clc.checksumFeed); + + // finished writing the client packet + MSG_WriteByte( &buf, svc_EOF ); + + // write it to the demo file + len = LittleLong( clc.serverMessageSequence - 1 ); + FS_Write (&len, 4, clc.demofile); + + len = LittleLong (buf.cursize); + FS_Write (&len, 4, clc.demofile); + FS_Write (buf.data, buf.cursize, clc.demofile); + + // the rest of the demo file will be copied from net messages +} + +/* +======================================================================= + +CLIENT SIDE DEMO PLAYBACK + +======================================================================= +*/ + +/* +================= +CL_DemoFrameDurationSDev +================= +*/ +static float CL_DemoFrameDurationSDev( void ) +{ + int i; + int numFrames; + float mean = 0.0f; + float variance = 0.0f; + + if( ( clc.timeDemoFrames - 1 ) > MAX_TIMEDEMO_DURATIONS ) + numFrames = MAX_TIMEDEMO_DURATIONS; + else + numFrames = clc.timeDemoFrames - 1; + + for( i = 0; i < numFrames; i++ ) + mean += clc.timeDemoDurations[ i ]; + mean /= numFrames; + + for( i = 0; i < numFrames; i++ ) + { + float x = clc.timeDemoDurations[ i ]; + + variance += ( ( x - mean ) * ( x - mean ) ); + } + variance /= numFrames; + + return sqrt( variance ); +} + +/* +================= +CL_DemoCompleted +================= +*/ +void CL_DemoCompleted( void ) +{ + char buffer[ MAX_STRING_CHARS ]; + + if( cl_timedemo && cl_timedemo->integer ) + { + int time; + + time = Sys_Milliseconds() - clc.timeDemoStart; + if( time > 0 ) + { + // Millisecond times are frame durations: + // minimum/average/maximum/std deviation + Com_sprintf( buffer, sizeof( buffer ), + "%i frames %3.1f seconds %3.1f fps %d.0/%.1f/%d.0/%.1f ms\n", + clc.timeDemoFrames, + time/1000.0, + clc.timeDemoFrames*1000.0 / time, + clc.timeDemoMinDuration, + time / (float)clc.timeDemoFrames, + clc.timeDemoMaxDuration, + CL_DemoFrameDurationSDev( ) ); + Com_Printf( "%s", buffer ); + + // Write a log of all the frame durations + if( cl_timedemoLog && strlen( cl_timedemoLog->string ) > 0 ) + { + int i; + int numFrames; + fileHandle_t f; + + if( ( clc.timeDemoFrames - 1 ) > MAX_TIMEDEMO_DURATIONS ) + numFrames = MAX_TIMEDEMO_DURATIONS; + else + numFrames = clc.timeDemoFrames - 1; + + f = FS_FOpenFileWrite( cl_timedemoLog->string ); + if( f ) + { + FS_Printf( f, "# %s", buffer ); + + for( i = 0; i < numFrames; i++ ) + FS_Printf( f, "%d\n", clc.timeDemoDurations[ i ] ); + + FS_FCloseFile( f ); + Com_Printf( "%s written\n", cl_timedemoLog->string ); + } + else + { + Com_Printf( "Couldn't open %s for writing\n", + cl_timedemoLog->string ); + } + } + } + } + + CL_Disconnect( qtrue ); + CL_NextDemo(); +} + +/* +================= +CL_ReadDemoMessage +================= +*/ +void CL_ReadDemoMessage( void ) { + int r; + msg_t buf; + byte bufData[ MAX_MSGLEN ]; + int s; + + if ( !clc.demofile ) { + CL_DemoCompleted (); + return; + } + + // get the sequence number + r = FS_Read( &s, 4, clc.demofile); + if ( r != 4 ) { + CL_DemoCompleted (); + return; + } + clc.serverMessageSequence = LittleLong( s ); + + // init the message + MSG_Init( &buf, bufData, sizeof( bufData ) ); + + // get the length + r = FS_Read (&buf.cursize, 4, clc.demofile); + if ( r != 4 ) { + CL_DemoCompleted (); + return; + } + buf.cursize = LittleLong( buf.cursize ); + if ( buf.cursize == -1 ) { + CL_DemoCompleted (); + return; + } + if ( buf.cursize > buf.maxsize ) { + Com_Error (ERR_DROP, "CL_ReadDemoMessage: demoMsglen > MAX_MSGLEN"); + } + r = FS_Read( buf.data, buf.cursize, clc.demofile ); + if ( r != buf.cursize ) { + Com_Printf( "Demo file was truncated.\n"); + CL_DemoCompleted (); + return; + } + + clc.lastPacketTime = cls.realtime; + buf.readcount = 0; + CL_ParseServerMessage( &buf ); +} + +/* +==================== +CL_WalkDemoExt +==================== +*/ +static void CL_WalkDemoExt(char *arg, char *name, int *demofile) +{ + int i = 0; + *demofile = 0; + + Com_sprintf (name, MAX_OSPATH, "demos/%s.%s%d", arg, DEMOEXT, PROTOCOL_VERSION); + + FS_FOpenFileRead( name, demofile, qtrue ); + + if (*demofile) + { + Com_Printf("Demo file: %s\n", name); + return; + } + + Com_Printf("Not found: %s\n", name); + + while(demo_protocols[i]) + { + Com_sprintf (name, MAX_OSPATH, "demos/%s.%s%d", arg, DEMOEXT, demo_protocols[i]); + FS_FOpenFileRead( name, demofile, qtrue ); + if (*demofile) + { + Com_Printf("Demo file: %s\n", name); + break; + } + else + Com_Printf("Not found: %s\n", name); + i++; + } +} + +/* +==================== +CL_CompleteDemoName +==================== +*/ +static void CL_CompleteDemoName( char *args, int argNum ) +{ + if( argNum == 2 ) + { + char demoExt[ 16 ]; + + Com_sprintf(demoExt, sizeof(demoExt), ".%s%d", DEMOEXT, PROTOCOL_VERSION); + Field_CompleteFilename( "demos", demoExt, qtrue, qtrue ); + } +} + +/* +==================== +CL_PlayDemo_f + +demo <demoname> + +==================== +*/ +void CL_PlayDemo_f( void ) { + char name[MAX_OSPATH]; + char *arg, *ext_test; + int protocol, i; + char retry[MAX_OSPATH]; + + if (Cmd_Argc() != 2) { + Com_Printf ("demo <demoname>\n"); + return; + } + + // make sure a local server is killed + // 2 means don't force disconnect of local client + Cvar_Set( "sv_killserver", "2" ); + + // open the demo file + arg = Cmd_Argv(1); + + CL_Disconnect( qtrue ); + + // check for an extension .DEMOEXT_?? (?? is protocol) + ext_test = Q_strrchr(arg, '.'); + + if(ext_test && !Q_stricmpn(ext_test + 1, DEMOEXT, ARRAY_LEN(DEMOEXT) - 1)) + { + protocol = atoi(ext_test + ARRAY_LEN(DEMOEXT)); + + for(i = 0; demo_protocols[i]; i++) + { + if(demo_protocols[i] == protocol) + break; + } + + if(demo_protocols[i] || protocol == PROTOCOL_VERSION) + { + Com_sprintf(name, sizeof(name), "demos/%s", arg); + FS_FOpenFileRead(name, &clc.demofile, qtrue); + } + else + { + int len; + + Com_Printf("Protocol %d not supported for demos\n", protocol); + len = ext_test - arg; + + if(len >= ARRAY_LEN(retry)) + len = ARRAY_LEN(retry) - 1; + + Q_strncpyz(retry, arg, len + 1); + retry[len] = '\0'; + CL_WalkDemoExt(retry, name, &clc.demofile); + } + } + else + CL_WalkDemoExt(arg, name, &clc.demofile); + + if (!clc.demofile) { + Com_Error( ERR_DROP, "couldn't open %s", name); + return; + } + Q_strncpyz( clc.demoName, Cmd_Argv(1), sizeof( clc.demoName ) ); + + Con_Close(); + + cls.state = CA_CONNECTED; + clc.demoplaying = qtrue; + Q_strncpyz( cls.servername, Cmd_Argv(1), sizeof( cls.servername ) ); + + // read demo messages until connected + while ( cls.state >= CA_CONNECTED && cls.state < CA_PRIMED ) { + CL_ReadDemoMessage(); + } + // don't get the first snapshot this frame, to prevent the long + // time from the gamestate load from messing causing a time skip + clc.firstDemoFrameSkipped = qfalse; +} + + +/* +==================== +CL_StartDemoLoop + +Closing the main menu will restart the demo loop +==================== +*/ +void CL_StartDemoLoop( void ) { + // start the demo loop again + Cbuf_AddText ("d1\n"); + Key_SetCatcher( 0 ); +} + +/* +================== +CL_NextDemo + +Called when a demo or cinematic finishes +If the "nextdemo" cvar is set, that command will be issued +================== +*/ +void CL_NextDemo( void ) { + char v[MAX_STRING_CHARS]; + + Q_strncpyz( v, Cvar_VariableString ("nextdemo"), sizeof(v) ); + v[MAX_STRING_CHARS-1] = 0; + Com_DPrintf("CL_NextDemo: %s\n", v ); + if (!v[0]) { + return; + } + + Cvar_Set ("nextdemo",""); + Cbuf_AddText (v); + Cbuf_AddText ("\n"); + Cbuf_Execute(); +} + +/* +================== +CL_DemoState + +Returns the current state of the demo system +================== +*/ +demoState_t CL_DemoState( void ) { + if( clc.demoplaying ) { + return DS_PLAYBACK; + } else if( clc.demorecording ) { + return DS_RECORDING; + } else { + return DS_NONE; + } +} + +/* +================== +CL_DemoPos + +Returns the current position of the demo +================== +*/ +int CL_DemoPos( void ) { + if( clc.demoplaying || clc.demorecording ) { + return FS_FTell( clc.demofile ); + } else { + return 0; + } +} + +/* +================== +CL_DemoName + +Returns the name of the demo +================== +*/ +void CL_DemoName( char *buffer, int size ) { + if( clc.demoplaying || clc.demorecording ) { + Q_strncpyz( buffer, clc.demoName, size ); + } else if( size >= 1 ) { + buffer[ 0 ] = '\0'; + } +} + +//====================================================================== + +/* +===================== +CL_ShutdownAll +===================== +*/ +void CL_ShutdownAll(void) { + +#ifdef USE_CURL + CL_cURL_Shutdown(); +#endif + // clear sounds + S_DisableSounds(); + // shutdown CGame + CL_ShutdownCGame(); + // shutdown UI + CL_ShutdownUI(); + + // shutdown the renderer + if ( re.Shutdown ) { + re.Shutdown( qfalse ); // don't destroy window or context + } + + cls.uiStarted = qfalse; + cls.cgameStarted = qfalse; + cls.rendererStarted = qfalse; + cls.soundRegistered = qfalse; +} + +/* +================= +CL_FlushMemory + +Called by CL_MapLoading, CL_Connect_f, CL_PlayDemo_f, and CL_ParseGamestate the only +ways a client gets into a game +Also called by Com_Error +================= +*/ +void CL_FlushMemory( void ) { + + // shutdown all the client stuff + CL_ShutdownAll(); + + // if not running a server clear the whole hunk + if ( !com_sv_running->integer ) { + // clear the whole hunk + Hunk_Clear(); + // clear collision map data + CM_ClearMap(); + } + else { + // clear all the client data on the hunk + Hunk_ClearToMark(); + } + + CL_StartHunkUsers( qfalse ); +} + +/* +===================== +CL_MapLoading + +A local server is starting to load a map, so update the +screen to let the user know about it, then dump all client +memory on the hunk from cgame, ui, and renderer +===================== +*/ +void CL_MapLoading( void ) { + if ( com_dedicated->integer ) { + cls.state = CA_DISCONNECTED; + Key_SetCatcher( KEYCATCH_CONSOLE ); + return; + } + + if ( !com_cl_running->integer ) { + return; + } + + Con_Close(); + Key_SetCatcher( 0 ); + + // if we are already connected to the local host, stay connected + if ( cls.state >= CA_CONNECTED && !Q_stricmp( cls.servername, "localhost" ) ) { + cls.state = CA_CONNECTED; // so the connect screen is drawn + Com_Memset( cls.updateInfoString, 0, sizeof( cls.updateInfoString ) ); + Com_Memset( clc.serverMessage, 0, sizeof( clc.serverMessage ) ); + Com_Memset( &cl.gameState, 0, sizeof( cl.gameState ) ); + clc.lastPacketSentTime = -9999; + SCR_UpdateScreen(); + } else { + CL_Disconnect( qtrue ); + Q_strncpyz( cls.servername, "localhost", sizeof(cls.servername) ); + cls.state = CA_CHALLENGING; // so the connect screen is drawn + Key_SetCatcher( 0 ); + SCR_UpdateScreen(); + clc.connectTime = -RETRANSMIT_TIMEOUT; + NET_StringToAdr( cls.servername, &clc.serverAddress, NA_UNSPEC); + // we don't need a challenge on the localhost + + CL_CheckForResend(); + } +} + +/* +===================== +CL_ClearState + +Called before parsing a gamestate +===================== +*/ +void CL_ClearState (void) { + +// S_StopAllSounds(); + + Com_Memset( &cl, 0, sizeof( cl ) ); +} + +/* +==================== +CL_UpdateGUID + +update cl_guid using QKEY_FILE and optional prefix +==================== +*/ +static void CL_UpdateGUID( const char *prefix, int prefix_len ) +{ + fileHandle_t f; + int len; + + len = FS_SV_FOpenFileRead( QKEY_FILE, &f ); + FS_FCloseFile( f ); + + if( len != QKEY_SIZE ) + Cvar_Set( "cl_guid", "" ); + else + Cvar_Set( "cl_guid", Com_MD5File( QKEY_FILE, QKEY_SIZE, + prefix, prefix_len ) ); +} + + +/* +===================== +CL_Disconnect + +Called when a connection, demo, or cinematic is being terminated. +Goes from a connected state to either a menu state or a console state +Sends a disconnect message to the server +This is also called on Com_Error and Com_Quit, so it shouldn't cause any errors +===================== +*/ +void CL_Disconnect( qboolean showMainMenu ) { + if ( !com_cl_running || !com_cl_running->integer ) { + return; + } + + Cvar_Set("fs_game", "gpp"); + + // shutting down the client so enter full screen ui mode + Cvar_Set("r_uiFullScreen", "1"); + + if ( clc.demorecording ) { + CL_StopRecord_f (); + } + + if (clc.download) { + FS_FCloseFile( clc.download ); + clc.download = 0; + } + *clc.downloadTempName = *clc.downloadName = 0; + Cvar_Set( "cl_downloadName", "" ); + +#ifdef USE_MUMBLE + if (cl_useMumble->integer && mumble_islinked()) { + Com_Printf("Mumble: Unlinking from Mumble application\n"); + mumble_unlink(); + } +#endif + +#ifdef USE_VOIP + if (cl_voipSend->integer) { + int tmp = cl_voipUseVAD->integer; + cl_voipUseVAD->integer = 0; // disable this for a moment. + clc.voipOutgoingDataSize = 0; // dump any pending VoIP transmission. + Cvar_Set("cl_voipSend", "0"); + CL_CaptureVoip(); // clean up any state... + cl_voipUseVAD->integer = tmp; + } + + if (clc.speexInitialized) { + int i; + speex_bits_destroy(&clc.speexEncoderBits); + speex_encoder_destroy(clc.speexEncoder); + speex_preprocess_state_destroy(clc.speexPreprocessor); + for (i = 0; i < MAX_CLIENTS; i++) { + speex_bits_destroy(&clc.speexDecoderBits[i]); + speex_decoder_destroy(clc.speexDecoder[i]); + } + } + Cmd_RemoveCommand ("voip"); +#endif + + if ( clc.demofile ) { + FS_FCloseFile( clc.demofile ); + clc.demofile = 0; + } + + if ( uivm && showMainMenu ) { + VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_NONE ); + } + + SCR_StopCinematic (); + S_ClearSoundBuffer(); + + // send a disconnect message to the server + // send it a few times in case one is dropped + if ( cls.state >= CA_CONNECTED ) { + CL_AddReliableCommand("disconnect", qtrue); + CL_WritePacket(); + CL_WritePacket(); + CL_WritePacket(); + } + + // Remove pure paks + FS_PureServerSetLoadedPaks("", ""); + + CL_ClearState (); + + // wipe the client connection + Com_Memset( &clc, 0, sizeof( clc ) ); + + cls.state = CA_DISCONNECTED; + + // allow cheats locally + Cvar_Set( "sv_cheats", "1" ); + + // not connected to a pure server anymore + cl_connectedToPureServer = qfalse; + +#ifdef USE_VOIP + // not connected to voip server anymore. + cl_connectedToVoipServer = qfalse; +#endif + + // Stop recording any video + if( CL_VideoRecording( ) ) { + // Finish rendering current frame + SCR_UpdateScreen( ); + CL_CloseAVI( ); + } + CL_UpdateGUID( NULL, 0 ); +} + + +/* +=================== +CL_ForwardCommandToServer + +adds the current command line as a clientCommand +things like godmode, noclip, etc, are commands directed to the server, +so when they are typed in at the console, they will need to be forwarded. +=================== +*/ +void CL_ForwardCommandToServer( const char *string ) { + char *cmd; + + cmd = Cmd_Argv(0); + + // ignore key up commands + if ( cmd[0] == '-' ) { + return; + } + + if ( clc.demoplaying || cls.state < CA_CONNECTED || cmd[0] == '+' ) { + Com_Printf ("Unknown command \"%s" S_COLOR_WHITE "\"\n", cmd); + return; + } + + if ( Cmd_Argc() > 1 ) { + CL_AddReliableCommand(string, qfalse); + } else { + CL_AddReliableCommand(cmd, qfalse); + } +} + +/* +=================== +CL_RequestMotd + +=================== +*/ +void CL_RequestMotd( void ) { + char info[MAX_INFO_STRING]; + + if ( !cl_motd->integer ) { + return; + } + Com_Printf( "Resolving %s\n", MASTER_SERVER_NAME ); + + switch( NET_StringToAdr( MASTER_SERVER_NAME, &cls.updateServer, + NA_UNSPEC ) ) + { + case 0: + Com_Printf( "Couldn't resolve master address\n" ); + return; + + case 2: + cls.updateServer.port = BigShort( PORT_MASTER ); + default: + break; + } + + Com_Printf( "%s resolved to %s\n", MASTER_SERVER_NAME, + NET_AdrToStringwPort( cls.updateServer ) ); + + info[0] = 0; + + Com_sprintf( cls.updateChallenge, sizeof( cls.updateChallenge ), + "%i", ((rand() << 16) ^ rand()) ^ Com_Milliseconds()); + + Info_SetValueForKey( info, "challenge", cls.updateChallenge ); + Info_SetValueForKey( info, "renderer", cls.glconfig.renderer_string ); + Info_SetValueForKey( info, "version", com_version->string ); + + NET_OutOfBandPrint( NS_CLIENT, cls.updateServer, "getmotd%s", info ); +} + +/* +====================================================================== + +CONSOLE COMMANDS + +====================================================================== +*/ + +/* +================== +CL_ForwardToServer_f +================== +*/ +void CL_ForwardToServer_f( void ) { + if ( cls.state != CA_ACTIVE || clc.demoplaying ) { + Com_Printf ("Not connected to a server.\n"); + return; + } + + // don't forward the first argument + if ( Cmd_Argc() > 1 ) { + CL_AddReliableCommand(Cmd_Args(), qfalse); + } +} + +/* +================== +CL_Disconnect_f +================== +*/ +void CL_Disconnect_f( void ) { + SCR_StopCinematic(); + Cvar_Set("ui_singlePlayerActive", "0"); + if ( cls.state != CA_DISCONNECTED && cls.state != CA_CINEMATIC ) { + Com_Error (ERR_DISCONNECT, "Disconnected from server"); + } +} + + +/* +================ +CL_Reconnect_f + +================ +*/ +void CL_Reconnect_f( void ) { + if ( !strlen( cls.servername ) || !strcmp( cls.servername, "localhost" ) ) { + Com_Printf( "Can't reconnect to localhost.\n" ); + return; + } + Cvar_Set("ui_singlePlayerActive", "0"); + Cbuf_AddText( va("connect %s\n", cls.servername ) ); +} + +/* +================ +CL_Connect_f + +================ +*/ +void CL_Connect_f( void ) { + char *server; + const char *serverString; + int argc = Cmd_Argc(); + netadrtype_t family = NA_UNSPEC; + + if ( argc != 2 && argc != 3 ) { + Com_Printf( "usage: connect [-4|-6] server\n"); + return; + } + + if(argc == 2) + server = Cmd_Argv(1); + else + { + if(!strcmp(Cmd_Argv(1), "-4")) + family = NA_IP; + else if(!strcmp(Cmd_Argv(1), "-6")) + family = NA_IP6; + else + Com_Printf( "warning: only -4 or -6 as address type understood.\n"); + + server = Cmd_Argv(2); + } + + Cvar_Set("ui_singlePlayerActive", "0"); + + // fire a message off to the motd server + CL_RequestMotd(); + + // clear any previous "server full" type messages + clc.serverMessage[0] = 0; + + if ( com_sv_running->integer && !strcmp( server, "localhost" ) ) { + // if running a local server, kill it + SV_Shutdown( "Server quit" ); + } + + // make sure a local server is killed + Cvar_Set( "sv_killserver", "1" ); + SV_Frame( 0 ); + + CL_Disconnect( qtrue ); + Con_Close(); + + Q_strncpyz( cls.servername, server, sizeof(cls.servername) ); + + if (!NET_StringToAdr(cls.servername, &clc.serverAddress, family) ) { + Com_Printf ("Bad server address\n"); + cls.state = CA_DISCONNECTED; + return; + } + if (clc.serverAddress.port == 0) { + clc.serverAddress.port = BigShort( PORT_SERVER ); + } + + serverString = NET_AdrToStringwPort(clc.serverAddress); + + Com_Printf( "%s resolved to %s\n", cls.servername, serverString); + + if( cl_guidServerUniq->integer ) + CL_UpdateGUID( serverString, strlen( serverString ) ); + else + CL_UpdateGUID( NULL, 0 ); + + // if we aren't playing on a lan, we need to authenticate + // with the cd key + if(NET_IsLocalAddress(clc.serverAddress)) + cls.state = CA_CHALLENGING; + else + { + cls.state = CA_CONNECTING; + + // Set a client challenge number that ideally is mirrored back by the server. + clc.challenge = ((rand() << 16) ^ rand()) ^ Com_Milliseconds(); + } + + Key_SetCatcher( 0 ); + clc.connectTime = -99999; // CL_CheckForResend() will fire immediately + clc.connectPacketCount = 0; + + // server connection string + Cvar_Set( "cl_currentServerAddress", server ); +} + +#define MAX_RCON_MESSAGE 1024 + +/* +================== +CL_CompleteRcon +================== +*/ +static void CL_CompleteRcon( char *args, int argNum ) +{ + if( argNum == 2 ) + { + // Skip "rcon " + char *p = Com_SkipTokens( args, 1, " " ); + + if( p > args ) + Field_CompleteCommand( p, qtrue, qtrue ); + } +} + +/* +===================== +CL_Rcon_f + + Send the rest of the command line over as + an unconnected command. +===================== +*/ +void CL_Rcon_f( void ) { + char message[MAX_RCON_MESSAGE]; + netadr_t to; + + if ( !rcon_client_password->string ) { + Com_Printf ("You must set 'rconpassword' before\n" + "issuing an rcon command.\n"); + return; + } + + message[0] = -1; + message[1] = -1; + message[2] = -1; + message[3] = -1; + message[4] = 0; + + Q_strcat (message, MAX_RCON_MESSAGE, "rcon "); + + Q_strcat (message, MAX_RCON_MESSAGE, rcon_client_password->string); + Q_strcat (message, MAX_RCON_MESSAGE, " "); + + // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=543 + Q_strcat (message, MAX_RCON_MESSAGE, Cmd_Cmd()+5); + + if ( cls.state >= CA_CONNECTED ) { + to = clc.netchan.remoteAddress; + } else { + if (!strlen(rconAddress->string)) { + Com_Printf ("You must either be connected,\n" + "or set the 'rconAddress' cvar\n" + "to issue rcon commands\n"); + + return; + } + NET_StringToAdr (rconAddress->string, &to, NA_UNSPEC); + if (to.port == 0) { + to.port = BigShort (PORT_SERVER); + } + } + + NET_SendPacket (NS_CLIENT, strlen(message)+1, message, to); +} + +/* +================= +CL_SendPureChecksums +================= +*/ +void CL_SendPureChecksums( void ) { + char cMsg[MAX_INFO_VALUE]; + + // if we are pure we need to send back a command with our referenced pk3 checksums + Com_sprintf(cMsg, sizeof(cMsg), "cp %d %s", cl.serverId, FS_ReferencedPakPureChecksums()); + + CL_AddReliableCommand(cMsg, qfalse); +} + +/* +================= +CL_ResetPureClientAtServer +================= +*/ +void CL_ResetPureClientAtServer( void ) { + CL_AddReliableCommand("vdr", qfalse); +} + +/* +================= +CL_Vid_Restart_f + +Restart the video subsystem + +we also have to reload the UI and CGame because the renderer +doesn't know what graphics to reload +================= +*/ +void CL_Vid_Restart_f( void ) { + + // Settings may have changed so stop recording now + if( CL_VideoRecording( ) ) { + CL_CloseAVI( ); + } + + if(clc.demorecording) + CL_StopRecord_f(); + + // don't let them loop during the restart + S_StopAllSounds(); + // shutdown the UI + CL_ShutdownUI(); + // shutdown the CGame + CL_ShutdownCGame(); + // shutdown the renderer and clear the renderer interface + CL_ShutdownRef(); + // client is no longer pure untill new checksums are sent + CL_ResetPureClientAtServer(); + // clear pak references + FS_ClearPakReferences( FS_UI_REF | FS_CGAME_REF ); + // reinitialize the filesystem if the game directory or checksum has changed + FS_ConditionalRestart( clc.checksumFeed ); + + cls.rendererStarted = qfalse; + cls.uiStarted = qfalse; + cls.cgameStarted = qfalse; + cls.soundRegistered = qfalse; + + // unpause so the cgame definately gets a snapshot and renders a frame + Cvar_Set( "cl_paused", "0" ); + + // if not running a server clear the whole hunk + if ( !com_sv_running->integer ) { + // clear the whole hunk + Hunk_Clear(); + } + else { + // clear all the client data on the hunk + Hunk_ClearToMark(); + } + + // initialize the renderer interface + CL_InitRef(); + + // startup all the client stuff + CL_StartHunkUsers( qfalse ); + + // start the cgame if connected + if ( cls.state > CA_CONNECTED && cls.state != CA_CINEMATIC ) { + cls.cgameStarted = qtrue; + CL_InitCGame(); + // send pure checksums + CL_SendPureChecksums(); + } +} + +/* +================= +CL_Snd_Restart + +Restart the sound subsystem +================= +*/ +void CL_Snd_Restart(void) +{ + S_Shutdown(); + S_Init(); +} + +/* +================= +CL_Snd_Restart_f + +Restart the sound subsystem +The cgame and game must also be forced to restart because +handles will be invalid +================= +*/ +void CL_Snd_Restart_f(void) +{ + CL_Snd_Restart(); + CL_Vid_Restart_f(); +} + + +/* +================== +CL_PK3List_f +================== +*/ +void CL_OpenedPK3List_f( void ) { + Com_Printf("Opened PK3 Names: %s\n", FS_LoadedPakNames()); +} + +/* +================== +CL_PureList_f +================== +*/ +void CL_ReferencedPK3List_f( void ) { + Com_Printf("Referenced PK3 Names: %s\n", FS_ReferencedPakNames()); +} + +/* +================== +CL_Configstrings_f +================== +*/ +void CL_Configstrings_f( void ) { + int i; + int ofs; + + if ( cls.state != CA_ACTIVE ) { + Com_Printf( "Not connected to a server.\n"); + return; + } + + for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) { + ofs = cl.gameState.stringOffsets[ i ]; + if ( !ofs ) { + continue; + } + Com_Printf( "%4i: %s\n", i, cl.gameState.stringData + ofs ); + } +} + +/* +============== +CL_Clientinfo_f +============== +*/ +void CL_Clientinfo_f( void ) { + Com_Printf( "--------- Client Information ---------\n" ); + Com_Printf( "state: %i\n", cls.state ); + Com_Printf( "Server: %s\n", cls.servername ); + Com_Printf ("User info settings:\n"); + Info_Print( Cvar_InfoString( CVAR_USERINFO ) ); + Com_Printf( "--------------------------------------\n" ); +} + + +//==================================================================== + +/* +================= +CL_DownloadsComplete + +Called when all downloading has been completed +================= +*/ +void CL_DownloadsComplete( void ) { + Com_Printf("Downloads complete\n"); + +#ifdef USE_CURL + // if we downloaded with cURL + if(clc.cURLUsed) { + clc.cURLUsed = qfalse; + CL_cURL_Shutdown(); + if( clc.cURLDisconnected ) { + if(clc.downloadRestart) { + if( !clc.activeCURLNotGameRelated ) + FS_Restart(clc.checksumFeed); + clc.downloadRestart = qfalse; + } + clc.cURLDisconnected = qfalse; + if( !clc.activeCURLNotGameRelated ) + CL_Reconnect_f(); + return; + } + } +#endif + + // if we downloaded files we need to restart the file system + if (clc.downloadRestart) { + clc.downloadRestart = qfalse; + + FS_Restart(clc.checksumFeed); // We possibly downloaded a pak, restart the file system to load it + + // inform the server so we get new gamestate info + CL_AddReliableCommand("donedl", qfalse); + + // by sending the donedl command we request a new gamestate + // so we don't want to load stuff yet + return; + } + + // let the client game init and load data + cls.state = CA_LOADING; + + // Pump the loop, this may change gamestate! + Com_EventLoop(); + + // if the gamestate was changed by calling Com_EventLoop + // then we loaded everything already and we don't want to do it again. + if ( cls.state != CA_LOADING ) { + return; + } + + // starting to load a map so we get out of full screen ui mode + Cvar_Set("r_uiFullScreen", "0"); + + // flush client memory and start loading stuff + // this will also (re)load the UI + // if this is a local client then only the client part of the hunk + // will be cleared, note that this is done after the hunk mark has been set + CL_FlushMemory(); + + // initialize the CGame + cls.cgameStarted = qtrue; + CL_InitCGame(); + + // set pure checksums + CL_SendPureChecksums(); + + CL_WritePacket(); + CL_WritePacket(); + CL_WritePacket(); +} + +/* +================= +CL_BeginDownload + +Requests a file to download from the server. Stores it in the current +game directory. +================= +*/ +void CL_BeginDownload( const char *localName, const char *remoteName ) { + + Com_DPrintf("***** CL_BeginDownload *****\n" + "Localname: %s\n" + "Remotename: %s\n" + "****************************\n", localName, remoteName); + + Q_strncpyz ( clc.downloadName, localName, sizeof(clc.downloadName) ); + Com_sprintf( clc.downloadTempName, sizeof(clc.downloadTempName), "%s.tmp", localName ); + + // Set so UI gets access to it + Cvar_Set( "cl_downloadName", remoteName ); + Cvar_Set( "cl_downloadSize", "0" ); + Cvar_Set( "cl_downloadCount", "0" ); + Cvar_SetValue( "cl_downloadTime", cls.realtime ); + + clc.downloadBlock = 0; // Starting new file + clc.downloadCount = 0; + + // Stop any errant looping sounds that may be playing + S_ClearLoopingSounds( qtrue ); + + CL_AddReliableCommand( va("download %s", remoteName), qfalse ); +} + +/* +================= +CL_NextDownload + +A download completed or failed +================= +*/ +void CL_NextDownload(void) +{ + char *s; + char *remoteName, *localName; + qboolean useCURL = qfalse; + int prompt; + + // A download has finished, check whether this matches a referenced checksum + if(*clc.downloadName && !clc.activeCURLNotGameRelated) + { + char *zippath = FS_BuildOSPath(Cvar_VariableString("fs_homepath"), clc.downloadName, ""); + zippath[strlen(zippath)-1] = '\0'; + + if(!FS_CompareZipChecksum(zippath)) + Com_Error(ERR_DROP, "Incorrect checksum for file: %s", clc.downloadName); + } + + *clc.downloadTempName = *clc.downloadName = 0; + Cvar_Set("cl_downloadName", ""); + + // We are looking to start a download here + if (*clc.downloadList) { + + // Prompt if we do not allow automatic downloads + prompt = com_downloadPrompt->integer; + if( !( prompt & DLP_TYPE_MASK ) && + !( cl_allowDownload->integer & DLF_ENABLE ) ) { + char files[ MAX_INFO_STRING ] = ""; + char *name, *head, *pure_msg; + char *url_msg = ""; + int i = 0, others = 0, swap = 0, max_list = 12; + + // Set the download URL message + if( ( clc.sv_allowDownload & DLF_ENABLE ) && + !( clc.sv_allowDownload & DLF_NO_REDIRECT ) ) { + url_msg = va("The server redirects to the following URL:\n%s", + clc.sv_dlURL); + max_list -= 6; + } + + // Make a pretty version of the download list + name = clc.downloadList; + if( *name == '@' ) + name++; + + do { + // Copy remote name + head = name; + while( *head && *head != '@' ) + head++; + + swap = *head; + *head = 0; + + if( i++ < max_list ) { + if( i > 1 ) + Q_strcat( files, sizeof( files ), ", " ); + Q_strcat( files, sizeof( files ), name ); + } else { + others++; + } + + *head = swap; + if( !swap ) + break; + + // Skip local name + head++; + while( *head && *head != '@' ) + head++; + + name = head + 1; + } while( *head ); + + if( others ) { + Q_strcat( files, sizeof( files ), va( "(%d other file%s)\n", + others, others > 1 ? "s" : "" ) ); + } + + // Set the pure message + if( cl_connectedToPureServer ) { + if( !( clc.sv_allowDownload & DLF_ENABLE ) || + ( ( clc.sv_allowDownload & DLF_NO_UDP ) && + ( clc.sv_allowDownload & DLF_NO_REDIRECT ) ) ) { + pure_msg = "You are missing files required by the server. " + "The server does not allow downloading. " + "You must install these files manually:"; + } else { + pure_msg = "You are missing files required by the server. " + "You must download these files or disconnect:"; + } + } else { + pure_msg = "You are missing optional files provided by the " + "server. You may not need them to play but can " + "choose to download them anyway:"; + } + + Cvar_Set( "com_downloadPromptText", + va("%s\n\n%s\n%s", pure_msg, files, url_msg ) ); + Cvar_Set( "com_downloadPrompt", va("%d", DLP_SHOW ) ); + return; + } + + if( !( prompt & DLP_PROMPTED ) ) + Cvar_Set( "com_downloadPrompt", va("%d", prompt | DLP_PROMPTED ) ); + + prompt &= DLP_TYPE_MASK; + + s = clc.downloadList; + + // format is: + // @remotename@localname@remotename@localname, etc. + + if (*s == '@') + s++; + remoteName = s; + + if ( (s = strchr(s, '@')) == NULL ) { + CL_DownloadsComplete(); + return; + } + + *s++ = 0; + localName = s; + if ( (s = strchr(s, '@')) != NULL ) + *s++ = 0; + else + s = localName + strlen(localName); // point at the nul byte +#ifdef USE_CURL + if( ( ( cl_allowDownload->integer & DLF_ENABLE ) && + !( cl_allowDownload->integer & DLF_NO_REDIRECT ) ) || + prompt == DLP_CURL ) { + Com_Printf("Trying CURL download: %s; %s\n", localName, remoteName); + if(clc.sv_allowDownload & DLF_NO_REDIRECT) { + Com_Printf("WARNING: server does not " + "allow download redirection " + "(sv_allowDownload is %d)\n", + clc.sv_allowDownload); + } + else if(!*clc.sv_dlURL) { + Com_Printf("WARNING: server allows " + "download redirection, but does not " + "have sv_dlURL set\n"); + } + else if(!CL_cURL_Init()) { + Com_Printf("WARNING: could not load " + "cURL library\n"); + } + else { + CL_cURL_BeginDownload(localName, va("%s/%s", + clc.sv_dlURL, remoteName)); + useCURL = qtrue; + } + } + else if(!(clc.sv_allowDownload & DLF_NO_REDIRECT)) { + Com_Printf("WARNING: server allows download " + "redirection, but it disabled by client " + "configuration (cl_allowDownload is %d)\n", + cl_allowDownload->integer); + } +#endif /* USE_CURL */ + if(!useCURL) { + Com_Printf("Trying UDP download: %s; %s\n", localName, remoteName); + + if( ( !( cl_allowDownload->integer & DLF_ENABLE ) || + ( cl_allowDownload->integer & DLF_NO_UDP ) ) && + prompt != DLP_UDP ) { + if( cl_connectedToPureServer ) { + Com_Error(ERR_DROP, "Automatic downloads are " + "disabled on your client (cl_allowDownload is %d). " + "You can enable automatic downloads in the Options " + "menu.", + cl_allowDownload->integer); + return; + } + + Com_Printf("WARNING: UDP downloads are disabled.\n"); + CL_DownloadsComplete(); + return; + } + else { + CL_BeginDownload( localName, remoteName ); + } + } + clc.downloadRestart = qtrue; + + // move over the rest + memmove( clc.downloadList, s, strlen(s) + 1); + + return; + } + + CL_DownloadsComplete(); +} + +/* +================= +CL_InitDownloads + +After receiving a valid game state, we valid the cgame and local zip files here +and determine if we need to download them +================= +*/ +void CL_InitDownloads(void) { + if ( FS_ComparePaks( clc.downloadList, sizeof( clc.downloadList ) , qtrue ) ) { + Com_Printf("Need paks: %s\n", clc.downloadList ); + Cvar_Set( "com_downloadPrompt", "0" ); + if ( *clc.downloadList ) { + cls.state = CA_CONNECTED; + + *clc.downloadTempName = *clc.downloadName = 0; + Cvar_Set( "cl_downloadName", "" ); + + CL_NextDownload(); + return; + } + } + CL_DownloadsComplete(); +} + +/* +================= +CL_CheckForResend + +Resend a connect message if the last one has timed out +================= +*/ +void CL_CheckForResend( void ) { + int port, i; + char info[MAX_INFO_STRING]; + char data[MAX_INFO_STRING]; + + // don't send anything if playing back a demo + if ( clc.demoplaying ) { + return; + } + + // resend if we haven't gotten a reply yet + if ( cls.state != CA_CONNECTING && cls.state != CA_CHALLENGING ) { + return; + } + + if ( cls.realtime - clc.connectTime < RETRANSMIT_TIMEOUT ) { + return; + } + + clc.connectTime = cls.realtime; // for retransmit requests + clc.connectPacketCount++; + + + switch ( cls.state ) { + case CA_CONNECTING: + // requesting a challenge + + // The challenge request shall be followed by a client challenge so no malicious server can hijack this connection. + Com_sprintf(data, sizeof(data), "getchallenge %d", clc.challenge); + + NET_OutOfBandPrint(NS_CLIENT, clc.serverAddress, "%s", data); + break; + + case CA_CHALLENGING: + // sending back the challenge + port = Cvar_VariableValue ("net_qport"); + + Q_strncpyz( info, Cvar_InfoString( CVAR_USERINFO ), sizeof( info ) ); + Info_SetValueForKey( info, "protocol", va("%i", PROTOCOL_VERSION ) ); + Info_SetValueForKey( info, "qport", va("%i", port ) ); + Info_SetValueForKey( info, "challenge", va("%i", clc.challenge ) ); + + strcpy(data, "connect "); + // TTimo adding " " around the userinfo string to avoid truncated userinfo on the server + // (Com_TokenizeString tokenizes around spaces) + data[8] = '"'; + + for(i=0;i<strlen(info);i++) { + data[9+i] = info[i]; // + (clc.challenge)&0x3; + } + data[9+i] = '"'; + data[10+i] = 0; + + // NOTE TTimo don't forget to set the right data length! + NET_OutOfBandData( NS_CLIENT, clc.serverAddress, (byte *) &data[0], i+10 ); + // the most current userinfo has been sent, so watch for any + // newer changes to userinfo variables + cvar_modifiedFlags &= ~CVAR_USERINFO; + break; + + default: + Com_Error( ERR_FATAL, "CL_CheckForResend: bad cls.state" ); + } +} + +/* +=================== +CL_DisconnectPacket + +Sometimes the server can drop the client and the netchan based +disconnect can be lost. If the client continues to send packets +to the server, the server will send out of band disconnect packets +to the client so it doesn't have to wait for the full timeout period. +=================== +*/ +void CL_DisconnectPacket( netadr_t from ) { + if ( cls.state < CA_AUTHORIZING ) { + return; + } + + // if not from our server, ignore it + if ( !NET_CompareAdr( from, clc.netchan.remoteAddress ) ) { + return; + } + + // if we have received packets within three seconds, ignore it + // (it might be a malicious spoof) + if ( cls.realtime - clc.lastPacketTime < 3000 ) { + return; + } + + // drop the connection + Com_Printf( "Server disconnected for unknown reason\n" ); + Cvar_Set("com_errorMessage", "Server disconnected for unknown reason\n" ); + CL_Disconnect( qtrue ); +} + + +/* +=================== +CL_MotdPacket + +=================== +*/ +void CL_MotdPacket( netadr_t from, const char *info ) { + const char *v; + + // if not from our server, ignore it + if ( !NET_CompareAdr( from, cls.updateServer ) ) { + Com_DPrintf( "MOTD packet from unexpected source\n" ); + return; + } + + Com_DPrintf( "MOTD packet: %s\n", info ); + while( *info != '\\' ) + info++; + + // check challenge + v = Info_ValueForKey( info, "challenge" ); + if ( strcmp( v, cls.updateChallenge ) ) { + Com_DPrintf( "MOTD packet mismatched challenge: " + "'%s' != '%s'\n", v, cls.updateChallenge ); + return; + } + + v = Info_ValueForKey( info, "motd" ); + + Q_strncpyz( cls.updateInfoString, info, sizeof( cls.updateInfoString ) ); + Cvar_Set( "cl_motdString", v ); +} + +/* +=================== +CL_InitServerInfo +=================== +*/ +void CL_InitServerInfo( serverInfo_t *server, netadr_t *address ) { + server->adr = *address; + server->clients = 0; + server->hostName[0] = '\0'; + server->mapName[0] = '\0'; + server->label[0] = '\0'; + server->maxClients = 0; + server->maxPing = 0; + server->minPing = 0; + server->ping = -1; + server->game[0] = '\0'; + server->gameType = 0; + server->netType = 0; +} + +/* +=================== +CL_GSRSequenceInformation + +Parses this packet's index and the number of packets from a master server's +response. Updates the packet count and returns the index. Advances the data +pointer as appropriate (but only when parsing was successful) + +The sequencing information isn't terribly useful at present (we can skip +duplicate packets, but we don't bother to make sure we've got all of them). +=================== +*/ +int CL_GSRSequenceInformation( byte **data ) +{ + char *p = (char *)*data, *e; + int ind, num; + // '\0'-delimited fields: this packet's index, total number of packets + if( *p++ != '\0' ) + return -1; + + ind = strtol( p, (char **)&e, 10 ); + if( *e++ != '\0' ) + return -1; + + num = strtol( e, (char **)&p, 10 ); + if( *p++ != '\0' ) + return -1; + + if( num <= 0 || ind <= 0 || ind > num ) + return -1; // nonsensical response + + if( cls.numMasterPackets > 0 && num != cls.numMasterPackets ) + { + // Assume we sent two getservers and somehow they changed in + // between - only use the results that arrive later + Com_DPrintf( "Master changed its mind about packet count!\n" ); + cls.receivedMasterPackets = 0; + cls.numglobalservers = 0; + cls.numGlobalServerAddresses = 0; + } + cls.numMasterPackets = num; + + // successfully parsed + *data = (byte *)p; + return ind; +} + +/* +=================== +CL_GSRFeaturedLabel + +Parses from the data an arbitrary text string labelling the servers in the +following getserversresponse packet. +The result is copied to *buf, and *data is advanced as appropriate +=================== +*/ +void CL_GSRFeaturedLabel( byte **data, char *buf, int size ) +{ + char *l = buf; + buf[0] = '\0'; + + // copy until '\0' which indicates field break + // or slash which indicates beginning of server list + while( **data && **data != '\\' && **data != '/' ) + { + if( l < &buf[ size - 1 ] ) + *l = **data; + else if( l == &buf[ size - 1 ] ) + Com_DPrintf( S_COLOR_YELLOW "Warning: " + "CL_GSRFeaturedLabel: overflow\n" ); + l++, (*data)++; + } +} + +#define MAX_SERVERSPERPACKET 256 + +/* +=================== +CL_ServersResponsePacket +=================== +*/ +void CL_ServersResponsePacket( const netadr_t* from, msg_t *msg, qboolean extended ) { + int i, count, total; + netadr_t addresses[MAX_SERVERSPERPACKET]; + int numservers; + byte* buffptr; + byte* buffend; + char label[MAX_FEATLABEL_CHARS] = ""; + + Com_DPrintf("CL_ServersResponsePacket%s\n", + (extended) ? " (extended)" : ""); + + if (cls.numglobalservers == -1) { + // state to detect lack of servers or lack of response + cls.numglobalservers = 0; + cls.numGlobalServerAddresses = 0; + cls.numMasterPackets = 0; + cls.receivedMasterPackets = 0; + } + + // parse through server response string + numservers = 0; + buffptr = msg->data; + buffend = buffptr + msg->cursize; + + // skip header + buffptr += 4; + + // advance to initial token + // I considered using strchr for this but I don't feel like relying + // on its behaviour with '\0' + while( *buffptr && *buffptr != '\\' && *buffptr != '/' ) + { + buffptr++; + + if( buffptr >= buffend ) + break; + } + + if( *buffptr == '\0' ) + { + int ind = CL_GSRSequenceInformation( &buffptr ); + if( ind >= 0 ) + { + // this denotes the start of new-syntax stuff + // have we already received this packet? + if( cls.receivedMasterPackets & ( 1 << ( ind - 1 ) ) ) + { + Com_DPrintf( "CL_ServersResponsePacket: " + "received packet %d again, ignoring\n", + ind ); + return; + } + // TODO: detect dropped packets and make another + // request + Com_DPrintf( "CL_ServersResponsePacket: packet " + "%d of %d\n", ind, cls.numMasterPackets ); + cls.receivedMasterPackets |= ( 1 << ( ind - 1 ) ); + + CL_GSRFeaturedLabel( &buffptr, label, sizeof( label ) ); + } + // now skip to the server list + for(; buffptr < buffend && *buffptr != '\\' && *buffptr != '/'; + buffptr++ ); + } + + while (buffptr + 1 < buffend) + { + // IPv4 address + if (*buffptr == '\\') + { + buffptr++; + + if (buffend - buffptr < sizeof(addresses[numservers].ip) + sizeof(addresses[numservers].port) + 1) + break; + + for(i = 0; i < sizeof(addresses[numservers].ip); i++) + addresses[numservers].ip[i] = *buffptr++; + + addresses[numservers].type = NA_IP; + } + // IPv6 address, if it's an extended response + else if (extended && *buffptr == '/') + { + buffptr++; + + if (buffend - buffptr < sizeof(addresses[numservers].ip6) + sizeof(addresses[numservers].port) + 1) + break; + + for(i = 0; i < sizeof(addresses[numservers].ip6); i++) + addresses[numservers].ip6[i] = *buffptr++; + + addresses[numservers].type = NA_IP6; + addresses[numservers].scope_id = from->scope_id; + } + else + // syntax error! + break; + + // parse out port + addresses[numservers].port = (*buffptr++) << 8; + addresses[numservers].port += *buffptr++; + addresses[numservers].port = BigShort( addresses[numservers].port ); + + // syntax check + if (*buffptr != '\\' && *buffptr != '/') + break; + + numservers++; + if (numservers >= MAX_SERVERSPERPACKET) + break; + } + + count = cls.numglobalservers; + + for (i = 0; i < numservers && count < MAX_GLOBAL_SERVERS; i++) { + // build net address + serverInfo_t *server = &cls.globalServers[count]; + + CL_InitServerInfo( server, &addresses[i] ); + Q_strncpyz( server->label, label, sizeof( server->label ) ); + // advance to next slot + count++; + } + + // if getting the global list + if ( count >= MAX_GLOBAL_SERVERS && cls.numGlobalServerAddresses < MAX_GLOBAL_SERVERS ) + { + // if we couldn't store the servers in the main list anymore + for (; i < numservers && cls.numGlobalServerAddresses < MAX_GLOBAL_SERVERS; i++) + { + // just store the addresses in an additional list + cls.globalServerAddresses[cls.numGlobalServerAddresses++] = addresses[i]; + } + } + + cls.numglobalservers = count; + total = count + cls.numGlobalServerAddresses; + + Com_Printf("%d servers parsed (total %d)\n", numservers, total); +} + +/* +================= +CL_ConnectionlessPacket + +Responses to broadcasts, etc +================= +*/ +void CL_ConnectionlessPacket( netadr_t from, msg_t *msg ) { + char *s; + char *c; + + MSG_BeginReadingOOB( msg ); + MSG_ReadLong( msg ); // skip the -1 + + s = MSG_ReadStringLine( msg ); + + Cmd_TokenizeString( s ); + + c = Cmd_Argv(0); + + Com_DPrintf ("CL packet %s: %s\n", NET_AdrToStringwPort(from), c); + + // challenge from the server we are connecting to + if (!Q_stricmp(c, "challengeResponse")) + { + if (cls.state != CA_CONNECTING) + { + Com_DPrintf("Unwanted challenge response received. Ignored.\n"); + return; + } + + if(!NET_CompareAdr(from, clc.serverAddress)) + { + // This challenge response is not coming from the expected address. + // Check whether we have a matching client challenge to prevent + // connection hi-jacking. + + c = Cmd_Argv(2); + + if(!*c || atoi(c) != clc.challenge) + { + Com_DPrintf("Challenge response received from unexpected source. Ignored.\n"); + return; + } + } + + // start sending challenge response instead of challenge request packets + clc.challenge = atoi(Cmd_Argv(1)); + cls.state = CA_CHALLENGING; + clc.connectPacketCount = 0; + clc.connectTime = -99999; + + // take this address as the new server address. This allows + // a server proxy to hand off connections to multiple servers + clc.serverAddress = from; + Com_DPrintf ("challengeResponse: %d\n", clc.challenge); + return; + } + + // server connection + if ( !Q_stricmp(c, "connectResponse") ) { + if ( cls.state >= CA_CONNECTED ) { + Com_Printf ("Dup connect received. Ignored.\n"); + return; + } + if ( cls.state != CA_CHALLENGING ) { + Com_Printf ("connectResponse packet while not connecting. Ignored.\n"); + return; + } + if ( !NET_CompareAdr( from, clc.serverAddress ) ) { + Com_Printf( "connectResponse from wrong address. Ignored.\n" ); + return; + } + Netchan_Setup (NS_CLIENT, &clc.netchan, from, Cvar_VariableValue( "net_qport" ) ); + cls.state = CA_CONNECTED; + clc.lastPacketSentTime = -9999; // send first packet immediately + return; + } + + // server responding to an info broadcast + if ( !Q_stricmp(c, "infoResponse") ) { + CL_ServerInfoPacket( from, msg ); + return; + } + + // server responding to a get playerlist + if ( !Q_stricmp(c, "statusResponse") ) { + CL_ServerStatusResponse( from, msg ); + return; + } + + // a disconnect message from the server, which will happen if the server + // dropped the connection but it is still getting packets from us + if (!Q_stricmp(c, "disconnect")) { + CL_DisconnectPacket( from ); + return; + } + + // echo request from server + if ( !Q_stricmp(c, "echo") ) { + NET_OutOfBandPrint( NS_CLIENT, from, "%s", Cmd_Argv(1) ); + return; + } + + // global MOTD from trem master + if ( !Q_stricmp(c, "motd") ) { + CL_MotdPacket( from, s ); + return; + } + + // echo request from server + if ( !Q_stricmp(c, "print") ) { + s = MSG_ReadString( msg ); + + Q_strncpyz( clc.serverMessage, s, sizeof( clc.serverMessage ) ); + + while( clc.serverMessage[ strlen( clc.serverMessage ) - 1 ] == '\n' ) + clc.serverMessage[ strlen( clc.serverMessage ) - 1 ] = '\0'; + + Com_Printf( "%s", s ); + return; + } + + // list of servers sent back by a master server (classic) + if ( !Q_strncmp(c, "getserversResponse", 18) ) { + CL_ServersResponsePacket( &from, msg, qfalse ); + return; + } + + // list of servers sent back by a master server (extended) + if ( !Q_strncmp(c, "getserversExtResponse", 21) ) { + CL_ServersResponsePacket( &from, msg, qtrue ); + return; + } + + Com_DPrintf ("Unknown connectionless packet command.\n"); +} + + +/* +================= +CL_PacketEvent + +A packet has arrived from the main event loop +================= +*/ +void CL_PacketEvent( netadr_t from, msg_t *msg ) { + int headerBytes; + + clc.lastPacketTime = cls.realtime; + + if ( msg->cursize >= 4 && *(int *)msg->data == -1 ) { + CL_ConnectionlessPacket( from, msg ); + return; + } + + if ( cls.state < CA_CONNECTED ) { + return; // can't be a valid sequenced packet + } + + if ( msg->cursize < 4 ) { + Com_Printf ("%s: Runt packet\n", NET_AdrToStringwPort( from )); + return; + } + + // + // packet from server + // + if ( !NET_CompareAdr( from, clc.netchan.remoteAddress ) ) { + Com_DPrintf ("%s:sequenced packet without connection\n" + , NET_AdrToStringwPort( from ) ); + // FIXME: send a client disconnect? + return; + } + + if (!CL_Netchan_Process( &clc.netchan, msg) ) { + return; // out of order, duplicated, etc + } + + // the header is different lengths for reliable and unreliable messages + headerBytes = msg->readcount; + + // track the last message received so it can be returned in + // client messages, allowing the server to detect a dropped + // gamestate + clc.serverMessageSequence = LittleLong( *(int *)msg->data ); + + clc.lastPacketTime = cls.realtime; + CL_ParseServerMessage( msg ); + + // + // we don't know if it is ok to save a demo message until + // after we have parsed the frame + // + if ( clc.demorecording && !clc.demowaiting ) { + CL_WriteDemoMessage( msg, headerBytes ); + } +} + +/* +================== +CL_CheckTimeout + +================== +*/ +void CL_CheckTimeout( void ) { + // + // check timeout + // + if ( ( !CL_CheckPaused() || !sv_paused->integer ) + && cls.state >= CA_CONNECTED && cls.state != CA_CINEMATIC + && cls.realtime - clc.lastPacketTime > cl_timeout->value*1000) { + if (++cl.timeoutcount > 5) { // timeoutcount saves debugger + Com_Printf ("\nServer connection timed out.\n"); + CL_Disconnect( qtrue ); + return; + } + } else { + cl.timeoutcount = 0; + } +} + +/* +================== +CL_CheckPaused +Check whether client has been paused. +================== +*/ +qboolean CL_CheckPaused(void) +{ + // if cl_paused->modified is set, the cvar has only been changed in + // this frame. Keep paused in this frame to ensure the server doesn't + // lag behind. + if(cl_paused->integer || cl_paused->modified) + return qtrue; + + return qfalse; +} + +//============================================================================ + +/* +================== +CL_CheckUserinfo + +================== +*/ +void CL_CheckUserinfo( void ) { + // don't add reliable commands when not yet connected + if(cls.state < CA_CHALLENGING) + return; + + // don't overflow the reliable command buffer when paused + if(CL_CheckPaused()) + return; + + // send a reliable userinfo update if needed + if(cvar_modifiedFlags & CVAR_USERINFO) + { + cvar_modifiedFlags &= ~CVAR_USERINFO; + CL_AddReliableCommand(va("userinfo \"%s\"", Cvar_InfoString( CVAR_USERINFO ) ), qfalse); + } +} + +/* +================== +CL_Frame + +================== +*/ +void CL_Frame ( int msec ) { + + if ( !com_cl_running->integer ) { + return; + } + + // We may have a download prompt ready + if( ( com_downloadPrompt->integer & DLP_TYPE_MASK ) && + !( com_downloadPrompt->integer & DLP_PROMPTED ) ) { + Com_Printf( "Download prompt returned %d\n", + com_downloadPrompt->integer ); + CL_NextDownload( ); + } + else if( com_downloadPrompt->integer & DLP_SHOW ) { + // If the UI VM does not support the download prompt, we need to catch + // the prompt here and replicate regular behavior. + // One frame will always run between requesting and showing the prompt. + + if( com_downloadPrompt->integer & DLP_STALE ) { + Com_Printf( "WARNING: UI VM does not support download prompt\n" ); + Cvar_Set( "com_downloadPrompt", va( "%d", DLP_IGNORE ) ); + CL_NextDownload( ); + } else { + Cvar_Set( "com_downloadPrompt", + va( "%d", com_downloadPrompt->integer | DLP_STALE ) ); + } + } + +#ifdef USE_CURL + if(clc.downloadCURLM) { + CL_cURL_PerformDownload(); + // we can't process frames normally when in disconnected + // download mode since the ui vm expects cls.state to be + // CA_CONNECTED + if(clc.cURLDisconnected) { + cls.realFrametime = msec; + cls.frametime = msec; + cls.realtime += cls.frametime; + SCR_UpdateScreen(); + S_Update(); + Con_RunConsole(); + cls.framecount++; + return; + } + } +#endif + + if ( cls.state == CA_DISCONNECTED && !( Key_GetCatcher( ) & KEYCATCH_UI ) + && !com_sv_running->integer && uivm ) { + // if disconnected, bring up the menu + S_StopAllSounds(); + VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_MAIN ); + } + + // if recording an avi, lock to a fixed fps + if ( CL_VideoRecording( ) && cl_aviFrameRate->integer && msec) { + // save the current screen + if ( cls.state == CA_ACTIVE || cl_forceavidemo->integer) { + CL_TakeVideoFrame( ); + + // fixed time for next frame' + msec = (int)ceil( (1000.0f / cl_aviFrameRate->value) * com_timescale->value ); + if (msec == 0) { + msec = 1; + } + } + } + + if( cl_autoRecordDemo->integer ) { + if( cls.state == CA_ACTIVE && !clc.demorecording && !clc.demoplaying ) { + // If not recording a demo, and we should be, start one + qtime_t now; + char *nowString; + char *p; + char mapName[ MAX_QPATH ]; + char serverName[ MAX_OSPATH ]; + + Com_RealTime( &now ); + nowString = va( "%04d%02d%02d%02d%02d%02d", + 1900 + now.tm_year, + 1 + now.tm_mon, + now.tm_mday, + now.tm_hour, + now.tm_min, + now.tm_sec ); + + Q_strncpyz( serverName, cls.servername, MAX_OSPATH ); + // Replace the ":" in the address as it is not a valid + // file name character + p = strstr( serverName, ":" ); + if( p ) { + *p = '.'; + } + + Q_strncpyz( mapName, COM_SkipPath( cl.mapname ), sizeof( cl.mapname ) ); + COM_StripExtension(mapName, mapName, sizeof(mapName)); + + Cbuf_ExecuteText( EXEC_NOW, + va( "record %s-%s-%s", nowString, serverName, mapName ) ); + } + else if( cls.state != CA_ACTIVE && clc.demorecording ) { + // Recording, but not CA_ACTIVE, so stop recording + CL_StopRecord_f( ); + } + } + + // save the msec before checking pause + cls.realFrametime = msec; + + // decide the simulation time + cls.frametime = msec; + + cls.realtime += cls.frametime; + + if ( cl_timegraph->integer ) { + SCR_DebugGraph ( cls.realFrametime * 0.25, 0 ); + } + + // see if we need to update any userinfo + CL_CheckUserinfo(); + + // if we haven't gotten a packet in a long time, + // drop the connection + CL_CheckTimeout(); + + // send intentions now + CL_SendCmd(); + + // resend a connection request if necessary + CL_CheckForResend(); + + // decide on the serverTime to render + CL_SetCGameTime(); + + // update the screen + SCR_UpdateScreen(); + + // update audio + S_Update(); + +#ifdef USE_VOIP + CL_CaptureVoip(); +#endif + +#ifdef USE_MUMBLE + CL_UpdateMumble(); +#endif + + // advance local effects for next frame + SCR_RunCinematic(); + + Con_RunConsole(); + + cls.framecount++; +} + + +//============================================================================ + +/* +================ +CL_RefPrintf + +DLL glue +================ +*/ +void QDECL CL_RefPrintf( int print_level, const char *fmt, ...) { + va_list argptr; + char msg[MAXPRINTMSG]; + + va_start (argptr,fmt); + Q_vsnprintf (msg, sizeof(msg), fmt, argptr); + va_end (argptr); + + if ( print_level == PRINT_ALL ) { + Com_Printf ("%s", msg); + } else if ( print_level == PRINT_WARNING ) { + Com_Printf (S_COLOR_YELLOW "%s", msg); // yellow + } else if ( print_level == PRINT_DEVELOPER ) { + Com_DPrintf (S_COLOR_RED "%s", msg); // red + } +} + + + +/* +============ +CL_ShutdownRef +============ +*/ +void CL_ShutdownRef( void ) { + if ( !re.Shutdown ) { + return; + } + re.Shutdown( qtrue ); + Com_Memset( &re, 0, sizeof( re ) ); +} + +/* +============ +CL_InitRenderer +============ +*/ +void CL_InitRenderer( void ) { + // this sets up the renderer and calls R_Init + re.BeginRegistration( &cls.glconfig ); + + // load character sets + cls.charSetShader = re.RegisterShader( "gfx/2d/bigchars" ); + cls.whiteShader = re.RegisterShader( "white" ); + cls.consoleShader = re.RegisterShader( "console" ); + g_console_field_width = cls.glconfig.vidWidth / SMALLCHAR_WIDTH - 2; + g_consoleField.widthInChars = g_console_field_width; +} + +/* +============================ +CL_StartHunkUsers + +After the server has cleared the hunk, these will need to be restarted +This is the only place that any of these functions are called from +============================ +*/ +void CL_StartHunkUsers( qboolean rendererOnly ) { + if (!com_cl_running) { + return; + } + + if ( !com_cl_running->integer ) { + return; + } + + if ( !cls.rendererStarted ) { + cls.rendererStarted = qtrue; + CL_InitRenderer(); + } + + if ( rendererOnly ) { + return; + } + + if ( !cls.soundStarted ) { + cls.soundStarted = qtrue; + S_Init(); + } + + if ( !cls.soundRegistered ) { + cls.soundRegistered = qtrue; + S_BeginRegistration(); + } + + if( com_dedicated->integer ) { + return; + } + + if ( !cls.uiStarted ) { + cls.uiStarted = qtrue; + CL_InitUI(); + } +} + +/* +============ +CL_RefMalloc +============ +*/ +void *CL_RefMalloc( int size ) { + return Z_TagMalloc( size, TAG_RENDERER ); +} + +int CL_ScaledMilliseconds(void) { + return Sys_Milliseconds()*com_timescale->value; +} + +/* +============ +CL_InitRef +============ +*/ +void CL_InitRef( void ) { + refimport_t ri; + refexport_t *ret; + + Com_Printf( "----- Initializing Renderer ----\n" ); + + ri.Cmd_AddCommand = Cmd_AddCommand; + ri.Cmd_RemoveCommand = Cmd_RemoveCommand; + ri.Cmd_Argc = Cmd_Argc; + ri.Cmd_Argv = Cmd_Argv; + ri.Cmd_ExecuteText = Cbuf_ExecuteText; + ri.Printf = CL_RefPrintf; + ri.Error = Com_Error; + ri.Milliseconds = CL_ScaledMilliseconds; + ri.Malloc = CL_RefMalloc; + ri.Free = Z_Free; +#ifdef HUNK_DEBUG + ri.Hunk_AllocDebug = Hunk_AllocDebug; +#else + ri.Hunk_Alloc = Hunk_Alloc; +#endif + ri.Hunk_AllocateTempMemory = Hunk_AllocateTempMemory; + ri.Hunk_FreeTempMemory = Hunk_FreeTempMemory; + ri.CM_DrawDebugSurface = CM_DrawDebugSurface; + ri.FS_ReadFile = FS_ReadFile; + ri.FS_FreeFile = FS_FreeFile; + ri.FS_WriteFile = FS_WriteFile; + ri.FS_FreeFileList = FS_FreeFileList; + ri.FS_ListFiles = FS_ListFiles; + ri.FS_FileIsInPAK = FS_FileIsInPAK; + ri.FS_FileExists = FS_FileExists; + ri.Cvar_Get = Cvar_Get; + ri.Cvar_Set = Cvar_Set; + ri.Cvar_CheckRange = Cvar_CheckRange; + + // cinematic stuff + + ri.CIN_UploadCinematic = CIN_UploadCinematic; + ri.CIN_PlayCinematic = CIN_PlayCinematic; + ri.CIN_RunCinematic = CIN_RunCinematic; + + ri.CL_WriteAVIVideoFrame = CL_WriteAVIVideoFrame; + + ret = GetRefAPI( REF_API_VERSION, &ri ); + +#if defined __USEA3D && defined __A3D_GEOM + hA3Dg_ExportRenderGeom (ret); +#endif + + Com_Printf( "-------------------------------\n"); + + if ( !ret ) { + Com_Error (ERR_FATAL, "Couldn't initialize refresh" ); + } + + re = *ret; + + // unpause so the cgame definately gets a snapshot and renders a frame + Cvar_Set( "cl_paused", "0" ); +} + + +//=========================================================================================== + + +void CL_SetModel_f( void ) { + char *arg; + char name[256]; + + arg = Cmd_Argv( 1 ); + if (arg[0]) { + Cvar_Set( "model", arg ); + Cvar_Set( "headmodel", arg ); + } else { + Cvar_VariableStringBuffer( "model", name, sizeof(name) ); + Com_Printf("model is set to %s\n", name); + } +} + + +//=========================================================================================== + + +/* +=============== +CL_Video_f + +video +video [filename] +=============== +*/ +void CL_Video_f( void ) +{ + char filename[ MAX_OSPATH ]; + int i, last; + + if( !clc.demoplaying ) + { + Com_Printf( "The video command can only be used when playing back demos\n" ); + return; + } + + if( Cmd_Argc( ) == 2 ) + { + // explicit filename + Com_sprintf( filename, MAX_OSPATH, "videos/%s.avi", Cmd_Argv( 1 ) ); + } + else + { + // scan for a free filename + for( i = 0; i <= 9999; i++ ) + { + int a, b, c, d; + + last = i; + + a = last / 1000; + last -= a * 1000; + b = last / 100; + last -= b * 100; + c = last / 10; + last -= c * 10; + d = last; + + Com_sprintf( filename, MAX_OSPATH, "videos/video%d%d%d%d.avi", + a, b, c, d ); + + if( !FS_FileExists( filename ) ) + break; // file doesn't exist + } + + if( i > 9999 ) + { + Com_Printf( S_COLOR_RED "ERROR: no free file names to create video\n" ); + return; + } + } + + CL_OpenAVIForWriting( filename ); +} + +/* +=============== +CL_StopVideo_f +=============== +*/ +void CL_StopVideo_f( void ) +{ + CL_CloseAVI( ); +} + +/* +=============== +CL_GenerateQKey + +test to see if a valid QKEY_FILE exists. If one does not, try to generate +it by filling it with 2048 bytes of random data. +=============== +*/ +static void CL_GenerateQKey(void) +{ + int len = 0; + unsigned char buff[ QKEY_SIZE ]; + fileHandle_t f; + + len = FS_SV_FOpenFileRead( QKEY_FILE, &f ); + FS_FCloseFile( f ); + if( len == QKEY_SIZE ) { + Com_Printf( "QKEY found.\n" ); + return; + } + else { + if( len > 0 ) { + Com_Printf( "QKEY file size != %d, regenerating\n", + QKEY_SIZE ); + } + + Com_Printf( "QKEY building random string\n" ); + Com_RandomBytes( buff, sizeof(buff) ); + + f = FS_SV_FOpenFileWrite( QKEY_FILE ); + if( !f ) { + Com_Printf( "QKEY could not open %s for write\n", + QKEY_FILE ); + return; + } + FS_Write( buff, sizeof(buff), f ); + FS_FCloseFile( f ); + Com_Printf( "QKEY generated\n" ); + } +} + +/* +==================== +CL_Init +==================== +*/ +void CL_Init( void ) { + Com_Printf( "----- Client Initialization -----\n" ); + + Con_Init (); + + CL_ClearState (); + + cls.state = CA_DISCONNECTED; // no longer CA_UNINITIALIZED + + cls.realtime = 0; + + CL_InitInput (); + + // + // register our variables + // + cl_noprint = Cvar_Get( "cl_noprint", "0", 0 ); + cl_motd = Cvar_Get ("cl_motd", "1", 0); + + cl_timeout = Cvar_Get ("cl_timeout", "200", 0); + + cl_timeNudge = Cvar_Get ("cl_timeNudge", "0", CVAR_TEMP ); + cl_shownet = Cvar_Get ("cl_shownet", "0", CVAR_TEMP ); + cl_showSend = Cvar_Get ("cl_showSend", "0", CVAR_TEMP ); + cl_showTimeDelta = Cvar_Get ("cl_showTimeDelta", "0", CVAR_TEMP ); + cl_freezeDemo = Cvar_Get ("cl_freezeDemo", "0", CVAR_TEMP ); + rcon_client_password = Cvar_Get ("rconPassword", "", CVAR_TEMP ); + cl_activeAction = Cvar_Get( "activeAction", "", CVAR_TEMP ); + + cl_timedemo = Cvar_Get ("timedemo", "0", 0); + cl_timedemoLog = Cvar_Get ("cl_timedemoLog", "", CVAR_ARCHIVE); + cl_autoRecordDemo = Cvar_Get ("cl_autoRecordDemo", "0", CVAR_ARCHIVE); + cl_aviFrameRate = Cvar_Get ("cl_aviFrameRate", "25", CVAR_ARCHIVE); + cl_aviMotionJpeg = Cvar_Get ("cl_aviMotionJpeg", "1", CVAR_ARCHIVE); + cl_forceavidemo = Cvar_Get ("cl_forceavidemo", "0", 0); + + rconAddress = Cvar_Get ("rconAddress", "", 0); + + cl_yawspeed = Cvar_Get ("cl_yawspeed", "140", CVAR_ARCHIVE); + cl_pitchspeed = Cvar_Get ("cl_pitchspeed", "140", CVAR_ARCHIVE); + cl_anglespeedkey = Cvar_Get ("cl_anglespeedkey", "1.5", 0); + + cl_maxpackets = Cvar_Get ("cl_maxpackets", "30", CVAR_ARCHIVE ); + cl_packetdup = Cvar_Get ("cl_packetdup", "1", CVAR_ARCHIVE ); + + cl_run = Cvar_Get ("cl_run", "1", CVAR_ARCHIVE); + cl_sensitivity = Cvar_Get ("sensitivity", "5", CVAR_ARCHIVE); + cl_mouseAccel = Cvar_Get ("cl_mouseAccel", "0", CVAR_ARCHIVE); + cl_freelook = Cvar_Get( "cl_freelook", "1", CVAR_ARCHIVE ); + + // 0: legacy mouse acceleration + // 1: new implementation + cl_mouseAccelStyle = Cvar_Get( "cl_mouseAccelStyle", "0", CVAR_ARCHIVE ); + // offset for the power function (for style 1, ignored otherwise) + // this should be set to the max rate value + cl_mouseAccelOffset = Cvar_Get( "cl_mouseAccelOffset", "5", CVAR_ARCHIVE ); + Cvar_CheckRange(cl_mouseAccelOffset, 0.001f, 50000.0f, qfalse); + + cl_showMouseRate = Cvar_Get ("cl_showmouserate", "0", 0); + + cl_allowDownload = Cvar_Get ("cl_allowDownload", "0", CVAR_ARCHIVE); +#ifdef USE_CURL + cl_cURLLib = Cvar_Get("cl_cURLLib", DEFAULT_CURL_LIB, CVAR_ARCHIVE); +#endif + com_downloadPrompt = Cvar_Get ("com_downloadPrompt", "0", CVAR_ROM); + Cvar_Get( "com_downloadPromptText", "", CVAR_TEMP ); + + cl_conXOffset = Cvar_Get ("cl_conXOffset", "0", 0); +#ifdef MACOS_X + // In game video is REALLY slow in Mac OS X right now due to driver slowness + cl_inGameVideo = Cvar_Get ("r_inGameVideo", "0", CVAR_ARCHIVE); +#else + cl_inGameVideo = Cvar_Get ("r_inGameVideo", "1", CVAR_ARCHIVE); +#endif + + cl_serverStatusResendTime = Cvar_Get ("cl_serverStatusResendTime", "750", 0); + + m_pitch = Cvar_Get ("m_pitch", "0.022", CVAR_ARCHIVE); + m_yaw = Cvar_Get ("m_yaw", "0.022", CVAR_ARCHIVE); + m_forward = Cvar_Get ("m_forward", "0.25", CVAR_ARCHIVE); + m_side = Cvar_Get ("m_side", "0.25", CVAR_ARCHIVE); +#ifdef MACOS_X + // Input is jittery on OS X w/o this + m_filter = Cvar_Get ("m_filter", "1", CVAR_ARCHIVE); +#else + m_filter = Cvar_Get ("m_filter", "0", CVAR_ARCHIVE); +#endif + + j_pitch = Cvar_Get ("j_pitch", "0.022", CVAR_ARCHIVE); + j_yaw = Cvar_Get ("j_yaw", "-0.022", CVAR_ARCHIVE); + j_forward = Cvar_Get ("j_forward", "-0.25", CVAR_ARCHIVE); + j_side = Cvar_Get ("j_side", "0.25", CVAR_ARCHIVE); + j_pitch_axis = Cvar_Get ("j_pitch_axis", "3", CVAR_ARCHIVE); + j_yaw_axis = Cvar_Get ("j_yaw_axis", "4", CVAR_ARCHIVE); + j_forward_axis = Cvar_Get ("j_forward_axis", "1", CVAR_ARCHIVE); + j_side_axis = Cvar_Get ("j_side_axis", "0", CVAR_ARCHIVE); + + cl_motdString = Cvar_Get( "cl_motdString", "", CVAR_ROM ); + + Cvar_Get( "cl_maxPing", "800", CVAR_ARCHIVE ); + + cl_lanForcePackets = Cvar_Get ("cl_lanForcePackets", "1", CVAR_ARCHIVE); + + cl_guidServerUniq = Cvar_Get ("cl_guidServerUniq", "1", CVAR_ARCHIVE); + + // ~ and `, as keys and characters + cl_consoleKeys = Cvar_Get( "cl_consoleKeys", "~ ` 0x7e 0x60", CVAR_ARCHIVE); + + cl_gamename = Cvar_Get("cl_gamename", GAMENAME_FOR_MASTER, CVAR_TEMP); + + // userinfo + Cvar_Get ("name", Sys_GetCurrentUser( ), CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get ("rate", "25000", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get ("snaps", "20", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get ("color1", "4", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get ("color2", "5", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get ("handicap", "100", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get ("sex", "male", CVAR_USERINFO | CVAR_ARCHIVE ); + + Cvar_Get ("password", "", CVAR_USERINFO); + +#ifdef USE_MUMBLE + cl_useMumble = Cvar_Get ("cl_useMumble", "0", CVAR_ARCHIVE | CVAR_LATCH); + cl_mumbleScale = Cvar_Get ("cl_mumbleScale", "0.0254", CVAR_ARCHIVE); +#endif + +#ifdef USE_VOIP + cl_voipSend = Cvar_Get ("cl_voipSend", "0", 0); + cl_voipSendTarget = Cvar_Get ("cl_voipSendTarget", "all", 0); + cl_voipGainDuringCapture = Cvar_Get ("cl_voipGainDuringCapture", "0.2", CVAR_ARCHIVE); + cl_voipCaptureMult = Cvar_Get ("cl_voipCaptureMult", "2.0", CVAR_ARCHIVE); + cl_voipUseVAD = Cvar_Get ("cl_voipUseVAD", "0", CVAR_ARCHIVE); + cl_voipVADThreshold = Cvar_Get ("cl_voipVADThreshold", "0.25", CVAR_ARCHIVE); + cl_voipShowMeter = Cvar_Get ("cl_voipShowMeter", "1", CVAR_ARCHIVE); + + // This is a protocol version number. + cl_voip = Cvar_Get ("cl_voip", "1", CVAR_USERINFO | CVAR_ARCHIVE | CVAR_LATCH); + Cvar_CheckRange( cl_voip, 0, 1, qtrue ); + + // If your data rate is too low, you'll get Connection Interrupted warnings + // when VoIP packets arrive, even if you have a broadband connection. + // This might work on rates lower than 25000, but for safety's sake, we'll + // just demand it. Who doesn't have at least a DSL line now, anyhow? If + // you don't, you don't need VoIP. :) + if ((cl_voip->integer) && (Cvar_VariableIntegerValue("rate") < 25000)) { + Com_Printf(S_COLOR_YELLOW "Your network rate is too slow for VoIP.\n"); + Com_Printf("Set 'Data Rate' to 'LAN/Cable/xDSL' in 'Setup/System/Network' and restart.\n"); + Com_Printf("Until then, VoIP is disabled.\n"); + Cvar_Set("cl_voip", "0"); + } +#endif + + + // cgame might not be initialized before menu is used + Cvar_Get ("cg_viewsize", "100", CVAR_ARCHIVE ); + // Make sure cg_stereoSeparation is zero as that variable is deprecated and should not be used anymore. + Cvar_Get ("cg_stereoSeparation", "0", CVAR_ROM); + + // + // register our commands + // + Cmd_AddCommand ("cmd", CL_ForwardToServer_f); + Cmd_AddCommand ("configstrings", CL_Configstrings_f); + Cmd_AddCommand ("clientinfo", CL_Clientinfo_f); + Cmd_AddCommand ("snd_restart", CL_Snd_Restart_f); + Cmd_AddCommand ("vid_restart", CL_Vid_Restart_f); + Cmd_AddCommand ("disconnect", CL_Disconnect_f); + Cmd_AddCommand ("record", CL_Record_f); + Cmd_AddCommand ("demo", CL_PlayDemo_f); + Cmd_SetCommandCompletionFunc( "demo", CL_CompleteDemoName ); + Cmd_AddCommand ("cinematic", CL_PlayCinematic_f); + Cmd_AddCommand ("stoprecord", CL_StopRecord_f); + Cmd_AddCommand ("connect", CL_Connect_f); + Cmd_AddCommand ("reconnect", CL_Reconnect_f); + Cmd_AddCommand ("localservers", CL_LocalServers_f); + Cmd_AddCommand ("globalservers", CL_GlobalServers_f); + Cmd_AddCommand ("rcon", CL_Rcon_f); + Cmd_SetCommandCompletionFunc( "rcon", CL_CompleteRcon ); + Cmd_AddCommand ("ping", CL_Ping_f ); + Cmd_AddCommand ("serverstatus", CL_ServerStatus_f ); + Cmd_AddCommand ("showip", CL_ShowIP_f ); + Cmd_AddCommand ("fs_openedList", CL_OpenedPK3List_f ); + Cmd_AddCommand ("fs_referencedList", CL_ReferencedPK3List_f ); + Cmd_AddCommand ("model", CL_SetModel_f ); + Cmd_AddCommand ("video", CL_Video_f ); + Cmd_AddCommand ("stopvideo", CL_StopVideo_f ); + Cmd_AddCommand("minimize", GLimp_Minimize); + CL_InitRef(); + + SCR_Init (); + +// Cbuf_Execute (); + + Cvar_Set( "cl_running", "1" ); + + CL_GenerateQKey(); + Cvar_Get( "cl_guid", "", CVAR_USERINFO | CVAR_ROM ); + CL_UpdateGUID( NULL, 0 ); + + Com_Printf( "----- Client Initialization Complete -----\n" ); +} + + +/* +=============== +CL_Shutdown + +=============== +*/ +void CL_Shutdown( char *finalmsg ) { + static qboolean recursive = qfalse; + + // check whether the client is running at all. + if(!(com_cl_running && com_cl_running->integer)) + return; + + Com_Printf( "----- Client Shutdown (%s) -----\n", finalmsg ); + + if ( recursive ) { + Com_Printf( "WARNING: Recursive shutdown\n" ); + return; + } + recursive = qtrue; + + CL_Disconnect( qtrue ); + + S_Shutdown(); + CL_ShutdownRef(); + + CL_ShutdownUI(); + + Cmd_RemoveCommand ("cmd"); + Cmd_RemoveCommand ("configstrings"); + Cmd_RemoveCommand ("userinfo"); + Cmd_RemoveCommand ("snd_restart"); + Cmd_RemoveCommand ("vid_restart"); + Cmd_RemoveCommand ("disconnect"); + Cmd_RemoveCommand ("record"); + Cmd_RemoveCommand ("demo"); + Cmd_RemoveCommand ("cinematic"); + Cmd_RemoveCommand ("stoprecord"); + Cmd_RemoveCommand ("connect"); + Cmd_RemoveCommand ("localservers"); + Cmd_RemoveCommand ("globalservers"); + Cmd_RemoveCommand ("rcon"); + Cmd_RemoveCommand ("ping"); + Cmd_RemoveCommand ("serverstatus"); + Cmd_RemoveCommand ("showip"); + Cmd_RemoveCommand ("model"); + Cmd_RemoveCommand ("video"); + Cmd_RemoveCommand ("stopvideo"); + + Cvar_Set( "cl_running", "0" ); + + recursive = qfalse; + + Com_Memset( &cls, 0, sizeof( cls ) ); + Key_SetCatcher( 0 ); + + Com_Printf( "-----------------------\n" ); + +} + +static void CL_SetServerInfo(serverInfo_t *server, const char *info, int ping) { + if (server) { + if (info) { + const char *game; + + server->clients = atoi(Info_ValueForKey(info, "clients")); + Q_strncpyz(server->hostName,Info_ValueForKey(info, "hostname"), MAX_HOSTNAME_LENGTH ); + Q_strncpyz(server->mapName, Info_ValueForKey(info, "mapname"), MAX_NAME_LENGTH); + server->maxClients = atoi(Info_ValueForKey(info, "sv_maxclients")); + game = Info_ValueForKey(info, "game"); + Q_strncpyz(server->game, (game[0]) ? game : BASEGAME, MAX_NAME_LENGTH); + server->gameType = atoi(Info_ValueForKey(info, "gametype")); + server->netType = atoi(Info_ValueForKey(info, "nettype")); + server->minPing = atoi(Info_ValueForKey(info, "minping")); + server->maxPing = atoi(Info_ValueForKey(info, "maxping")); + } + server->ping = ping; + } +} + +static void CL_SetServerInfoByAddress(netadr_t from, const char *info, int ping) { + int i; + + for (i = 0; i < MAX_OTHER_SERVERS; i++) { + if (NET_CompareAdr(from, cls.localServers[i].adr)) { + CL_SetServerInfo(&cls.localServers[i], info, ping); + } + } + + for (i = 0; i < MAX_GLOBAL_SERVERS; i++) { + if (NET_CompareAdr(from, cls.globalServers[i].adr)) { + CL_SetServerInfo(&cls.globalServers[i], info, ping); + } + } + + for (i = 0; i < MAX_OTHER_SERVERS; i++) { + if (NET_CompareAdr(from, cls.favoriteServers[i].adr)) { + CL_SetServerInfo(&cls.favoriteServers[i], info, ping); + } + } + +} + +/* +=================== +CL_ServerInfoPacket +=================== +*/ +void CL_ServerInfoPacket( netadr_t from, msg_t *msg ) { + int i, type; + char info[MAX_INFO_STRING]; + char *infoString; + int prot; + + infoString = MSG_ReadString( msg ); + + // if this isn't the correct protocol version, ignore it + prot = atoi( Info_ValueForKey( infoString, "protocol" ) ); + if ( prot != PROTOCOL_VERSION ) { + Com_DPrintf( "Different protocol info packet: %s\n", infoString ); + return; + } + + // iterate servers waiting for ping response + for (i=0; i<MAX_PINGREQUESTS; i++) + { + if ( cl_pinglist[i].adr.port && !cl_pinglist[i].time && NET_CompareAdr( from, cl_pinglist[i].adr ) ) + { + // calc ping time + cl_pinglist[i].time = Sys_Milliseconds() - cl_pinglist[i].start; + Com_DPrintf( "ping time %dms from %s\n", cl_pinglist[i].time, NET_AdrToString( from ) ); + + // save of info + Q_strncpyz( cl_pinglist[i].info, infoString, sizeof( cl_pinglist[i].info ) ); + + // tack on the net type + // NOTE: make sure these types are in sync with the netnames strings in the UI + switch (from.type) + { + case NA_BROADCAST: + case NA_IP: + type = 1; + break; + case NA_IP6: + type = 2; + break; + default: + type = 0; + break; + } + Info_SetValueForKey( cl_pinglist[i].info, "nettype", va("%d", type) ); + CL_SetServerInfoByAddress(from, infoString, cl_pinglist[i].time); + + return; + } + } + + // if not just sent a local broadcast or pinging local servers + if (cls.pingUpdateSource != AS_LOCAL) { + return; + } + + for ( i = 0 ; i < MAX_OTHER_SERVERS ; i++ ) { + // empty slot + if ( cls.localServers[i].adr.port == 0 ) { + break; + } + + // avoid duplicate + if ( NET_CompareAdr( from, cls.localServers[i].adr ) ) { + return; + } + } + + if ( i == MAX_OTHER_SERVERS ) { + Com_DPrintf( "MAX_OTHER_SERVERS hit, dropping infoResponse\n" ); + return; + } + + // add this to the list + cls.numlocalservers = i+1; + cls.localServers[i].adr = from; + cls.localServers[i].clients = 0; + cls.localServers[i].hostName[0] = '\0'; + cls.localServers[i].mapName[0] = '\0'; + cls.localServers[i].maxClients = 0; + cls.localServers[i].maxPing = 0; + cls.localServers[i].minPing = 0; + cls.localServers[i].ping = -1; + cls.localServers[i].game[0] = '\0'; + cls.localServers[i].gameType = 0; + cls.localServers[i].netType = from.type; + + Q_strncpyz( info, MSG_ReadString( msg ), MAX_INFO_STRING ); + if (strlen(info)) { + if (info[strlen(info)-1] != '\n') { + strncat(info, "\n", sizeof(info) - 1); + } + Com_Printf( "%s: %s", NET_AdrToStringwPort( from ), info ); + } +} + +/* +=================== +CL_GetServerStatus +=================== +*/ +serverStatus_t *CL_GetServerStatus( netadr_t from ) { + serverStatus_t *serverStatus; + int i, oldest, oldestTime; + + serverStatus = NULL; + for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) { + if ( NET_CompareAdr( from, cl_serverStatusList[i].address ) ) { + return &cl_serverStatusList[i]; + } + } + for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) { + if ( cl_serverStatusList[i].retrieved ) { + return &cl_serverStatusList[i]; + } + } + oldest = -1; + oldestTime = 0; + for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) { + if (oldest == -1 || cl_serverStatusList[i].startTime < oldestTime) { + oldest = i; + oldestTime = cl_serverStatusList[i].startTime; + } + } + if (oldest != -1) { + return &cl_serverStatusList[oldest]; + } + serverStatusCount++; + return &cl_serverStatusList[serverStatusCount & (MAX_SERVERSTATUSREQUESTS-1)]; +} + +/* +=================== +CL_ServerStatus +=================== +*/ +int CL_ServerStatus( char *serverAddress, char *serverStatusString, int maxLen ) { + int i; + netadr_t to; + serverStatus_t *serverStatus; + + // if no server address then reset all server status requests + if ( !serverAddress ) { + for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) { + cl_serverStatusList[i].address.port = 0; + cl_serverStatusList[i].retrieved = qtrue; + } + return qfalse; + } + // get the address + if ( !NET_StringToAdr( serverAddress, &to, NA_UNSPEC) ) { + return qfalse; + } + serverStatus = CL_GetServerStatus( to ); + // if no server status string then reset the server status request for this address + if ( !serverStatusString ) { + serverStatus->retrieved = qtrue; + return qfalse; + } + + // if this server status request has the same address + if ( NET_CompareAdr( to, serverStatus->address) ) { + // if we recieved an response for this server status request + if (!serverStatus->pending) { + Q_strncpyz(serverStatusString, serverStatus->string, maxLen); + serverStatus->retrieved = qtrue; + serverStatus->startTime = 0; + return qtrue; + } + // resend the request regularly + else if ( serverStatus->startTime < Com_Milliseconds() - cl_serverStatusResendTime->integer ) { + serverStatus->print = qfalse; + serverStatus->pending = qtrue; + serverStatus->retrieved = qfalse; + serverStatus->time = 0; + serverStatus->startTime = Com_Milliseconds(); + NET_OutOfBandPrint( NS_CLIENT, to, "getstatus" ); + return qfalse; + } + } + // if retrieved + else if ( serverStatus->retrieved ) { + serverStatus->address = to; + serverStatus->print = qfalse; + serverStatus->pending = qtrue; + serverStatus->retrieved = qfalse; + serverStatus->startTime = Com_Milliseconds(); + serverStatus->time = 0; + NET_OutOfBandPrint( NS_CLIENT, to, "getstatus" ); + return qfalse; + } + return qfalse; +} + +/* +=================== +CL_ServerStatusResponse +=================== +*/ +void CL_ServerStatusResponse( netadr_t from, msg_t *msg ) { + char *s; + char info[MAX_INFO_STRING]; + int i, l, score, ping; + int len; + serverStatus_t *serverStatus; + + serverStatus = NULL; + for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) { + if ( NET_CompareAdr( from, cl_serverStatusList[i].address ) ) { + serverStatus = &cl_serverStatusList[i]; + break; + } + } + // if we didn't request this server status + if (!serverStatus) { + return; + } + + s = MSG_ReadStringLine( msg ); + + len = 0; + Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "%s", s); + + if (serverStatus->print) { + Com_Printf("Server settings:\n"); + // print cvars + while (*s) { + for (i = 0; i < 2 && *s; i++) { + if (*s == '\\') + s++; + l = 0; + while (*s) { + info[l++] = *s; + if (l >= MAX_INFO_STRING-1) + break; + s++; + if (*s == '\\') { + break; + } + } + info[l] = '\0'; + if (i) { + Com_Printf("%s\n", info); + } + else { + Com_Printf("%-24s", info); + } + } + } + } + + len = strlen(serverStatus->string); + Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "\\"); + + if (serverStatus->print) { + Com_Printf("\nPlayers:\n"); + Com_Printf("num: score: ping: name:\n"); + } + for (i = 0, s = MSG_ReadStringLine( msg ); *s; s = MSG_ReadStringLine( msg ), i++) { + + len = strlen(serverStatus->string); + Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "\\%s", s); + + if (serverStatus->print) { + score = ping = 0; + sscanf(s, "%d %d", &score, &ping); + s = strchr(s, ' '); + if (s) + s = strchr(s+1, ' '); + if (s) + s++; + else + s = "unknown"; + Com_Printf("%-2d %-3d %-3d %s\n", i, score, ping, s ); + } + } + len = strlen(serverStatus->string); + Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "\\"); + + serverStatus->time = Com_Milliseconds(); + serverStatus->address = from; + serverStatus->pending = qfalse; + if (serverStatus->print) { + serverStatus->retrieved = qtrue; + } +} + +/* +================== +CL_LocalServers_f +================== +*/ +void CL_LocalServers_f( void ) { + char *message; + int i, j; + netadr_t to; + + Com_Printf( "Scanning for servers on the local network...\n"); + + // reset the list, waiting for response + cls.numlocalservers = 0; + cls.pingUpdateSource = AS_LOCAL; + + for (i = 0; i < MAX_OTHER_SERVERS; i++) { + qboolean b = cls.localServers[i].visible; + Com_Memset(&cls.localServers[i], 0, sizeof(cls.localServers[i])); + cls.localServers[i].visible = b; + } + Com_Memset( &to, 0, sizeof( to ) ); + + // The 'xxx' in the message is a challenge that will be echoed back + // by the server. We don't care about that here, but master servers + // can use that to prevent spoofed server responses from invalid ip + message = "\377\377\377\377getinfo xxx"; + + // send each message twice in case one is dropped + for ( i = 0 ; i < 2 ; i++ ) { + // send a broadcast packet on each server port + // we support multiple server ports so a single machine + // can nicely run multiple servers + for ( j = 0 ; j < NUM_SERVER_PORTS ; j++ ) { + to.port = BigShort( (short)(PORT_SERVER + j) ); + + to.type = NA_BROADCAST; + NET_SendPacket( NS_CLIENT, strlen( message ), message, to ); + to.type = NA_MULTICAST6; + NET_SendPacket( NS_CLIENT, strlen( message ), message, to ); + } + } +} + +/* +================== +CL_GlobalServers_f +================== +*/ +void CL_GlobalServers_f( void ) { + netadr_t to; + int count, i, masterNum; + char command[1024], *masteraddress; + + if ((count = Cmd_Argc()) < 2 || (masterNum = atoi(Cmd_Argv(1))) < 0 || masterNum > MAX_MASTER_SERVERS - 1) + { + Com_Printf("usage: globalservers <master# 0-%d> [keywords]\n", MAX_MASTER_SERVERS - 1); + return; + } + + sprintf(command, "sv_master%d", masterNum + 1); + masteraddress = Cvar_VariableString(command); + + if(!*masteraddress) + { + Com_Printf( "CL_GlobalServers_f: Error: No master server address given.\n"); + return; + } + + // reset the list, waiting for response + // -1 is used to distinguish a "no response" + + i = NET_StringToAdr(masteraddress, &to, NA_UNSPEC); + + if(!i) + { + Com_Printf( "CL_GlobalServers_f: Error: could not resolve address of master %s\n", masteraddress); + return; + } + else if(i == 2) + to.port = BigShort(PORT_MASTER); + + Com_Printf("Requesting servers from master %s...\n", masteraddress); + + cls.numglobalservers = -1; + cls.pingUpdateSource = AS_GLOBAL; + + Com_sprintf( command, sizeof( command ), "getserversExt %s %d", + cl_gamename->string, PROTOCOL_VERSION ); + // TODO: test if we only have IPv4/IPv6, if so request only the relevant + // servers with getserversExt %s %d ipvX + // not that big a deal since the extra servers won't respond to getinfo + // anyway. + + for (i=3; i < count; i++) + { + Q_strcat(command, sizeof(command), " "); + Q_strcat(command, sizeof(command), Cmd_Argv(i)); + } + + NET_OutOfBandPrint( NS_SERVER, to, "%s", command ); + CL_RequestMotd(); +} + + +/* +================== +CL_GetPing +================== +*/ +void CL_GetPing( int n, char *buf, int buflen, int *pingtime ) +{ + const char *str; + int time; + int maxPing; + + if (n < 0 || n >= MAX_PINGREQUESTS || !cl_pinglist[n].adr.port) + { + // empty or invalid slot + buf[0] = '\0'; + *pingtime = 0; + return; + } + + str = NET_AdrToStringwPort( cl_pinglist[n].adr ); + Q_strncpyz( buf, str, buflen ); + + time = cl_pinglist[n].time; + if (!time) + { + // check for timeout + time = Sys_Milliseconds() - cl_pinglist[n].start; + maxPing = Cvar_VariableIntegerValue( "cl_maxPing" ); + if( maxPing < 100 ) { + maxPing = 100; + } + if (time < maxPing) + { + // not timed out yet + time = 0; + } + } + + CL_SetServerInfoByAddress(cl_pinglist[n].adr, cl_pinglist[n].info, cl_pinglist[n].time); + + *pingtime = time; +} + +/* +================== +CL_GetPingInfo +================== +*/ +void CL_GetPingInfo( int n, char *buf, int buflen ) +{ + if (n < 0 || n >= MAX_PINGREQUESTS || !cl_pinglist[n].adr.port) + { + // empty or invalid slot + if (buflen) + buf[0] = '\0'; + return; + } + + Q_strncpyz( buf, cl_pinglist[n].info, buflen ); +} + +/* +================== +CL_ClearPing +================== +*/ +void CL_ClearPing( int n ) +{ + if (n < 0 || n >= MAX_PINGREQUESTS) + return; + + cl_pinglist[n].adr.port = 0; +} + +/* +================== +CL_GetPingQueueCount +================== +*/ +int CL_GetPingQueueCount( void ) +{ + int i; + int count; + ping_t* pingptr; + + count = 0; + pingptr = cl_pinglist; + + for (i=0; i<MAX_PINGREQUESTS; i++, pingptr++ ) { + if (pingptr->adr.port) { + count++; + } + } + + return (count); +} + +/* +================== +CL_GetFreePing +================== +*/ +ping_t* CL_GetFreePing( void ) +{ + ping_t* pingptr; + ping_t* best; + int oldest; + int i; + int time; + + pingptr = cl_pinglist; + for (i=0; i<MAX_PINGREQUESTS; i++, pingptr++ ) + { + // find free ping slot + if (pingptr->adr.port) + { + if (!pingptr->time) + { + if (Sys_Milliseconds() - pingptr->start < 500) + { + // still waiting for response + continue; + } + } + else if (pingptr->time < 500) + { + // results have not been queried + continue; + } + } + + // clear it + pingptr->adr.port = 0; + return (pingptr); + } + + // use oldest entry + pingptr = cl_pinglist; + best = cl_pinglist; + oldest = INT_MIN; + for (i=0; i<MAX_PINGREQUESTS; i++, pingptr++ ) + { + // scan for oldest + time = Sys_Milliseconds() - pingptr->start; + if (time > oldest) + { + oldest = time; + best = pingptr; + } + } + + return (best); +} + +/* +================== +CL_Ping_f +================== +*/ +void CL_Ping_f( void ) { + netadr_t to; + ping_t* pingptr; + char* server; + int argc; + netadrtype_t family = NA_UNSPEC; + + argc = Cmd_Argc(); + + if ( argc != 2 && argc != 3 ) { + Com_Printf( "usage: ping [-4|-6] server\n"); + return; + } + + if(argc == 2) + server = Cmd_Argv(1); + else + { + if(!strcmp(Cmd_Argv(1), "-4")) + family = NA_IP; + else if(!strcmp(Cmd_Argv(1), "-6")) + family = NA_IP6; + else + Com_Printf( "warning: only -4 or -6 as address type understood.\n"); + + server = Cmd_Argv(2); + } + + Com_Memset( &to, 0, sizeof(netadr_t) ); + + if ( !NET_StringToAdr( server, &to, family ) ) { + return; + } + + pingptr = CL_GetFreePing(); + + memcpy( &pingptr->adr, &to, sizeof (netadr_t) ); + pingptr->start = Sys_Milliseconds(); + pingptr->time = 0; + + CL_SetServerInfoByAddress(pingptr->adr, NULL, 0); + + NET_OutOfBandPrint( NS_CLIENT, to, "getinfo xxx" ); +} + +/* +================== +CL_UpdateVisiblePings_f +================== +*/ +qboolean CL_UpdateVisiblePings_f(int source) { + int slots, i; + char buff[MAX_STRING_CHARS]; + int pingTime; + int max; + qboolean status = qfalse; + + if (source < 0 || source > AS_FAVORITES) { + return qfalse; + } + + cls.pingUpdateSource = source; + + slots = CL_GetPingQueueCount(); + if (slots < MAX_PINGREQUESTS) { + serverInfo_t *server = NULL; + + max = (source == AS_GLOBAL) ? MAX_GLOBAL_SERVERS : MAX_OTHER_SERVERS; + switch (source) { + case AS_LOCAL : + server = &cls.localServers[0]; + max = cls.numlocalservers; + break; + case AS_GLOBAL : + server = &cls.globalServers[0]; + max = cls.numglobalservers; + break; + case AS_FAVORITES : + server = &cls.favoriteServers[0]; + max = cls.numfavoriteservers; + break; + default: + return qfalse; + } + for (i = 0; i < max; i++) { + if (server[i].visible) { + if (server[i].ping == -1) { + int j; + + if (slots >= MAX_PINGREQUESTS) { + break; + } + for (j = 0; j < MAX_PINGREQUESTS; j++) { + if (!cl_pinglist[j].adr.port) { + continue; + } + if (NET_CompareAdr( cl_pinglist[j].adr, server[i].adr)) { + // already on the list + break; + } + } + if (j >= MAX_PINGREQUESTS) { + status = qtrue; + for (j = 0; j < MAX_PINGREQUESTS; j++) { + if (!cl_pinglist[j].adr.port) { + break; + } + } + memcpy(&cl_pinglist[j].adr, &server[i].adr, sizeof(netadr_t)); + cl_pinglist[j].start = Sys_Milliseconds(); + cl_pinglist[j].time = 0; + NET_OutOfBandPrint( NS_CLIENT, cl_pinglist[j].adr, "getinfo xxx" ); + slots++; + } + } + // if the server has a ping higher than cl_maxPing or + // the ping packet got lost + else if (server[i].ping == 0) { + // if we are updating global servers + if (source == AS_GLOBAL) { + // + if ( cls.numGlobalServerAddresses > 0 ) { + // overwrite this server with one from the additional global servers + cls.numGlobalServerAddresses--; + CL_InitServerInfo(&server[i], &cls.globalServerAddresses[cls.numGlobalServerAddresses]); + // NOTE: the server[i].visible flag stays untouched + } + } + } + } + } + } + + if (slots) { + status = qtrue; + } + for (i = 0; i < MAX_PINGREQUESTS; i++) { + if (!cl_pinglist[i].adr.port) { + continue; + } + CL_GetPing( i, buff, MAX_STRING_CHARS, &pingTime ); + if (pingTime != 0) { + CL_ClearPing(i); + status = qtrue; + } + } + + return status; +} + +/* +================== +CL_ServerStatus_f +================== +*/ +void CL_ServerStatus_f(void) { + netadr_t to, *toptr = NULL; + char *server; + serverStatus_t *serverStatus; + int argc; + netadrtype_t family = NA_UNSPEC; + + argc = Cmd_Argc(); + + if ( argc != 2 && argc != 3 ) + { + if (cls.state != CA_ACTIVE || clc.demoplaying) + { + Com_Printf ("Not connected to a server.\n"); + Com_Printf( "usage: serverstatus [-4|-6] server\n"); + return; + } + + toptr = &clc.serverAddress; + } + + if(!toptr) + { + Com_Memset( &to, 0, sizeof(netadr_t) ); + + if(argc == 2) + server = Cmd_Argv(1); + else + { + if(!strcmp(Cmd_Argv(1), "-4")) + family = NA_IP; + else if(!strcmp(Cmd_Argv(1), "-6")) + family = NA_IP6; + else + Com_Printf( "warning: only -4 or -6 as address type understood.\n"); + + server = Cmd_Argv(2); + } + + toptr = &to; + if ( !NET_StringToAdr( server, toptr, family ) ) + return; + } + + NET_OutOfBandPrint( NS_CLIENT, *toptr, "getstatus" ); + + serverStatus = CL_GetServerStatus( *toptr ); + serverStatus->address = *toptr; + serverStatus->print = qtrue; + serverStatus->pending = qtrue; +} + +/* +================== +CL_ShowIP_f +================== +*/ +void CL_ShowIP_f(void) { + Sys_ShowIP(); +} |