newtree/source/cl_main.c
Bill Currie 1aa9084aa9 add support for COMPRESSED downloads!!! Of course, both client and server must
both support this. The client tells the server it can support compressed
downloads by setting the z flag in the *cap userinfo. If the server detects
that the client supports compression, and the file to be downloaded is
compressed (more accurately, has the .gz extension), the server sends a special
download packet with a size of -2 (-1 indicates error),, percent of 0, followed
by the new name of the file (eg maps/foo.bsp.gz for maps/foo.bsp). The client
WILL NOT accept a new filename that doesn not match the old name for the length
of the old name. The client also will not accept a new name if there are . or
.. path components. If the client rejects the new name, it prints a warning
message and aborts that download.
2000-09-28 06:11:55 +00:00

1664 lines
35 KiB
C
Raw Blame History

/*
cl_main.c
Client main loop
Copyright (C) 1996-1997 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:
Free Software Foundation, Inc.
59 Temple Place - Suite 330
Boston, MA 02111-1307, USA
$Id$
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <ctype.h>
#include "bothdefs.h"
#include "input.h"
#include "in_win.h"
#include "sys.h"
#include "sys.h"
#include "cvar.h"
#include "sizebuf.h"
#include "msg.h"
#include "client.h"
#include "commdef.h"
#include "cmd.h"
#include "console.h"
#include "qendian.h"
#include "quakefs.h"
#include "quakedef.h"
#include "pmove.h"
#include "view.h"
#include "checksum.h"
#include "sys.h"
#include "menu.h"
#include "compat.h"
#include "buildnum.h"
#include "keys.h"
#include "screen.h"
#include "sbar.h"
#include "draw.h"
#include "qargs.h"
#include "cdaudio.h"
#include "teamplay.h"
#ifdef __sun
/* Sun's model_t in sys/model.h conflicts w/ Quake's model_t */
#define model_t sunmodel_t
#endif
#ifdef _WIN32
#include "winquake.h"
#include "winsock.h"
#include "in_win.h"
#else
#include <netinet/in.h>
#endif
#ifdef __sun
#undef model_t
#endif
#include <cl_slist.h>
#ifdef HAVE_STRINGS_H
#include <strings.h>
#endif
// we need to declare some mouse variables here, because the menu system
// references them even when on a unix system.
qboolean noclip_anglehack; // remnant from old quake
cvar_t *fs_globalcfg;
cvar_t *rcon_password;
cvar_t *rcon_address;
cvar_t *cl_timeout;
cvar_t *cl_shownet; // can be 0, 1, or 2
cvar_t *cl_autoexec;
cvar_t *cl_sbar;
cvar_t *cl_hudswap;
cvar_t *cl_maxfps;
cvar_t *lookspring;
cvar_t *lookstrafe;
cvar_t *sensitivity;
cvar_t *cl_freelook;
cvar_t *m_pitch;
cvar_t *m_yaw;
cvar_t *m_forward;
cvar_t *m_side;
cvar_t *entlatency;
cvar_t *cl_predict_players;
cvar_t *cl_predict_players2;
cvar_t *cl_solid_players;
cvar_t *localid;
static qboolean allowremotecmd = true;
//
// info mirrors
//
cvar_t *password;
cvar_t *spectator;
cvar_t *name;
cvar_t *team;
cvar_t *skin;
cvar_t *topcolor;
cvar_t *bottomcolor;
cvar_t *rate;
cvar_t *noaim;
cvar_t *msg;
extern cvar_t *cl_hightrack;
client_static_t cls;
client_state_t cl;
entity_state_t cl_baselines[MAX_EDICTS];
efrag_t cl_efrags[MAX_EFRAGS];
entity_t cl_static_entities[MAX_STATIC_ENTITIES];
lightstyle_t cl_lightstyle[MAX_LIGHTSTYLES];
dlight_t cl_dlights[MAX_DLIGHTS];
// refresh list
// this is double buffered so the last frame
// can be scanned for oldorigins of trailing objects
int cl_numvisedicts, cl_oldnumvisedicts;
entity_t *cl_visedicts, *cl_oldvisedicts;
entity_t cl_visedicts_list[2][MAX_VISEDICTS];
double connect_time = -1; // for connection retransmits
quakeparms_t host_parms;
qboolean host_initialized; // true if into command execution
qboolean nomaster;
double host_frametime;
double realtime; // without any filtering or bounding
double oldrealtime; // last frame run
int host_framecount;
int host_hunklevel;
byte *host_basepal;
byte *host_colormap;
cvar_t *host_speeds; // set for running times
cvar_t *show_fps; // set for running times
cvar_t *developer;
int fps_count;
jmp_buf host_abort;
void Master_Connect_f (void);
char *server_version = NULL; // version of server we connected to
char emodel_name[] =
{ 'e' ^ 0xff, 'm' ^ 0xff, 'o' ^ 0xff, 'd' ^ 0xff, 'e' ^ 0xff, 'l' ^ 0xff, 0 };
char pmodel_name[] =
{ 'p' ^ 0xff, 'm' ^ 0xff, 'o' ^ 0xff, 'd' ^ 0xff, 'e' ^ 0xff, 'l' ^ 0xff, 0 };
char prespawn_name[] =
{ 'p'^0xff, 'r'^0xff, 'e'^0xff, 's'^0xff, 'p'^0xff, 'a'^0xff, 'w'^0xff, 'n'^0xff,
' '^0xff, '%'^0xff, 'i'^0xff, ' '^0xff, '0'^0xff, ' '^0xff, '%'^0xff, 'i'^0xff, 0 };
char modellist_name[] =
{ 'm'^0xff, 'o'^0xff, 'd'^0xff, 'e'^0xff, 'l'^0xff, 'l'^0xff, 'i'^0xff, 's'^0xff, 't'^0xff,
' '^0xff, '%'^0xff, 'i'^0xff, ' '^0xff, '%'^0xff, 'i'^0xff, 0 };
char soundlist_name[] =
{ 's'^0xff, 'o'^0xff, 'u'^0xff, 'n'^0xff, 'd'^0xff, 'l'^0xff, 'i'^0xff, 's'^0xff, 't'^0xff,
' '^0xff, '%'^0xff, 'i'^0xff, ' '^0xff, '%'^0xff, 'i'^0xff, 0 };
/*
==================
CL_Quit_f
==================
*/
void CL_Quit_f (void)
{
if (1 /* key_dest != key_console */ /* && cls.state != ca_dedicated */)
{
M_Menu_Quit_f ();
return;
}
CL_Disconnect ();
Sys_Quit ();
}
/*
=======================
CL_Version_f
======================
*/
void CL_Version_f (void)
{
Con_Printf ("%s Version %s\n", PROGRAM, VERSION);
Con_Printf ("Binary: "__TIME__" "__DATE__"\n");
}
/*
=======================
CL_SendConnectPacket
called by CL_Connect_f and CL_CheckResend
======================
*/
void CL_SendConnectPacket (void)
{
netadr_t adr;
char data[2048];
double t1, t2;
// JACK: Fixed bug where DNS lookups would cause two connects real fast
// Now, adds lookup time to the connect time.
// Should I add it to realtime instead?!?!
if (cls.state != ca_disconnected)
return;
t1 = Sys_DoubleTime ();
if (!NET_StringToAdr (cls.servername, &adr))
{
Con_Printf ("Bad server address\n");
connect_time = -1;
return;
}
if (!NET_IsClientLegal(&adr))
{
Con_Printf ("Illegal server address\n");
connect_time = -1;
return;
}
if (adr.port == 0)
adr.port = BigShort (27500);
t2 = Sys_DoubleTime ();
connect_time = realtime+t2-t1; // for retransmit requests
cls.qport = Cvar_VariableValue("qport");
// Arrgh, this was not in the old binary only release, and eats up
// far too much of the 196 chars in the userinfo space, leaving nothing
// for player use, thus, its commented out for the moment..
//
//Info_SetValueForStarKey (cls.userinfo, "*ip", NET_AdrToString(adr), MAX_INFO_STRING);
// Con_Printf ("Connecting to %s...\n", cls.servername);
snprintf (data, sizeof(data), "%c%c%c%cconnect %i %i %i \"%s\"\n",
255, 255, 255, 255, PROTOCOL_VERSION, cls.qport, cls.challenge, cls.userinfo);
NET_SendPacket (strlen(data), data, adr);
}
/*
=================
CL_CheckForResend
Resend a connect message if the last one has timed out
=================
*/
void CL_CheckForResend (void)
{
netadr_t adr;
char data[2048];
double t1, t2;
if (connect_time == -1)
return;
if (cls.state != ca_disconnected)
return;
if (connect_time && realtime - connect_time < 5.0)
return;
t1 = Sys_DoubleTime ();
if (!NET_StringToAdr (cls.servername, &adr))
{
Con_Printf ("Bad server address\n");
connect_time = -1;
return;
}
if (!NET_IsClientLegal(&adr))
{
Con_Printf ("Illegal server address\n");
connect_time = -1;
return;
}
if (adr.port == 0)
adr.port = BigShort (27500);
t2 = Sys_DoubleTime ();
connect_time = realtime+t2-t1; // for retransmit requests
Con_Printf ("Connecting to %s...\n", cls.servername);
sprintf (data, "%c%c%c%cgetchallenge\n", 255, 255, 255, 255);
NET_SendPacket (strlen(data), data, adr);
}
void CL_BeginServerConnect(void)
{
connect_time = 0;
CL_CheckForResend();
}
/*
================
CL_Connect_f
================
*/
void CL_Connect_f (void)
{
char *server;
if (Cmd_Argc() != 2)
{
Con_Printf ("usage: connect <server>\n");
return;
}
server = Cmd_Argv (1);
CL_Disconnect ();
strncpy (cls.servername, server, sizeof(cls.servername)-1);
CL_BeginServerConnect();
}
/*
=====================
CL_Rcon_f
Send the rest of the command line over as
an unconnected command.
=====================
*/
void CL_Rcon_f (void)
{
char message[1024];
int i;
netadr_t to;
if (!rcon_password->string)
{
Con_Printf ("You must set 'rcon_password' before\n"
"issuing an rcon command.\n");
return;
}
message[0] = 255;
message[1] = 255;
message[2] = 255;
message[3] = 255;
message[4] = 0;
strcat (message, "rcon ");
strcat (message, rcon_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))
{
Con_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);
}
NET_SendPacket (strlen(message)+1, message
, to);
}
/*
=====================
CL_ClearState
=====================
*/
void CL_ClearState (void)
{
int i;
S_StopAllSounds (true);
Con_DPrintf ("Clearing memory\n");
D_FlushCaches ();
Mod_ClearAll ();
if (host_hunklevel) // FIXME: check this...
Hunk_FreeToLowMark (host_hunklevel);
CL_ClearTEnts ();
// wipe the entire cl structure
memset (&cl, 0, sizeof(cl));
SZ_Clear (&cls.netchan.message);
// clear other arrays
memset (cl_efrags, 0, sizeof(cl_efrags));
memset (cl_dlights, 0, sizeof(cl_dlights));
memset (cl_lightstyle, 0, sizeof(cl_lightstyle));
//
// allocate the efrags and chain together into a free list
//
cl.free_efrags = cl_efrags;
for (i=0 ; i<MAX_EFRAGS-1 ; i++)
cl.free_efrags[i].entnext = &cl.free_efrags[i+1];
cl.free_efrags[i].entnext = NULL;
}
/*
=====================
CL_Disconnect
Sends a disconnect message to the server
This is also called on Host_Error, so it shouldn't cause any errors
=====================
*/
void CL_Disconnect (void)
{
byte final[10];
connect_time = -1;
VID_SetCaption(PROGRAM ": disconnected");
// stop sounds (especially looping!)
S_StopAllSounds (true);
// if running a local server, shut it down
if (cls.demoplayback)
CL_StopPlayback ();
else if (cls.state != ca_disconnected)
{
if (cls.demorecording)
CL_Stop_f ();
final[0] = clc_stringcmd;
strcpy (final+1, "drop");
Netchan_Transmit (&cls.netchan, 6, final);
Netchan_Transmit (&cls.netchan, 6, final);
Netchan_Transmit (&cls.netchan, 6, final);
cls.state = ca_disconnected;
cls.demoplayback = cls.demorecording = cls.timedemo = false;
}
Cam_Reset();
if (cls.download) {
Qclose(cls.download);
cls.download = NULL;
}
CL_StopUpload();
}
void CL_Disconnect_f (void)
{
CL_Disconnect ();
}
/*
====================
CL_User_f
user <name or userid>
Dump userdata / masterdata for a user
====================
*/
void CL_User_f (void)
{
int uid;
int i;
if (Cmd_Argc() != 2)
{
Con_Printf ("Usage: user <username / userid>\n");
return;
}
uid = atoi(Cmd_Argv(1));
for (i=0 ; i<MAX_CLIENTS ; i++)
{
if (!cl.players[i].name[0])
continue;
if (cl.players[i].userid == uid
|| !strcmp(cl.players[i].name, Cmd_Argv(1)) )
{
Info_Print (cl.players[i].userinfo);
return;
}
}
Con_Printf ("User not in server.\n");
}
/*
====================
CL_Users_f
Dump userids for all current players
====================
*/
void CL_Users_f (void)
{
int i;
int c;
c = 0;
Con_Printf ("userid frags name\n");
Con_Printf ("------ ----- ----\n");
for (i=0 ; i<MAX_CLIENTS ; i++)
{
if (cl.players[i].name[0])
{
Con_Printf ("%6i %4i %s\n", cl.players[i].userid, cl.players[i].frags, cl.players[i].name);
c++;
}
}
Con_Printf ("%i total users\n", c);
}
void CL_Color_f (void)
{
// just for quake compatability...
int top, bottom;
char num[16];
if (Cmd_Argc() == 1)
{
Con_Printf ("\"color\" is \"%s %s\"\n",
Info_ValueForKey (cls.userinfo, "topcolor"),
Info_ValueForKey (cls.userinfo, "bottomcolor") );
Con_Printf ("color <0-13> [0-13]\n");
return;
}
if (Cmd_Argc() == 2)
top = bottom = atoi(Cmd_Argv(1));
else
{
top = atoi(Cmd_Argv(1));
bottom = atoi(Cmd_Argv(2));
}
top &= 15;
if (top > 13)
top = 13;
bottom &= 15;
if (bottom > 13)
bottom = 13;
sprintf (num, "%i", top);
Cvar_Set (topcolor, num);
sprintf (num, "%i", bottom);
Cvar_Set (bottomcolor, num);
}
/*
==================
CL_FullServerinfo_f
Sent by server when serverinfo changes
==================
*/
void CL_FullServerinfo_f (void)
{
char *p;
if (Cmd_Argc() != 2)
{
Con_Printf ("usage: fullserverinfo <complete info string>\n");
return;
}
Con_DPrintf("Cmd_Argv(1): '%s'\n", Cmd_Argv(1));
strcpy (cl.serverinfo, Cmd_Argv(1));
Con_DPrintf("cl.serverinfo: '%s'\n", cl.serverinfo);
if ((p = Info_ValueForKey(cl.serverinfo, "*qf_version")) && *p)
{
if (server_version == NULL)
Con_Printf("QuakeForge Version %s Server\n", p);
server_version = strdup(p);
} else if ((p = Info_ValueForKey(cl.serverinfo, "*version")) && *p)
{
if (server_version == NULL)
Con_Printf("Version %s Server\n", p);
server_version = strdup(p);
}
if ((p = Info_ValueForKey(cl.serverinfo, "*qsg_version")) && *p)
{
if ((cl.stdver = atoi (p)))
Con_Printf("QSG Standard version %i\n", cl.stdver);
else
Con_Printf("Invalid standards version: %s", p);
}
if ((p = Info_ValueForKey(cl.serverinfo, "skybox")) && *p)
{
if (stricmp (p, "none") == 0)
{
allowskybox = false;
} else {
allowskybox = true;
}
} else {
allowskybox = false;
}
}
/*
==================
CL_FullInfo_f
Allow clients to change userinfo
==================
Casey was here :)
*/
void CL_FullInfo_f (void)
{
char key[512];
char value[512];
char *o;
char *s;
if (Cmd_Argc() != 2)
{
Con_Printf ("fullinfo <complete info string>\n");
return;
}
s = Cmd_Argv(1);
if (*s == '\\')
s++;
while (*s)
{
o = key;
while (*s && *s != '\\')
*o++ = *s++;
*o = 0;
if (!*s)
{
Con_Printf ("MISSING VALUE\n");
return;
}
o = value;
s++;
while (*s && *s != '\\')
*o++ = *s++;
*o = 0;
if (*s)
s++;
if (!stricmp(key, pmodel_name) || !stricmp(key, emodel_name))
continue;
Info_SetValueForKey (cls.userinfo, key, value, MAX_INFO_STRING);
}
}
/*
==================
CL_SetInfo_f
Allow clients to change userinfo
==================
*/
void CL_SetInfo_f (void)
{
if (Cmd_Argc() == 1)
{
Info_Print (cls.userinfo);
return;
}
if (Cmd_Argc() != 3)
{
Con_Printf ("usage: setinfo [ <key> <value> ]\n");
return;
}
if (!stricmp(Cmd_Argv(1), pmodel_name) || !strcmp(Cmd_Argv(1), emodel_name))
return;
Info_SetValueForKey (cls.userinfo, Cmd_Argv(1), Cmd_Argv(2), MAX_INFO_STRING);
if (cls.state >= ca_connected)
Cmd_ForwardToServer ();
}
/*
====================
CL_Packet_f
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)
{
Con_Printf ("packet <destination> <contents>\n");
return;
}
if (!NET_StringToAdr (Cmd_Argv(1), &adr))
{
Con_Printf ("Bad address\n");
return;
}
in = Cmd_Argv(2);
out = send+4;
send[0] = send[1] = send[2] = send[3] = 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 (out-send, send, adr);
}
/*
=====================
CL_NextDemo
Called to play the next demo in the demo loop
=====================
*/
void CL_NextDemo (void)
{
char str[1024];
if (cls.demonum == -1)
return; // don't play demos
if (!cls.demos[cls.demonum][0] || cls.demonum == MAX_DEMOS)
{
cls.demonum = 0;
if (!cls.demos[cls.demonum][0])
{
// Con_Printf ("No demos listed with startdemos\n");
cls.demonum = -1;
return;
}
}
snprintf (str, sizeof(str), "playdemo %s\n", cls.demos[cls.demonum]);
Cbuf_InsertText (str);
cls.demonum++;
}
/*
=================
CL_Changing_f
Just sent as a hint to the client that they should
drop to full console
=================
*/
void CL_Changing_f (void)
{
if (cls.download) // don't change when downloading
return;
S_StopAllSounds (true);
cl.intermission = 0;
cls.state = ca_connected; // not active anymore, but not disconnected
Con_Printf ("\nChanging map...\n");
}
/*
=================
CL_Reconnect_f
The server is changing levels
=================
*/
void CL_Reconnect_f (void)
{
if (cls.download) // don't change when downloading
return;
S_StopAllSounds (true);
if (cls.state == ca_connected) {
Con_Printf ("reconnecting...\n");
MSG_WriteChar (&cls.netchan.message, clc_stringcmd);
MSG_WriteString (&cls.netchan.message, "new");
return;
}
if (!*cls.servername) {
Con_Printf("No server to reconnect to...\n");
return;
}
CL_Disconnect();
CL_BeginServerConnect();
}
/*
=================
CL_ConnectionlessPacket
Responses to broadcasts, etc
=================
*/
void CL_ConnectionlessPacket (void)
{
char *s;
int c;
MSG_BeginReading ();
MSG_ReadLong (); // skip the -1
c = MSG_ReadByte ();
if (!cls.demoplayback)
Con_Printf ("%s: ", NET_AdrToString (net_from));
// Con_DPrintf ("%s", net_message.data + 5);
if (c == S2C_CONNECTION)
{
Con_Printf ("connection\n");
if (cls.state >= ca_connected)
{
if (!cls.demoplayback)
Con_Printf ("Dup connect received. Ignored.\n");
return;
}
Netchan_Setup (&cls.netchan, net_from, cls.qport);
MSG_WriteChar (&cls.netchan.message, clc_stringcmd);
MSG_WriteString (&cls.netchan.message, "new");
cls.state = ca_connected;
Con_Printf ("Connected.\n");
allowremotecmd = false; // localid required now for remote cmds
return;
}
// remote command from gui front end
if (c == A2C_CLIENT_COMMAND)
{
char cmdtext[2048];
Con_Printf ("client command\n");
if ((*(unsigned int *)net_from.ip != *(unsigned int *)net_local_adr.ip
&& *(unsigned int *)net_from.ip != htonl(INADDR_LOOPBACK)) )
{
Con_Printf ("Command packet from remote host. Ignored.\n");
return;
}
#ifdef _WIN32
ShowWindow (mainwindow, SW_RESTORE);
SetForegroundWindow (mainwindow);
#endif
s = MSG_ReadString ();
strncpy(cmdtext, s, sizeof(cmdtext) - 1);
cmdtext[sizeof(cmdtext) - 1] = 0;
s = MSG_ReadString ();
while (*s && isspace((int)*s))
s++;
while (*s && isspace((int)(s[strlen(s) - 1])))
s[strlen(s) - 1] = 0;
if (!allowremotecmd && (!*localid->string || strcmp(localid->string, s))) {
if (!*localid->string) {
Con_Printf("===========================\n");
Con_Printf("Command packet received from local host, but no "
"localid has been set. You may need to upgrade your server "
"browser.\n");
Con_Printf("===========================\n");
return;
}
Con_Printf("===========================\n");
Con_Printf("Invalid localid on command packet received from local host. "
"\n|%s| != |%s|\n"
"You may need to reload your server browser and %s.\n",
s, localid->string, PROGRAM);
Con_Printf("===========================\n");
Cvar_Set(localid, "");
return;
}
Cbuf_AddText (cmdtext);
allowremotecmd = false;
return;
}
// print command from somewhere
if (c == A2C_PRINT)
{
Con_Printf ("print\n");
s = MSG_ReadString ();
Con_Print (s);
return;
}
// ping from somewhere
if (c == A2A_PING)
{
char data[6];
Con_Printf ("ping\n");
data[0] = 0xff;
data[1] = 0xff;
data[2] = 0xff;
data[3] = 0xff;
data[4] = A2A_ACK;
data[5] = 0;
NET_SendPacket (6, &data, net_from);
return;
}
if (c == S2C_CHALLENGE) {
Con_Printf ("challenge\n");
s = MSG_ReadString ();
cls.challenge = atoi(s);
CL_SendConnectPacket ();
return;
}
if (c == svc_disconnect) {
if (cls.demoplayback)
Host_EndGame ("End of demo");
else
Con_Printf ("svc_disconnect\n");
// Host_EndGame ("Server disconnected");
return;
}
Con_Printf ("unknown: %c\n", c);
}
/*
=================
CL_ReadPackets
=================
*/
void CL_ReadPackets (void)
{
// while (NET_GetPacket ())
while (CL_GetMessage())
{
//
// remote command packet
//
if (*(int *)net_message.data == -1)
{
CL_ConnectionlessPacket ();
continue;
}
if (net_message.cursize < 8)
{
Con_Printf ("%s: Runt packet\n",NET_AdrToString(net_from));
continue;
}
//
// packet from server
//
if (!cls.demoplayback &&
!NET_CompareAdr (net_from, cls.netchan.remote_address))
{
Con_DPrintf ("%s:sequenced packet without connection\n"
,NET_AdrToString(net_from));
continue;
}
if (!Netchan_Process(&cls.netchan))
continue; // wasn't accepted for some reason
CL_ParseServerMessage ();
// if (cls.demoplayback && cls.state >= ca_active && !CL_DemoBehind())
// return;
}
//
// check timeout
//
if (cls.state >= ca_connected
&& realtime - cls.netchan.last_received > cl_timeout->value)
{
Con_Printf ("\nServer connection timed out.\n");
CL_Disconnect ();
return;
}
}
//=============================================================================
/*
=====================
CL_Download_f
=====================
*/
void CL_Download_f (void)
{
char *p, *q;
if (cls.state == ca_disconnected)
{
Con_Printf ("Must be connected.\n");
return;
}
if (Cmd_Argc() != 2)
{
Con_Printf ("Usage: download <datafile>\n");
return;
}
snprintf (cls.downloadname, sizeof(cls.downloadname), "%s/%s", com_gamedir, Cmd_Argv(1));
p = cls.downloadname;
for (;;) {
if ((q = strchr(p, '/')) != NULL) {
*q = 0;
Sys_mkdir(cls.downloadname);
*q = '/';
p = q + 1;
} else
break;
}
strncpy (cls.downloadtempname, cls.downloadname, sizeof(cls.downloadtempname));
cls.download = Qopen (cls.downloadname, "wb");
cls.downloadtype = dl_single;
MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
SZ_Print (&cls.netchan.message, va("download %s\n",Cmd_Argv(1)));
}
#ifdef _WIN32
#include <windows.h>
/*
=================
CL_Minimize_f
=================
*/
void CL_Windows_f (void) {
// if (modestate == MS_WINDOWED)
// ShowWindow(mainwindow, SW_MINIMIZE);
// else
SendMessage(mainwindow, WM_SYSKEYUP, VK_TAB, 1 | (0x0F << 16) | (1<<29));
}
#endif
/*
=================
CL_Init
=================
*/
void CL_Init (void)
{
QFile *servlist;
char st[80];
char e_path[MAX_OSPATH];
cls.state = ca_disconnected;
Info_SetValueForKey (cls.userinfo, "name", "unnamed", MAX_INFO_STRING);
Info_SetValueForKey (cls.userinfo, "topcolor", "0", MAX_INFO_STRING);
Info_SetValueForKey (cls.userinfo, "bottomcolor", "0", MAX_INFO_STRING);
Info_SetValueForKey (cls.userinfo, "rate", "2500", MAX_INFO_STRING);
Info_SetValueForKey (cls.userinfo, "msg", "1", MAX_INFO_STRING);
// sprintf (st, "%s-%04d", QW_VERSION, build_number());
sprintf (st, "%s", QW_VERSION);
Info_SetValueForStarKey (cls.userinfo, "*ver", st, MAX_INFO_STRING);
Info_SetValueForStarKey (cls.userinfo, "stdver", QSG_VERSION, MAX_INFO_STRING);
// set the capabilities info. single char flags (possibly with modifiefs)
// defined capabilities:
// z client can accept gzipped files.
Info_SetValueForStarKey (cls.userinfo, "*cap", "z", MAX_INFO_STRING);
CL_InitInput ();
CL_InitTEnts ();
CL_InitPrediction ();
CL_InitCam ();
Pmove_Init ();
Qexpand_squiggle(fs_userpath->string, e_path);
if ((servlist = Qopen(va("%s/servers.txt", e_path), "r"))) {
slist = SL_LoadF(servlist,slist);
Qclose(servlist);
}
//
// register our commands
//
Cmd_AddCommand ("version", CL_Version_f);
Cmd_AddCommand ("changing", CL_Changing_f);
Cmd_AddCommand ("disconnect", CL_Disconnect_f);
Cmd_AddCommand ("record", CL_Record_f);
Cmd_AddCommand ("rerecord", CL_ReRecord_f);
Cmd_AddCommand ("stop", CL_Stop_f);
Cmd_AddCommand ("playdemo", CL_PlayDemo_f);
Cmd_AddCommand ("timedemo", CL_TimeDemo_f);
Cmd_AddCommand ("skins", Skin_Skins_f);
Cmd_AddCommand ("allskins", Skin_AllSkins_f);
Cmd_AddCommand ("maplist", COM_Maplist_f);
Cmd_AddCommand ("quit", CL_Quit_f);
Cmd_AddCommand ("connect", CL_Connect_f);
Cmd_AddCommand ("reconnect", CL_Reconnect_f);
Cmd_AddCommand ("rcon", CL_Rcon_f);
Cmd_AddCommand ("packet", CL_Packet_f);
Cmd_AddCommand ("user", CL_User_f);
Cmd_AddCommand ("users", CL_Users_f);
Cmd_AddCommand ("setinfo", CL_SetInfo_f);
Cmd_AddCommand ("fullinfo", CL_FullInfo_f);
Cmd_AddCommand ("fullserverinfo", CL_FullServerinfo_f);
Cmd_AddCommand ("color", CL_Color_f);
Cmd_AddCommand ("download", CL_Download_f);
Cmd_AddCommand ("nextul", CL_NextUpload);
Cmd_AddCommand ("stopul", CL_StopUpload);
//
// forward to server commands
//
Cmd_AddCommand ("kill", NULL);
Cmd_AddCommand ("pause", NULL);
Cmd_AddCommand ("say", NULL);
Cmd_AddCommand ("say_team", NULL);
Cmd_AddCommand ("serverinfo", NULL);
//
// Windows commands
//
#ifdef _WIN32
Cmd_AddCommand ("windows", CL_Windows_f);
#endif
}
void CL_InitCvars (void)
{
extern cvar_t *baseskin;
extern cvar_t *noskins;
show_fps = Cvar_Get("show_fps", "0", CVAR_NONE, "None");
host_speeds = Cvar_Get("host_speeds", "0", CVAR_NONE, "None");
developer = Cvar_Get("developer", "0", CVAR_NONE, "None");
cl_autoexec = Cvar_Get ("cl_autoexec","0",CVAR_ROM,"exec autoexec.cfg on gamedir change");
cl_warncmd = Cvar_Get("cl_warncmd", "0", CVAR_NONE, "None");
cl_upspeed = Cvar_Get("cl_upspeed", "200", CVAR_NONE, "None");
cl_forwardspeed = Cvar_Get("cl_forwardspeed", "200", CVAR_ARCHIVE, "None");
cl_backspeed = Cvar_Get("cl_backspeed", "200", CVAR_ARCHIVE, "None");
cl_sidespeed = Cvar_Get("cl_sidespeed", "350", CVAR_NONE, "None");
cl_movespeedkey = Cvar_Get("cl_movespeedkey", "2.0", CVAR_NONE, "None");
cl_yawspeed = Cvar_Get("cl_yawspeed", "140", CVAR_NONE, "None");
cl_pitchspeed = Cvar_Get("cl_pitchspeed", "150", CVAR_NONE, "None");
cl_anglespeedkey = Cvar_Get("cl_anglespeedkey", "1.5", CVAR_NONE, "None");
cl_shownet = Cvar_Get("cl_shownet", "0", CVAR_NONE, "None");
cl_sbar = Cvar_Get("cl_sbar", "0", CVAR_ARCHIVE, "None");
cl_hudswap = Cvar_Get("cl_hudswap", "0", CVAR_ARCHIVE, "None");
cl_maxfps = Cvar_Get("cl_maxfps", "0", CVAR_ARCHIVE, "None");
cl_timeout = Cvar_Get("cl_timeout", "60", CVAR_ARCHIVE, "None");
lookspring = Cvar_Get("lookspring", "0", CVAR_ARCHIVE, "None");
lookstrafe = Cvar_Get("lookstrafe", "0", CVAR_ARCHIVE, "None");
sensitivity = Cvar_Get("sensitivity", "3", CVAR_ARCHIVE, "None");
cl_freelook = Cvar_Get("freelook", "0", CVAR_ARCHIVE, "None");
m_pitch = Cvar_Get("m_pitch", "0.022", CVAR_ARCHIVE, "None");
m_yaw = Cvar_Get("m_yaw", "0.022", CVAR_NONE, "None");
m_forward = Cvar_Get("m_forward", "1", CVAR_NONE, "None");
m_side = Cvar_Get("m_side", "0.8", CVAR_NONE, "None");
rcon_password = Cvar_Get("rcon_password", "", CVAR_NONE, "None");
rcon_address = Cvar_Get("rcon_address", "", CVAR_NONE, "None");
entlatency = Cvar_Get("entlatency", "20", CVAR_NONE, "None");
cl_predict_players2 = Cvar_Get("cl_predict_players2", "1", CVAR_NONE, "None");
cl_predict_players = Cvar_Get("cl_predict_players", "1", CVAR_NONE, "None");
cl_solid_players = Cvar_Get("cl_solid_players", "1", CVAR_NONE, "None");
localid = Cvar_Get("localid", "", CVAR_NONE, "None");
baseskin = Cvar_Get("baseskin", "base", CVAR_NONE, "None");
noskins = Cvar_Get("noskins", "0", CVAR_NONE, "None");
//
// info mirrors
//
name = Cvar_Get("name", "unnamed", CVAR_ARCHIVE|CVAR_USERINFO, "None");
password = Cvar_Get("password", "", CVAR_USERINFO, "None");
spectator = Cvar_Get("spectator", "", CVAR_USERINFO, "None");
skin = Cvar_Get("skin", "", CVAR_ARCHIVE|CVAR_USERINFO, "None");
team = Cvar_Get("team", "", CVAR_ARCHIVE|CVAR_USERINFO, "None");
topcolor = Cvar_Get("topcolor", "0", CVAR_ARCHIVE|CVAR_USERINFO, "None");
bottomcolor = Cvar_Get("bottomcolor", "0", CVAR_ARCHIVE|CVAR_USERINFO, "None");
rate = Cvar_Get("rate", "2500", CVAR_ARCHIVE|CVAR_USERINFO, "None");
msg = Cvar_Get("msg", "1", CVAR_ARCHIVE|CVAR_USERINFO, "None");
noaim = Cvar_Get("noaim", "0", CVAR_ARCHIVE|CVAR_USERINFO, "None");
}
/*
================
Host_EndGame
Call this to drop to a console without exiting the qwcl
================
*/
void Host_EndGame (char *message, ...)
{
va_list argptr;
char string[1024];
va_start (argptr, message);
vsnprintf (string, sizeof(string), message, argptr);
va_end (argptr);
Con_Printf ("\n===========================\n");
Con_Printf ("Host_EndGame: %s\n",string);
Con_Printf ("===========================\n\n");
CL_Disconnect ();
longjmp (host_abort, 1);
}
/*
================
Host_Error
This shuts down the client and exits qwcl
================
*/
void Host_Error (char *error, ...)
{
va_list argptr;
char string[1024];
static qboolean inerror = false;
if (inerror)
Sys_Error ("Host_Error: recursively entered");
inerror = true;
va_start (argptr,error);
vsnprintf (string, sizeof(string), error, argptr);
va_end (argptr);
Con_Printf ("Host_Error: %s\n",string);
CL_Disconnect ();
cls.demonum = -1;
inerror = false;
// FIXME
Sys_Error ("Host_Error: %s\n",string);
}
/*
===============
Host_WriteConfiguration
Writes key bindings and archived cvars to config.cfg
===============
*/
void Host_WriteConfiguration (void)
{
QFile *f;
if (host_initialized)
{
f = Qopen (va("%s/config.cfg",com_gamedir), "w");
if (!f)
{
Con_Printf ("Couldn't write config.cfg.\n");
return;
}
Key_WriteBindings (f);
Cvar_WriteVariables (f);
Qclose (f);
}
}
//============================================================================
#if 0
/*
==================
Host_SimulationTime
This determines if enough time has passed to run a simulation frame
==================
*/
qboolean Host_SimulationTime(float time)
{
float fps;
if (oldrealtime > realtime)
oldrealtime = 0;
if (cl_maxfps->value)
fps = max(30.0, min(cl_maxfps->value, 72.0));
else
fps = max(30.0, min(rate->value/80.0, 72.0));
if (!cls.timedemo && (realtime + time) - oldrealtime < 1.0/fps)
return false; // framerate is too high
return true;
}
#endif
/*
==================
Host_Frame
Runs all active servers
==================
*/
int nopacketcount;
void Host_Frame (float time)
{
static double time1 = 0;
static double time2 = 0;
static double time3 = 0;
int pass1, pass2, pass3;
float fps;
if (setjmp (host_abort) )
return; // something bad happened, or the server disconnected
// decide the simulation time
realtime += time;
if (oldrealtime > realtime)
oldrealtime = 0;
if (cl_maxfps->value)
fps = max(30.0, min(cl_maxfps->value, 72.0));
else
fps = max(30.0, min(rate->value/80.0, 72.0));
if (!cls.timedemo && realtime - oldrealtime < 1.0/fps)
return; // framerate is too high
host_frametime = realtime - oldrealtime;
oldrealtime = realtime;
if (host_frametime > 0.2)
host_frametime = 0.2;
// get new key events
IN_SendKeyEvents ();
// allow mice or other external controllers to add commands
IN_Commands ();
// process console commands
Cbuf_Execute ();
// fetch results from server
CL_ReadPackets ();
// send intentions now
// resend a connection request if necessary
if (cls.state == ca_disconnected) {
CL_CheckForResend ();
} else
CL_SendCmd ();
// Set up prediction for other players
CL_SetUpPlayerPrediction(false);
// do client side motion prediction
CL_PredictMove ();
// Set up prediction for other players
CL_SetUpPlayerPrediction(true);
// build a refresh entity list
CL_EmitEntities ();
// update video
if (host_speeds->value)
time1 = Sys_DoubleTime ();
SCR_UpdateScreen ();
if (host_speeds->value)
time2 = Sys_DoubleTime ();
// update audio
if (cls.state == ca_active)
{
S_Update (r_origin, vpn, vright, vup);
CL_DecayLights ();
}
else
S_Update (vec3_origin, vec3_origin, vec3_origin, vec3_origin);
CDAudio_Update();
if (host_speeds->value)
{
pass1 = (time1 - time3)*1000;
time3 = Sys_DoubleTime ();
pass2 = (time2 - time1)*1000;
pass3 = (time3 - time2)*1000;
Con_Printf ("%3i tot %3i server %3i gfx %3i snd\n",
pass1+pass2+pass3, pass1, pass2, pass3);
}
host_framecount++;
fps_count++;
}
static void simple_crypt(char *buf, int len)
{
while (len--)
*buf++ ^= 0xff;
}
void Host_FixupModelNames(void)
{
simple_crypt(emodel_name, sizeof(emodel_name) - 1);
simple_crypt(pmodel_name, sizeof(pmodel_name) - 1);
simple_crypt(prespawn_name, sizeof(prespawn_name) - 1);
simple_crypt(modellist_name, sizeof(modellist_name) - 1);
simple_crypt(soundlist_name, sizeof(soundlist_name) - 1);
}
//============================================================================
/*
====================
Host_Init
====================
*/
void Host_Init (quakeparms_t *parms)
{
COM_InitArgv (parms->argc, parms->argv);
if (COM_CheckParm ("-minmemory"))
parms->memsize = MINIMUM_MEMORY;
host_parms = *parms;
if (parms->memsize < MINIMUM_MEMORY)
Sys_Error ("Only %4.1f megs of memory reported, can't execute game",
parms->memsize / (float)0x100000);
Memory_Init (parms->membase, parms->memsize);
Cvar_Init ();
Sys_Init();
CL_InitCvars ();
Cbuf_Init ();
Cmd_Init ();
cl_Cmd_Init ();
// execute +set as early as possible
Cmd_StuffCmds_f ();
Cbuf_Execute_Sets ();
// execute the global configuration file if it exists
// would have been nice if Cmd_Exec_f could have been used, but it
// only reads from within the quake file system, and changing that is
// probably Not A Good Thing (tm).
fs_globalcfg = Cvar_Get("fs_globalcfg", FS_GLOBALCFG,
CVAR_ROM, "global configuration file");
Cmd_Exec_File (fs_globalcfg->string);
Cbuf_Execute_Sets ();
Cmd_StuffCmds_f ();
Cbuf_Execute_Sets ();
V_Init ();
SCR_InitCvars ();
VID_InitCvars ();
COM_Init ();
CL_InitTeamplay ();
// reparse the command line for + commands other than set
// (sets still done, but it doesn't matter)
Cmd_StuffCmds_f ();
Cbuf_Execute ();
Host_FixupModelNames();
NET_Init (PORT_CLIENT);
Netchan_Init ();
W_LoadWadFile ("gfx.wad");
Key_Init ();
Con_Init ();
M_Init ();
Mod_Init ();
// Con_Printf ("Exe: "__TIME__" "__DATE__"\n");
Con_Printf ("%4.1f megs RAM used.\n",parms->memsize/ (1024*1024.0));
R_InitTextures ();
host_basepal = (byte *)COM_LoadHunkFile ("gfx/palette.lmp");
if (!host_basepal)
Sys_Error ("Couldn't load gfx/palette.lmp");
host_colormap = (byte *)COM_LoadHunkFile ("gfx/colormap.lmp");
if (!host_colormap)
Sys_Error ("Couldn't load gfx/colormap.lmp");
#ifdef __linux__
CDAudio_Init ();
VID_Init (host_basepal);
IN_Init ();
Draw_Init ();
SCR_Init ();
R_Init ();
S_Init ();
cls.state = ca_disconnected;
Sbar_Init ();
CL_Init ();
#else
VID_Init (host_basepal);
Draw_Init ();
SCR_Init ();
R_Init ();
S_Init ();
cls.state = ca_disconnected;
CDAudio_Init ();
Sbar_Init ();
CL_Init ();
IN_Init ();
#endif
Cbuf_InsertText ("exec quake.rc\n");
Cbuf_AddText ("echo Type connect <internet address> or use GameSpy to connect to a game.\n");
Cbuf_AddText ("cl_warncmd 1\n");
Hunk_AllocName (0, "-HOST_HUNKLEVEL-");
host_hunklevel = Hunk_LowMark ();
host_initialized = true;
Con_Printf ("\nClient Version %s (Build %04d)\n\n", VERSION, build_number());
Con_Printf ("<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> %s Initialized <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>\n", PROGRAM);
}
/*
===============
Host_Shutdown
FIXME: this is a callback from Sys_Quit and Sys_Error. It would be better
to run quit through here before the final handoff to the sys code.
===============
*/
void Host_Shutdown(void)
{
static qboolean isdown = false;
if (isdown)
{
printf ("recursive shutdown\n");
return;
}
isdown = true;
SL_Shutdown (slist);
Host_WriteConfiguration ();
CDAudio_Shutdown ();
NET_Shutdown ();
S_Shutdown();
IN_Shutdown ();
if (host_basepal)
VID_Shutdown();
}