/* Copyright (C) 1997-2001 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // cl_main.c -- client main loop #include "client.h" #include "../qcommon/webdownload.h" cvar_t *cl_freelook; cvar_t *cl_stereo_separation; cvar_t *cl_stereo; cvar_t *rcon_client_password; cvar_t *rcon_address; cvar_t *cl_timeout; cvar_t *cl_maxfps; cvar_t *cl_maxpackets; cvar_t *cl_compresspackets; cvar_t *cl_synchusercmd; cvar_t *cl_shownet; cvar_t *cl_timedemo; cvar_t *cl_demoavi_fps; cvar_t *cl_demoavi_scissor; cvar_t *lookspring; cvar_t *lookstrafe; cvar_t *sensitivity; cvar_t *m_accel; // wsw : pb : add mouseaccel cvar_t *m_filter; cvar_t *in_minMsecs; // wsw : jal : millisecs to wait before asking the system for key/mouse input again cvar_t *m_pitch; cvar_t *m_yaw; cvar_t *m_forward; cvar_t *m_side; // // userinfo // cvar_t *info_password; cvar_t *rate; #ifdef BATTLEYE cvar_t *cl_battleye; #endif cvar_t *cl_masterservers; // wsw : debug netcode cvar_t *cl_debug_serverCmd; cvar_t *cl_downloads; cvar_t *cl_downloads_from_web; client_static_t cls; client_state_t cl; entity_state_t cl_baselines[MAX_EDICTS]; void CL_RestartMedia( void ); //====================================================================== /* ======================================================================= 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 ) { int index; if( !cmd || !strlen(cmd) ) return; // if we would be losing an old command that hasn't been acknowledged, // we must drop the connection if ( cls.reliableSequence - cls.reliableAcknowledge > MAX_RELIABLE_COMMANDS ) { Com_Error( ERR_DROP, "Client command overflow" ); } cls.reliableSequence++; index = cls.reliableSequence & ( MAX_RELIABLE_COMMANDS - 1 ); Q_strncpyz( cls.reliableCommands[ index ], cmd, sizeof( cls.reliableCommands[ index ] ) ); } /* ====================== CL_UpdateClientCommandsToServer Add the pending commands to the message ====================== */ void CL_UpdateClientCommandsToServer( msg_t *msg ) { int i; //if( cl_debug_serverCmd->integer & 2 ) // Com_Printf( "cls.reliableAcknowledge: %i cls.reliableSequence:%i\n", cls.reliableAcknowledge,cls.reliableSequence ); // write any unacknowledged clientCommands for ( i = cls.reliableAcknowledge + 1 ; i <= cls.reliableSequence ; i++ ) { if( !strlen( cls.reliableCommands[ i & (MAX_RELIABLE_COMMANDS-1) ] ) ) continue; MSG_WriteByte( msg, clc_clientcommand ); MSG_WriteLong( msg, i ); MSG_WriteString( msg, cls.reliableCommands[ i & (MAX_RELIABLE_COMMANDS-1) ] ); if( cl_debug_serverCmd->integer & 2 && cls.state < CA_ACTIVE ) Com_Printf( "CL_UpdateClientCommandsToServer(%i): %s\n", i, cls.reliableCommands[ i & (MAX_RELIABLE_COMMANDS-1) ] ); } cls.reliableSent = cls.reliableSequence; } #ifdef BATTLEYE void CL_AddBattleyeCommandToMessage( msg_t *msg ) { qbyte *bepacket = NULL; int bepacketlen = 0; if( cl_battleye->integer ) { // the server is not acknowledging packets if( cls.BE.acknowledgedPacket + BE_UPDATE_BACKUP <= cls.BE.headPacket ) { Com_Error( ERR_DROP, "Missing server acknowledge of too many BattlEye packets" ); return; } if( msg ) { if( cls.BE.lastSentTime > cls.realtime ) // clamp cls.BE.lastSentTime = cls.realtime; if( cls.BE.lastSentTime + BE_MIN_RESEND < cls.realtime ) { // send the next buffered unacknowledged packet (if any) if( cls.BE.headPacket > cls.BE.acknowledgedPacket ) { bepacketlen = cls.BE.packetLens[cls.BE.acknowledgedPacket & BE_UPDATE_MASK]; if( bepacketlen ) { bepacket = cls.BE.packets[cls.BE.acknowledgedPacket & BE_UPDATE_MASK]; } } } MSG_WriteByte( msg, clc_battleye ); //write svc_battleye acknowledge MSG_WriteLong( msg, cls.BE.commandReceived ); if( bepacket ) { MSG_WriteShort( msg, bepacketlen ); MSG_Write( msg, bepacket, bepacketlen ); MSG_WriteLong( msg, cls.BE.acknowledgedPacket+1 ); // id of this BE packet cls.BE.lastSentTime = cls.realtime; } else { MSG_WriteShort( msg, 0 ); // no data, just acknowledge of svc_battleye } } } } #endif /* =================== Cmd_ForwardToServer adds the current command line as a command to the client message. 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 Cmd_ForwardToServer( void ) { char *cmd; if( cls.demoplaying ) return; cmd = Cmd_Argv(0); if( cls.state <= CA_CONNECTED || *cmd == '-' || *cmd == '+' ) { Com_Printf( "Unknown command \"%s\"\n", cmd ); return; } CL_AddReliableCommand( va("%s %s\n", cmd, Cmd_Args()) ); } /* ================== CL_ForwardToServer_f ================== */ void CL_ForwardToServer_f( void ) { if( cls.demoplaying ) return; if( cls.state != CA_CONNECTED && cls.state != CA_ACTIVE ) { Com_Printf( "Can't \"%s\", not connected\n", Cmd_Argv(0) ); return; } // don't forward the first argument if( Cmd_Argc() > 1 ) { CL_AddReliableCommand( Cmd_Args() ); } } /* ================== CL_ServerDisconnect_f ================== */ void CL_ServerDisconnect_f( void ) { char menuparms[MAX_STRING_CHARS]; int type; char reason[MAX_STRING_CHARS]; type = atoi(Cmd_Argv(1)); if( type < 0 || type >= DROP_TYPE_TOTAL ) type = DROP_TYPE_GENERAL; Q_strncpyz( reason, Cmd_Argv(2), sizeof(reason) ); CL_Disconnect_f(); Com_Printf( "Connection was closed by server: %s\n", reason ); Q_snprintfz( menuparms, sizeof(menuparms), "menu_failed 1 \"%s\" %i \"%s\"", cls.servername, type, reason ); Cbuf_ExecuteText( EXEC_NOW, menuparms ); } /* ================== CL_Quit ================== */ void CL_Quit( void ) { CL_Disconnect( NULL ); Com_Quit(); } /* ================== CL_Quit_f ================== */ void CL_Quit_f( void ) { CL_Quit(); } /* ======================= CL_SendConnectPacket We have gotten a challenge from the server, so try and connect. ====================== */ void CL_SendConnectPacket( void ) { cls.quakePort = Cvar_VariableValue( "qport" ); userinfo_modified = qfalse; Netchan_OutOfBandPrint( NS_CLIENT, cls.serveraddress, "connect %i %i %i \"%s\"\n", PROTOCOL_VERSION, cls.quakePort, cls.challenge, Cvar_Userinfo() ); } /* ================= CL_CheckForResend Resend a connect message if the last one has timed out ================= */ void CL_CheckForResend( void ) { if( cls.demoplaying ) return; // if the local server is running and we aren't then connect if( cls.state == CA_DISCONNECTED && Com_ServerState() ) { CL_SetClientState( CA_CONNECTING ); Q_strncpyz( cls.servername, "localhost", sizeof(cls.servername) ); NET_StringToAdr( "localhost", &cls.serveraddress ); if( cls.serveraddress.port == 0 ) cls.serveraddress.port = BigShort( PORT_SERVER ); } // resend if we haven't gotten a reply yet if( cls.state != CA_CONNECTING ) return; if( curtime - cls.connect_time < 3000 ) return; cls.connect_count++; cls.connect_time = curtime; // for retransmit requests Com_Printf( "Connecting to %s...\n", cls.servername ); Netchan_OutOfBandPrint( NS_CLIENT, cls.serveraddress, "getchallenge\n" ); } /* ================ CL_Connect_f ================ */ void CL_Connect_f( void ) { netadr_t serveraddress; // save of address before calling CL_Disconnect if( Cmd_Argc() != 2 ) { Com_Printf( "usage: connect \n" ); return; } // do it there cause CL_Disconnect destroy Argv[1] // and then we loose the address if( !NET_StringToAdr(Cmd_Argv(1), &serveraddress) ) { Com_Printf( "Bad server address\n" ); return; } // set it now for connection ui cls.serveraddress=serveraddress; Q_strncpyz( cls.servername, Cmd_Argv (1), sizeof(cls.servername) ); if( Com_ServerState () ) { // if running a local server, kill it and reissue SV_Shutdown( "Server quit\n", qfalse ); } CL_Disconnect( NULL ); NET_Config( qtrue ); // allow remote // maybe not needed // set it in case of disconnect overwriting it cls.serveraddress=serveraddress; if( cls.serveraddress.port == 0 ) cls.serveraddress.port = BigShort( PORT_SERVER ); memset( cl.configstrings, 0, sizeof(cl.configstrings) ); // moved upper due to CL_Disconnect overwriting Cmd_Argv(1) //Q_strncpyz( cls.servername, Cmd_Argv (1), sizeof(cls.servername) ); CL_SetClientState( CA_CONNECTING ); cls.connect_time = -99999; // CL_CheckForResend() will fire immediately cls.connect_count = 0; cls.rejected = qfalse; cls.lastPacketReceivedTime = cls.realtime; // reset the timeout limit } /* ===================== CL_Rcon_f Send the rest of the command line over as an unconnected command. ===================== */ void CL_Rcon_f( void ) { char message[1024]; int i; netadr_t to; if( cls.demoplaying ) return; if( strlen(rcon_client_password->string) == 0 ) { Com_Printf( "You must set 'rcon_password' before issuing an rcon command.\n" ); return; } // wsw : jal : check for msg len abuse (thx to r1Q2) if( strlen(Cmd_Args()) + strlen(rcon_client_password->string) + 16 >= sizeof(message) ) { Com_Printf( "Length of password + command exceeds maximum allowed length.\n" ); return; } message[0] = (qbyte)255; message[1] = (qbyte)255; message[2] = (qbyte)255; message[3] = (qbyte)255; message[4] = 0; NET_Config( qtrue ); // allow remote Q_strncatz( message, "rcon ", sizeof(message) ); Q_strncatz( message, rcon_client_password->string, sizeof(message) ); Q_strncatz( message, " ", sizeof(message) ); for( i = 1; i < Cmd_Argc(); i++ ) { Q_strncatz( message, Cmd_Argv(i), sizeof(message) ); Q_strncatz( message, " ", sizeof(message) ); } if( cls.state >= CA_CONNECTED ) to = cls.netchan.remoteAddress; else { if( !strlen(rcon_address->string) ) { Com_Printf( "You must be connected, or set the 'rcon_address' cvar to issue rcon commands\n" ); return; } if( rcon_address->modified ) { if( !NET_StringToAdr( rcon_address->string, &cls.rconaddress ) ) { Com_Printf("Bad rcon_address.\n"); return; // we don't clear modified, so it will whine the next time too } if( cls.rconaddress.port == 0 ) cls.rconaddress.port = BigShort( PORT_SERVER ); rcon_address->modified = qfalse; } to = cls.rconaddress; } NET_SendPacket( NS_CLIENT, (int)strlen(message)+1, message, to ); } /* ===================== CL_GetClipboardData ===================== */ void CL_GetClipboardData( char *string, int size ) { char *cbd; if( !string || size <= 0 ) return; string[0] = 0; cbd = Sys_GetClipboardData(); if( cbd && cbd[0] ) { Q_strncpyz ( string, cbd, size ); //Q_free ( cbd ); } } /* ===================== CL_SetKeyDest ===================== */ void CL_SetKeyDest( int key_dest ) { if( key_dest < key_game || key_dest > key_menu ) Com_Error( ERR_DROP, "CL_SetKeyDest: invalid key_dest" ); if( cls.key_dest != key_dest ) { Key_ClearStates(); cls.key_dest = key_dest; } } /* ===================== CL_SetOldKeyDest ===================== */ void CL_SetOldKeyDest( int key_dest ) { if( key_dest < key_game || key_dest > key_menu ) Com_Error( ERR_DROP, "CL_SetKeyDest: invalid key_dest" ); cls.old_key_dest = key_dest; } /* ===================== CL_ResetServerCount ===================== */ void CL_ResetServerCount( void ) { cl.servercount = -1; } /* ===================== CL_ClearState ===================== */ void CL_ClearState( void ) { // wipe the entire cl structure memset( &cl, 0, sizeof(cl) ); memset( cl_baselines, 0, sizeof(cl_baselines) ); //userinfo_modified = qtrue; cls.lastExecutedServerCommand = 0; cls.reliableAcknowledge = 0; cls.reliableSequence = 0; cls.reliableSent = 0; memset( &cls.reliableCommands, 0, sizeof(cls.reliableCommands) ); #ifdef BATTLEYE memset( &cls.BE, 0, sizeof(cls.BE) ); #endif //restart realtime and lastPacket times cls.realtime = 0; cls.lastPacketSentTime = 0; cls.lastPacketReceivedTime = 0; } //===================== //CL_SetNext_f // //Next is used to set an action which is executed at disconnecting. //===================== char cl_NextString[MAX_STRING_CHARS]; void CL_SetNext_f( void ) { if( Cmd_Argc() < 2 ) { Com_Printf( "USAGE: next \n" ); return; } // jalfixme: I'm afraid of this being too powerful, since it basically // is allowed to execute everyting. Shall we check for something? Q_strncpyz( cl_NextString, Cmd_Args(), sizeof(cl_NextString) ); Com_Printf( "NEXT: %s\n", cl_NextString ); } //===================== //CL_ExecuteNext //===================== void CL_ExecuteNext( void ) { if( !strlen(cl_NextString) ) return; Cbuf_ExecuteText( EXEC_APPEND, cl_NextString ); memset( cl_NextString, 0, sizeof(cl_NextString) ); } /* ===================== CL_Disconnect_SendCommand Sends a disconnect message to the server ===================== */ void CL_Disconnect_SendCommand( void ) { // wsw : jal : send the packet 3 times to make sure isn't lost CL_AddReliableCommand( "disconnect" ); CL_WritePacket( NULL ); CL_AddReliableCommand( "disconnect" ); CL_WritePacket( NULL ); CL_AddReliableCommand( "disconnect" ); CL_WritePacket( NULL ); } /* ===================== CL_Disconnect Goes from a connected state to full screen console state Sends a disconnect message to the server This is also called on Com_Error, so it shouldn't cause any errors ===================== */ void CL_Disconnect( const char *message ) { char menuparms[MAX_STRING_CHARS]; qboolean wasconnecting; // We have to shut down webdownloading first if( cls.download.web ) { cls.download.disconnect = qtrue; return; } if( cls.state == CA_UNINITIALIZED ) return; if( cls.state == CA_DISCONNECTED ) goto done; if( cls.state < CA_LOADING ) wasconnecting = qtrue; else wasconnecting = qfalse; if( cl_timedemo && cl_timedemo->integer ) { unsigned int time; int i; Com_Printf("\n"); for( i = 1; i < 100; i++ ) { if( cl.timedemo_counts[i] > 0 ) { Com_Printf("%2ims - %7.2ffps: %6.2f%c\n", i, 1000.0/i, (cl.timedemo_counts[i]*1.0/cl.timedemo_frames)*100.0, '%'); } } Com_Printf("\n"); time = Sys_Milliseconds() - cl.timedemo_start; if( time > 0 ) Com_Printf( "%i frames, %3.1f seconds: %3.1f fps\n", cl.timedemo_frames, time/1000.0, cl.timedemo_frames*1000.0 / time ); } cls.connect_time = 0; cls.connect_count = 0; cls.rejected = 0; SCR_StopCinematic(); if( cls.demorecording ) CL_Stop_f(); if( cls.demoplaying ) { CL_DemoCompleted(); } else { CL_Disconnect_SendCommand(); // send a disconnect message to the server } CL_RestartMedia(); if( cls.download.filenum ) CL_StopServerDownload(); CL_ClearState(); CL_SetClientState( CA_DISCONNECTED ); if( message != NULL ) { Q_snprintfz( menuparms, sizeof(menuparms), "menu_failed %i \"%s\" %i \"%s\"", (wasconnecting ? 0 : 2 ), cls.servername, DROP_TYPE_GENERAL, message ); Cbuf_ExecuteText( EXEC_NOW, menuparms ); } done: // drop loading plaque unless this is the initial game start if( cls.disable_servercount != -1 ) SCR_EndLoadingPlaque(); // get rid of loading plaque // in case we disconnect while in download phase CL_FreeDownloadList(); #ifdef BATTLEYE if( cls.runBattlEye ) cls.runBattlEye = qfalse; #endif CL_ExecuteNext(); // start next action if any is defined } void CL_Disconnect_f( void ) { // We have to shut down webdownloading first if( cls.download.web ) { cls.download.disconnect = qtrue; return; } CL_Disconnect( NULL ); SV_Shutdown( "Owner left the listen server", qfalse ); } /* ================= CL_Changing_f Just sent as a hint to the client that they should drop to full console ================= */ void CL_Changing_f( void ) { //ZOID //if we are downloading, we don't change! This so we don't suddenly stop downloading a map if( cls.download.filenum || cls.download.web ) return; Com_Printf( "CL:Changing\n" ); memset( cl.configstrings, 0, sizeof(cl.configstrings) ); CL_SetClientState( CA_CONNECTED ); // not active anymore, but not disconnected } /* ================= CL_Reconnect_f The server is changing levels ================= */ void CL_Reconnect_f( void ) { if( cls.demoplaying ) return; //if we are downloading, we don't change! This so we don't suddenly stop downloading a map if( cls.download.filenum || cls.download.web ) return; cls.connect_count = 0; cls.rejected = 0; S_StopAllSounds (); if( cls.state == CA_CONNECTED ) { Com_Printf( "reconnecting...\n" ); memset( cl.configstrings, 0, sizeof(cl.configstrings) ); CL_SetClientState( CA_CONNECTED ); CL_AddReliableCommand( "new" ); return; } if( *cls.servername ) { if( cls.state >= CA_CONNECTED ) { CL_Disconnect( NULL ); cls.connect_time = curtime - 1500; } else cls.connect_time = -99999; // fire immediately Com_Printf( "reconnecting...\n" ); } CL_SetClientState( CA_CONNECTING ); } /* ================= CL_ConnectionlessPacket Responses to broadcasts, etc ================= */ void CL_ConnectionlessPacket( msg_t *msg ) { char *s; char *c; MSG_BeginReading( msg ); MSG_ReadLong( msg ); // skip the -1 s = MSG_ReadStringLine( msg ); if( !strncmp(s, "getserversResponse\\", 19) ) { Com_Printf( "%s: %s\n", NET_AdrToString(&net_from), "getserversResponse" ); CL_ParseGetServersResponse( msg ); return; } Cmd_TokenizeString( s, qfalse ); c = Cmd_Argv(0); Com_Printf( "%s: %s\n", NET_AdrToString(&net_from), s ); // server connection if( !strcmp(c, "client_connect") ) { if( cls.state == CA_CONNECTED ) { Com_Printf( "Dup connect received. Ignored.\n" ); return; } // these two are from Q3 if( cls.state != CA_CONNECTING ) { Com_Printf ("client_connect packet while not connecting. Ignored.\n"); return; } if ( !NET_CompareBaseAdr( &net_from, &cls.serveraddress ) ) { Com_Printf( "client_connect from a different address. Ignored.\n" ); Com_Printf( "Was %s should have been %s\n", NET_AdrToString(&net_from), NET_AdrToString(&cls.serveraddress) ); return; } cls.rejected = qfalse; Netchan_Setup( NS_CLIENT, &cls.netchan, net_from, cls.quakePort ); CL_AddReliableCommand( "new" ); memset( cl.configstrings, 0, sizeof(cl.configstrings) ); CL_SetClientState( CA_CONNECTED ); return; } // reject packet, used to inform the client that connection attemp didn't succeed if( !strcmp(c, "reject") ) { int rejectflag; if( cls.state != CA_CONNECTING ) { Com_Printf ("reject packet while not connecting. Ignored.\n"); return; } if ( !NET_CompareBaseAdr( &net_from, &cls.serveraddress ) ) { Com_Printf( "reject from a different address. Ignored.\n" ); Com_Printf( "Was %s should have been %s\n", NET_AdrToString(&net_from), NET_AdrToString(&cls.serveraddress) ); return; } cls.rejected = qtrue; cls.rejecttype = atoi(MSG_ReadStringLine( msg )); if( cls.rejecttype < 0 || cls.rejecttype >= DROP_TYPE_TOTAL ) cls.rejecttype = DROP_TYPE_GENERAL; rejectflag = atoi(MSG_ReadStringLine( msg )); Q_strncpyz(cls.rejectmessage, MSG_ReadStringLine( msg ), sizeof(cls.rejectmessage)); if( strlen(cls.rejectmessage) > sizeof(cls.rejectmessage)-2 ) { cls.rejectmessage[strlen(cls.rejectmessage)-2] = '.'; cls.rejectmessage[strlen(cls.rejectmessage)-1] = '.'; cls.rejectmessage[strlen(cls.rejectmessage)] = '.'; } Com_Printf( "Connection refused: %s\n", cls.rejectmessage); if( rejectflag & DROP_FLAG_AUTORECONNECT ) { Com_Printf( "Automatic reconnecting allowed.\n"); } else { char menuparms[MAX_STRING_CHARS]; Com_Printf( "Automatic reconnecting not allowed.\n"); CL_Disconnect( NULL ); Q_snprintfz( menuparms, sizeof(menuparms), "menu_failed 0 \"%s\" %i \"%s\"", cls.servername, cls.rejecttype, cls.rejectmessage ); Cbuf_ExecuteText( EXEC_NOW, menuparms ); } return; } // server responding to a status broadcast if( !strcmp(c, "info") ) { CL_ParseStatusMessage( msg ); return; } // remote command from gui front end if( !strcmp(c, "cmd") ) { if( !NET_IsLocalAddress(&net_from) ) { Com_Printf( "Command packet from remote host. Ignored.\n" ); return; } Sys_AppActivate(); s = MSG_ReadString( msg ); Cbuf_AddText( s ); Cbuf_AddText( "\n" ); return; } // print command from somewhere if( !strcmp(c, "print") ) { // CA_CONNECTING is allowed, because old servers send protocol mismatch connection error message with it if( ((cls.state != CA_UNINITIALIZED && cls.state != CA_DISCONNECTED) && NET_CompareBaseAdr( &net_from, &cls.serveraddress )) || (strlen(rcon_address->string) > 0 && NET_CompareBaseAdr( &net_from, &cls.rconaddress )) ) { s = MSG_ReadString( msg ); Com_Printf( "%s", s ); return; } else { Com_Printf( "Print packet from unknown host. Ignored.\n" ); return; } } // ping from somewhere if( !strcmp(c, "ping") ) { Netchan_OutOfBandPrint( NS_CLIENT, net_from, "ack" ); return; } // challenge from the server we are connecting to if( !strcmp(c, "challenge") ) { // these two are from Q3 if( cls.state != CA_CONNECTING ) { Com_Printf ("challenge packet while not connecting. Ignored.\n"); return; } if ( !NET_CompareBaseAdr( &net_from, &cls.serveraddress ) ) { Com_Printf( "challenge from a different address. Ignored.\n" ); Com_Printf( "Was %s should have been %s\n", NET_AdrToString(&net_from), NET_AdrToString(&cls.serveraddress) ); return; } cls.challenge = atoi( Cmd_Argv(1) ); //wsw : r1q2[start] //r1: reset the timer so we don't send dup. getchallenges cls.connect_time = curtime; //wsw : r1q2[end] CL_SendConnectPacket(); return; } // echo request from server if( !strcmp(c, "echo") ) { Netchan_OutOfBandPrint( NS_CLIENT, net_from, "%s", Cmd_Argv(1) ); return; } // jal : wsw // server responding to a detailed info broadcast if( !strcmp(c, "infoResponse") ) { CL_ParseGetInfoResponse( msg ); return; } Com_Printf( "Unknown command.\n" ); } //================= // CL_ProcessPacket //================= qboolean CL_ProcessPacket( netchan_t *netchan, msg_t *msg ) { if( !Netchan_Process( netchan, msg ) ) return qfalse; // wasn't accepted for some reason { int sequence, sequence_ack; int zerror; // now if compressed, expand it MSG_BeginReading( msg ); sequence = MSG_ReadLong( msg ); sequence_ack = MSG_ReadLong( msg ); if( msg->compressed ) { zerror = Netchan_DecompressMessage( msg ); if( zerror < 0 ) { // compression error. Drop the packet Com_DPrintf( "SV_ProcessPacket: Compression error %i. Dropping packet\n", zerror ); return qfalse; } } } return qtrue; } /* ================= CL_ReadPackets ================= */ void CL_ReadPackets( void ) { static msg_t msg; static qbyte msgData[MAX_MSGLEN]; MSG_Init( &msg, msgData, sizeof(msgData) ); MSG_Clear( &msg ); while( NET_GetPacket(NS_CLIENT, &net_from, &msg) ) { // // remote command packet // if( *(int *)msg.data == -1 ) { CL_ConnectionlessPacket( &msg ); continue; } if( cls.state == CA_DISCONNECTED || cls.state == CA_CONNECTING ) continue; // dump it if not connected if( msg.cursize < 8 ) { //wsw : r1q2[start] //r1: delegated to DPrintf (someone could spam your console with crap otherwise) Com_DPrintf( "%s: Runt packet\n", NET_AdrToString(&net_from) ); //wsw : r1q2[end] continue; } // // packet from server // if( !NET_CompareAdr(&net_from, &cls.netchan.remoteAddress) ) { Com_DPrintf ("%s:sequenced packet without connection\n" ,NET_AdrToString(&net_from) ); continue; } if( !CL_ProcessPacket(&cls.netchan, &msg) ) continue; // wasn't accepted for some reason CL_ParseServerMessage( &msg ); cls.lastPacketReceivedTime = cls.realtime; } // not expected, but could happen if svs.realtime is cleared and lastPacketReceivedTime is not if( cls.lastPacketReceivedTime > cls.realtime ) cls.lastPacketReceivedTime = cls.realtime; // check timeout if( cls.state >= CA_CONNECTED && !cl.cin.time && cls.lastPacketReceivedTime ) { if( cls.lastPacketReceivedTime + cl_timeout->value*1000 < cls.realtime ) { if( ++cl.timeoutcount > 5 ) // timeoutcount saves debugger { Com_Printf( "\nServer connection timed out.\n" ); CL_Disconnect( "Connection timed out" ); return; } } } else cl.timeoutcount = 0; } //============================================================================= /* ============== CL_Userinfo_f ============== */ void CL_Userinfo_f( void ) { Com_Printf( "User info settings:\n" ); Info_Print( Cvar_Userinfo() ); } int precache_check; // for autodownload of precache items int precache_spawncount; int precache_tex; #define PLAYER_MULT 5 // ENV_CNT is map load #define ENV_CNT (CS_PLAYERINFOS + MAX_CLIENTS * PLAYER_MULT) #define TEXTURE_CNT (ENV_CNT+1) void CL_RequestNextDownload( void ) { char fn[MAX_OSPATH]; if( cls.state != CA_CONNECTED ) return; if( !cl_downloads->integer && precache_check < ENV_CNT ) precache_check = ENV_CNT; //ZOID if( precache_check == CS_MODELS ) { // confirm map precache_check = CS_MODELS+2; // 0 isn't used if( !CL_CheckOrDownloadFile(cl.configstrings[CS_MODELS+1]) ) return; // started a download } if( precache_check >= CS_MODELS && precache_check < CS_MODELS+MAX_MODELS ) { while( precache_check < CS_MODELS+MAX_MODELS && cl.configstrings[precache_check][0] ) { if (cl.configstrings[precache_check][0] == '*' || cl.configstrings[precache_check][0] == '$' || // disable playermodel downloading for now cl.configstrings[precache_check][0] == '#') { precache_check++; continue; } if( !CL_CheckOrDownloadFile(cl.configstrings[precache_check++]) ) { return; // started a download } } precache_check = CS_SOUNDS; } if( precache_check >= CS_SOUNDS && precache_check < CS_SOUNDS+MAX_SOUNDS ) { if( precache_check == CS_SOUNDS ) precache_check++; // zero is blank while( precache_check < CS_SOUNDS+MAX_SOUNDS && cl.configstrings[precache_check][0] ) { if( cl.configstrings[precache_check][0] == '*' ) { precache_check++; continue; } Q_strncpyz( fn, cl.configstrings[precache_check++], sizeof(fn) ); COM_DefaultExtension( fn, ".wav", sizeof(fn) ); if( !CL_CheckOrDownloadFile(fn) ) return; // started a download } precache_check = CS_IMAGES; } if( precache_check >= CS_IMAGES && precache_check < CS_IMAGES+MAX_IMAGES ) { if( precache_check == CS_IMAGES ) precache_check++; // zero is blank #if 1 // precache phase completed precache_check = ENV_CNT; } #else precache_check = CS_PLAYERINFOS; } // skins are special, since a player has three things to download: // model, weapon model and skin // so precache_check is now *3 if( precache_check >= CS_PLAYERINFOS && precache_check < CS_PLAYERINFOS + MAX_CLIENTS * PLAYER_MULT ) { while( precache_check < CS_PLAYERINFOS + MAX_CLIENTS * PLAYER_MULT ) { int i, n; //char model[MAX_QPATH], skin[MAX_QPATH], *p; i = (precache_check - CS_PLAYERINFOS)/PLAYER_MULT; n = (precache_check - CS_PLAYERINFOS)%PLAYER_MULT; // Vic: disabled for now #if 1 precache_check = CS_PLAYERINFOS + (i + 1) * PLAYER_MULT; #else if( !cl.configstrings[CS_PLAYERINFOS+i][0] ) { precache_check = CS_PLAYERINFOS + (i + 1) * PLAYER_MULT; continue; } if( (p = strchr( cl.configstrings[CS_PLAYERINFOS+i], '\\') ) != NULL ) { p++; Q_strncpyz(model, p, sizeof(model)); if( ( p = strchr(model, '\\') ) != NULL ) p++; } else p = cl.configstrings[CS_PLAYERINFOS+i]; Q_strncpyz(model, p, sizeof(model)); p = strchr(model, '/'); if (!p) p = strchr( model, '\\' ); if (p) { *p++ = 0; Q_strncpyz( skin, p, sizeof(skin) ); } else *skin = 0; switch (n) { case 0: // model Q_snprintfz( fn, sizeof(fn), "players/%s/tris.md2", model ); if( !CL_CheckOrDownloadFile(fn) ) { precache_check = CS_PLAYERINFOS + i * PLAYER_MULT + 1; return; // started a download } n++; /*FALL THROUGH*/ case 1: // weapon model Q_snprintfz( fn, sizeof(fn), "players/%s/weapon.md2", model ); if( !CL_CheckOrDownloadFile(fn) ) { precache_check = CS_PLAYERINFOS + i * PLAYER_MULT + 2; return; // started a download } n++; /*FALL THROUGH*/ case 2: // weapon skin Q_snprintfz( fn, sizeof(fn), "players/%s/weapon.pcx", model ); if( !CL_CheckOrDownloadFile(fn) ) { precache_check = CS_PLAYERINFOS + i * PLAYER_MULT + 3; return; // started a download } n++; /*FALL THROUGH*/ case 3: // skin Q_snprintfz( fn, sizeof(fn), "players/%s/%s.pcx", model, skin ); if( !CL_CheckOrDownloadFile(fn) ) { precache_check = CS_PLAYERINFOS + i * PLAYER_MULT + 4; return; // started a download } n++; /*FALL THROUGH*/ case 4: // skin_i Q_snprintfz( fn, sizeof(fn), "players/%s/%s_i.pcx", model, skin ); if( !CL_CheckOrDownloadFile(fn) ) { precache_check = CS_PLAYERINFOS + i * PLAYER_MULT + 5; return; // started a download } // move on to next model precache_check = CS_PLAYERINFOS + (i + 1) * PLAYER_MULT; } #endif // 1 } // precache phase completed precache_check = ENV_CNT; } #endif // 1 if( precache_check == ENV_CNT ) { unsigned map_checksum; // we're done with the download phase, so clear the list CL_FreeDownloadList(); // check memory integrity Mem_CheckSentinelsGlobal(); CM_LoadMap( cl.configstrings[CS_MODELS+1], qtrue, &map_checksum ); // check memory integrity Mem_CheckSentinelsGlobal(); if( map_checksum != (unsigned)atoi(cl.configstrings[CS_MAPCHECKSUM]) ) { Com_Error( ERR_DROP, "Local map version differs from server: %i != '%s'", map_checksum, cl.configstrings[CS_MAPCHECKSUM] ); return; } precache_check = TEXTURE_CNT; } if( precache_check == TEXTURE_CNT ) { precache_check = TEXTURE_CNT+1; precache_tex = 0; } // confirm existance of textures, download any that don't exist if( precache_check == TEXTURE_CNT+1 ) { precache_check = TEXTURE_CNT+999; } //ZOID // load client game module CL_GameModule_Init(); CL_AddReliableCommand( va("begin %i\n", precache_spawncount) ); } /* ================= CL_Precache_f The server will send this command right before allowing the client into the server ================= */ void CL_Precache_f( void ) { if( Cmd_Argc() < 2 ) { // demo playback unsigned map_checksum; // check memory integrity Mem_CheckSentinelsGlobal(); CM_LoadMap( cl.configstrings[CS_MODELS+1], qtrue, &map_checksum ); // check memory integrity Mem_CheckSentinelsGlobal(); CL_GameModule_Init(); return; } precache_check = CS_MODELS; precache_spawncount = atoi( Cmd_Argv(1) ); CL_RequestNextDownload(); } /* =============== CL_WriteConfiguration Writes key bindings, archived cvars and aliases to a config file =============== */ static void CL_WriteConfiguration( const char *name, qboolean warn ) { int file; if( FS_FOpenFile( name, &file, FS_WRITE ) == -1 ) { Com_Printf( "Couldn't write %s.\n", name ); return; } if( warn ) { FS_Printf( file, "// This file is automatically generated by Warsow, do not modify.\n\n" ); } FS_Printf( file, "// key bindings\n" ); Key_WriteBindings( file ); FS_Printf( file, "\n// variables\n" ); Cvar_WriteVariables( file ); FS_Printf( file, "\n// aliases\n" ); Cmd_WriteAliases( file ); FS_FCloseFile( file ); } /* =============== CL_WriteConfig_f =============== */ void CL_WriteConfig_f( void ) { char name[MAX_QPATH]; if( Cmd_Argc() != 2 ) { Com_Printf( "usage: writeconfig \n" ); return; } Q_strncpyz( name, Cmd_Argv(1), sizeof(name) ); COM_DefaultExtension( name, ".cfg", sizeof(name) ); Com_Printf( "Writing %s\n", name ); CL_WriteConfiguration( name, qfalse ); } /* ================= CL_SetClientState ================= */ void CL_SetClientState( int state ) { cls.state = state; Com_SetClientState( state ); switch( state ) { case CA_DISCONNECTED: Con_Close(); Cbuf_ExecuteText( EXEC_NOW, "menu_main" ); //CL_UIModule_MenuMain (); CL_SetKeyDest( key_menu ); // SCR_UpdateScreen(); break; case CA_CONNECTING: Con_Close(); CL_SetKeyDest( key_game ); // SCR_UpdateScreen(); break; case CA_CONNECTED: Con_Close(); Cvar_FixCheatVars(); // SCR_UpdateScreen(); break; case CA_ACTIVE: Con_Close(); CL_SetKeyDest( key_game ); // SCR_UpdateScreen(); break; default: break; } } /* ================= CL_InitMedia ================= */ void CL_InitMedia( void ) { if( cls.mediaInitialized ) return; if( cls.state == CA_UNINITIALIZED ) return; cls.mediaInitialized = qtrue; // restart renderer R_Restart(); // free all sounds S_FreeSounds(); // register console font and background SCR_RegisterConsoleMedia(); // load user interface CL_UIModule_Init(); // check memory integrity Mem_CheckSentinelsGlobal(); S_SoundsInMemory(); } /* ================= CL_ShutdownMedia ================= */ void CL_ShutdownMedia( void ) { if( !cls.mediaInitialized ) return; cls.mediaInitialized = qfalse; // shutdown cgame CL_GameModule_Shutdown(); // shutdown user interface CL_UIModule_Shutdown(); // stop and free all sounds S_StopAllSounds(); S_FreeSounds(); // wsw : jalfonts SCR_ShutDownConsoleMedia(); } /* ================= CL_RestartMedia ================= */ void CL_RestartMedia( void ) { CL_ShutdownMedia(); CL_InitMedia(); } /* ================== CL_ShowIP_f - wsw : jal : taken from Q3 (it only shows the ip when server was started) ================== */ void CL_ShowIP_f( void ) { Sys_ShowIP(); } /* ================= CL_InitLocal ================= */ void CL_InitLocal( void ) { cvar_t *color; cls.state = CA_DISCONNECTED; Com_SetClientState( CA_DISCONNECTED ); cls.realtime = Sys_Milliseconds(); #ifdef BATTLEYE cls.runBattlEye = qfalse; #endif // // register our variables // cl_stereo_separation = Cvar_Get( "cl_stereo_separation", "0.4", CVAR_ARCHIVE ); cl_stereo = Cvar_Get( "cl_stereo", "0", CVAR_ARCHIVE ); cl_maxfps = Cvar_Get( "cl_maxfps", "90", CVAR_ARCHIVE); cl_maxpackets = Cvar_Get( "cl_maxpackets", "60", CVAR_ARCHIVE); cl_compresspackets = Cvar_Get( "cl_compresspackets", "0", CVAR_ARCHIVE); cl_synchusercmd = Cvar_Get( "cl_synchusercmd", "1", CVAR_ARCHIVE); cl_upspeed = Cvar_Get( "cl_upspeed", "400", 0 ); cl_forwardspeed = Cvar_Get( "cl_forwardspeed", "400", 0 ); cl_sidespeed = Cvar_Get( "cl_sidespeed", "400", 0 ); cl_yawspeed = Cvar_Get( "cl_yawspeed", "140", 0 ); cl_pitchspeed = Cvar_Get( "cl_pitchspeed", "150", 0 ); cl_anglespeedkey = Cvar_Get( "cl_anglespeedkey", "1.5", 0 ); cl_run = Cvar_Get( "cl_run", "1", CVAR_ARCHIVE); cl_freelook = Cvar_Get( "cl_freelook", "1", CVAR_ARCHIVE ); lookspring = Cvar_Get( "lookspring", "0", CVAR_ARCHIVE ); lookstrafe = Cvar_Get( "lookstrafe", "0", CVAR_ARCHIVE ); sensitivity = Cvar_Get( "sensitivity", "3", CVAR_ARCHIVE ); m_accel = Cvar_Get( "m_accel", "0", CVAR_ARCHIVE ); // wsw : pb : add mouseaccel m_filter = Cvar_Get( "m_filter", "0", CVAR_ARCHIVE ); in_minMsecs = Cvar_Get( "in_minmsecs", "5", CVAR_ARCHIVE ); // wsw : jal : key/mouse input frametime 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", "1", CVAR_ARCHIVE ); m_side = Cvar_Get( "m_side", "1", CVAR_ARCHIVE ); cl_masterservers = Cvar_Get( "masterservers", DEFAULT_MASTER_SERVERS_IPS, 0 ); cl_shownet = Cvar_Get( "cl_shownet", "0", 0 ); cl_timeout = Cvar_Get( "cl_timeout", "120", 0 ); cl_timedemo = Cvar_Get( "timedemo", "0", CVAR_CHEAT ); cl_demoavi_fps = Cvar_Get( "cl_demoavi_fps", "25", CVAR_ARCHIVE ); cl_demoavi_scissor = Cvar_Get( "cl_demoavi_scissor", "0", CVAR_ARCHIVE ); rcon_client_password = Cvar_Get( "rcon_password", "", 0 ); rcon_address = Cvar_Get( "rcon_address", "", 0 ); // wsw : debug netcode cl_debug_serverCmd = Cvar_Get( "cl_debug_serverCmd", "0", CVAR_ARCHIVE|CVAR_CHEAT ); cl_downloads = Cvar_Get( "cl_downloads", "1", CVAR_ARCHIVE ); cl_downloads_from_web = Cvar_Get( "cl_downloads_from_web", "1", CVAR_ARCHIVE ); // // userinfo // info_password = Cvar_Get( "password", "", CVAR_USERINFO ); rate = Cvar_Get( "rate", "25000", CVAR_USERINFO | CVAR_ARCHIVE ); // FIXME Cvar_Get( "name", "player", CVAR_USERINFO | CVAR_ARCHIVE ); Cvar_Get( "model", DEFAULT_PLAYERMODEL, CVAR_USERINFO | CVAR_ARCHIVE ); // wsw : jal Cvar_Get( "skin", DEFAULT_PLAYERSKIN, CVAR_USERINFO | CVAR_ARCHIVE ); // wsw : jal Cvar_Get( "hand", "0", CVAR_USERINFO | CVAR_ARCHIVE ); Cvar_Get( "fov", "90", CVAR_USERINFO | CVAR_ARCHIVE ); // wsw : jal color = Cvar_Get( "color", "", CVAR_ARCHIVE|CVAR_USERINFO ); if( COM_ReadColorRGBString( color->string ) == -1 ) { // first time create a random color for it time_t long_time; // random isn't working fine at this point. struct tm *newtime; // so we get the user local time and use some values from there time( &long_time ); newtime = localtime( &long_time ); long_time *= newtime->tm_sec * newtime->tm_min * newtime->tm_wday; Cvar_Set( "color", va("%i %i %i", (long_time)&0xff, ((long_time)>>8)&0xff, ((long_time)>>16)&0xff) ); } #ifdef BATTLEYE #ifdef WIN32 cl_battleye = Cvar_Get( "cl_battleye", "1", CVAR_USERINFO | CVAR_ARCHIVE ); #else cl_battleye = Cvar_Get( "cl_battleye", "0", CVAR_USERINFO | CVAR_READONLY ); #endif #endif // // register our commands // Cmd_AddCommand( "cmd", CL_ForwardToServer_f ); Cmd_AddCommand( "requestservers", CL_GetServers_f ); Cmd_AddCommand( "getinfo", CL_QueryGetInfoMessage_f ); // wsw : jal : ask for server info Cmd_AddCommand( "userinfo", CL_Userinfo_f ); Cmd_AddCommand( "disconnect", CL_Disconnect_f ); Cmd_AddCommand( "record", CL_Record_f ); Cmd_AddCommand( "stop", CL_Stop_f ); Cmd_AddCommand( "quit", CL_Quit_f ); Cmd_AddCommand( "connect", CL_Connect_f ); Cmd_AddCommand( "reconnect", CL_Reconnect_f ); Cmd_AddCommand( "rcon", CL_Rcon_f ); Cmd_AddCommand( "download", CL_Download_f ); Cmd_AddCommand( "writeconfig", CL_WriteConfig_f ); Cmd_AddCommand( "showip", CL_ShowIP_f ); // jal : wsw : print our ip Cmd_AddCommand( "demo", CL_PlayDemo_f ); Cmd_AddCommand( "demoavi", CL_PlayDemoToAvi_f ); Cmd_AddCommand( "cinematic", CL_PlayCinematic_f ); Cmd_AddCommand( "next", CL_SetNext_f ); Cmd_AddCommand( "pingserver", CL_PingServer_f ); #ifdef DEMOCAM // -- PLX Cmd_AddCommand( "demopause", CL_PauseDemo_f ); #endif } //============================================================================ //================== //CL_Frame //================== void CL_AdjustServerTime( void ) { if( !cl.curFrame || !cl.curFrame->valid ) cl.serverTimeDelta = 0; if( cls.realtime + cl.serverTimeDelta < 0 ) { // should never happen cl.serverTimeDelta = 0; } cl.serverTime = cls.realtime + cl.serverTimeDelta; } /* ================== CL_SendCommand ================== */ void CL_SendCommand( void ) { // build a command even if not connected cl.cmdNum = cls.netchan.outgoingSequence & CMD_MASK; // store last sent command number for prediction cl.cmd_time[cls.netchan.outgoingSequence & CMD_MASK] = cls.realtime; // wsw : jal : cinematics are client side if( cl.cmds[cl.cmdNum].buttons && cl.cin.time > 0 && cls.realtime - cl.cin.time > 1000 ) { // skip the rest of the cinematic SCR_StopCinematic(); SCR_FinishCinematic(); SCR_UpdateScreen(); } // send intentions now CL_WritePacket( &cl.cmds[cl.cmdNum] ); // init the new command (outgoingSequence was bumped at CL_WritePacket if connected) memset( &cl.cmds[cls.netchan.outgoingSequence & CMD_MASK], 0, sizeof(usercmd_t) ); CL_UpdateUserCommand(); } #ifndef NEWFPSTIMER /* ================== CL_MinFrameFrame ================== */ double CL_MinFrameFrame( void ) { if( !cl_timedemo->integer ) { if( cls.state == CA_CONNECTED ) return 0.1; // don't flood packets out while connecting if( cl_maxfps->integer ) return 1.0 / (double)cl_maxfps->integer; } return 0; } /* ================== CL_MinPacketFrame ================== */ double CL_MinPacketFrame( void ) { int maxpackets; if( cl_timedemo->integer || cls.demoplaying ) return CL_MinFrameFrame(); if( cls.state == CA_CONNECTED ) return 0.1; // don't flood packets out while connecting maxpackets = cl_maxpackets->integer; // don't let people abuse cl_maxpackets if( cl_maxpackets->integer < 10 ) { maxpackets = 10; } else if( cl_maxpackets->integer > 90 && !Sys_IsLANAddress( cls.netchan.remoteAddress ) ) { maxpackets = 90; } // return the smaller between maxpackets and maxfps if( cl_maxfps->integer && (cl_maxfps->integer < maxpackets) ) return CL_MinFrameFrame(); return 1.0 / (double)maxpackets; } #endif // NEWFPSTIMER //================== //CL_Netchan_Transmit //================== void CL_Netchan_Transmit( msg_t *msg ) { //int zerror; // if we got here with unsent fragments, fire them all now Netchan_PushAllFragments( &cls.netchan ); #ifdef BATTLEYE CL_AddBattleyeCommandToMessage( msg ); #endif // do not enable client compression until I fix the compression+fragmentation rare case bug //if( cl_compresspackets->integer ) { // zerror = Netchan_CompressMessage( msg ); // if( zerror < 0 ) { // it's compression error, just send uncompressed // Com_DPrintf( "CL_Netchan_Transmit (ignoring compression): Compression error %i\n", zerror ); // } //} Netchan_Transmit( &cls.netchan, msg ); cls.lastPacketSentTime = cls.realtime; } //#ifdef NEWFPSTIMER void CL_NetSendUserCommand( int msec ) { static int minMsec = 1, allMsec = 0, extraMsec = 0; int maxpackets; if( cls.state == CA_CONNECTED ) maxpackets = 10; // don't flood packets out while connecting maxpackets = cl_maxpackets->integer; // don't let people abuse cl_maxpackets //if( !Sys_IsLANAddress( cls.netchan.remoteAddress ) ) clamp( maxpackets, 10, 90 ); if( !cl_timedemo->integer && !cls.demoplaying ) { minMsec = 1000 / maxpackets; } else { minMsec = 1; } if( minMsec > extraMsec ) // remove, from min frametime, the extra time we spent in last frame minMsec -= extraMsec; allMsec += msec; if( allMsec < minMsec ) { return; } extraMsec = allMsec - minMsec; if( extraMsec > minMsec ) { //Com_Printf( "Dropped %i full net frame\n", (int)(extraMsec / minMsec) ); extraMsec = minMsec - 1; } allMsec = 0; // resend a connection request if necessary CL_CheckForResend(); CL_CheckDownloadTimeout(); // send a new user command message to the server CL_SendCommand(); } //#endif /* ================== CL_NetFrame ================== */ void CL_NetFrame( int msec ) { #ifdef NEWFPSTIMER static int minMsec = 1, allMsec = 0, extraMsec = 0; int maxpackets; #else static double extrapackettime = 0.001; static double truepackettime; double minpackettime; #endif // read packets from server if( msec > 5000 ) // if in the debugger last frame, don't timeout cls.lastPacketReceivedTime = cls.realtime; if( cls.demoplaying ) CL_ReadDemoPackets(); // fetch results from demo file else CL_ReadPackets(); // fetch results from server // if we have any unsent fragment if( cls.netchan.unsentFragments ) Netchan_TransmitNextFragment( &cls.netchan ); #ifdef NEWFPSTIMER if( !cl_synchusercmd->integer ) { if( cls.state == CA_CONNECTED ) maxpackets = 10; // don't flood packets out while connecting maxpackets = cl_maxpackets->integer; // don't let people abuse cl_maxpackets //if( !Sys_IsLANAddress( cls.netchan.remoteAddress ) ) clamp( maxpackets, 10, 90 ); if( cl_maxfps->integer && (cl_maxfps->integer < maxpackets) ) maxpackets = cl_maxfps->integer; if( !cl_timedemo->integer && !cls.demoplaying ) { minMsec = 1000 / maxpackets; } else { minMsec = 1; } if( minMsec > extraMsec ) // remove, from min frametime, the extra time we spent in last frame minMsec -= extraMsec; allMsec += msec; if( allMsec < minMsec ) { return; } extraMsec = allMsec - minMsec; if( extraMsec > minMsec ) { //Com_Printf( "Dropped %i full net frame\n", (int)(extraMsec / minMsec) ); extraMsec = minMsec - 1; } allMsec = 0; // resend a connection request if necessary CL_CheckForResend(); CL_CheckDownloadTimeout(); // send a new user command message to the server CL_SendCommand(); } #else if( !cl_synchusercmd->integer ) { // see if it's time to send a new command to the server extrapackettime += msec * 0.001; minpackettime = CL_MinPacketFrame(); if( extrapackettime > minpackettime ) { // decide the simulation time truepackettime = extrapackettime - 0.001; if( truepackettime < minpackettime ) truepackettime = minpackettime; extrapackettime -= truepackettime; // resend a connection request if necessary CL_CheckForResend(); CL_CheckDownloadTimeout(); // send a new user command message to the server CL_SendCommand(); }// else if( !cls.netchan.unsentFragments ) { // NET_Sleep( (minpackettime - extrapackettime)*1000 ); //} } #endif } void CL_TimedemoStats( void ) { if( cl_timedemo->integer ) { static int lasttime = 0; if( lasttime != 0 ) { if( curtime - lasttime >= 100 ) cl.timedemo_counts[99]++; else cl.timedemo_counts[curtime-lasttime]++; } lasttime = curtime; } } #ifdef _DEBUG static void CL_LogStats( void ) { static unsigned int lasttimecalled = 0; if( log_stats->integer ) { if( cls.state == CA_ACTIVE ) { if ( !lasttimecalled ) { lasttimecalled = Sys_Milliseconds(); if ( log_stats_file ) FS_Printf( log_stats_file, "0\n" ); } else { unsigned int now = Sys_Milliseconds(); if ( log_stats_file ) FS_Printf( log_stats_file, "%u\n", now - lasttimecalled ); lasttimecalled = now; } } } } #endif /* ================== CL_Frame ================== */ void CL_Frame( int msec ) { #ifdef NEWFPSTIMER static int minMsec = 1, allMsec = 0, extraMsec = 0; #else static double extratime = 0.001; static double trueframetime; double minframetime; #endif if( dedicated->integer ) return; cls.realtime += msec; #ifdef NEWFPSTIMER CL_UserInputFrame(); CL_AdjustServerTime(); CL_NetFrame( msec ); // demoavi if( cls.demoavi && msec && cls.state == CA_ACTIVE ) { R_WriteAviFrame( cls.demoavi_frame++, cl_demoavi_scissor->integer ); // fixed time for next frame msec = (1000.0 / (double)cl_demoavi_fps->integer) * Cvar_VariableValue( "timescale" ); if( msec < 1 ) { msec = 1; } } cls.demoplay_framemsecs += msec; if( cl_maxfps->integer > 0 && !cl_timedemo->integer ) { minMsec = 1000 / cl_maxfps->integer; } else { minMsec = 1; } if( minMsec > extraMsec ) // remove, from min frametime, the extra time we spent in last frame minMsec -= extraMsec; allMsec += msec; if( allMsec < minMsec ) { return; } if( cl_synchusercmd->integer ) CL_NetSendUserCommand( allMsec ); cls.frametime = (float)allMsec * 0.001f; cls.trueframetime = cls.frametime; extraMsec = allMsec - minMsec; if( extraMsec > minMsec ) { //Com_Printf( "Lost %i full frames\n", (int)(extraMsec / minMsec) ); extraMsec = minMsec - 1; } allMsec = 0; CL_TimedemoStats(); #else CL_UserInputFrame(); CL_AdjustServerTime(); CL_NetFrame( msec ); // demoavi if( cls.demoavi && msec && cls.state == CA_ACTIVE ) { R_WriteAviFrame( cls.demoavi_frame++, cl_demoavi_scissor->integer ); // fixed time for next frame msec = (1000.0 / (double)cl_demoavi_fps->integer) * Cvar_VariableValue( "timescale" ); if( msec < 1 ) { msec = 1; } } cls.demoplay_framemsecs += msec; extratime += msec * 0.001; minframetime = CL_MinFrameFrame(); if( extratime < minframetime ) return; CL_TimedemoStats(); if( cl_synchusercmd->integer ) CL_NetSendUserCommand( (unsigned int)(extratime * 1000) ); // decide the simulation time trueframetime = extratime - 0.001; if( trueframetime < minframetime ) trueframetime = minframetime; extratime -= trueframetime; cls.frametime = trueframetime; cls.trueframetime = trueframetime; #endif // allow rendering DLL change VID_CheckChanges(); // update the screen if( host_speeds->integer ) time_before_ref = Sys_Milliseconds(); SCR_UpdateScreen(); if( host_speeds->integer ) time_after_ref = Sys_Milliseconds(); // update audio if( cls.state != CA_ACTIVE || cl.cin.time > 0 ) S_Update( vec3_origin, vec3_origin, vec3_origin, vec3_origin ); // advance local effects for next frame SCR_RunCinematic(); SCR_RunConsole(); cls.framecount++; #ifdef _DEBUG CL_LogStats(); #endif } //============================================================================ /* =============== CL_CheckForUpdate retrieve a file with the last version umber on a web server, compare with current version display a message box in case the user need to update =============== */ #define CHECKUPDATE_URL "http://www.warsow.net/" #define CHECKUPDATE_FILE "warsow_last_version.txt" void CL_CheckForUpdate(void) { char url[MAX_STRING_CHARS]; qboolean success; float local_version, net_version; FILE *f; // step one get the last version file Com_Printf("Checking for Warsow update.\n"); Q_snprintfz( url, sizeof(url), CHECKUPDATE_URL CHECKUPDATE_FILE ); success = Web_Get( url, NULL, "warsow_last_version.txt", qfalse, 2, NULL ); if(!success) return; // got the file // this look stupid but is the safe way to do it local_version=atof(va("%4.3f",VERSION)); f=fopen(CHECKUPDATE_FILE,"r"); if(f==NULL) { Com_Printf("Fail to open last version file.\n"); return; } if(fscanf(f,"%f",&net_version)!=1) { // error fclose(f); Com_Printf("Fail to parse last version file.\n"); return; } // we have the version //Com_Printf("CheckForUpdate: local: %f net: %f\n", local_version, net_version); if(net_version>local_version) { char cmd[1024]; // you should update Com_Printf("Warsow version %4.3f is available.\n", net_version); Q_snprintfz(cmd, 1024, "menu_msgbox \"Version %4.3f of Warsow is available\"", net_version); Cbuf_ExecuteText(EXEC_APPEND, cmd ); } else if(net_version==local_version) { Com_Printf("This Warsow version is up-to-date.\n"); } fclose(f); // cleanup FS_RemoveFile(CHECKUPDATE_FILE); } /* ==================== CL_Init ==================== */ void CL_Init( void ) { if( dedicated->integer ) return; // nothing running on the client // all archived variables will now be loaded Con_Init(); #ifndef VID_INITFIRST S_Init(); VID_Init(); #else VID_Init(); S_Init(); // sound must be initialized after window is created #endif cls.lastExecutedServerCommand = 0; cls.reliableAcknowledge = 0; cls.reliableSequence = 0; cls.reliableSent = 0; memset( &cls.reliableCommands, 0, sizeof(cls.reliableCommands) ); #ifdef BATTLEYE memset( &cls.BE, 0, sizeof(cls.BE) ); #endif RoQ_Init(); SCR_InitScreen(); cls.disable_screen = qtrue; // don't draw yet CL_InitLocal(); CL_InitInput(); CL_InitMedia(); Cbuf_ExecuteText( EXEC_NOW, "menu_main" ); // check for update CL_CheckForUpdate( ); } /* =============== CL_Shutdown FIXME: this is a callback from Sys_Quit and Com_Error. It would be better to run quit through here before the final handoff to the sys code. =============== */ void CL_Shutdown( void ) { static qboolean isdown = qfalse; if( cls.state == CA_UNINITIALIZED ) return; if( isdown ) return; isdown = qtrue; CL_WriteConfiguration( "config.cfg", qtrue ); CL_UIModule_Shutdown(); CL_GameModule_Shutdown(); S_Shutdown(); CL_ShutdownInput(); VID_Shutdown(); }