📄 cl_main.c
字号:
/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
This file is part of Quake III Arena source code.
Quake III Arena source code 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.
Quake III Arena source code 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 Foobar; 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>
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_avidemo;
cvar_t *cl_forceavidemo;
cvar_t *cl_freelook;
cvar_t *cl_sensitivity;
cvar_t *cl_mouseAccel;
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 *cl_activeAction;
cvar_t *cl_motdString;
cvar_t *cl_allowDownload;
cvar_t *cl_conXOffset;
cvar_t *cl_inGameVideo;
cvar_t *cl_serverStatusResendTime;
cvar_t *cl_trn;
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 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
}
/*
=======================================================================
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 we would be losing an old command that hasn't been acknowledged,
// we must drop the connection
if ( clc.reliableSequence - clc.reliableAcknowledge > MAX_RELIABLE_COMMANDS ) {
Com_Error( ERR_DROP, "Client command overflow" );
}
clc.reliableSequence++;
index = clc.reliableSequence & ( MAX_RELIABLE_COMMANDS - 1 );
Q_strncpyz( clc.reliableCommands[ index ], cmd, sizeof( clc.reliableCommands[ index ] ) );
}
/*
======================
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 ) {
Com_sprintf( fileName, MAX_OSPATH, "demo9999.tga" );
return;
}
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 ( !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.dm_%d", demoName, 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.dm_%d", demoName, PROTOCOL_VERSION );
len = FS_ReadFile( name, NULL );
if ( len <= 0 ) {
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_DemoCompleted
=================
*/
void CL_DemoCompleted( void ) {
if (cl_timedemo && cl_timedemo->integer) {
int time;
time = Sys_Milliseconds() - clc.timeDemoStart;
if ( time > 0 ) {
Com_Printf ("%i frames, %3.1f seconds: %3.1f fps\n", clc.timeDemoFrames,
time/1000.0, clc.timeDemoFrames*1000.0 / time);
}
}
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;
while(demo_protocols[i])
{
Com_sprintf (name, MAX_OSPATH, "demos/%s.dm_%d", arg, 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_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 ("playdemo <demoname>\n");
return;
}
// make sure a local server is killed
Cvar_Set( "sv_killserver", "1" );
CL_Disconnect( qtrue );
// open the demo file
arg = Cmd_Argv(1);
// check for an extension .dm_?? (?? is protocol)
ext_test = arg + strlen(arg) - 6;
if ((strlen(arg) > 6) && (ext_test[0] == '.') && ((ext_test[1] == 'd') || (ext_test[1] == 'D')) && ((ext_test[2] == 'm') || (ext_test[2] == 'M')) && (ext_test[3] == '_'))
{
protocol = atoi(ext_test+4);
i=0;
while(demo_protocols[i])
{
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -