lilium-voyager/code/client/cl_main.c

4867 lines
114 KiB
C
Raw Normal View History

2005-08-26 17:39:27 +00:00
/*
===========================================================================
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 Quake III Arena source code; if not, write to the Free Software
2005-08-26 17:39:27 +00:00
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
// cl_main.c -- client main loop
#include "client.h"
#include <limits.h>
#include "../sys/sys_local.h"
#include "../sys/sys_loadlib.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;
2008-06-25 05:50:44 +00:00
cvar_t *cl_voipShowMeter;
cvar_t *cl_voipProtocol;
cvar_t *cl_voip;
#endif
#ifdef USE_RENDERER_DLOPEN
cvar_t *cl_renderer;
#endif
2005-08-26 17:39:27 +00:00
cvar_t *cl_nodelta;
cvar_t *cl_debugMove;
cvar_t *cl_noprint;
#ifdef UPDATE_SERVER_NAME
2005-08-26 17:39:27 +00:00
cvar_t *cl_motd;
#endif
2005-08-26 17:39:27 +00:00
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;
2005-08-26 17:39:27 +00:00
cvar_t *cl_forceavidemo;
cvar_t *cl_freelook;
cvar_t *cl_sensitivity;
cvar_t *cl_mouseAccel;
cvar_t *cl_mouseAccelOffset;
cvar_t *cl_mouseAccelStyle;
2005-08-26 17:39:27 +00:00
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_up;
cvar_t *j_pitch_axis;
cvar_t *j_yaw_axis;
cvar_t *j_forward_axis;
cvar_t *j_side_axis;
cvar_t *j_up_axis;
2005-08-26 17:39:27 +00:00
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_lanForcePackets;
cvar_t *cl_guidServerUniq;
cvar_t *cl_consoleKeys;
cvar_t *cl_rate;
2005-08-26 17:39:27 +00:00
clientActive_t cl;
clientConnection_t clc;
clientStatic_t cls;
vm_t *cgvm;
char cl_reconnectArgs[MAX_OSPATH];
char cl_oldGame[MAX_QPATH];
qboolean cl_oldGameSet;
2005-08-26 17:39:27 +00:00
// Structure containing functions exported from refresh DLL
refexport_t re;
#ifdef USE_RENDERER_DLOPEN
static void *rendererLib = NULL;
#endif
2005-08-26 17:39:27 +00:00
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];
#if defined __USEA3D && defined __A3D_GEOM
void hA3Dg_ExportRenderGeom (refexport_t *incoming_re);
#endif
static int noGameRestart = qfalse;
2005-08-26 17:39:27 +00:00
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
2005-08-26 17:39:27 +00:00
#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);
}
}
}
void CL_Voip_f( void )
{
const char *cmd = Cmd_Argv(1);
const char *reason = NULL;
if (clc.state != CA_ACTIVE)
reason = "Not connected to a server";
else if (!clc.voipCodecInitialized)
reason = "Voip codec not initialized";
else if (!clc.voipEnabled)
reason = "Server doesn't support VoIP";
else if (!clc.demoplaying && (Cvar_VariableValue( "g_gametype" ) == GT_SINGLE_PLAYER || Cvar_VariableValue("ui_singlePlayerActive")))
reason = "running in single-player mode";
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;
opus_encoder_ctl(clc.opusEncoder, OPUS_RESET_STATE);
}
/*
===============
CL_VoipParseTargets
sets clc.voipTargets according to cl_voipSendTarget
Generally we don't want who's listening to change during a transmission,
so this is only called when the key is first pressed
===============
*/
void CL_VoipParseTargets(void)
{
const char *target = cl_voipSendTarget->string;
char *end;
int val;
Com_Memset(clc.voipTargets, 0, sizeof(clc.voipTargets));
clc.voipFlags &= ~VOIP_SPATIAL;
while(target)
{
while(*target == ',' || *target == ' ')
target++;
if(!*target)
break;
if(isdigit(*target))
{
val = strtol(target, &end, 10);
target = end;
}
else
{
if(!Q_stricmpn(target, "all", 3))
{
Com_Memset(clc.voipTargets, ~0, sizeof(clc.voipTargets));
return;
}
if(!Q_stricmpn(target, "spatial", 7))
{
clc.voipFlags |= VOIP_SPATIAL;
target += 7;
continue;
}
else
{
if(!Q_stricmpn(target, "attacker", 8))
{
val = VM_Call(cgvm, CG_LAST_ATTACKER);
target += 8;
}
else if(!Q_stricmpn(target, "crosshair", 9))
{
val = VM_Call(cgvm, CG_CROSSHAIR_PLAYER);
target += 9;
}
else
{
while(*target && *target != ',' && *target != ' ')
target++;
continue;
}
if(val < 0)
continue;
}
}
if(val < 0 || val >= MAX_CLIENTS)
{
Com_Printf(S_COLOR_YELLOW "WARNING: VoIP "
"target %d is not a valid client "
"number\n", val);
continue;
}
clc.voipTargets[val / 8] |= 1 << (val % 8);
}
}
/*
===============
CL_CaptureVoip
Record more audio from the hardware if required and encode it into Opus
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 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->modified || cl_rate->modified) {
if ((cl_voip->integer) && (cl_rate->integer < 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'.\n");
Com_Printf("Until then, VoIP is disabled.\n");
Cvar_Set("cl_voip", "0");
}
Cvar_Set("cl_voipProtocol", cl_voip->integer ? "opus" : "");
cl_voip->modified = qfalse;
cl_rate->modified = qfalse;
}
if (!clc.voipCodecInitialized)
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 (clc.state != CA_ACTIVE)
dontCapture = qtrue; // not connected to a server.
else if (!clc.voipEnabled)
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)
{
Cvar_Set("cl_voipSend", "0");
return;
}
if (cl_voipSend->integer) {
initialFrame = qtrue;
} else {
finalFrame = qtrue;
}
}
// try to get more audio data from the sound card...
if (initialFrame) {
S_MasterGain(Com_Clamp(0.0f, 1.0f, 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 packetSamples = (finalFrame) ? VOIP_MAX_FRAME_SAMPLES : VOIP_MAX_PACKET_SAMPLES;
// enough data buffered in audio hardware to process yet?
if (samples >= packetSamples) {
// audio capture is always MONO16.
static int16_t sampbuffer[VOIP_MAX_PACKET_SAMPLES];
float voipPower = 0.0f;
int voipFrames;
int i, bytes;
if (samples > VOIP_MAX_PACKET_SAMPLES)
samples = VOIP_MAX_PACKET_SAMPLES;
// !!! FIXME: maybe separate recording from encoding, so voipPower
// !!! FIXME: updates faster than 4Hz?
samples -= samples % VOIP_MAX_FRAME_SAMPLES;
if (samples != 120 && samples != 240 && samples != 480 && samples != 960 && samples != 1920 && samples != 2880 ) {
Com_Printf("Voip: bad number of samples %d\n", samples);
return;
}
voipFrames = samples / VOIP_MAX_FRAME_SAMPLES;
S_Capture(samples, (byte *) sampbuffer); // grab from audio card.
// check the "power" of this packet...
for (i = 0; i < samples; i++) {
const float flsamp = (float) sampbuffer[i];
const float s = fabs(flsamp);
voipPower += s * s;
sampbuffer[i] = (int16_t) ((flsamp) * audioMult);
}
// encode raw audio samples into Opus data...
bytes = opus_encode(clc.opusEncoder, sampbuffer, samples,
(unsigned char *) clc.voipOutgoingData,
sizeof (clc.voipOutgoingData));
if ( bytes <= 0 ) {
Com_DPrintf("VoIP: Error encoding %d samples\n", samples);
bytes = 0;
}
2008-06-25 05:50:44 +00:00
clc.voipPower = (voipPower / (32768.0f * 32768.0f *
((float) samples))) * 100.0f;
if ((useVad) && (clc.voipPower < cl_voipVADThreshold->value)) {
CL_VoipNewGeneration(); // no "talk" for at least 1/4 second.
} else {
clc.voipOutgoingDataSize = bytes;
clc.voipOutgoingDataFrames = voipFrames;
Com_DPrintf("VoIP: Send %d frames, %d bytes, %f power\n",
voipFrames, bytes, clc.voipPower);
#if 0
static FILE *encio = NULL;
if (encio == NULL) encio = fopen("voip-outgoing-encoded.bin", "wb");
if (encio != NULL) { fwrite(clc.voipOutgoingData, bytes, 1, encio); fflush(encio); }
static FILE *decio = NULL;
if (decio == NULL) decio = fopen("voip-outgoing-decoded.bin", "wb");
if (decio != NULL) { fwrite(sampbuffer, voipFrames * VOIP_MAX_FRAME_SAMPLES * 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
2005-08-26 17:39:27 +00:00
/*
=======================================================================
CLIENT RELIABLE COMMAND COMMUNICATION
=======================================================================
*/
/*
======================
CL_AddReliableCommand
2017-11-22 07:40:20 +00:00
The given command will be transmitted to the server, and is guaranteed to
2005-08-26 17:39:27 +00:00
not have future usercmd_t executed before it is executed
======================
*/
void CL_AddReliableCommand(const char *cmd, qboolean isDisconnectCmd)
{
int unacknowledged = clc.reliableSequence - clc.reliableAcknowledge;
2005-08-26 17:39:27 +00:00
// 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");
2005-08-26 17:39:27 +00:00
}
Q_strncpyz(clc.reliableCommands[++clc.reliableSequence & (MAX_RELIABLE_COMMANDS - 1)],
cmd, sizeof(*clc.reliableCommands));
2005-08-26 17:39:27 +00:00
}
/*
=======================================================================
CLIENT SIDE DEMO RECORDING
=======================================================================
*/
/*
====================
CL_WriteDemoMessage
Dumps the current net message, prefixed by the length
====================
*/
2005-08-26 17:39:27 +00:00
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 fileNameSize ) {
2005-08-26 17:39:27 +00:00
int a,b,c,d;
if(number < 0 || number > 9999)
number = 9999;
2005-08-26 17:39:27 +00:00
a = number / 1000;
number -= a*1000;
b = number / 100;
number -= b*100;
c = number / 10;
number -= c*10;
d = number;
Com_sprintf( fileName, fileNameSize, "demo%i%i%i%i"
2005-08-26 17:39:27 +00:00
, 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 ( clc.state != CA_ACTIVE ) {
2005-08-26 17:39:27 +00:00
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" ) ) {
2005-08-26 17:39:27 +00:00
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 ) );
#ifdef LEGACY_PROTOCOL
if(clc.compat)
#ifdef ELITEFORCE
Com_sprintf(name, sizeof(name), "demos/%s.efdemo", demoName);
#else
Com_sprintf(name, sizeof(name), "demos/%s.%s%d", demoName, DEMOEXT, com_legacyprotocol->integer);
#endif
else
#endif
Com_sprintf(name, sizeof(name), "demos/%s.%s%d", demoName, DEMOEXT, com_protocol->integer);
2005-08-26 17:39:27 +00:00
} else {
int number;
// scan for a free demo name
for ( number = 0 ; number <= 9999 ; number++ ) {
CL_DemoFilename( number, demoName, sizeof( demoName ) );
#ifdef LEGACY_PROTOCOL
if(clc.compat)
#ifdef ELITEFORCE
Com_sprintf(name, sizeof(name), "demos/%s.efdemo", demoName);
#else
Com_sprintf(name, sizeof(name), "demos/%s.%s%d", demoName, DEMOEXT, com_legacyprotocol->integer);
#endif
else
#endif
Com_sprintf(name, sizeof(name), "demos/%s.%s%d", demoName, DEMOEXT, com_protocol->integer);
2005-08-26 17:39:27 +00:00
if (!FS_FileExists(name))
2005-08-26 17:39:27 +00:00
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
#ifdef ELITEFORCE
if(clc.compat)
{
MSG_InitOOB(&buf, bufData, sizeof(bufData));
buf.compat = qtrue;
}
else
{
#endif
MSG_Init (&buf, bufData, sizeof(bufData));
MSG_Bitstream(&buf);
2005-08-26 17:39:27 +00:00
// NOTE, MRE: all server->client messages now acknowledge
MSG_WriteLong( &buf, clc.reliableSequence );
#ifdef ELITEFORCE
}
#endif
2005-08-26 17:39:27 +00:00
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 );
}
#ifdef ELITEFORCE
if(buf.compat)
MSG_WriteByte(&buf, 0);
else
#endif
MSG_WriteByte( &buf, svc_EOF );
2005-08-26 17:39:27 +00:00
// finished writing the gamestate stuff
#ifdef ELITEFORCE
if(!buf.compat)
{
#endif
// write the client num
MSG_WriteLong(&buf, clc.clientNum);
2005-08-26 17:39:27 +00:00
// write the checksum feed
MSG_WriteLong(&buf, clc.checksumFeed);
// finished writing the client packet
MSG_WriteByte( &buf, svc_EOF );
#ifdef ELITEFORCE
}
#endif
2005-08-26 17:39:27 +00:00
// 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 );
}
2005-08-26 17:39:27 +00:00
/*
=================
CL_DemoCompleted
=================
*/
void CL_DemoCompleted( void )
{
char buffer[ MAX_STRING_CHARS ];
if( cl_timedemo && cl_timedemo->integer )
{
2005-08-26 17:39:27 +00:00
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 );
}
}
2005-08-26 17:39:27 +00:00
}
}
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
#ifdef ELITEFORCE
if(clc.compat)
{
MSG_InitOOB(&buf, bufData, sizeof(bufData));
buf.compat = qtrue;
}
else
#endif
MSG_Init( &buf, bufData, sizeof( bufData ) );
2005-08-26 17:39:27 +00:00
// 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 int CL_WalkDemoExt(char *arg, char *name, int *demofile)
2005-08-26 17:39:27 +00:00
{
int i = 0;
*demofile = 0;
#ifdef LEGACY_PROTOCOL
if(com_legacyprotocol->integer > 0)
{
#ifdef ELITEFORCE
Com_sprintf(name, MAX_OSPATH, "demos/%s.efdemo", arg);
#else
Com_sprintf(name, MAX_OSPATH, "demos/%s.%s%d", arg, DEMOEXT, com_legacyprotocol->integer);
#endif
FS_FOpenFileRead(name, demofile, qtrue);
if (*demofile)
{
Com_Printf("Demo file: %s\n", name);
return com_legacyprotocol->integer;
}
}
if(com_protocol->integer != com_legacyprotocol->integer)
#endif
{
Com_sprintf(name, MAX_OSPATH, "demos/%s.%s%d", arg, DEMOEXT, com_protocol->integer);
FS_FOpenFileRead(name, demofile, qtrue);
if (*demofile)
{
Com_Printf("Demo file: %s\n", name);
return com_protocol->integer;
}
}
Com_Printf("Not found: %s\n", name);
2005-08-26 17:39:27 +00:00
while(demo_protocols[i])
{
#ifdef LEGACY_PROTOCOL
if(demo_protocols[i] == com_legacyprotocol->integer)
continue;
#endif
if(demo_protocols[i] == com_protocol->integer)
continue;
Com_sprintf (name, MAX_OSPATH, "demos/%s.%s%d", arg, DEMOEXT, demo_protocols[i]);
2005-08-26 17:39:27 +00:00
FS_FOpenFileRead( name, demofile, qtrue );
if (*demofile)
{
Com_Printf("Demo file: %s\n", name);
return demo_protocols[i];
2005-08-26 17:39:27 +00:00
}
else
Com_Printf("Not found: %s\n", name);
i++;
}
return -1;
2005-08-26 17:39:27 +00:00
}
/*
====================
CL_CompleteDemoName
====================
*/
static void CL_CompleteDemoName( char *args, int argNum )
{
if( argNum == 2 )
{
char demoExt[ 16 ];
Com_sprintf(demoExt, sizeof(demoExt), ".%s%d", DEMOEXT, com_protocol->integer);
Field_CompleteFilename( "demos", demoExt, qtrue, qtrue );
}
}
2005-08-26 17:39:27 +00:00
/*
====================
CL_PlayDemo_f
demo <demoname>
====================
*/
void CL_PlayDemo_f( void ) {
char name[MAX_OSPATH];
char arg[MAX_OSPATH];
char *ext_test;
2005-08-26 17:39:27 +00:00
int protocol, i;
char retry[MAX_OSPATH];
if (Cmd_Argc() != 2) {
Com_Printf ("demo <demoname>\n");
2005-08-26 17:39:27 +00:00
return;
}
// make sure a local server is killed
// 2 means don't force disconnect of local client
Cvar_Set( "sv_killserver", "2" );
2005-08-26 17:39:27 +00:00
// open the demo file
Q_strncpyz( arg, Cmd_Argv(1), sizeof( arg ) );
CL_Disconnect( qtrue );
#ifdef ELITEFORCE
ext_test = arg + strlen(arg) - 7;
if(!strcmp(ext_test, ".efdemo"))
{
Com_sprintf (name, sizeof(name), "demos/%s", arg);
FS_FOpenFileRead(name, &clc.demofile, qtrue);
protocol = com_legacyprotocol->integer;
}
else
#endif
{
// check for an extension .DEMOEXT_?? (?? is protocol)
ext_test = strrchr(arg, '.');
if(ext_test && !Q_stricmpn(ext_test + 1, DEMOEXT, ARRAY_LEN(DEMOEXT) - 1))
2005-08-26 17:39:27 +00:00
{
protocol = atoi(ext_test + ARRAY_LEN(DEMOEXT));
for(i = 0; demo_protocols[i]; i++)
2005-08-26 17:39:27 +00:00
{
if(demo_protocols[i] == protocol)
2005-08-26 17:39:27 +00:00
break;
}
if(demo_protocols[i] || protocol == com_protocol->integer
#ifdef LEGACY_PROTOCOL
|| protocol == com_legacyprotocol->integer
#endif
)
2005-08-26 17:39:27 +00:00
{
Com_sprintf(name, sizeof(name), "demos/%s", arg);
FS_FOpenFileRead(name, &clc.demofile, qtrue);
}
else
{
int len;
2005-08-26 17:39:27 +00:00
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';
protocol = CL_WalkDemoExt(retry, name, &clc.demofile);
2005-08-26 17:39:27 +00:00
}
}
else
protocol = CL_WalkDemoExt(arg, name, &clc.demofile);
}
2005-08-26 17:39:27 +00:00
if (!clc.demofile) {
Com_Error( ERR_DROP, "couldn't open %s", name);
return;
}
Q_strncpyz( clc.demoName, arg, sizeof( clc.demoName ) );
2005-08-26 17:39:27 +00:00
Con_Close();
clc.state = CA_CONNECTED;
2005-08-26 17:39:27 +00:00
clc.demoplaying = qtrue;
Q_strncpyz( clc.servername, arg, sizeof( clc.servername ) );
2005-08-26 17:39:27 +00:00
#ifdef LEGACY_PROTOCOL
if(protocol <= com_legacyprotocol->integer)
clc.compat = qtrue;
else
clc.compat = qfalse;
#endif
2005-08-26 17:39:27 +00:00
// read demo messages until connected
while ( clc.state >= CA_CONNECTED && clc.state < CA_PRIMED ) {
2005-08-26 17:39:27 +00:00
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 );
2005-08-26 17:39:27 +00:00
}
/*
==================
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_ShutdownAll
=====================
*/
void CL_ShutdownAll(qboolean shutdownRef)
{
if(CL_VideoRecording())
CL_CloseAVI();
if(clc.demorecording)
CL_StopRecord_f();
2005-08-26 17:39:27 +00:00
#ifdef USE_CURL
CL_cURL_Shutdown();
#endif
2005-08-26 17:39:27 +00:00
// clear sounds
S_DisableSounds();
// shutdown CGame
CL_ShutdownCGame();
// shutdown UI
CL_ShutdownUI();
// shutdown the renderer
if(shutdownRef)
CL_ShutdownRef();
else if(re.Shutdown)
re.Shutdown(qfalse); // don't destroy window or context
2005-08-26 17:39:27 +00:00
cls.uiStarted = qfalse;
cls.cgameStarted = qfalse;
cls.rendererStarted = qfalse;
cls.soundRegistered = qfalse;
}
/*
=================
CL_ClearMemory
2005-08-26 17:39:27 +00:00
Called by Com_GameRestart
2005-08-26 17:39:27 +00:00
=================
*/
void CL_ClearMemory(qboolean shutdownRef)
{
2005-08-26 17:39:27 +00:00
// shutdown all the client stuff
CL_ShutdownAll(shutdownRef);
2005-08-26 17:39:27 +00:00
// if not running a server clear the whole hunk
if ( !com_sv_running || !com_sv_running->integer ) {
2005-08-26 17:39:27 +00:00
// clear the whole hunk
Hunk_Clear();
// clear collision map data
CM_ClearMap();
}
else {
// clear all the client data on the hunk
Hunk_ClearToMark();
}
}
2005-08-26 17:39:27 +00:00
/*
=================
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)
{
CL_ClearMemory(qfalse);
CL_StartHunkUsers(qfalse);
2005-08-26 17:39:27 +00:00
}
/*
=====================
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 ) {
clc.state = CA_DISCONNECTED;
Key_SetCatcher( KEYCATCH_CONSOLE );
return;
}
2005-08-26 17:39:27 +00:00
if ( !com_cl_running->integer ) {
return;
}
Con_Close();
Key_SetCatcher( 0 );
2005-08-26 17:39:27 +00:00
// if we are already connected to the local host, stay connected
if ( clc.state >= CA_CONNECTED && !Q_stricmp( clc.servername, "localhost" ) ) {
clc.state = CA_CONNECTED; // so the connect screen is drawn
2005-08-26 17:39:27 +00:00
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 {
// clear nextmap so the cinematic shutdown doesn't execute it
Cvar_Set( "nextmap", "" );
CL_Disconnect( qtrue );
Q_strncpyz( clc.servername, "localhost", sizeof(clc.servername) );
clc.state = CA_CHALLENGING; // so the connect screen is drawn
Key_SetCatcher( 0 );
2005-08-26 17:39:27 +00:00
SCR_UpdateScreen();
clc.connectTime = -RETRANSMIT_TIMEOUT;
NET_StringToAdr( clc.servername, &clc.serverAddress, NA_UNSPEC);
2005-08-26 17:39:27 +00:00
// 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 ) );
}
static void CL_OldGame(void)
{
if(cl_oldGameSet)
{
// change back to previous fs_game
cl_oldGameSet = qfalse;
Cvar_Set2("fs_game", cl_oldGame, qtrue);
FS_ConditionalRestart(clc.checksumFeed, qfalse);
}
}
2005-08-26 17:39:27 +00:00
/*
=====================
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;
}
// 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.voipCodecInitialized) {
int i;
opus_encoder_destroy(clc.opusEncoder);
for (i = 0; i < MAX_CLIENTS; i++) {
opus_decoder_destroy(clc.opusDecoder[i]);
}
clc.voipCodecInitialized = qfalse;
}
Cmd_RemoveCommand ("voip");
#endif
2005-08-26 17:39:27 +00:00
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 ( clc.state >= CA_CONNECTED ) {
CL_AddReliableCommand("disconnect", qtrue);
2005-08-26 17:39:27 +00:00
CL_WritePacket();
CL_WritePacket();
CL_WritePacket();
}
// Remove pure paks
FS_PureServerSetLoadedPaks("", "");
FS_PureServerSetReferencedPaks( "", "" );
2005-08-26 17:39:27 +00:00
CL_ClearState ();
// wipe the client connection
Com_Memset( &clc, 0, sizeof( clc ) );
clc.state = CA_DISCONNECTED;
2005-08-26 17:39:27 +00:00
// 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.
clc.voipEnabled = qfalse;
#endif
// Stop recording any video
if( CL_VideoRecording( ) ) {
// Finish rendering current frame
SCR_UpdateScreen( );
CL_CloseAVI( );
}
CL_UpdateGUID( NULL, 0 );
if(!noGameRestart)
CL_OldGame();
else
noGameRestart = qfalse;
2005-08-26 17:39:27 +00:00
}
/*
===================
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 || clc.state < CA_CONNECTED || cmd[0] == '+' ) {
Com_Printf ("Unknown command \"%s" S_COLOR_WHITE "\"\n", cmd);
2005-08-26 17:39:27 +00:00
return;
}
if ( Cmd_Argc() > 1 ) {
CL_AddReliableCommand(string, qfalse);
2005-08-26 17:39:27 +00:00
} else {
CL_AddReliableCommand(cmd, qfalse);
2005-08-26 17:39:27 +00:00
}
}
/*
===================
CL_RequestMotd
===================
*/
void CL_RequestMotd( void ) {
#ifdef UPDATE_SERVER_NAME
2005-08-26 17:39:27 +00:00
char info[MAX_INFO_STRING];
if ( !cl_motd->integer ) {
return;
}
Com_Printf( "Resolving %s\n", UPDATE_SERVER_NAME );
if ( !NET_StringToAdr( UPDATE_SERVER_NAME, &cls.updateServer, NA_IP ) ) {
2005-08-26 17:39:27 +00:00
Com_Printf( "Couldn't resolve address\n" );
return;
}
cls.updateServer.port = BigShort( PORT_UPDATE );
Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", UPDATE_SERVER_NAME,
cls.updateServer.ip[0], cls.updateServer.ip[1],
cls.updateServer.ip[2], cls.updateServer.ip[3],
BigShort( cls.updateServer.port ) );
info[0] = 0;
Com_sprintf( cls.updateChallenge, sizeof( cls.updateChallenge ), "%i", (int)((((unsigned int)rand() << 16) ^ (unsigned int)rand()) ^ Com_Milliseconds()));
2005-08-26 17:39:27 +00:00
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\"\n", info );
#endif
2005-08-26 17:39:27 +00:00
}
/*
===================
CL_RequestAuthorization
Authorization server protocol
-----------------------------
All commands are text in Q3 out of band packets (leading 0xff 0xff 0xff 0xff).
Whenever the client tries to get a challenge from the server it wants to
connect to, it also blindly fires off a packet to the authorize server:
getKeyAuthorize <challenge> <cdkey>
cdkey may be "demo"
#OLD The authorize server returns a:
#OLD
#OLD keyAthorize <challenge> <accept | deny>
#OLD
#OLD A client will be accepted if the cdkey is valid and it has not been used by any other IP
#OLD address in the last 15 minutes.
The server sends a:
getIpAuthorize <challenge> <ip>
The authorize server returns a:
ipAuthorize <challenge> <accept | deny | demo | unknown >
A client will be accepted if a valid cdkey was sent by that ip (only) in the last 15 minutes.
If no response is received from the authorize server after two tries, the client will be let
in anyway.
===================
*/
#ifndef STANDALONE
2005-08-26 17:39:27 +00:00
void CL_RequestAuthorization( void ) {
char nums[64];
int i, j, l;
cvar_t *fs;
if ( !cls.authorizeServer.port ) {
Com_Printf( "Resolving %s\n", AUTHORIZE_SERVER_NAME );
if ( !NET_StringToAdr( AUTHORIZE_SERVER_NAME, &cls.authorizeServer, NA_IP ) ) {
2005-08-26 17:39:27 +00:00
Com_Printf( "Couldn't resolve address\n" );
return;
}
cls.authorizeServer.port = BigShort( PORT_AUTHORIZE );
Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", AUTHORIZE_SERVER_NAME,
cls.authorizeServer.ip[0], cls.authorizeServer.ip[1],
cls.authorizeServer.ip[2], cls.authorizeServer.ip[3],
BigShort( cls.authorizeServer.port ) );
}
if ( cls.authorizeServer.type == NA_BAD ) {
return;
}
// only grab the alphanumeric values from the cdkey, to avoid any dashes or spaces
j = 0;
l = strlen( cl_cdkey );
if ( l > 32 ) {
l = 32;
}
for ( i = 0 ; i < l ; i++ ) {
if ( ( cl_cdkey[i] >= '0' && cl_cdkey[i] <= '9' )
2005-08-26 17:39:27 +00:00
|| ( cl_cdkey[i] >= 'a' && cl_cdkey[i] <= 'z' )
|| ( cl_cdkey[i] >= 'A' && cl_cdkey[i] <= 'Z' )
) {
nums[j] = cl_cdkey[i];
j++;
2005-08-26 17:39:27 +00:00
}
}
nums[j] = 0;
2005-08-26 17:39:27 +00:00
fs = Cvar_Get ("cl_anonymous", "0", CVAR_INIT|CVAR_SYSTEMINFO );
NET_OutOfBandPrint(NS_CLIENT, cls.authorizeServer, "getKeyAuthorize %i %s", fs->integer, nums );
2005-08-26 17:39:27 +00:00
}
#endif
2005-08-26 17:39:27 +00:00
/*
======================================================================
CONSOLE COMMANDS
======================================================================
*/
/*
==================
CL_ForwardToServer_f
==================
*/
void CL_ForwardToServer_f( void ) {
if ( clc.state != CA_ACTIVE || clc.demoplaying ) {
2005-08-26 17:39:27 +00:00
Com_Printf ("Not connected to a server.\n");
return;
}
// don't forward the first argument
if ( Cmd_Argc() > 1 ) {
CL_AddReliableCommand(Cmd_Args(), qfalse);
2005-08-26 17:39:27 +00:00
}
}
/*
==================
CL_Disconnect_f
==================
*/
void CL_Disconnect_f( void ) {
SCR_StopCinematic();
Cvar_Set("ui_singlePlayerActive", "0");
if ( clc.state != CA_DISCONNECTED && clc.state != CA_CINEMATIC ) {
2005-08-26 17:39:27 +00:00
Com_Error (ERR_DISCONNECT, "Disconnected from server");
}
}
/*
================
CL_Reconnect_f
================
*/
void CL_Reconnect_f( void ) {
if ( !strlen( cl_reconnectArgs ) )
return;
2005-08-26 17:39:27 +00:00
Cvar_Set("ui_singlePlayerActive", "0");
Cbuf_AddText( va("connect %s\n", cl_reconnectArgs ) );
2005-08-26 17:39:27 +00:00
}
/*
================
CL_Connect_f
================
*/
void CL_Connect_f( void ) {
char server[MAX_OSPATH];
const char *serverString;
int argc = Cmd_Argc();
netadrtype_t family = NA_UNSPEC;
2005-08-26 17:39:27 +00:00
if ( argc != 2 && argc != 3 ) {
Com_Printf( "usage: connect [-4|-6] server\n");
2005-08-26 17:39:27 +00:00
return;
}
if(argc == 2)
Q_strncpyz( server, Cmd_Argv(1), sizeof( server ) );
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");
Q_strncpyz( server, Cmd_Argv(2), sizeof( server ) );
}
2005-08-26 17:39:27 +00:00
// save arguments for reconnect
Q_strncpyz( cl_reconnectArgs, Cmd_Args(), sizeof( cl_reconnectArgs ) );
2005-08-26 17:39:27 +00:00
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" );
2005-08-26 17:39:27 +00:00
}
// make sure a local server is killed
Cvar_Set( "sv_killserver", "1" );
SV_Frame( 0 );
noGameRestart = qtrue;
2005-08-26 17:39:27 +00:00
CL_Disconnect( qtrue );
Con_Close();
Q_strncpyz( clc.servername, server, sizeof(clc.servername) );
2005-08-26 17:39:27 +00:00
if (!NET_StringToAdr(clc.servername, &clc.serverAddress, family) ) {
2005-08-26 17:39:27 +00:00
Com_Printf ("Bad server address\n");
clc.state = CA_DISCONNECTED;
2005-08-26 17:39:27 +00:00
return;
}
if (clc.serverAddress.port == 0) {
clc.serverAddress.port = BigShort( PORT_SERVER );
}
serverString = NET_AdrToStringwPort(clc.serverAddress);
Com_Printf( "%s resolved to %s\n", clc.servername, serverString);
if( cl_guidServerUniq->integer )
CL_UpdateGUID( serverString, strlen( serverString ) );
else
CL_UpdateGUID( NULL, 0 );
2005-08-26 17:39:27 +00:00
// if we aren't playing on a lan, we need to authenticate
// with the cd key
if(NET_IsLocalAddress(clc.serverAddress))
clc.state = CA_CHALLENGING;
else
{
clc.state = CA_CONNECTING;
// Set a client challenge number that ideally is mirrored back by the server.
clc.challenge = (((unsigned int)rand() << 16) ^ (unsigned int)rand()) ^ Com_Milliseconds();
2005-08-26 17:39:27 +00:00
}
Key_SetCatcher( 0 );
2005-08-26 17:39:27 +00:00
clc.connectTime = -99999; // CL_CheckForResend() will fire immediately
clc.connectPacketCount = 0;
// server connection string
Cvar_Set( "cl_currentServerAddress", server );
}
#define MAX_RCON_MESSAGE 1024
2005-08-26 17:39:27 +00:00
/*
==================
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 );
}
}
2014-07-17 22:15:05 +00:00
/*
==================
CL_CompletePlayerName
==================
*/
static void CL_CompletePlayerName( char *args, int argNum )
{
if( argNum == 2 )
{
char names[MAX_CLIENTS][MAX_NAME_LENGTH];
const char *namesPtr[MAX_CLIENTS];
2014-07-17 22:15:05 +00:00
int i;
int clientCount;
int nameCount;
const char *info;
const char *name;
//configstring
info = cl.gameState.stringData + cl.gameState.stringOffsets[CS_SERVERINFO];
clientCount = atoi( Info_ValueForKey( info, "sv_maxclients" ) );
nameCount = 0;
for( i = 0; i < clientCount; i++ ) {
if( i == clc.clientNum )
continue;
info = cl.gameState.stringData + cl.gameState.stringOffsets[CS_PLAYERS+i];
name = Info_ValueForKey( info, "n" );
if( name[0] == '\0' )
continue;
Q_strncpyz( names[nameCount], name, sizeof(names[nameCount]) );
Q_CleanStr( names[nameCount] );
namesPtr[nameCount] = names[nameCount];
nameCount++;
}
qsort( (void*)namesPtr, nameCount, sizeof( namesPtr[0] ), Com_strCompare );
Field_CompletePlayerName( namesPtr, nameCount );
}
}
2005-08-26 17:39:27 +00:00
/*
=====================
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];
2005-08-26 17:39:27 +00:00
netadr_t to;
if ( !rcon_client_password->string[0] ) {
2005-08-26 17:39:27 +00:00
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 ");
2005-08-26 17:39:27 +00:00
Q_strcat (message, MAX_RCON_MESSAGE, rcon_client_password->string);
Q_strcat (message, MAX_RCON_MESSAGE, " ");
2005-08-26 17:39:27 +00:00
// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=543
Q_strcat (message, MAX_RCON_MESSAGE, Cmd_Cmd()+5);
2005-08-26 17:39:27 +00:00
if ( clc.state >= CA_CONNECTED ) {
2005-08-26 17:39:27 +00:00
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);
2005-08-26 17:39:27 +00:00
if (to.port == 0) {
to.port = BigShort (PORT_SERVER);
}
}
NET_SendPacket (NS_CLIENT, strlen(message)+1, message, to);
cls.rconAddress = to;
2005-08-26 17:39:27 +00:00
}
/*
=================
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
#ifdef ELITEFORCE
if(clc.compat)
Com_sprintf(cMsg, sizeof(cMsg), "cp %s", FS_ReferencedPakPureChecksums());
else
#endif
Com_sprintf(cMsg, sizeof(cMsg), "cp %d %s", cl.serverId, FS_ReferencedPakPureChecksums());
2008-03-25 21:59:31 +00:00
CL_AddReliableCommand(cMsg, qfalse);
2005-08-26 17:39:27 +00:00
}
/*
=================
CL_ResetPureClientAtServer
=================
*/
void CL_ResetPureClientAtServer( void ) {
2009-10-12 03:21:02 +00:00
CL_AddReliableCommand("vdr", qfalse);
2005-08-26 17:39:27 +00:00
}
/*
=================
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();
2005-08-26 17:39:27 +00:00
// don't let them loop during the restart
S_StopAllSounds();
if(!FS_ConditionalRestart(clc.checksumFeed, qtrue))
{
// if not running a server clear the whole hunk
if(com_sv_running->integer)
{
// clear all the client data on the hunk
Hunk_ClearToMark();
}
else
{
// clear the whole hunk
Hunk_Clear();
}
// shutdown the UI
CL_ShutdownUI();
// shutdown the CGame
CL_ShutdownCGame();
// shutdown the renderer and clear the renderer interface
CL_ShutdownRef();
2017-11-22 07:40:20 +00:00
// client is no longer pure until 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
cls.rendererStarted = qfalse;
cls.uiStarted = qfalse;
cls.cgameStarted = qfalse;
cls.soundRegistered = qfalse;
2017-11-22 07:40:20 +00:00
// unpause so the cgame definitely gets a snapshot and renders a frame
Cvar_Set("cl_paused", "0");
// initialize the renderer interface
CL_InitRef();
// startup all the client stuff
CL_StartHunkUsers(qfalse);
// start the cgame if connected
if(clc.state > CA_CONNECTED && clc.state != CA_CINEMATIC)
{
cls.cgameStarted = qtrue;
CL_InitCGame();
// send pure checksums
CL_SendPureChecksums();
}
2005-08-26 17:39:27 +00:00
}
}
/*
=================
CL_Snd_Restart
2005-08-26 17:39:27 +00:00
Restart the sound subsystem
=================
*/
void CL_Snd_Shutdown(void)
{
2005-08-26 17:39:27 +00:00
S_Shutdown();
cls.soundStarted = qfalse;
}
/*
=================
CL_Snd_Restart_f
2005-08-26 17:39:27 +00:00
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_Shutdown();
// sound will be reinitialized by vid_restart
2005-08-26 17:39:27 +00:00
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 ( clc.state != CA_ACTIVE ) {
2005-08-26 17:39:27 +00:00
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", clc.state );
Com_Printf( "Server: %s\n", clc.servername );
2005-08-26 17:39:27 +00:00
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 ) {
#ifdef USE_CURL
// if we downloaded with cURL
if(clc.cURLUsed) {
clc.cURLUsed = qfalse;
CL_cURL_Shutdown();
if( clc.cURLDisconnected ) {
if(clc.downloadRestart) {
FS_Restart(clc.checksumFeed);
clc.downloadRestart = qfalse;
}
clc.cURLDisconnected = qfalse;
CL_Reconnect_f();
return;
}
}
#endif
2005-08-26 17:39:27 +00:00
// 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);
2005-08-26 17:39:27 +00:00
// 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
clc.state = CA_LOADING;
2005-08-26 17:39:27 +00:00
// 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 ( clc.state != CA_LOADING ) {
2005-08-26 17:39:27 +00:00
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;
CL_AddReliableCommand(va("download %s", remoteName), qfalse);
2005-08-26 17:39:27 +00:00
}
/*
=================
CL_NextDownload
A download completed or failed
=================
*/
void CL_NextDownload(void)
{
2005-08-26 17:39:27 +00:00
char *s;
char *remoteName, *localName;
qboolean useCURL = qfalse;
2005-08-26 17:39:27 +00:00
// A download has finished, check whether this matches a referenced checksum
if(*clc.downloadName)
{
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", "");
2005-08-26 17:39:27 +00:00
// We are looking to start a download here
if (*clc.downloadList) {
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_NO_REDIRECT)) {
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) {
if((cl_allowDownload->integer & DLF_NO_UDP)) {
Com_Error(ERR_DROP, "UDP Downloads are "
"disabled on your client. "
"(cl_allowDownload is %d)",
cl_allowDownload->integer);
return;
}
else {
CL_BeginDownload( localName, remoteName );
}
}
2005-08-26 17:39:27 +00:00
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) {
char missingfiles[1024];
if ( !(cl_allowDownload->integer & DLF_ENABLE) )
2005-08-26 17:39:27 +00:00
{
// autodownload is disabled on the client
// but it's possible that some referenced files on the server are missing
if (FS_ComparePaks( missingfiles, sizeof( missingfiles ), qfalse ) )
{
// NOTE TTimo I would rather have that printed as a modal message box
// but at this point while joining the game we don't know wether we will successfully join or not
Com_Printf( "\nWARNING: You are missing some files referenced by the server:\n%s"
"You might not be able to join the game\n"
"Go to the setting menu to turn on autodownload, or get the file elsewhere\n\n", missingfiles );
}
}
else if ( FS_ComparePaks( clc.downloadList, sizeof( clc.downloadList ) , qtrue ) ) {
Com_Printf("Need paks: %s\n", clc.downloadList );
if ( *clc.downloadList ) {
// if autodownloading is not enabled on the server
clc.state = CA_CONNECTED;
*clc.downloadTempName = *clc.downloadName = 0;
Cvar_Set( "cl_downloadName", "" );
2005-08-26 17:39:27 +00:00
CL_NextDownload();
return;
}
}
CL_DownloadsComplete();
}
/*
=================
CL_CheckForResend
Resend a connect message if the last one has timed out
=================
*/
void CL_CheckForResend( void ) {
int port;
2005-08-26 17:39:27 +00:00
char info[MAX_INFO_STRING];
char data[MAX_INFO_STRING + 10];
2005-08-26 17:39:27 +00:00
// don't send anything if playing back a demo
if ( clc.demoplaying ) {
return;
}
// resend if we haven't gotten a reply yet
if ( clc.state != CA_CONNECTING && clc.state != CA_CHALLENGING ) {
2005-08-26 17:39:27 +00:00
return;
}
if ( cls.realtime - clc.connectTime < RETRANSMIT_TIMEOUT ) {
return;
}
clc.connectTime = cls.realtime; // for retransmit requests
clc.connectPacketCount++;
switch ( clc.state ) {
2005-08-26 17:39:27 +00:00
case CA_CONNECTING:
// requesting a challenge .. IPv6 users always get in as authorize server supports no ipv6.
#ifndef STANDALONE
if (!com_standalone->integer && clc.serverAddress.type == NA_IP && !Sys_IsLANAddress( clc.serverAddress ) )
2005-08-26 17:39:27 +00:00
CL_RequestAuthorization();
#endif
// The challenge request shall be followed by a client challenge so no malicious server can hijack this connection.
// Add the gamename so the server knows we're running the correct game or can reject the client
// with a meaningful message
Com_sprintf(data, sizeof(data), "getchallenge %d %s", clc.challenge, com_gamename->string);
NET_OutOfBandPrint(NS_CLIENT, clc.serverAddress, "%s", data);
2005-08-26 17:39:27 +00:00
break;
case CA_CHALLENGING:
// sending back the challenge
port = Cvar_VariableValue ("net_qport");
Q_strncpyz( info, Cvar_InfoString( CVAR_USERINFO ), sizeof( info ) );
#ifdef LEGACY_PROTOCOL
if(com_legacyprotocol->integer == com_protocol->integer)
clc.compat = qtrue;
if(clc.compat)
Info_SetValueForKey(info, "protocol", va("%i", com_legacyprotocol->integer));
else
#endif
Info_SetValueForKey(info, "protocol", va("%i", com_protocol->integer));
2005-08-26 17:39:27 +00:00
Info_SetValueForKey( info, "qport", va("%i", port ) );
Info_SetValueForKey( info, "challenge", va("%i", clc.challenge ) );
Com_sprintf( data, sizeof(data), "connect \"%s\"", info );
#ifdef ELITEFORCE
NET_OutOfBandPrint( NS_CLIENT, clc.serverAddress, "%s", data);
#else
NET_OutOfBandData( NS_CLIENT, clc.serverAddress, (byte *) data, strlen ( data ) );
#endif
2005-08-26 17:39:27 +00:00
// 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 clc.state" );
2005-08-26 17:39:27 +00:00
}
}
/*
===================
CL_MotdPacket
===================
*/
void CL_MotdPacket( netadr_t from ) {
#ifdef UPDATE_SERVER_NAME
2005-08-26 17:39:27 +00:00
char *challenge;
char *info;
// if not from our server, ignore it
if ( !NET_CompareAdr( from, cls.updateServer ) ) {
return;
}
info = Cmd_Argv(1);
// check challenge
challenge = Info_ValueForKey( info, "challenge" );
if ( strcmp( challenge, cls.updateChallenge ) ) {
return;
}
challenge = Info_ValueForKey( info, "motd" );
Q_strncpyz( cls.updateInfoString, info, sizeof( cls.updateInfoString ) );
Cvar_Set( "cl_motdString", challenge );
#endif
2005-08-26 17:39:27 +00:00
}
/*
===================
CL_InitServerInfo
===================
*/
void CL_InitServerInfo( serverInfo_t *server, netadr_t *address ) {
server->adr = *address;
2005-08-26 17:39:27 +00:00
server->clients = 0;
server->hostName[0] = '\0';
server->mapName[0] = '\0';
server->maxClients = 0;
server->maxPing = 0;
server->minPing = 0;
server->ping = -1;
server->game[0] = '\0';
server->gameType = 0;
server->netType = 0;
server->punkbuster = 0;
server->g_humanplayers = 0;
server->g_needpass = 0;
2005-08-26 17:39:27 +00:00
}
#define MAX_SERVERSPERPACKET 256
/*
===================
CL_ServersResponsePacket
===================
*/
void CL_ServersResponsePacket( const netadr_t* from, msg_t *msg, qboolean extended ) {
int i, j, count, total;
netadr_t addresses[MAX_SERVERSPERPACKET];
2005-08-26 17:39:27 +00:00
int numservers;
byte* buffptr;
byte* buffend;
#ifdef ELITEFORCE
char strbyte[3] = "FF";
#endif
2005-08-26 17:39:27 +00:00
Com_Printf("CL_ServersResponsePacket from %s\n", NET_AdrToStringwPort(*from));
2005-08-26 17:39:27 +00:00
if (cls.numglobalservers == -1) {
// state to detect lack of servers or lack of response
cls.numglobalservers = 0;
cls.numGlobalServerAddresses = 0;
}
// parse through server response string
numservers = 0;
buffptr = msg->data;
buffend = buffptr + msg->cursize;
// advance to initial token
do
{
if(*buffptr == '\\' || (extended && *buffptr == '/'))
break;
buffptr++;
} while (buffptr < buffend);
while (buffptr + 1 < buffend)
{
// IPv4 address
if (*buffptr == '\\')
{
buffptr++;
#ifdef ELITEFORCE
if (buffend - buffptr < sizeof(addresses[numservers].ip) * 2 + sizeof(addresses[numservers].port) * 2 + 1)
break;
// EliteForce uses a slightly different format with bytes encoded
// in hex values.
for(i = 0; i < 6; i++)
{
strbyte[0] = toupper(*buffptr++);
strbyte[1] = toupper(*buffptr++);
2005-08-26 17:39:27 +00:00
if(i < 4)
addresses[numservers].ip[i] = strtoul(strbyte, NULL, 16);
else
((unsigned char *) &addresses[numservers].port)[i - 4] =
strtoul(strbyte, NULL, 16);
}
#else
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++;
#endif
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;
#ifdef ELITEFORCE
// parse out port
addresses[numservers].port = (*buffptr++) << 8;
addresses[numservers].port += *buffptr++;
addresses[numservers].port = BigShort( addresses[numservers].port );
#endif
addresses[numservers].scope_id = from->scope_id;
2005-08-26 17:39:27 +00:00
}
else
// syntax error!
break;
#ifndef ELITEFORCE
2005-08-26 17:39:27 +00:00
// parse out port
addresses[numservers].port = (*buffptr++) << 8;
2005-08-26 17:39:27 +00:00
addresses[numservers].port += *buffptr++;
addresses[numservers].port = BigShort( addresses[numservers].port );
#endif
2005-08-26 17:39:27 +00:00
// syntax check
if (*buffptr != '\\' && *buffptr != '/')
2005-08-26 17:39:27 +00:00
break;
2005-08-26 17:39:27 +00:00
numservers++;
if (numservers >= MAX_SERVERSPERPACKET)
2005-08-26 17:39:27 +00:00
break;
}
count = cls.numglobalservers;
2005-08-26 17:39:27 +00:00
for (i = 0; i < numservers && count < MAX_GLOBAL_SERVERS; i++) {
2005-08-26 17:39:27 +00:00
// build net address
serverInfo_t *server = &cls.globalServers[count];
2005-08-26 17:39:27 +00:00
// Tequila: It's possible to have sent many master server requests. Then
// we may receive many times the same addresses from the master server.
// We just avoid to add a server if it is still in the global servers list.
for (j = 0; j < count; j++)
{
if (NET_CompareAdr(cls.globalServers[j].adr, addresses[i]))
break;
}
if (j < count)
continue;
2005-08-26 17:39:27 +00:00
CL_InitServerInfo( server, &addresses[i] );
// 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];
2005-08-26 17:39:27 +00:00
}
}
cls.numglobalservers = count;
total = count + cls.numGlobalServerAddresses;
2005-08-26 17:39:27 +00:00
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;
int challenge = 0;
2005-08-26 17:39:27 +00:00
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);
2005-08-26 17:39:27 +00:00
// challenge from the server we are connecting to
if (!Q_stricmp(c, "challengeResponse"))
{
char *strver;
int ver;
if (clc.state != CA_CONNECTING)
{
Batch of bug fixes for gamecode. Patch compiled and log message written by Tobias Kuehnhammer (#5144) ################################################################################ This Patch fixes: ################################################################################ - The "fraglimit warning" was not played at all, if on the blue team. - The "where" console command was broken. - Obelisk explosion wasn't drawn if no Rocketlauncher was loaded. - Impact marks sometimes didn't draw at all. - IMPORTANT BUGFIX: No killing for cheaters with Lightning gun and Gauntlet. - If two doors are close to each other a spectator couldn't fly through them. - More robust, efficient and logical respawning routine. NOTE: The game.qvm will get notable smaller and will use LESS MEMORY! - Drowning sounds are fixed. Now they are played as intended. (as the id comment in the source code shows). - Some AI bugs (OVERFLOW!) in the bot movement code. - Several "Team Arena" Overload and Harvester bugs. - Stops bots from attacking a team mate (player) who only changed teams. - Some voice chats and CTF commands fixed. - "Team_ReturnFlag" was called twice, which did wired things sometimes. NOTE: (G_RunItem checks CONTENTS_NODROP already!) - A bugfix for Gauntlet animation. - Incorrect CTF scoring. - A bunch of corrected comments and print lines ("\n"). - Some regularity of expression and some small trivial bugs. ################################################################################ Details: ################################################################################ ******************************************************************************** BUG: in gamemode GT_TEAM the fraglimit warning will not be played if joining the blue team! -------------------------------------------------------------------------------- Solution: In "CG_CheckLocalSounds": if cgs.scores2 > highScore, highScore should be cgs.scores2. ******************************************************************************** BUG: the "where" console command doesn't work as expected (it's always 0 0 0) but not in id Quake 3 Arena. It seems that now Ioquake3 is affected! -------------------------------------------------------------------------------- Solution: In Function "Cmd_Where_f" ent->s.origin should be ent->r.currentOrigin. ******************************************************************************** BUG: in gamemode GT_OBELISK obelisk explosion won't be drawn if there is no Rocketlauncher loaded. (The "maps without Rocketlauncher" bug) -------------------------------------------------------------------------------- Solution: in "cg_main.c": cgs.media.rocketExplosionShader should be registered if gamemode is GT_OBELISK. ******************************************************************************** BUG: Impact marks sometimes doesn't draw at all. Not easy to reproduce if you don't play (io)Quake3 every day and know the places where it happens! ;) But anyway... Test: start q3dm12 go to "Long Jump Canyon" (where the small platform teleporter for the BFG is) place yourself at the point where the railgun spawns, look in the direction where the red suspended armor is. Now shoot at the sloped wall on the out/leftside of the door you see. (the sloped wall should be nearly in the center of your screen now). If you choose the correct brush face and shoot up and down at this brush face, the impact marks sometimes aren't visible. There are hundreds of custom maps where this can happen! -------------------------------------------------------------------------------- Solution: I replaced the function "SnapVectorTowards" with the one from "Wolfenstein - Enemy Territory (GPL Source Code)" ******************************************************************************** BUG: Normally "NOCLIP" cheaters are logically not allowed to fire a gun. Unfortunatly the Gauntlet (and Lightning gun) was forgotten and not restricted to that. All weapons except those two were handled correct. -------------------------------------------------------------------------------- Solution: Make Gauntlet and Lightning gun not firing for someone who cheats with "NOCLIP" (like all other weapons). ******************************************************************************** NOTE: A few bugfixes are not mine and are reported here: http://www.quake3world.com/forum/viewtopic.php?f=16&t=9179. Thanks to Quake3world, for all those years and the good guys there! ******************************************************************************** BUG: During making a mod I found a very strange bug, which mainly occurs if someone tries to implement a lot of singleplayer monsters which should walk slowly (like the "Crash" bot). So if someone wants to make slow down bots or monsters when they are walking towards a goal and alter the function "BotMoveInGoalArea" then the bots/monsters do stupid things. Otherwise and this is the default (also buggy) behavior they start running although they shouldn't (as seen with the "Crash" bot and will not be fixed here). -------------------------------------------------------------------------------- Solution: Fix overflow in bot user command. BUGFIX from "Hunt" mod by J. Hoffman. ******************************************************************************** BUG: in function "BotMoveToGoal" the special elevator case doesn't make sense. -------------------------------------------------------------------------------- Solution: in "be_ai_move.c": ((result->flags & MOVERESULT_ONTOPOF_FUNCBOB) || (result->flags & MOVERESULT_ONTOPOF_FUNCBOB)) should be ((result->flags & MOVERESULT_ONTOPOF_ELEVATOR) || (result->flags & MOVERESULT_ONTOPOF_FUNCBOB)). ******************************************************************************** BUG: in function "BotWantsToRetreat" and "BotWantsToChase" this is wrong: "(bs->enemy != redobelisk.entitynum || bs->enemy != blueobelisk.entitynum)" -------------------------------------------------------------------------------- Solution: "... redobelisk.entitynum) && (bs->enemy != blueobelisk.." is correct. ******************************************************************************** BUG: in gamemode GT_OBELISK there are too many node switches for bots (test: mpq3tourney6 with many bots). If that happens, game becomes unplayable. I don't know if this is the best solution but here it is: -------------------------------------------------------------------------------- Solution: In function "AINode_Battle_Fight" right after: if (!BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) { I added this: #ifdef MISSIONPACK if (bs->enemy == redobelisk.entitynum || bs->enemy == blueobelisk.entitynum) { AIEnter_Battle_Chase(bs, "battle fight: obelisk out of sight"); return qfalse; } #endif ******************************************************************************** BUG: in gamemode >= GT_TEAM, after team change, bots will (sometimes) not stop shooting at you, although you are on their team now. It seems that the configstrings are f***** up or not reliable in this case! -------------------------------------------------------------------------------- Solution: In function "BotTeam" and "BotSameTeam" get the real team values. ******************************************************************************** BUG: Some of the bots voice commands are wrong. They are commanded to attack the enemy base but they say "Okay, I will defend!" -------------------------------------------------------------------------------- Solution: Corrected some voice commands in "BotCTFOrders_FlagNotAtBase" and "Bot1FCTFOrders_EnemyDroppedFlag" ******************************************************************************** BUG: Spectators couldn't fly through doors if they are very close to each other. You can test it with some regular id maps (q3dm14, q3dm12) but there are also many custom maps where this can happen! This is annoying because in the worst case you can't move at all and are caught inside a door. -------------------------------------------------------------------------------- Solution: There is a solution in a mod called "Hunt" by J. Hoffman. Bugfix is included in this patch! ******************************************************************************** BUG: During making a mod I found it very hard to implement some of my ideas (something like "Limbo" or "Meeting") because of the way the player spawn effect, intermission and spawning on victory pads is handled. I reworked it a bit and simplified it so that the effect is handled when a client respawns (as the name says) and not when a client begins. I think this will help more mod makers everytime they want to make changes to spawning of players, bots on victory pads or monsters... and want to avoid spectators with Machineguns which can kill and score... :() NOTE: I also renamed the poorly named function "respawn" to "ClientRespawn". If someone searches the code base for "respawn" it was really hard to find the correct place for what was meant. "respawn" is used so often, that you really get headache ... now with "ClientRespawn" it's easier! IMPORTANT: The whole respawning, moving to intermission point and everything related to that is now done in a more reliable way without changing the default behavior. (How critical the whole spwaning mess was did you see by yourself (ioquake3 rev. 2076). With this patch it's safer. Trust me, I spent hours of fixing silly problems... -------------------------------------------------------------------------------- Solution: Simplified "ClientBegin" and moved the teleport event to "ClientSpawn". ******************************************************************************** BUG: If a player is dying or hurted under water the hurt/dying sounds AND the drowning sounds are played together. This is silly. Moreover it's no good idea to let the server play client sounds! There was a solution in a mod called "Q3A++" by Dan 'Neurobasher' Gomes which fixes the problem. -------------------------------------------------------------------------------- Solution: Created a "CG_WaterLevel" function to play the appropriate sounds. ******************************************************************************** ################################################################################
2011-08-01 11:39:33 +00:00
Com_DPrintf("Unwanted challenge response received. Ignored.\n");
return;
2005-08-26 17:39:27 +00:00
}
c = Cmd_Argv(2);
if(*c)
challenge = atoi(c);
strver = Cmd_Argv(3);
if(*strver)
{
ver = atoi(strver);
if(ver != com_protocol->integer)
{
#ifdef LEGACY_PROTOCOL
if(com_legacyprotocol->integer > 0)
{
// Server is ioq3 but has a different protocol than we do.
// Fall back to idq3 protocol.
clc.compat = qtrue;
Com_Printf(S_COLOR_YELLOW "Warning: Server reports protocol version %d, "
"we have %d. Trying legacy protocol %d.\n",
ver, com_protocol->integer, com_legacyprotocol->integer);
}
else
#endif
{
Com_Printf(S_COLOR_YELLOW "Warning: Server reports protocol version %d, we have %d. "
"Trying anyways.\n", ver, com_protocol->integer);
}
}
}
#ifdef LEGACY_PROTOCOL
else
clc.compat = qtrue;
if(clc.compat)
{
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.
if(!*c || challenge != clc.challenge)
{
Com_DPrintf("Challenge response received from unexpected source. Ignored.\n");
return;
}
}
}
else
#endif
{
if(!*c || challenge != clc.challenge)
{
Com_Printf("Bad challenge for challengeResponse. Ignored.\n");
return;
}
}
// start sending challenge response instead of challenge request packets
clc.challenge = atoi(Cmd_Argv(1));
clc.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);
2005-08-26 17:39:27 +00:00
return;
}
// server connection
if ( !Q_stricmp(c, "connectResponse") ) {
if ( clc.state >= CA_CONNECTED ) {
Batch of bug fixes for gamecode. Patch compiled and log message written by Tobias Kuehnhammer (#5144) ################################################################################ This Patch fixes: ################################################################################ - The "fraglimit warning" was not played at all, if on the blue team. - The "where" console command was broken. - Obelisk explosion wasn't drawn if no Rocketlauncher was loaded. - Impact marks sometimes didn't draw at all. - IMPORTANT BUGFIX: No killing for cheaters with Lightning gun and Gauntlet. - If two doors are close to each other a spectator couldn't fly through them. - More robust, efficient and logical respawning routine. NOTE: The game.qvm will get notable smaller and will use LESS MEMORY! - Drowning sounds are fixed. Now they are played as intended. (as the id comment in the source code shows). - Some AI bugs (OVERFLOW!) in the bot movement code. - Several "Team Arena" Overload and Harvester bugs. - Stops bots from attacking a team mate (player) who only changed teams. - Some voice chats and CTF commands fixed. - "Team_ReturnFlag" was called twice, which did wired things sometimes. NOTE: (G_RunItem checks CONTENTS_NODROP already!) - A bugfix for Gauntlet animation. - Incorrect CTF scoring. - A bunch of corrected comments and print lines ("\n"). - Some regularity of expression and some small trivial bugs. ################################################################################ Details: ################################################################################ ******************************************************************************** BUG: in gamemode GT_TEAM the fraglimit warning will not be played if joining the blue team! -------------------------------------------------------------------------------- Solution: In "CG_CheckLocalSounds": if cgs.scores2 > highScore, highScore should be cgs.scores2. ******************************************************************************** BUG: the "where" console command doesn't work as expected (it's always 0 0 0) but not in id Quake 3 Arena. It seems that now Ioquake3 is affected! -------------------------------------------------------------------------------- Solution: In Function "Cmd_Where_f" ent->s.origin should be ent->r.currentOrigin. ******************************************************************************** BUG: in gamemode GT_OBELISK obelisk explosion won't be drawn if there is no Rocketlauncher loaded. (The "maps without Rocketlauncher" bug) -------------------------------------------------------------------------------- Solution: in "cg_main.c": cgs.media.rocketExplosionShader should be registered if gamemode is GT_OBELISK. ******************************************************************************** BUG: Impact marks sometimes doesn't draw at all. Not easy to reproduce if you don't play (io)Quake3 every day and know the places where it happens! ;) But anyway... Test: start q3dm12 go to "Long Jump Canyon" (where the small platform teleporter for the BFG is) place yourself at the point where the railgun spawns, look in the direction where the red suspended armor is. Now shoot at the sloped wall on the out/leftside of the door you see. (the sloped wall should be nearly in the center of your screen now). If you choose the correct brush face and shoot up and down at this brush face, the impact marks sometimes aren't visible. There are hundreds of custom maps where this can happen! -------------------------------------------------------------------------------- Solution: I replaced the function "SnapVectorTowards" with the one from "Wolfenstein - Enemy Territory (GPL Source Code)" ******************************************************************************** BUG: Normally "NOCLIP" cheaters are logically not allowed to fire a gun. Unfortunatly the Gauntlet (and Lightning gun) was forgotten and not restricted to that. All weapons except those two were handled correct. -------------------------------------------------------------------------------- Solution: Make Gauntlet and Lightning gun not firing for someone who cheats with "NOCLIP" (like all other weapons). ******************************************************************************** NOTE: A few bugfixes are not mine and are reported here: http://www.quake3world.com/forum/viewtopic.php?f=16&t=9179. Thanks to Quake3world, for all those years and the good guys there! ******************************************************************************** BUG: During making a mod I found a very strange bug, which mainly occurs if someone tries to implement a lot of singleplayer monsters which should walk slowly (like the "Crash" bot). So if someone wants to make slow down bots or monsters when they are walking towards a goal and alter the function "BotMoveInGoalArea" then the bots/monsters do stupid things. Otherwise and this is the default (also buggy) behavior they start running although they shouldn't (as seen with the "Crash" bot and will not be fixed here). -------------------------------------------------------------------------------- Solution: Fix overflow in bot user command. BUGFIX from "Hunt" mod by J. Hoffman. ******************************************************************************** BUG: in function "BotMoveToGoal" the special elevator case doesn't make sense. -------------------------------------------------------------------------------- Solution: in "be_ai_move.c": ((result->flags & MOVERESULT_ONTOPOF_FUNCBOB) || (result->flags & MOVERESULT_ONTOPOF_FUNCBOB)) should be ((result->flags & MOVERESULT_ONTOPOF_ELEVATOR) || (result->flags & MOVERESULT_ONTOPOF_FUNCBOB)). ******************************************************************************** BUG: in function "BotWantsToRetreat" and "BotWantsToChase" this is wrong: "(bs->enemy != redobelisk.entitynum || bs->enemy != blueobelisk.entitynum)" -------------------------------------------------------------------------------- Solution: "... redobelisk.entitynum) && (bs->enemy != blueobelisk.." is correct. ******************************************************************************** BUG: in gamemode GT_OBELISK there are too many node switches for bots (test: mpq3tourney6 with many bots). If that happens, game becomes unplayable. I don't know if this is the best solution but here it is: -------------------------------------------------------------------------------- Solution: In function "AINode_Battle_Fight" right after: if (!BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) { I added this: #ifdef MISSIONPACK if (bs->enemy == redobelisk.entitynum || bs->enemy == blueobelisk.entitynum) { AIEnter_Battle_Chase(bs, "battle fight: obelisk out of sight"); return qfalse; } #endif ******************************************************************************** BUG: in gamemode >= GT_TEAM, after team change, bots will (sometimes) not stop shooting at you, although you are on their team now. It seems that the configstrings are f***** up or not reliable in this case! -------------------------------------------------------------------------------- Solution: In function "BotTeam" and "BotSameTeam" get the real team values. ******************************************************************************** BUG: Some of the bots voice commands are wrong. They are commanded to attack the enemy base but they say "Okay, I will defend!" -------------------------------------------------------------------------------- Solution: Corrected some voice commands in "BotCTFOrders_FlagNotAtBase" and "Bot1FCTFOrders_EnemyDroppedFlag" ******************************************************************************** BUG: Spectators couldn't fly through doors if they are very close to each other. You can test it with some regular id maps (q3dm14, q3dm12) but there are also many custom maps where this can happen! This is annoying because in the worst case you can't move at all and are caught inside a door. -------------------------------------------------------------------------------- Solution: There is a solution in a mod called "Hunt" by J. Hoffman. Bugfix is included in this patch! ******************************************************************************** BUG: During making a mod I found it very hard to implement some of my ideas (something like "Limbo" or "Meeting") because of the way the player spawn effect, intermission and spawning on victory pads is handled. I reworked it a bit and simplified it so that the effect is handled when a client respawns (as the name says) and not when a client begins. I think this will help more mod makers everytime they want to make changes to spawning of players, bots on victory pads or monsters... and want to avoid spectators with Machineguns which can kill and score... :() NOTE: I also renamed the poorly named function "respawn" to "ClientRespawn". If someone searches the code base for "respawn" it was really hard to find the correct place for what was meant. "respawn" is used so often, that you really get headache ... now with "ClientRespawn" it's easier! IMPORTANT: The whole respawning, moving to intermission point and everything related to that is now done in a more reliable way without changing the default behavior. (How critical the whole spwaning mess was did you see by yourself (ioquake3 rev. 2076). With this patch it's safer. Trust me, I spent hours of fixing silly problems... -------------------------------------------------------------------------------- Solution: Simplified "ClientBegin" and moved the teleport event to "ClientSpawn". ******************************************************************************** BUG: If a player is dying or hurted under water the hurt/dying sounds AND the drowning sounds are played together. This is silly. Moreover it's no good idea to let the server play client sounds! There was a solution in a mod called "Q3A++" by Dan 'Neurobasher' Gomes which fixes the problem. -------------------------------------------------------------------------------- Solution: Created a "CG_WaterLevel" function to play the appropriate sounds. ******************************************************************************** ################################################################################
2011-08-01 11:39:33 +00:00
Com_Printf ("Dup connect received. Ignored.\n");
2005-08-26 17:39:27 +00:00
return;
}
if ( clc.state != CA_CHALLENGING ) {
Com_Printf ("connectResponse packet while not connecting. Ignored.\n");
2005-08-26 17:39:27 +00:00
return;
}
if ( !NET_CompareAdr( from, clc.serverAddress ) ) {
Com_Printf( "connectResponse from wrong address. Ignored.\n" );
2005-08-26 17:39:27 +00:00
return;
}
#ifdef LEGACY_PROTOCOL
if(!clc.compat)
#endif
{
c = Cmd_Argv(1);
if(*c)
challenge = atoi(c);
else
{
Com_Printf("Bad connectResponse received. Ignored.\n");
return;
}
if(challenge != clc.challenge)
{
Com_Printf("ConnectResponse with bad challenge received. Ignored.\n");
return;
}
}
#ifdef LEGACY_PROTOCOL
Netchan_Setup(NS_CLIENT, &clc.netchan, from, Cvar_VariableValue("net_qport"),
clc.challenge, clc.compat);
#else
Netchan_Setup(NS_CLIENT, &clc.netchan, from, Cvar_VariableValue("net_qport"),
clc.challenge, qfalse);
#endif
clc.state = CA_CONNECTED;
2005-08-26 17:39:27 +00:00
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;
}
// echo request from server
if ( !Q_stricmp(c, "echo") ) {
// NOTE: we may have to add exceptions for auth and update servers
if ( NET_CompareAdr( from, clc.serverAddress ) || NET_CompareAdr( from, cls.rconAddress ) ) {
NET_OutOfBandPrint( NS_CLIENT, from, "%s", Cmd_Argv(1) );
}
2005-08-26 17:39:27 +00:00
return;
}
// cd check
if ( !Q_stricmp(c, "keyAuthorize") ) {
// we don't use these now, so dump them on the floor
return;
}
// global MOTD from id
if ( !Q_stricmp(c, "motd") ) {
CL_MotdPacket( from );
return;
}
// echo request from server
if ( !Q_stricmp(c, "print") ) {
// NOTE: we may have to add exceptions for auth and update servers
if ( NET_CompareAdr( from, clc.serverAddress ) || NET_CompareAdr( from, cls.rconAddress ) ) {
s = MSG_ReadString( msg );
Q_strncpyz( clc.serverMessage, s, sizeof( clc.serverMessage ) );
Com_Printf( "%s", s );
}
2005-08-26 17:39:27 +00:00
return;
}
// list of servers sent back by a master server (classic)
2005-08-26 17:39:27 +00:00
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 );
2005-08-26 17:39:27 +00:00
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;
#ifdef ELITEFORCE
msg->compat = clc.compat;
#endif
2005-08-26 17:39:27 +00:00
if ( msg->cursize >= 4 && *(int *)msg->data == -1 ) {
CL_ConnectionlessPacket( from, msg );
return;
}
if ( clc.state < CA_CONNECTED ) {
2005-08-26 17:39:27 +00:00
return; // can't be a valid sequenced packet
}
if ( msg->cursize < 4 ) {
Com_Printf ("%s: Runt packet\n", NET_AdrToStringwPort( from ));
2005-08-26 17:39:27 +00:00
return;
}
//
// packet from server
//
if ( !NET_CompareAdr( from, clc.netchan.remoteAddress ) ) {
Com_DPrintf ("%s:sequenced packet without connection\n"
, NET_AdrToStringwPort( from ) );
2005-08-26 17:39:27 +00:00
// 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 )
&& clc.state >= CA_CONNECTED && clc.state != CA_CINEMATIC
2005-08-26 17:39:27 +00:00
&& 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;
}
2005-08-26 17:39:27 +00:00
//============================================================================
/*
==================
CL_CheckUserinfo
==================
*/
void CL_CheckUserinfo( void ) {
// don't add reliable commands when not yet connected
if(clc.state < CA_CONNECTED)
2005-08-26 17:39:27 +00:00
return;
2005-08-26 17:39:27 +00:00
// don't overflow the reliable command buffer when paused
if(CL_CheckPaused())
2005-08-26 17:39:27 +00:00
return;
2005-08-26 17:39:27 +00:00
// send a reliable userinfo update if needed
if(cvar_modifiedFlags & CVAR_USERINFO)
{
2005-08-26 17:39:27 +00:00
cvar_modifiedFlags &= ~CVAR_USERINFO;
CL_AddReliableCommand(va("userinfo \"%s\"", Cvar_InfoString( CVAR_USERINFO ) ), qfalse);
2005-08-26 17:39:27 +00:00
}
}
/*
==================
CL_Frame
==================
*/
void CL_Frame ( int msec ) {
if ( !com_cl_running->integer ) {
return;
}
#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 clc.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
2005-08-26 17:39:27 +00:00
if ( cls.cddialog ) {
// bring up the cd error dialog if needed
cls.cddialog = qfalse;
VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_NEED_CD );
} else if ( clc.state == CA_DISCONNECTED && !( Key_GetCatcher( ) & KEYCATCH_UI )
&& !com_sv_running->integer && uivm ) {
2005-08-26 17:39:27 +00:00
// 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) {
2005-08-26 17:39:27 +00:00
// save the current screen
if ( clc.state == CA_ACTIVE || cl_forceavidemo->integer) {
float fps = MIN(cl_aviFrameRate->value * com_timescale->value, 1000.0f);
float frameDuration = MAX(1000.0f / fps, 1.0f) + clc.aviVideoFrameRemainder;
CL_TakeVideoFrame( );
msec = (int)frameDuration;
clc.aviVideoFrameRemainder = frameDuration - msec;
2005-08-26 17:39:27 +00:00
}
}
if( cl_autoRecordDemo->integer ) {
if( clc.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, clc.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( clc.state != CA_ACTIVE && clc.demorecording ) {
// Recording, but not CA_ACTIVE, so stop recording
CL_StopRecord_f( );
}
}
2005-08-26 17:39:27 +00:00
// 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 );
2005-08-26 17:39:27 +00:00
}
// 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
2005-08-26 17:39:27 +00:00
// advance local effects for next frame
SCR_RunCinematic();
Con_RunConsole();
cls.framecount++;
}
//============================================================================
/*
================
CL_RefPrintf
DLL glue
================
*/
static __attribute__ ((format (printf, 2, 3))) void QDECL CL_RefPrintf( int print_level, const char *fmt, ...) {
2005-08-26 17:39:27 +00:00
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 ) {
2013-07-08 22:36:30 +00:00
if ( re.Shutdown ) {
re.Shutdown( qtrue );
2005-08-26 17:39:27 +00:00
}
2013-07-08 22:36:30 +00:00
2005-08-26 17:39:27 +00:00
Com_Memset( &re, 0, sizeof( re ) );
2013-07-08 22:36:30 +00:00
#ifdef USE_RENDERER_DLOPEN
if ( rendererLib ) {
Sys_UnloadLibrary( rendererLib );
rendererLib = NULL;
}
#endif
2005-08-26 17:39:27 +00:00
}
/*
============
CL_InitRenderer
============
*/
void CL_InitRenderer( void ) {
// this sets up the renderer and calls R_Init
re.BeginRegistration( &cls.glconfig );
// load character sets
#ifdef ELITEFORCE
cls.charSetShader = re.RegisterShaderNoMip( "gfx/2d/charsgrid_med" );
#else
2005-08-26 17:39:27 +00:00
cls.charSetShader = re.RegisterShader( "gfx/2d/bigchars" );
#endif
2005-08-26 17:39:27 +00:00
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 ) {
2005-08-26 17:39:27 +00:00
if (!com_cl_running) {
return;
}
if ( !com_cl_running->integer ) {
return;
}
if ( !cls.rendererStarted ) {
cls.rendererStarted = qtrue;
CL_InitRenderer();
}
if ( rendererOnly ) {
return;
}
2005-08-26 17:39:27 +00:00
if ( !cls.soundStarted ) {
cls.soundStarted = qtrue;
S_Init();
}
if ( !cls.soundRegistered ) {
cls.soundRegistered = qtrue;
S_BeginRegistration();
}
if( com_dedicated->integer ) {
return;
}
2005-08-26 17:39:27 +00:00
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;
#ifdef USE_RENDERER_DLOPEN
GetRefAPI_t GetRefAPI;
char dllName[MAX_OSPATH];
#endif
2005-08-26 17:39:27 +00:00
Com_Printf( "----- Initializing Renderer ----\n" );
#ifdef USE_RENDERER_DLOPEN
cl_renderer = Cvar_Get("cl_renderer", "opengl2", CVAR_ARCHIVE | CVAR_LATCH);
Com_sprintf(dllName, sizeof(dllName), "renderer_%s_" ARCH_STRING DLL_EXT, cl_renderer->string);
if(!(rendererLib = Sys_LoadDll(dllName, qfalse)) && strcmp(cl_renderer->string, cl_renderer->resetString))
{
Com_Printf("failed:\n\"%s\"\n", Sys_LibraryError());
Cvar_ForceReset("cl_renderer");
2016-10-03 03:00:59 +00:00
Com_sprintf(dllName, sizeof(dllName), "renderer_opengl2_" ARCH_STRING DLL_EXT);
rendererLib = Sys_LoadDll(dllName, qfalse);
}
if(!rendererLib)
{
Com_Printf("failed:\n\"%s\"\n", Sys_LibraryError());
Com_Error(ERR_FATAL, "Failed to load renderer");
}
GetRefAPI = Sys_LoadFunction(rendererLib, "GetRefAPI");
if(!GetRefAPI)
{
Com_Error(ERR_FATAL, "Can't load symbol GetRefAPI: '%s'", Sys_LibraryError());
}
#endif
2005-08-26 17:39:27 +00:00
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_ClusterPVS = CM_ClusterPVS;
2005-08-26 17:39:27 +00:00
ri.CM_DrawDebugSurface = CM_DrawDebugSurface;
2005-08-26 17:39:27 +00:00
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_SetValue = Cvar_SetValue;
ri.Cvar_CheckRange = Cvar_CheckRange;
2014-09-26 13:29:51 +00:00
ri.Cvar_SetDescription = Cvar_SetDescription;
ri.Cvar_VariableIntegerValue = Cvar_VariableIntegerValue;
2005-08-26 17:39:27 +00:00
// cinematic stuff
ri.CIN_UploadCinematic = CIN_UploadCinematic;
ri.CIN_PlayCinematic = CIN_PlayCinematic;
ri.CIN_RunCinematic = CIN_RunCinematic;
ri.CL_WriteAVIVideoFrame = CL_WriteAVIVideoFrame;
2005-08-26 17:39:27 +00:00
ri.IN_Init = IN_Init;
ri.IN_Shutdown = IN_Shutdown;
ri.IN_Restart = IN_Restart;
ri.ftol = Q_ftol;
ri.Sys_SetEnv = Sys_SetEnv;
ri.Sys_GLimpSafeInit = Sys_GLimpSafeInit;
ri.Sys_GLimpInit = Sys_GLimpInit;
ri.Sys_LowPhysicalMemory = Sys_LowPhysicalMemory;
2005-08-26 17:39:27 +00:00
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;
2017-11-22 07:40:20 +00:00
// unpause so the cgame definitely gets a snapshot and renders a frame
2005-08-26 17:39:27 +00:00
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" );
}
}
2014-07-17 22:15:05 +00:00
void CL_Sayto_f( void ) {
char *rawname;
char name[MAX_NAME_LENGTH];
char cleanName[MAX_NAME_LENGTH];
const char *info;
int count;
int i;
int clientNum;
char *p;
if ( Cmd_Argc() < 3 ) {
Com_Printf ("sayto <player name> <text>\n");
return;
}
rawname = Cmd_Argv(1);
Com_FieldStringToPlayerName( name, MAX_NAME_LENGTH, rawname );
info = cl.gameState.stringData + cl.gameState.stringOffsets[CS_SERVERINFO];
count = atoi( Info_ValueForKey( info, "sv_maxclients" ) );
clientNum = -1;
for( i = 0; i < count; i++ ) {
info = cl.gameState.stringData + cl.gameState.stringOffsets[CS_PLAYERS+i];
Q_strncpyz( cleanName, Info_ValueForKey( info, "n" ), sizeof(cleanName) );
Q_CleanStr( cleanName );
if ( !Q_stricmp( cleanName, name ) ) {
clientNum = i;
break;
}
}
if( clientNum <= -1 )
{
Com_Printf ("No such player name: %s.\n", name);
return;
}
p = Cmd_ArgsFrom(2);
if ( *p == '"' ) {
p++;
p[strlen(p)-1] = 0;
}
CL_AddReliableCommand(va("tell %i \"%s\"", clientNum, p ), qfalse);
}
2005-08-26 17:39:27 +00:00
/*
====================
CL_Init
====================
*/
void CL_Init( void ) {
Com_Printf( "----- Client Initialization -----\n" );
Con_Init ();
2005-08-26 17:39:27 +00:00
if(!com_fullyInitialized)
{
CL_ClearState();
clc.state = CA_DISCONNECTED; // no longer CA_UNINITIALIZED
cl_oldGameSet = qfalse;
}
2005-08-26 17:39:27 +00:00
cls.realtime = 0;
clc.lastPacketTime = 0;
clc.lastPacketSentTime = -9999;
2005-08-26 17:39:27 +00:00
CL_InitInput ();
//
// register our variables
//
cl_noprint = Cvar_Get( "cl_noprint", "0", 0 );
#ifdef UPDATE_SERVER_NAME
2005-08-26 17:39:27 +00:00
cl_motd = Cvar_Get ("cl_motd", "1", 0);
#endif
2005-08-26 17:39:27 +00:00
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);
2005-08-26 17:39:27 +00:00
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);
#ifdef ELITEFORCE
cl_maxpackets = Cvar_Get ("cl_maxpackets", "43", CVAR_ARCHIVE );
#else
2005-08-26 17:39:27 +00:00
cl_maxpackets = Cvar_Get ("cl_maxpackets", "30", CVAR_ARCHIVE );
#endif
2005-08-26 17:39:27 +00:00
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 );
2011-02-04 17:22:42 +00:00
Cvar_CheckRange(cl_mouseAccelOffset, 0.001f, 50000.0f, qfalse);
2005-08-26 17:39:27 +00:00
cl_showMouseRate = Cvar_Get ("cl_showmouserate", "0", 0);
cl_allowDownload = Cvar_Get ("cl_allowDownload", "0", CVAR_ARCHIVE);
#ifdef USE_CURL_DLOPEN
cl_cURLLib = Cvar_Get("cl_cURLLib", DEFAULT_CURL_LIB, CVAR_ARCHIVE | CVAR_PROTECTED);
#endif
2005-08-26 17:39:27 +00:00
cl_conXOffset = Cvar_Get ("cl_conXOffset", "0", 0);
#ifdef __APPLE__
// In game video is REALLY slow in Mac OS X right now due to driver slowness
2005-08-26 17:39:27 +00:00
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);
// init autoswitch so the ui will have it correctly even
// if the cgame hasn't been started
Cvar_Get ("cg_autoswitch", "1", CVAR_ARCHIVE);
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 __APPLE__
// Input is jittery on OS X w/o this
2005-08-26 17:39:27 +00:00
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_up = Cvar_Get ("j_up", "0", CVAR_ARCHIVE);
2012-02-15 18:47:50 +00:00
j_pitch_axis = Cvar_Get ("j_pitch_axis", "3", CVAR_ARCHIVE);
j_yaw_axis = Cvar_Get ("j_yaw_axis", "2", CVAR_ARCHIVE);
j_forward_axis = Cvar_Get ("j_forward_axis", "1", CVAR_ARCHIVE);
j_side_axis = Cvar_Get ("j_side_axis", "0", CVAR_ARCHIVE);
j_up_axis = Cvar_Get ("j_up_axis", "4", CVAR_ARCHIVE);
2012-02-15 18:47:50 +00:00
Cvar_CheckRange(j_pitch_axis, 0, MAX_JOYSTICK_AXIS-1, qtrue);
Cvar_CheckRange(j_yaw_axis, 0, MAX_JOYSTICK_AXIS-1, qtrue);
Cvar_CheckRange(j_forward_axis, 0, MAX_JOYSTICK_AXIS-1, qtrue);
Cvar_CheckRange(j_side_axis, 0, MAX_JOYSTICK_AXIS-1, qtrue);
Cvar_CheckRange(j_up_axis, 0, MAX_JOYSTICK_AXIS-1, qtrue);
2005-08-26 17:39:27 +00:00
cl_motdString = Cvar_Get( "cl_motdString", "", CVAR_ROM );
Cvar_Get( "cl_maxPing", "800", CVAR_ARCHIVE );
cl_lanForcePackets = Cvar_Get ("cl_lanForcePackets", "1", CVAR_ARCHIVE);
2005-08-26 17:39:27 +00:00
cl_guidServerUniq = Cvar_Get ("cl_guidServerUniq", "1", CVAR_ARCHIVE);
// ~ and `, as keys and characters
cl_consoleKeys = Cvar_Get( "cl_consoleKeys", "~ ` 0x7e 0x60", CVAR_ARCHIVE);
2005-08-26 17:39:27 +00:00
// userinfo
Cvar_Get ("name", "UnnamedPlayer", CVAR_USERINFO | CVAR_ARCHIVE );
cl_rate = Cvar_Get ("rate", "25000", CVAR_USERINFO | CVAR_ARCHIVE );
2005-08-26 17:39:27 +00:00
Cvar_Get ("snaps", "20", CVAR_USERINFO | CVAR_ARCHIVE );
Cvar_Get ("model", "munro/red", CVAR_USERINFO | CVAR_ARCHIVE );
#ifndef ELITEFORCE
2014-10-29 15:52:23 +00:00
Cvar_Get ("headmodel", "sarge", CVAR_USERINFO | CVAR_ARCHIVE );
2005-08-26 17:39:27 +00:00
Cvar_Get ("team_model", "james", CVAR_USERINFO | CVAR_ARCHIVE );
Cvar_Get ("team_headmodel", "*james", CVAR_USERINFO | CVAR_ARCHIVE );
Cvar_Get ("g_redTeam", "Stroggs", CVAR_SERVERINFO | CVAR_ARCHIVE);
Cvar_Get ("g_blueTeam", "Pagans", CVAR_SERVERINFO | CVAR_ARCHIVE);
#endif
2005-08-26 17:39:27 +00:00
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 ("teamtask", "0", CVAR_USERINFO );
Cvar_Get ("sex", "male", CVAR_USERINFO | CVAR_ARCHIVE );
Cvar_Get ("cl_anonymous", "0", CVAR_USERINFO | CVAR_ARCHIVE );
Cvar_Get ("password", "", CVAR_USERINFO);
Cvar_Get ("cg_predictItems", "1", CVAR_USERINFO | CVAR_ARCHIVE );
#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", "spatial", 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);
2008-06-25 05:50:44 +00:00
cl_voipVADThreshold = Cvar_Get ("cl_voipVADThreshold", "0.25", CVAR_ARCHIVE);
cl_voipShowMeter = Cvar_Get ("cl_voipShowMeter", "1", CVAR_ARCHIVE);
cl_voip = Cvar_Get ("cl_voip", "1", CVAR_ARCHIVE);
Cvar_CheckRange( cl_voip, 0, 1, qtrue );
cl_voipProtocol = Cvar_Get ("cl_voipProtocol", cl_voip->integer ? "opus" : "", CVAR_USERINFO | CVAR_ROM);
#endif
2005-08-26 17:39:27 +00:00
// 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);
2005-08-26 17:39:27 +00:00
//
// 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 );
2005-08-26 17:39:27 +00:00
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 );
2005-08-26 17:39:27 +00:00
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 );
2014-07-17 22:15:05 +00:00
if( !com_dedicated->integer ) {
Cmd_AddCommand ("sayto", CL_Sayto_f );
Cmd_SetCommandCompletionFunc( "sayto", CL_CompletePlayerName );
}
2005-08-26 17:39:27 +00:00
CL_InitRef();
SCR_Init ();
// Cbuf_Execute ();
2005-08-26 17:39:27 +00:00
Cvar_Set( "cl_running", "1" );
CL_GenerateQKey();
Cvar_Get( "cl_guid", "", CVAR_USERINFO | CVAR_ROM );
CL_UpdateGUID( NULL, 0 );
2005-08-26 17:39:27 +00:00
Com_Printf( "----- Client Initialization Complete -----\n" );
}
/*
===============
CL_Shutdown
===============
*/
void CL_Shutdown(char *finalmsg, qboolean disconnect, qboolean quit)
{
2005-08-26 17:39:27 +00:00
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 );
2005-08-26 17:39:27 +00:00
if ( recursive ) {
Com_Printf( "WARNING: Recursive shutdown\n" );
2005-08-26 17:39:27 +00:00
return;
}
recursive = qtrue;
noGameRestart = quit;
if(disconnect)
CL_Disconnect(qtrue);
2005-08-26 17:39:27 +00:00
CL_ClearMemory(qtrue);
CL_Snd_Shutdown();
2005-08-26 17:39:27 +00:00
Cmd_RemoveCommand ("cmd");
Cmd_RemoveCommand ("configstrings");
Cmd_RemoveCommand ("clientinfo");
2005-08-26 17:39:27 +00:00
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 ("reconnect");
2005-08-26 17:39:27 +00:00
Cmd_RemoveCommand ("localservers");
Cmd_RemoveCommand ("globalservers");
Cmd_RemoveCommand ("rcon");
Cmd_RemoveCommand ("ping");
Cmd_RemoveCommand ("serverstatus");
Cmd_RemoveCommand ("showip");
Cmd_RemoveCommand ("fs_openedList");
Cmd_RemoveCommand ("fs_referencedList");
2005-08-26 17:39:27 +00:00
Cmd_RemoveCommand ("model");
Cmd_RemoveCommand ("video");
Cmd_RemoveCommand ("stopvideo");
CL_ShutdownInput();
Con_Shutdown();
2005-08-26 17:39:27 +00:00
Cvar_Set( "cl_running", "0" );
recursive = qfalse;
Com_Memset( &cls, 0, sizeof( cls ) );
Key_SetCatcher( 0 );
2005-08-26 17:39:27 +00:00
Com_Printf( "-----------------------\n" );
}
static void CL_SetServerInfo(serverInfo_t *server, const char *info, int ping) {
if (server) {
if (info) {
server->clients = atoi(Info_ValueForKey(info, "clients"));
Q_strncpyz(server->hostName,Info_ValueForKey(info, "hostname"), MAX_NAME_LENGTH);
Q_strncpyz(server->mapName, Info_ValueForKey(info, "mapname"), MAX_NAME_LENGTH);
server->maxClients = atoi(Info_ValueForKey(info, "sv_maxclients"));
Q_strncpyz(server->game,Info_ValueForKey(info, "game"), 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->punkbuster = atoi(Info_ValueForKey(info, "punkbuster"));
server->g_humanplayers = atoi(Info_ValueForKey(info, "g_humanplayers"));
server->g_needpass = atoi(Info_ValueForKey(info, "g_needpass"));
2005-08-26 17:39:27 +00:00
}
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;
#ifndef ELITEFORCE
int type;
#endif
2005-08-26 17:39:27 +00:00
char info[MAX_INFO_STRING];
char *infoString;
int prot;
char *gamename;
qboolean gameMismatch;
2005-08-26 17:39:27 +00:00
#ifdef ELITEFORCE
// eliteforce doesn't send a \n after infoResponse..
infoString = strchr((char *) msg->data, '"');
if(!infoString)
return;
msg->readcount = (int) ((byte *) ++infoString - msg->data);
msg->bit = msg->readcount << 3;
// find the second " character and empty it.
infoString = strchr(infoString, '"');
if(infoString)
*infoString = '\0';
#endif
2005-08-26 17:39:27 +00:00
infoString = MSG_ReadString( msg );
// if this isn't the correct gamename, ignore it
gamename = Info_ValueForKey( infoString, "gamename" );
#ifdef LEGACY_PROTOCOL
// gamename is optional for legacy protocol
if (com_legacyprotocol->integer && !*gamename)
gameMismatch = qfalse;
else
#endif
gameMismatch = !*gamename || strcmp(gamename, com_gamename->string) != 0;
if (gameMismatch)
{
Com_DPrintf( "Game mismatch in info packet: %s\n", infoString );
return;
}
2005-08-26 17:39:27 +00:00
// if this isn't the correct protocol version, ignore it
prot = atoi( Info_ValueForKey( infoString, "protocol" ) );
if(prot != com_protocol->integer
#ifdef LEGACY_PROTOCOL
&& prot != com_legacyprotocol->integer
#endif
)
{
2005-08-26 17:39:27 +00:00
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 ) )
{
#ifdef ELITEFORCE
char *str;
#endif
2005-08-26 17:39:27 +00:00
// calc ping time
cl_pinglist[i].time = Sys_Milliseconds() - cl_pinglist[i].start;
2005-08-26 17:39:27 +00:00
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:
#ifdef ELITEFORCE
str = "udp";
#else
type = 1;
#endif
2005-08-26 17:39:27 +00:00
break;
case NA_IP6:
#ifdef ELITEFORCE
str = "udp6";
#else
type = 2;
#endif
break;
2005-08-26 17:39:27 +00:00
default:
#ifdef ELITEFORCE
str = "";
#else
2005-08-26 17:39:27 +00:00
type = 0;
#endif
2005-08-26 17:39:27 +00:00
break;
}
#ifdef ELITEFORCE
Info_SetValueForKey( cl_pinglist[i].info, "nettype", str);
#else
Info_SetValueForKey( cl_pinglist[i].info, "nettype", va("%d", type));
#endif
2005-08-26 17:39:27 +00:00
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;
CL_InitServerInfo( &cls.localServers[i], &from );
2005-08-26 17:39:27 +00:00
Q_strncpyz( info, MSG_ReadString( msg ), MAX_INFO_STRING );
if (strlen(info)) {
if (info[strlen(info)-1] != '\n') {
Q_strcat(info, sizeof(info), "\n");
2005-08-26 17:39:27 +00:00
}
Com_Printf( "%s: %s", NET_AdrToStringwPort( from ), info );
2005-08-26 17:39:27 +00:00
}
}
/*
===================
CL_GetServerStatus
===================
*/
serverStatus_t *CL_GetServerStatus( netadr_t from ) {
int i, oldest, oldestTime;
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;
}
}
return &cl_serverStatusList[oldest];
2005-08-26 17:39:27 +00:00
}
/*
===================
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) ) {
2005-08-26 17:39:27 +00:00
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 received a response for this server status request
2005-08-26 17:39:27 +00:00
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 );
2005-08-26 17:39:27 +00:00
}
}
}
/*
==================
CL_GlobalServers_f
Originally master 0 was Internet and master 1 was MPlayer.
ioquake3 2008; added support for requesting five separate master servers using 0-4.
ioquake3 2017; made master 0 fetch all master servers and 1-5 request a single master server.
Elite Force requests five separate master servers using master 0-4.
2005-08-26 17:39:27 +00:00
==================
*/
void CL_GlobalServers_f( void ) {
netadr_t to;
int count, i, masterNum;
char command[1024], *masteraddress;
2005-08-26 17:39:27 +00:00
#ifdef ELITEFORCE
if ((count = Cmd_Argc()) < 3 || (masterNum = atoi(Cmd_Argv(1))) < 0 || masterNum >= MAX_MASTER_SERVERS)
{
Com_Printf("usage: globalservers <master# 0-%d> <protocol> [keywords]\n", MAX_MASTER_SERVERS-1);
return;
}
sprintf(command, "sv_master%d", masterNum+1);
#else
if ((count = Cmd_Argc()) < 3 || (masterNum = atoi(Cmd_Argv(1))) < 0 || masterNum > MAX_MASTER_SERVERS)
{
Com_Printf("usage: globalservers <master# 0-%d> <protocol> [keywords]\n", MAX_MASTER_SERVERS);
2005-08-26 17:39:27 +00:00
return;
}
// request from all master servers
if ( masterNum == 0 ) {
int numAddress = 0;
for ( i = 1; i <= MAX_MASTER_SERVERS; i++ ) {
sprintf(command, "sv_master%d", i);
masteraddress = Cvar_VariableString(command);
if(!*masteraddress)
continue;
numAddress++;
Com_sprintf(command, sizeof(command), "globalservers %d %s %s\n", i, Cmd_Argv(2), Cmd_ArgsFrom(3));
Cbuf_AddText(command);
}
if ( !numAddress ) {
Com_Printf( "CL_GlobalServers_f: Error: No master server addresses.\n");
}
return;
}
sprintf(command, "sv_master%d", masterNum);
#endif
masteraddress = Cvar_VariableString(command);
if(!*masteraddress)
{
Com_Printf( "CL_GlobalServers_f: Error: No master server address given.\n");
return;
}
2005-08-26 17:39:27 +00:00
// 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;
2005-08-26 17:39:27 +00:00
}
else if(i == 2)
to.port = BigShort(PORT_MASTER);
Com_Printf("Requesting servers from %s (%s)...\n", masteraddress, NET_AdrToStringwPort(to));
cls.numglobalservers = -1;
cls.pingUpdateSource = AS_GLOBAL;
2005-08-26 17:39:27 +00:00
// Use the extended query for IPv6 masters
if (to.type == NA_IP6 || to.type == NA_MULTICAST6)
{
int v4enabled = Cvar_VariableIntegerValue("net_enabled") & NET_ENABLEV4;
if(v4enabled)
{
2011-06-25 00:17:44 +00:00
Com_sprintf(command, sizeof(command), "getserversExt %s %s",
com_gamename->string, Cmd_Argv(2));
}
else
{
2011-06-25 00:17:44 +00:00
Com_sprintf(command, sizeof(command), "getserversExt %s %s ipv6",
com_gamename->string, Cmd_Argv(2));
}
}
else if ( !Q_stricmp( com_gamename->string, LEGACY_MASTER_GAMENAME ) )
Com_sprintf(command, sizeof(command), "getservers %s",
Cmd_Argv(2));
else
Com_sprintf(command, sizeof(command), "getservers %s %s",
com_gamename->string, Cmd_Argv(2));
2005-08-26 17:39:27 +00:00
for (i=3; i < count; i++)
{
Q_strcat(command, sizeof(command), " ");
Q_strcat(command, sizeof(command), Cmd_Argv(i));
}
2005-08-26 17:39:27 +00:00
NET_OutOfBandPrint( NS_SERVER, to, "%s", command );
2005-08-26 17:39:27 +00:00
}
/*
==================
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)
2005-08-26 17:39:27 +00:00
{
// empty or invalid slot
2005-08-26 17:39:27 +00:00
buf[0] = '\0';
*pingtime = 0;
return;
}
str = NET_AdrToStringwPort( cl_pinglist[n].adr );
2005-08-26 17:39:27 +00:00
Q_strncpyz( buf, str, buflen );
time = cl_pinglist[n].time;
if (!time)
{
// check for timeout
time = Sys_Milliseconds() - cl_pinglist[n].start;
2005-08-26 17:39:27 +00:00
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)
2005-08-26 17:39:27 +00:00
{
// empty or invalid slot
2005-08-26 17:39:27 +00:00
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)
2005-08-26 17:39:27 +00:00
{
// 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;
2005-08-26 17:39:27 +00:00
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();
2005-08-26 17:39:27 +00:00
if ( argc != 2 && argc != 3 ) {
Com_Printf( "usage: ping [-4|-6] server\n");
2005-08-26 17:39:27 +00:00
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);
}
2005-08-26 17:39:27 +00:00
Com_Memset( &to, 0, sizeof(netadr_t) );
if ( !NET_StringToAdr( server, &to, family ) ) {
2005-08-26 17:39:27 +00:00
return;
}
pingptr = CL_GetFreePing();
memcpy( &pingptr->adr, &to, sizeof (netadr_t) );
pingptr->start = Sys_Milliseconds();
2005-08-26 17:39:27 +00:00
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;
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;
2005-08-26 17:39:27 +00:00
}
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();
2005-08-26 17:39:27 +00:00
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;
2005-08-26 17:39:27 +00:00
char *server;
serverStatus_t *serverStatus;
int argc;
netadrtype_t family = NA_UNSPEC;
2005-08-26 17:39:27 +00:00
argc = Cmd_Argc();
2005-08-26 17:39:27 +00:00
if ( argc != 2 && argc != 3 )
{
if (clc.state != CA_ACTIVE || clc.demoplaying)
{
2005-08-26 17:39:27 +00:00
Com_Printf ("Not connected to a server.\n");
Com_Printf( "usage: serverstatus [-4|-6] server\n");
return;
2005-08-26 17:39:27 +00:00
}
toptr = &clc.serverAddress;
2005-08-26 17:39:27 +00:00
}
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);
}
2005-08-26 17:39:27 +00:00
toptr = &to;
if ( !NET_StringToAdr( server, toptr, family ) )
return;
2005-08-26 17:39:27 +00:00
}
NET_OutOfBandPrint( NS_CLIENT, *toptr, "getstatus" );
2005-08-26 17:39:27 +00:00
serverStatus = CL_GetServerStatus( *toptr );
serverStatus->address = *toptr;
2005-08-26 17:39:27 +00:00
serverStatus->print = qtrue;
serverStatus->pending = qtrue;
}
/*
==================
CL_ShowIP_f
==================
*/
void CL_ShowIP_f(void) {
Sys_ShowIP();
}
/*
=================
CL_CDKeyValidate
2005-08-26 17:39:27 +00:00
=================
*/
qboolean CL_CDKeyValidate( const char *key, const char *checksum ) {
#ifdef STANDALONE
return qtrue;
#else
2005-08-26 17:39:27 +00:00
char ch;
byte sum;
char chs[3];
int i, len;
len = strlen(key);
if( len != CDKEY_LEN ) {
return qfalse;
}
if( checksum && strlen( checksum ) != CDCHKSUM_LEN ) {
return qfalse;
}
sum = 0;
// for loop gets rid of conditional assignment warning
for (i = 0; i < len; i++) {
ch = *key++;
if (ch>='a' && ch<='z') {
ch -= 32;
}
switch( ch ) {
case '2':
case '3':
case '7':
case 'A':
case 'B':
case 'C':
case 'D':
case 'G':
case 'H':
case 'J':
case 'L':
case 'P':
case 'R':
case 'S':
case 'T':
case 'W':
sum += ch;
continue;
default:
return qfalse;
}
}
sprintf(chs, "%02x", sum);
if (checksum && !Q_stricmp(chs, checksum)) {
return qtrue;
}
if (!checksum) {
return qtrue;
}
return qfalse;
#endif
}