mirror of
https://github.com/yquake2/yquake2remaster.git
synced 2025-01-22 09:11:33 +00:00
70a94f759f
Demos are special. To the client they're more or less the same as normal maps, the only difference is that the client never joines the server. So we're never getting the first valid frame txpaht indicates the end of the connection process. We're using that one to drop out of pause mode... As a result the client stays in pause mode forever and "hangs". Work around this by entering pause mode only if the local server is in state ss_game. Demos are ss_demo. There're some more states for cinematics, pictures, etc. Since the player can't save in those states it should be save not check them.
773 lines
15 KiB
C
773 lines
15 KiB
C
/*
|
|
* Copyright (C) 1997-2001 Id Software, Inc.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or (at
|
|
* your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
*
|
|
* See the GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
|
|
* 02111-1307, USA.
|
|
*
|
|
* =======================================================================
|
|
*
|
|
* This file implements generic network functions
|
|
*
|
|
* =======================================================================
|
|
*/
|
|
|
|
#include "header/client.h"
|
|
#include "../client/sound/header/local.h"
|
|
|
|
void CL_ParseStatusMessage(void);
|
|
|
|
extern cvar_t *msg;
|
|
extern cvar_t *rcon_client_password;
|
|
extern cvar_t *rcon_address;
|
|
extern cvar_t *cl_timeout;
|
|
|
|
/*
|
|
* adds the current command line as a clc_stringcmd to the client
|
|
* message. things like godmode, noclip, etc, are commands directed to
|
|
* the server, so when they are typed in at the console, they will need
|
|
* to be forwarded.
|
|
*/
|
|
void
|
|
Cmd_ForwardToServer(void)
|
|
{
|
|
char *cmd;
|
|
|
|
cmd = Cmd_Argv(0);
|
|
|
|
if ((cls.state <= ca_connected) || (*cmd == '-') || (*cmd == '+'))
|
|
{
|
|
Com_Printf("Unknown command \"%s\"\n", cmd);
|
|
return;
|
|
}
|
|
|
|
MSG_WriteByte(&cls.netchan.message, clc_stringcmd);
|
|
|
|
SZ_Print(&cls.netchan.message, cmd);
|
|
|
|
if (Cmd_Argc() > 1)
|
|
{
|
|
SZ_Print(&cls.netchan.message, " ");
|
|
SZ_Print(&cls.netchan.message, Cmd_Args());
|
|
}
|
|
}
|
|
|
|
void
|
|
CL_ForwardToServer_f(void)
|
|
{
|
|
if ((cls.state != ca_connected) && (cls.state != ca_active))
|
|
{
|
|
Com_Printf("Can't \"%s\", not connected\n", Cmd_Argv(0));
|
|
return;
|
|
}
|
|
|
|
/* don't forward the first argument */
|
|
if (Cmd_Argc() > 1)
|
|
{
|
|
MSG_WriteByte(&cls.netchan.message, clc_stringcmd);
|
|
SZ_Print(&cls.netchan.message, Cmd_Args());
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Called after an ERR_DROP was thrown
|
|
*/
|
|
void
|
|
CL_Drop(void)
|
|
{
|
|
if (cls.state == ca_uninitialized)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (cls.state == ca_disconnected)
|
|
{
|
|
return;
|
|
}
|
|
|
|
CL_Disconnect();
|
|
|
|
/* drop loading plaque unless this is the initial game start */
|
|
if (cls.disable_servercount != -1)
|
|
{
|
|
SCR_EndLoadingPlaque(); /* get rid of loading plaque */
|
|
}
|
|
}
|
|
|
|
/*
|
|
* We have gotten a challenge from the server, so try and
|
|
* connect.
|
|
*/
|
|
void
|
|
CL_SendConnectPacket(void)
|
|
{
|
|
netadr_t adr;
|
|
int port;
|
|
|
|
memset(&adr, 0, sizeof(adr));
|
|
|
|
if (!NET_StringToAdr(cls.servername, &adr))
|
|
{
|
|
Com_Printf("Bad server address\n");
|
|
cls.connect_time = 0;
|
|
return;
|
|
}
|
|
|
|
if (adr.port == 0)
|
|
{
|
|
adr.port = BigShort(PORT_SERVER);
|
|
}
|
|
|
|
port = Cvar_VariableValue("qport");
|
|
|
|
userinfo_modified = false;
|
|
|
|
Netchan_OutOfBandPrint(NS_CLIENT, adr, "connect %i %i %i \"%s\"\n",
|
|
PROTOCOL_VERSION, port, cls.challenge, Cvar_Userinfo());
|
|
}
|
|
|
|
/*
|
|
* Resend a connect message if the last one has timed out
|
|
*/
|
|
void
|
|
CL_CheckForResend(void)
|
|
{
|
|
netadr_t adr;
|
|
|
|
/* if the local server is running and we aren't just connect */
|
|
if ((cls.state == ca_disconnected) && Com_ServerState())
|
|
{
|
|
cls.state = ca_connecting;
|
|
Q_strlcpy(cls.servername, "localhost", sizeof(cls.servername));
|
|
/* we don't need a challenge on the localhost */
|
|
CL_SendConnectPacket();
|
|
return;
|
|
}
|
|
|
|
/* resend if we haven't gotten a reply yet */
|
|
if (cls.state != ca_connecting)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (cls.realtime - cls.connect_time < 3000)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!NET_StringToAdr(cls.servername, &adr))
|
|
{
|
|
Com_Printf("Bad server address\n");
|
|
cls.state = ca_disconnected;
|
|
return;
|
|
}
|
|
|
|
if (adr.port == 0)
|
|
{
|
|
adr.port = BigShort(PORT_SERVER);
|
|
}
|
|
|
|
cls.connect_time = cls.realtime;
|
|
|
|
Com_Printf("Connecting to %s...\n", cls.servername);
|
|
|
|
Netchan_OutOfBandPrint(NS_CLIENT, adr, "getchallenge\n");
|
|
}
|
|
|
|
void
|
|
CL_Connect_f(void)
|
|
{
|
|
char server[256];
|
|
|
|
if (Cmd_Argc() != 2)
|
|
{
|
|
Com_Printf("usage: connect <server>\n");
|
|
return;
|
|
}
|
|
|
|
Q_strlcpy(server, Cmd_Argv(1), sizeof(server));
|
|
|
|
if (Com_ServerState())
|
|
{
|
|
/* if running a local server, kill it and reissue
|
|
note: this is connect with the save game system */
|
|
SV_Shutdown("Server quit\n", false);
|
|
}
|
|
|
|
NET_Config(true); /* allow remote */
|
|
CL_Disconnect();
|
|
|
|
cls.state = ca_connecting;
|
|
Q_strlcpy(cls.servername, server, sizeof(cls.servername));
|
|
cls.connect_time = -99999; /* HACK: CL_CheckForResend() will fire immediately */
|
|
}
|
|
|
|
/*
|
|
* Send the rest of the command line over as
|
|
* an unconnected command.
|
|
*/
|
|
void
|
|
CL_Rcon_f(void)
|
|
{
|
|
char message[1024];
|
|
int i;
|
|
netadr_t to;
|
|
|
|
if (!rcon_client_password->string)
|
|
{
|
|
Com_Printf("You must set 'rcon_password' before\n"
|
|
"issuing an rcon command.\n");
|
|
return;
|
|
}
|
|
|
|
memset(&to, 0, sizeof(to));
|
|
|
|
message[0] = (char)255;
|
|
message[1] = (char)255;
|
|
message[2] = (char)255;
|
|
message[3] = (char)255;
|
|
message[4] = 0;
|
|
|
|
NET_Config(true); /* allow remote */
|
|
|
|
strcat(message, "rcon ");
|
|
|
|
strcat(message, rcon_client_password->string);
|
|
strcat(message, " ");
|
|
|
|
for (i = 1; i < Cmd_Argc(); i++)
|
|
{
|
|
strcat(message, Cmd_Argv(i));
|
|
strcat(message, " ");
|
|
}
|
|
|
|
if (cls.state >= ca_connected)
|
|
{
|
|
to = cls.netchan.remote_address;
|
|
}
|
|
else
|
|
{
|
|
if (!strlen(rcon_address->string))
|
|
{
|
|
Com_Printf("You must either be connected,\n"
|
|
"or set the 'rcon_address' cvar\n"
|
|
"to issue rcon commands\n");
|
|
|
|
return;
|
|
}
|
|
|
|
NET_StringToAdr(rcon_address->string, &to);
|
|
|
|
if (to.port == 0)
|
|
{
|
|
to.port = BigShort(PORT_SERVER);
|
|
}
|
|
}
|
|
|
|
NET_SendPacket(NS_CLIENT, strlen(message) + 1, message, to);
|
|
}
|
|
|
|
void CL_WriteConfiguration(void);
|
|
|
|
/*
|
|
* Goes from a connected state to full screen
|
|
* console state Sends a disconnect message to
|
|
* the server This is also called on Com_Error, so
|
|
* it shouldn't cause any errors
|
|
*/
|
|
void
|
|
CL_Disconnect(void)
|
|
{
|
|
byte final[32];
|
|
|
|
if (cls.state == ca_disconnected)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (cl_timedemo && cl_timedemo->value)
|
|
{
|
|
int time;
|
|
|
|
time = Sys_Milliseconds() - cl.timedemo_start;
|
|
|
|
if (time > 0)
|
|
{
|
|
Com_Printf("%i frames, %3.1f seconds: %3.1f fps\n",
|
|
cl.timedemo_frames, time / 1000.0,
|
|
cl.timedemo_frames * 1000.0 / time);
|
|
}
|
|
}
|
|
|
|
VectorClear(cl.refdef.blend);
|
|
|
|
R_SetPalette(NULL);
|
|
|
|
M_ForceMenuOff();
|
|
|
|
cls.connect_time = 0;
|
|
|
|
SCR_StopCinematic();
|
|
|
|
OGG_Stop();
|
|
|
|
if (cls.demorecording)
|
|
{
|
|
CL_Stop_f();
|
|
}
|
|
|
|
/* send a disconnect message to the server */
|
|
final[0] = clc_stringcmd;
|
|
|
|
strcpy((char *)final + 1, "disconnect");
|
|
|
|
Netchan_Transmit(&cls.netchan, strlen((const char *)final), final);
|
|
Netchan_Transmit(&cls.netchan, strlen((const char *)final), final);
|
|
Netchan_Transmit(&cls.netchan, strlen((const char *)final), final);
|
|
|
|
CL_ClearState();
|
|
|
|
/* stop file download */
|
|
if (cls.download)
|
|
{
|
|
fclose(cls.download);
|
|
cls.download = NULL;
|
|
}
|
|
|
|
#ifdef USE_CURL
|
|
CL_CancelHTTPDownloads(true);
|
|
cls.downloadReferer[0] = 0;
|
|
cls.downloadname[0] = 0;
|
|
cls.downloadposition = 0;
|
|
#endif
|
|
|
|
cls.state = ca_disconnected;
|
|
|
|
snd_is_underwater = false;
|
|
|
|
// save config for old game/mod
|
|
CL_WriteConfiguration();
|
|
|
|
// we disconnected, so revert to default game/mod (might have been different mod on MP server)
|
|
Cvar_Set("game", userGivenGame);
|
|
}
|
|
|
|
void
|
|
CL_Disconnect_f(void)
|
|
{
|
|
Com_Error(ERR_DROP, "Disconnected from server");
|
|
}
|
|
|
|
/*
|
|
* packet <destination> <contents>
|
|
*
|
|
* Contents allows \n escape character
|
|
*/
|
|
void
|
|
CL_Packet_f(void)
|
|
{
|
|
char send[2048];
|
|
int i, l;
|
|
char *in, *out;
|
|
netadr_t adr;
|
|
|
|
if (Cmd_Argc() != 3)
|
|
{
|
|
Com_Printf("packet <destination> <contents>\n");
|
|
return;
|
|
}
|
|
|
|
NET_Config(true); /* allow remote */
|
|
|
|
if (!NET_StringToAdr(Cmd_Argv(1), &adr))
|
|
{
|
|
Com_Printf("Bad address\n");
|
|
return;
|
|
}
|
|
|
|
if (!adr.port)
|
|
{
|
|
adr.port = BigShort(PORT_SERVER);
|
|
}
|
|
|
|
in = Cmd_Argv(2);
|
|
|
|
out = send + 4;
|
|
|
|
send[0] = send[1] = send[2] = send[3] = (char)0xff;
|
|
|
|
l = strlen(in);
|
|
|
|
for (i = 0; i < l; i++)
|
|
{
|
|
if ((in[i] == '\\') && (in[i + 1] == 'n'))
|
|
{
|
|
*out++ = '\n';
|
|
i++;
|
|
}
|
|
|
|
else
|
|
{
|
|
*out++ = in[i];
|
|
}
|
|
}
|
|
|
|
*out = 0;
|
|
|
|
NET_SendPacket(NS_CLIENT, out - send, send, adr);
|
|
}
|
|
|
|
/*
|
|
* Just sent as a hint to the client that they should
|
|
* drop to full console
|
|
*/
|
|
void
|
|
CL_Changing_f(void)
|
|
{
|
|
/* if we are downloading, we don't change!
|
|
This so we don't suddenly stop downloading a map */
|
|
if (cls.download)
|
|
{
|
|
return;
|
|
}
|
|
|
|
SCR_BeginLoadingPlaque();
|
|
cls.state = ca_connected; /* not active anymore, but not disconnected */
|
|
Com_Printf("\nChanging map...\n");
|
|
|
|
#ifdef USE_CURL
|
|
if (cls.downloadServerRetry[0] != 0)
|
|
{
|
|
CL_SetHTTPServer(cls.downloadServerRetry);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* The server is changing levels
|
|
*/
|
|
void
|
|
CL_Reconnect_f(void)
|
|
{
|
|
/* if we are downloading, we don't change!
|
|
This so we don't suddenly stop downloading a map */
|
|
if (cls.download)
|
|
{
|
|
return;
|
|
}
|
|
|
|
S_StopAllSounds();
|
|
|
|
if (cls.state == ca_connected)
|
|
{
|
|
Com_Printf("reconnecting...\n");
|
|
cls.state = ca_connected;
|
|
MSG_WriteChar(&cls.netchan.message, clc_stringcmd);
|
|
MSG_WriteString(&cls.netchan.message, "new");
|
|
return;
|
|
}
|
|
|
|
if (*cls.servername)
|
|
{
|
|
if (cls.state >= ca_connected)
|
|
{
|
|
CL_Disconnect();
|
|
cls.connect_time = cls.realtime - 1500;
|
|
}
|
|
|
|
else
|
|
{
|
|
cls.connect_time = -99999; /* Hack: fire immediately */
|
|
}
|
|
|
|
cls.state = ca_connecting;
|
|
|
|
Com_Printf("reconnecting...\n");
|
|
}
|
|
}
|
|
|
|
void
|
|
CL_PingServers_f(void)
|
|
{
|
|
int i;
|
|
netadr_t adr;
|
|
char name[32];
|
|
const char *adrstring;
|
|
cvar_t *noudp;
|
|
cvar_t *noipx;
|
|
|
|
NET_Config(true); /* allow remote but do we even need lokal pings? */
|
|
|
|
/* send a broadcast packet */
|
|
Com_Printf("pinging broadcast...\n");
|
|
|
|
noudp = Cvar_Get("noudp", "0", CVAR_NOSET);
|
|
|
|
if (!noudp->value)
|
|
{
|
|
adr.type = NA_BROADCAST;
|
|
adr.port = BigShort(PORT_SERVER);
|
|
Netchan_OutOfBandPrint(NS_CLIENT, adr, va("info %i", PROTOCOL_VERSION));
|
|
|
|
Com_Printf("pinging multicast...\n");
|
|
adr.type = NA_MULTICAST6;
|
|
adr.port = BigShort(PORT_SERVER);
|
|
Netchan_OutOfBandPrint(NS_CLIENT, adr, va("info %i", PROTOCOL_VERSION));
|
|
}
|
|
|
|
noipx = Cvar_Get("noipx", "0", CVAR_NOSET);
|
|
|
|
if (!noipx->value)
|
|
{
|
|
adr.type = NA_BROADCAST_IPX;
|
|
adr.port = BigShort(PORT_SERVER);
|
|
Netchan_OutOfBandPrint(NS_CLIENT, adr, va("info %i", PROTOCOL_VERSION));
|
|
}
|
|
|
|
/* send a packet to each address book entry */
|
|
for (i = 0; i < 16; i++)
|
|
{
|
|
Com_sprintf(name, sizeof(name), "adr%i", i);
|
|
adrstring = Cvar_VariableString(name);
|
|
|
|
if (!adrstring || !adrstring[0])
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Com_Printf("pinging %s...\n", adrstring);
|
|
|
|
if (!NET_StringToAdr(adrstring, &adr))
|
|
{
|
|
Com_Printf("Bad address: %s\n", adrstring);
|
|
continue;
|
|
}
|
|
|
|
if (!adr.port)
|
|
{
|
|
adr.port = BigShort(PORT_SERVER);
|
|
}
|
|
|
|
Netchan_OutOfBandPrint(NS_CLIENT, adr, va("info %i", PROTOCOL_VERSION));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Responses to broadcasts, etc
|
|
*/
|
|
void
|
|
CL_ConnectionlessPacket(void)
|
|
{
|
|
char *s;
|
|
char *c;
|
|
|
|
MSG_BeginReading(&net_message);
|
|
MSG_ReadLong(&net_message); /* skip the -1 */
|
|
|
|
s = MSG_ReadStringLine(&net_message);
|
|
|
|
Cmd_TokenizeString(s, false);
|
|
|
|
c = Cmd_Argv(0);
|
|
|
|
Com_Printf("%s: %s\n", NET_AdrToString(net_from), c);
|
|
|
|
/* server connection */
|
|
if (!strcmp(c, "client_connect"))
|
|
{
|
|
if (cls.state == ca_connected)
|
|
{
|
|
Com_Printf("Dup connect received. Ignored.\n");
|
|
return;
|
|
}
|
|
|
|
Netchan_Setup(NS_CLIENT, &cls.netchan, net_from, cls.quakePort);
|
|
char *buff = NET_AdrToString(cls.netchan.remote_address);
|
|
|
|
for(int i = 1; i < Cmd_Argc(); i++)
|
|
{
|
|
char *p = Cmd_Argv(i);
|
|
|
|
if(!strncmp(p, "dlserver=", 9))
|
|
{
|
|
#ifdef USE_CURL
|
|
p += 9;
|
|
Com_sprintf(cls.downloadReferer, sizeof(cls.downloadReferer), "quake2://%s", buff);
|
|
CL_SetHTTPServer (p);
|
|
|
|
if (cls.downloadServer[0])
|
|
{
|
|
Com_Printf("HTTP downloading enabled, URL: %s\n", cls.downloadServer);
|
|
}
|
|
#else
|
|
Com_Printf("HTTP downloading supported by server but not the client.\n");
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/* Put client into pause mode when connecting to a local server.
|
|
This prevents the world from being forwarded while the client
|
|
is connecting, loading assets, etc. It's not 100%, there're
|
|
still 4 world frames (for baseq2) processed in the game and
|
|
100 frames by the server if the player enters a level that he
|
|
or she already visited. In practise both shouldn't be a big
|
|
problem. 4 frames are hardly enough for monsters staring to
|
|
attack and in most levels the starting area in unreachable by
|
|
monsters and free from environmental effects.
|
|
|
|
Com_Serverstate() returns 2 if the server is local and we're
|
|
running a real game and no timedemo, cinematic, etc. The 2 is
|
|
taken from the server_state_t enum value 'ss_game'. If it's a
|
|
local server, maxclients aus either 0 (for single player), or
|
|
2 to 8 (coop and deathmatch) if we're reaching this code.
|
|
For remote servers it's always 1. So this should trigger only
|
|
if it's a local single player server.
|
|
|
|
Since the player can load savegames from a paused state (e.g.
|
|
through the console) we'll need to communicate if we entered
|
|
paused mode (and it should left as soon as the player joined
|
|
the server) or if it was already there.
|
|
|
|
Last but not least this can be disabled by cl_loadpaused 0. */
|
|
if (Com_ServerState() == 2 && (Cvar_VariableValue("maxclients") <= 1))
|
|
{
|
|
if (cl_loadpaused->value)
|
|
{
|
|
if (!cl_paused->value)
|
|
{
|
|
paused_at_load = true;
|
|
Cvar_Set("paused", "1");
|
|
}
|
|
}
|
|
}
|
|
|
|
MSG_WriteChar(&cls.netchan.message, clc_stringcmd);
|
|
MSG_WriteString(&cls.netchan.message, "new");
|
|
cls.state = ca_connected;
|
|
return;
|
|
}
|
|
|
|
/* server responding to a status broadcast */
|
|
if (!strcmp(c, "info"))
|
|
{
|
|
CL_ParseStatusMessage();
|
|
return;
|
|
}
|
|
|
|
/* remote command from gui front end */
|
|
if (!strcmp(c, "cmd"))
|
|
{
|
|
if (!NET_IsLocalAddress(net_from))
|
|
{
|
|
Com_Printf("Command packet from remote host. Ignored.\n");
|
|
return;
|
|
}
|
|
|
|
s = MSG_ReadString(&net_message);
|
|
Cbuf_AddText(s);
|
|
Cbuf_AddText("\n");
|
|
return;
|
|
}
|
|
|
|
/* print command from somewhere */
|
|
if (!strcmp(c, "print"))
|
|
{
|
|
s = MSG_ReadString(&net_message);
|
|
Com_Printf("%s", s);
|
|
return;
|
|
}
|
|
|
|
/* ping from somewhere */
|
|
if (!strcmp(c, "ping"))
|
|
{
|
|
Netchan_OutOfBandPrint(NS_CLIENT, net_from, "ack");
|
|
return;
|
|
}
|
|
|
|
/* challenge from the server we are connecting to */
|
|
if (!strcmp(c, "challenge"))
|
|
{
|
|
cls.challenge = (int)strtol(Cmd_Argv(1), (char **)NULL, 10);
|
|
CL_SendConnectPacket();
|
|
return;
|
|
}
|
|
|
|
/* echo request from server */
|
|
if (!strcmp(c, "echo"))
|
|
{
|
|
Netchan_OutOfBandPrint(NS_CLIENT, net_from, "%s", Cmd_Argv(1));
|
|
return;
|
|
}
|
|
|
|
Com_Printf("Unknown command.\n");
|
|
}
|
|
|
|
void
|
|
CL_ReadPackets(void)
|
|
{
|
|
while (NET_GetPacket(NS_CLIENT, &net_from, &net_message))
|
|
{
|
|
/* remote command packet */
|
|
if (*(int *)net_message.data == -1)
|
|
{
|
|
CL_ConnectionlessPacket();
|
|
continue;
|
|
}
|
|
|
|
if ((cls.state == ca_disconnected) || (cls.state == ca_connecting))
|
|
{
|
|
continue; /* dump it if not connected */
|
|
}
|
|
|
|
if (net_message.cursize < 8)
|
|
{
|
|
Com_Printf("%s: Runt packet\n", NET_AdrToString(net_from));
|
|
continue;
|
|
}
|
|
|
|
/* packet from server */
|
|
if (!NET_CompareAdr(net_from, cls.netchan.remote_address))
|
|
{
|
|
Com_DPrintf("%s:sequenced packet without connection\n",
|
|
NET_AdrToString(net_from));
|
|
continue;
|
|
}
|
|
|
|
if (!Netchan_Process(&cls.netchan, &net_message))
|
|
{
|
|
continue; /* wasn't accepted for some reason */
|
|
}
|
|
|
|
CL_ParseServerMessage();
|
|
}
|
|
|
|
/* check timeout */
|
|
if ((cls.state >= ca_connected) &&
|
|
(cls.realtime - cls.netchan.last_received > cl_timeout->value * 1000))
|
|
{
|
|
if (++cl.timeoutcount > 5)
|
|
{
|
|
Com_Printf("\nServer connection timed out.\n");
|
|
CL_Disconnect();
|
|
return;
|
|
}
|
|
}
|
|
|
|
else
|
|
{
|
|
cl.timeoutcount = 0;
|
|
}
|
|
}
|
|
|