ref: d36975e335e73da20cb130c8c7bcd839de8831dd
dir: /cl_main.c/
// cl_main.c -- client main loop
#include <u.h>
#include <libc.h>
#include <stdio.h>
#include "dat.h"
#include "fns.h"
cvar_t *adr0;
cvar_t *adr1;
cvar_t *adr2;
cvar_t *adr3;
cvar_t *adr4;
cvar_t *adr5;
cvar_t *adr6;
cvar_t *adr7;
cvar_t *adr8;
cvar_t *cl_stereo_separation;
cvar_t *cl_stereo;
cvar_t *rcon_client_password;
cvar_t *rcon_address;
cvar_t *cl_noskins;
cvar_t *cl_autoskins;
cvar_t *cl_footsteps;
cvar_t *cl_timeout;
cvar_t *cl_predict;
//cvar_t *cl_minfps;
cvar_t *cl_maxfps;
cvar_t *cl_gun;
cvar_t *cl_add_particles;
cvar_t *cl_add_lights;
cvar_t *cl_add_entities;
cvar_t *cl_add_blend;
cvar_t *cl_shownet;
cvar_t *cl_showmiss;
cvar_t *cl_showclamp;
cvar_t *cl_paused;
cvar_t *cl_timedemo;
cvar_t *cl_lightlevel;
//
// userinfo
//
cvar_t *info_password;
cvar_t *info_spectator;
cvar_t *name;
cvar_t *skin;
cvar_t *rate;
cvar_t *fov;
cvar_t *msg;
cvar_t *hand;
cvar_t *gender;
cvar_t *gender_auto;
cvar_t *cl_vwep;
client_static_t cls;
client_state_t cl;
centity_t cl_entities[MAX_EDICTS];
entity_state_t cl_parse_entities[MAX_PARSE_ENTITIES];
extern cvar_t *allow_download;
extern cvar_t *allow_download_players;
extern cvar_t *allow_download_models;
extern cvar_t *allow_download_sounds;
extern cvar_t *allow_download_maps;
//======================================================================
void
CL_SetGameInput(k)
{
switch(cls.key_dest = k){
case key_game:
IN_Grabm(1);
if(Cvar_VariableValue("maxclients") == 1 && Com_ServerState())
Cvar_Set("paused", "0");
break;
default:
IN_Grabm(0);
if(Cvar_VariableValue("maxclients") == 1 && Com_ServerState())
Cvar_Set("paused", "1");
break;
}
}
/*
====================
CL_WriteDemoMessage
Dumps the current net message, prefixed by the length
====================
*/
void CL_WriteDemoMessage (void)
{
int len, swlen;
// the first eight bytes are just packet sequencing stuff
len = net_message.cursize-8;
swlen = LittleLong(len);
fwrite (&swlen, 4, 1, cls.demofile);
fwrite (net_message.data+8, len, 1, cls.demofile);
}
/*
====================
CL_Stop_f
stop recording a demo
====================
*/
void CL_Stop_f (void)
{
int len;
if (!cls.demorecording)
{
Com_Printf ("Not recording a demo.\n");
return;
}
// finish up
len = -1;
fwrite (&len, 4, 1, cls.demofile);
fclose (cls.demofile);
cls.demofile = NULL;
cls.demorecording = false;
Com_Printf ("Stopped demo.\n");
}
/*
====================
CL_Record_f
record <demoname>
Begins recording a demo from the current position
====================
*/
void CL_Record_f (void)
{
char name[MAX_OSPATH];
char buf_data[MAX_MSGLEN];
sizebuf_t buf;
int i;
int len;
entity_state_t *ent;
entity_state_t nullstate;
if (Cmd_Argc() != 2)
{
Com_Printf ("record <demoname>\n");
return;
}
if (cls.demorecording)
{
Com_Printf ("Already recording.\n");
return;
}
if (cls.state != ca_active)
{
Com_Printf ("You must be in a level to record.\n");
return;
}
//
// open the demo file
//
Com_sprintf (name, sizeof(name), "%s/demos/%s.dm2", FS_Gamedir(), Cmd_Argv(1));
Com_Printf ("recording to %s.\n", name);
FS_CreatePath (name);
cls.demofile = fopen (name, "wb");
if (!cls.demofile)
{
Com_Printf ("ERROR: couldn't open.\n");
return;
}
cls.demorecording = true;
// don't start saving messages until a non-delta compressed message is received
cls.demowaiting = true;
//
// write out messages to hold the startup information
//
SZ_Init (&buf, (uchar *)buf_data, sizeof(buf_data));
// send the serverdata
MSG_WriteByte (&buf, svc_serverdata);
MSG_WriteLong (&buf, PROTOCOL_VERSION);
MSG_WriteLong (&buf, 0x10000 + cl.servercount);
MSG_WriteByte (&buf, 1); // demos are always attract loops
MSG_WriteString (&buf, cl.gamedir);
MSG_WriteShort (&buf, cl.playernum);
MSG_WriteString (&buf, cl.configstrings[CS_NAME]);
// configstrings
for (i=0 ; i<MAX_CONFIGSTRINGS ; i++)
{
if (cl.configstrings[i][0])
{
if (buf.cursize + strlen (cl.configstrings[i]) + 32 > buf.maxsize)
{ // write it out
len = LittleLong (buf.cursize);
fwrite (&len, 4, 1, cls.demofile);
fwrite (buf.data, buf.cursize, 1, cls.demofile);
buf.cursize = 0;
}
MSG_WriteByte (&buf, svc_configstring);
MSG_WriteShort (&buf, i);
MSG_WriteString (&buf, cl.configstrings[i]);
}
}
// baselines
memset (&nullstate, 0, sizeof(nullstate));
for (i=0; i<MAX_EDICTS ; i++)
{
ent = &cl_entities[i].baseline;
if (!ent->modelindex)
continue;
if (buf.cursize + 64 > buf.maxsize)
{ // write it out
len = LittleLong (buf.cursize);
fwrite (&len, 4, 1, cls.demofile);
fwrite (buf.data, buf.cursize, 1, cls.demofile);
buf.cursize = 0;
}
MSG_WriteByte (&buf, svc_spawnbaseline);
MSG_WriteDeltaEntity (&nullstate, &cl_entities[i].baseline, &buf, true, true);
}
MSG_WriteByte (&buf, svc_stufftext);
MSG_WriteString (&buf, "precache\n");
// write it to the demo file
len = LittleLong (buf.cursize);
fwrite (&len, 4, 1, cls.demofile);
fwrite (buf.data, buf.cursize, 1, cls.demofile);
// the rest of the demo file will be individual frames
}
/* 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_Setenv_f(void)
{
int i, l = 0, argc;
char name[1024], val[1024], *env;
argc = Cmd_Argc();
if(argc > 2){
strncpy(name, Cmd_Argv(1), sizeof name);
for(i = 2; i < argc; i++){
strncpy(val+l, Cmd_Argv(i), sizeof(name) - l - 2);
val[sizeof(val)-2] = 0;
strcat(val, " ");
l = strlen(val);
}
putenv(name, val);
}else if(argc == 2){
env = getenv(Cmd_Argv(1));
if(env){
Com_Printf("%s=%s\n", Cmd_Argv(1), env);
free(env);
}else
Com_Printf("%s undefined\n", Cmd_Argv(1), env);
}
}
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());
}
}
/*
==================
CL_Pause_f
==================
*/
void CL_Pause_f (void)
{
// never pause in multiplayer
if (Cvar_VariableValue ("maxclients") > 1 || !Com_ServerState ())
{
Cvar_SetValue ("paused", 0);
return;
}
Cvar_SetValue ("paused", !cl_paused->value);
}
/*
==================
CL_Quit_f
==================
*/
void CL_Quit_f (void)
{
CL_Disconnect ();
Com_Quit ();
}
/*
================
CL_Drop
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
}
/*
=======================
CL_SendConnectPacket
We have gotten a challenge from the server, so try and
connect.
======================
*/
void CL_SendConnectPacket (void)
{
netadr_t adr;
int port;
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() );
}
/*
=================
CL_CheckForResend
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
// then connect
if (cls.state == ca_disconnected && Com_ServerState() )
{
cls.state = ca_connecting;
strncpy (cls.servername, "localhost", sizeof(cls.servername)-1);
// we don't need a challenge on the localhost
CL_SendConnectPacket ();
return;
// cls.connect_time = -99999; // CL_CheckForResend() will fire immediately
}
// 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; // for retransmit requests
Com_Printf ("Connecting to %s...\n", cls.servername);
Netchan_OutOfBandPrint (NS_CLIENT, adr, "getchallenge\n");
}
/*
================
CL_Connect_f
================
*/
void CL_Connect_f (void)
{
char *server;
if (Cmd_Argc() != 2)
{
Com_Printf ("usage: connect <server>\n");
return;
}
if (Com_ServerState ())
{ // if running a local server, kill it and reissue
SV_Shutdown (va("Server quit\n", msg), false);
}
else
{
CL_Disconnect ();
}
server = Cmd_Argv (1);
NET_Config (true); // allow remote
CL_Disconnect ();
cls.state = ca_connecting;
strncpy (cls.servername, server, sizeof(cls.servername)-1);
cls.connect_time = -99999; // CL_CheckForResend() will fire immediately
}
/*
=====================
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_client_password->string)
{
Com_Printf ("You must set 'rcon_password' before\n"
"issuing an rcon command.\n");
return;
}
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);
}
/*
=====================
CL_ClearState
=====================
*/
void CL_ClearState (void)
{
S_StopAllSounds ();
CL_ClearEffects ();
CL_ClearTEnts ();
// wipe the entire cl structure
memset (&cl, 0, sizeof(cl));
memset (cl_entities, 0, sizeof(cl_entities));
SZ_Clear (&cls.netchan.message);
}
/*
=====================
CL_Disconnect
Goes from a connected state to full screen console state
Sends a disconnect message to the server
This is also called on Com_Error, so it shouldn't cause any errors
=====================
*/
void CL_Disconnect (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);
re.CinematicSetPalette(NULL);
M_ForceMenuOff(false);
CDAudio_Stop();
cls.connect_time = 0;
SCR_StopCinematic ();
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((char *)final), final);
Netchan_Transmit (&cls.netchan, strlen((char *)final), final);
Netchan_Transmit (&cls.netchan, strlen((char *)final), final);
CL_ClearState ();
// stop download
if (cls.download) {
fclose(cls.download);
cls.download = NULL;
}
cls.state = ca_disconnected;
}
void CL_Disconnect_f (void)
{
Com_Error (ERR_DROP, "Disconnected from server");
}
/*
====================
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)
{
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);
}
/*
=================
CL_Changing_f
Just sent as a hint to the client that they should
drop to full console
=================
*/
void CL_Changing_f (void)
{
//ZOID
//if we are downloading, we don't change! This so we don't suddenly stop downloading a map
if (cls.download)
return;
SCR_BeginLoadingPlaque ();
cls.state = ca_connected; // not active anymore, but not disconnected
Com_Printf ("\nChanging map...\n");
}
/*
=================
CL_Reconnect_f
The server is changing levels
=================
*/
void CL_Reconnect_f (void)
{
//ZOID
//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; // fire immediately
cls.state = ca_connecting;
Com_Printf ("reconnecting...\n");
}
}
/*
=================
CL_ParseStatusMessage
Handle a reply from a ping
=================
*/
void CL_ParseStatusMessage (void)
{
char *s;
s = MSG_ReadString(&net_message);
Com_Printf ("%s\n", s);
M_AddToServerList (net_from, s);
}
/*
=================
CL_PingServers_f
=================
*/
void CL_PingServers_f (void)
{
int i;
netadr_t adr;
char name[32];
char *adrstring;
cvar_t *noudp;
cvar_t *noipx;
NET_Config (true); // allow remote
// 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));
}
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));
}
}
/*
=================
CL_Skins_f
Load or download any custom player skins and models
=================
*/
void CL_Skins_f (void)
{
int i;
for (i=0 ; i<MAX_CLIENTS ; i++)
{
if (!cl.configstrings[CS_PLAYERSKINS+i][0])
continue;
Com_Printf ("client %i: %s\n", i, cl.configstrings[CS_PLAYERSKINS+i]);
SCR_UpdateScreen ();
Sys_SendKeyEvents (); // pump message loop
CL_ParseClientinfo (i);
}
}
/*
=================
CL_ConnectionlessPacket
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);
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 = atoi(Cmd_Argv(1));
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");
}
/*
=================
CL_DumpPackets
A vain attempt to help bad TCP stacks that cause problems
when they overflow
=================
*/
void CL_DumpPackets (void)
{
while (NET_GetPacket (NS_CLIENT, &net_from, &net_message))
{
Com_Printf ("dumnping a packet\n");
}
}
/*
=================
CL_ReadPackets
=================
*/
void CL_ReadPackets (void)
{
while (NET_GetPacket (NS_CLIENT, &net_from, &net_message))
{
// Com_Printf ("packet\n");
//
// 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) // timeoutcount saves debugger
{
Com_Printf ("\nServer connection timed out.\n");
CL_Disconnect ();
return;
}
}
else
cl.timeoutcount = 0;
}
//=============================================================================
/*
==============
CL_FixUpGender_f
==============
*/
void CL_FixUpGender(void)
{
char *p;
char sk[80];
if (gender_auto->value) {
if (gender->modified) {
// was set directly, don't override the user
gender->modified = false;
return;
}
strncpy(sk, skin->string, sizeof(sk) - 1);
if ((p = strchr(sk, '/')) != NULL)
*p = 0;
if (cistrcmp(sk, "male") == 0 || cistrcmp(sk, "cyborg") == 0)
Cvar_Set ("gender", "male");
else if (cistrcmp(sk, "female") == 0 || cistrcmp(sk, "crackhor") == 0)
Cvar_Set ("gender", "female");
else
Cvar_Set ("gender", "none");
gender->modified = false;
}
}
/*
==============
CL_Userinfo_f
==============
*/
void CL_Userinfo_f (void)
{
Com_Printf ("User info settings:\n");
Info_Print (Cvar_Userinfo());
}
int precache_check; // for autodownload of precache items
int precache_spawncount;
int precache_tex;
int precache_model_skin;
byte *precache_model; // used for skin checking in alias models
#define PLAYER_MULT 5
// ENV_CNT is map load, ENV_CNT+1 is first env map
#define ENV_CNT (CS_PLAYERSKINS + MAX_CLIENTS * PLAYER_MULT)
#define TEXTURE_CNT (ENV_CNT+13)
static char *env_suf[6] = {"rt", "bk", "lf", "ft", "up", "dn"};
void CL_RequestNextDownload (void)
{
unsigned map_checksum; // for detecting cheater maps
char fn[MAX_OSPATH];
dmdl_t *pheader;
if (cls.state != ca_connected)
return;
if (!allow_download->value && precache_check < ENV_CNT)
precache_check = ENV_CNT;
//ZOID
if (precache_check == CS_MODELS) { // confirm map
precache_check = CS_MODELS+2; // 0 isn't used
if (allow_download_maps->value)
if (!CL_CheckOrDownloadFile(cl.configstrings[CS_MODELS+1]))
return; // started a download
}
if (precache_check >= CS_MODELS && precache_check < CS_MODELS+MAX_MODELS) {
if (allow_download_models->value) {
while (precache_check < CS_MODELS+MAX_MODELS &&
cl.configstrings[precache_check][0]) {
if (cl.configstrings[precache_check][0] == '*' ||
cl.configstrings[precache_check][0] == '#') {
precache_check++;
continue;
}
if (precache_model_skin == 0) {
if (!CL_CheckOrDownloadFile(cl.configstrings[precache_check])) {
precache_model_skin = 1;
return; // started a download
}
precache_model_skin = 1;
}
// checking for skins in the model
if (!precache_model) {
FS_LoadFile (cl.configstrings[precache_check], (void **)&precache_model);
if (!precache_model) {
precache_model_skin = 0;
precache_check++;
continue; // couldn't load it
}
if (LittleLong(*(unsigned *)precache_model) != IDALIASHEADER) {
// not an alias model
FS_FreeFile(precache_model);
precache_model = 0;
precache_model_skin = 0;
precache_check++;
continue;
}
pheader = (dmdl_t *)precache_model;
if (LittleLong (pheader->version) != ALIAS_VERSION) {
precache_check++;
precache_model_skin = 0;
continue; // couldn't load it
}
}
pheader = (dmdl_t *)precache_model;
while (precache_model_skin - 1 < LittleLong(pheader->num_skins)) {
if (!CL_CheckOrDownloadFile((char *)precache_model +
LittleLong(pheader->ofs_skins) +
(precache_model_skin - 1)*MAX_SKINNAME)) {
precache_model_skin++;
return; // started a download
}
precache_model_skin++;
}
if (precache_model) {
FS_FreeFile(precache_model);
precache_model = 0;
}
precache_model_skin = 0;
precache_check++;
}
}
precache_check = CS_SOUNDS;
}
if (precache_check >= CS_SOUNDS && precache_check < CS_SOUNDS+MAX_SOUNDS) {
if (allow_download_sounds->value) {
if (precache_check == CS_SOUNDS)
precache_check++; // zero is blank
while (precache_check < CS_SOUNDS+MAX_SOUNDS &&
cl.configstrings[precache_check][0]) {
if (cl.configstrings[precache_check][0] == '*') {
precache_check++;
continue;
}
Com_sprintf(fn, sizeof(fn), "sound/%s", cl.configstrings[precache_check++]);
if (!CL_CheckOrDownloadFile(fn))
return; // started a download
}
}
precache_check = CS_IMAGES;
}
if (precache_check >= CS_IMAGES && precache_check < CS_IMAGES+MAX_IMAGES) {
if (precache_check == CS_IMAGES)
precache_check++; // zero is blank
while (precache_check < CS_IMAGES+MAX_IMAGES &&
cl.configstrings[precache_check][0]) {
Com_sprintf(fn, sizeof(fn), "pics/%s.pcx", cl.configstrings[precache_check++]);
if (!CL_CheckOrDownloadFile(fn))
return; // started a download
}
precache_check = CS_PLAYERSKINS;
}
// skins are special, since a player has three things to download:
// model, weapon model and skin
// so precache_check is now *3
if (precache_check >= CS_PLAYERSKINS && precache_check < CS_PLAYERSKINS + MAX_CLIENTS * PLAYER_MULT) {
if (allow_download_players->value) {
while (precache_check < CS_PLAYERSKINS + MAX_CLIENTS * PLAYER_MULT) {
int i, n;
char model[MAX_QPATH], skin[MAX_QPATH], *p;
i = (precache_check - CS_PLAYERSKINS)/PLAYER_MULT;
n = (precache_check - CS_PLAYERSKINS)%PLAYER_MULT;
if (!cl.configstrings[CS_PLAYERSKINS+i][0]) {
precache_check = CS_PLAYERSKINS + (i + 1) * PLAYER_MULT;
continue;
}
if ((p = strchr(cl.configstrings[CS_PLAYERSKINS+i], '\\')) != NULL)
p++;
else
p = cl.configstrings[CS_PLAYERSKINS+i];
strcpy(model, p);
p = strchr(model, '/');
if (!p)
p = strchr(model, '\\');
if (p) {
*p++ = 0;
strcpy(skin, p);
} else
*skin = 0;
switch (n) {
case 0: // model
Com_sprintf(fn, sizeof(fn), "players/%s/tris.md2", model);
if (!CL_CheckOrDownloadFile(fn)) {
precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 1;
return; // started a download
}
/*FALL THROUGH*/
case 1: // weapon model
Com_sprintf(fn, sizeof(fn), "players/%s/weapon.md2", model);
if (!CL_CheckOrDownloadFile(fn)) {
precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 2;
return; // started a download
}
/*FALL THROUGH*/
case 2: // weapon skin
Com_sprintf(fn, sizeof(fn), "players/%s/weapon.pcx", model);
if (!CL_CheckOrDownloadFile(fn)) {
precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 3;
return; // started a download
}
/*FALL THROUGH*/
case 3: // skin
Com_sprintf(fn, sizeof(fn), "players/%s/%s.pcx", model, skin);
if (!CL_CheckOrDownloadFile(fn)) {
precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 4;
return; // started a download
}
/*FALL THROUGH*/
case 4: // skin_i
Com_sprintf(fn, sizeof(fn), "players/%s/%s_i.pcx", model, skin);
if (!CL_CheckOrDownloadFile(fn)) {
precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 5;
return; // started a download
}
// move on to next model
precache_check = CS_PLAYERSKINS + (i + 1) * PLAYER_MULT;
}
}
}
// precache phase completed
precache_check = ENV_CNT;
}
if (precache_check == ENV_CNT) {
precache_check = ENV_CNT + 1;
CM_LoadMap (cl.configstrings[CS_MODELS+1], true, &map_checksum);
if (map_checksum != atoi(cl.configstrings[CS_MAPCHECKSUM])) {
Com_Error (ERR_DROP, "Local map version differs from server: %i != '%s'\n",
map_checksum, cl.configstrings[CS_MAPCHECKSUM]);
return;
}
}
if (precache_check > ENV_CNT && precache_check < TEXTURE_CNT) {
if (allow_download->value && allow_download_maps->value) {
while (precache_check < TEXTURE_CNT) {
int n = precache_check++ - ENV_CNT - 1;
if (n & 1)
Com_sprintf(fn, sizeof(fn), "env/%s%s.pcx",
cl.configstrings[CS_SKY], env_suf[n/2]);
else
Com_sprintf(fn, sizeof(fn), "env/%s%s.tga",
cl.configstrings[CS_SKY], env_suf[n/2]);
if (!CL_CheckOrDownloadFile(fn))
return; // started a download
}
}
precache_check = TEXTURE_CNT;
}
if (precache_check == TEXTURE_CNT) {
precache_check = TEXTURE_CNT+1;
precache_tex = 0;
}
// confirm existance of textures, download any that don't exist
if (precache_check == TEXTURE_CNT+1) {
// from qcommon/cmodel.c
extern int numtexinfo;
extern mapsurface_t map_surfaces[];
if (allow_download->value && allow_download_maps->value) {
while (precache_tex < numtexinfo) {
char fn[MAX_OSPATH];
sprintf(fn, "textures/%s.wal", map_surfaces[precache_tex++].rname);
if (!CL_CheckOrDownloadFile(fn))
return; // started a download
}
}
precache_check = TEXTURE_CNT+999;
}
//ZOID
CL_RegisterSounds ();
CL_PrepRefresh ();
MSG_WriteByte (&cls.netchan.message, clc_stringcmd);
MSG_WriteString (&cls.netchan.message, va("begin %i\n", precache_spawncount) );
}
/*
=================
CL_Precache_f
The server will send this command right
before allowing the client into the server
=================
*/
void CL_Precache_f (void)
{
//Yet another hack to let old demos work
//the old precache sequence
if (Cmd_Argc() < 2) {
unsigned map_checksum; // for detecting cheater maps
CM_LoadMap (cl.configstrings[CS_MODELS+1], true, &map_checksum);
CL_RegisterSounds ();
CL_PrepRefresh ();
return;
}
precache_check = CS_MODELS;
precache_spawncount = atoi(Cmd_Argv(1));
precache_model = 0;
precache_model_skin = 0;
CL_RequestNextDownload();
}
/*
=================
CL_InitLocal
=================
*/
void CL_InitLocal (void)
{
cls.state = ca_disconnected;
cls.realtime = Sys_Milliseconds ();
CL_InitInput ();
adr0 = Cvar_Get( "adr0", "", CVAR_ARCHIVE );
adr1 = Cvar_Get( "adr1", "", CVAR_ARCHIVE );
adr2 = Cvar_Get( "adr2", "", CVAR_ARCHIVE );
adr3 = Cvar_Get( "adr3", "", CVAR_ARCHIVE );
adr4 = Cvar_Get( "adr4", "", CVAR_ARCHIVE );
adr5 = Cvar_Get( "adr5", "", CVAR_ARCHIVE );
adr6 = Cvar_Get( "adr6", "", CVAR_ARCHIVE );
adr7 = Cvar_Get( "adr7", "", CVAR_ARCHIVE );
adr8 = Cvar_Get( "adr8", "", CVAR_ARCHIVE );
//
// register our variables
//
cl_stereo_separation = Cvar_Get( "cl_stereo_separation", "0.4", CVAR_ARCHIVE );
cl_stereo = Cvar_Get( "cl_stereo", "0", 0 );
cl_add_blend = Cvar_Get ("cl_blend", "1", 0);
cl_add_lights = Cvar_Get ("cl_lights", "1", 0);
cl_add_particles = Cvar_Get ("cl_particles", "1", 0);
cl_add_entities = Cvar_Get ("cl_entities", "1", 0);
cl_gun = Cvar_Get ("cl_gun", "1", 0);
cl_footsteps = Cvar_Get ("cl_footsteps", "1", 0);
cl_noskins = Cvar_Get ("cl_noskins", "0", 0);
cl_autoskins = Cvar_Get ("cl_autoskins", "0", 0);
cl_predict = Cvar_Get ("cl_predict", "1", 0);
// cl_minfps = Cvar_Get ("cl_minfps", "5", 0);
cl_maxfps = Cvar_Get ("cl_maxfps", "90", 0);
cl_upspeed = Cvar_Get ("cl_upspeed", "200", 0);
cl_forwardspeed = Cvar_Get ("cl_forwardspeed", "200", 0);
cl_sidespeed = Cvar_Get ("cl_sidespeed", "200", 0);
cl_yawspeed = Cvar_Get ("cl_yawspeed", "140", 0);
cl_pitchspeed = Cvar_Get ("cl_pitchspeed", "150", 0);
cl_anglespeedkey = Cvar_Get ("cl_anglespeedkey", "1.5", 0);
cl_run = Cvar_Get ("cl_run", "0", CVAR_ARCHIVE);
cl_shownet = Cvar_Get ("cl_shownet", "0", 0);
cl_showmiss = Cvar_Get ("cl_showmiss", "0", 0);
cl_showclamp = Cvar_Get ("showclamp", "0", 0);
cl_timeout = Cvar_Get ("cl_timeout", "120", 0);
cl_paused = Cvar_Get ("paused", "0", 0);
cl_timedemo = Cvar_Get ("timedemo", "0", 0);
rcon_client_password = Cvar_Get ("rcon_password", "", 0);
rcon_address = Cvar_Get ("rcon_address", "", 0);
cl_lightlevel = Cvar_Get ("r_lightlevel", "0", 0);
//
// userinfo
//
info_password = Cvar_Get ("password", "", CVAR_USERINFO);
info_spectator = Cvar_Get ("spectator", "0", CVAR_USERINFO);
name = Cvar_Get ("name", "unnamed", CVAR_USERINFO | CVAR_ARCHIVE);
skin = Cvar_Get ("skin", "male/grunt", CVAR_USERINFO | CVAR_ARCHIVE);
rate = Cvar_Get ("rate", "25000", CVAR_USERINFO | CVAR_ARCHIVE); // FIXME
msg = Cvar_Get ("msg", "1", CVAR_USERINFO | CVAR_ARCHIVE);
hand = Cvar_Get ("hand", "0", CVAR_USERINFO | CVAR_ARCHIVE);
fov = Cvar_Get ("fov", "90", CVAR_USERINFO | CVAR_ARCHIVE);
gender = Cvar_Get ("gender", "male", CVAR_USERINFO | CVAR_ARCHIVE);
gender_auto = Cvar_Get ("gender_auto", "1", CVAR_ARCHIVE);
gender->modified = false; // clear this so we know when user sets it manually
cl_vwep = Cvar_Get ("cl_vwep", "1", CVAR_ARCHIVE);
//
// register our commands
//
Cmd_AddCommand ("cmd", CL_ForwardToServer_f);
Cmd_AddCommand ("pause", CL_Pause_f);
Cmd_AddCommand ("pingservers", CL_PingServers_f);
Cmd_AddCommand ("skins", CL_Skins_f);
Cmd_AddCommand ("userinfo", CL_Userinfo_f);
Cmd_AddCommand ("changing", CL_Changing_f);
Cmd_AddCommand ("disconnect", CL_Disconnect_f);
Cmd_AddCommand ("record", CL_Record_f);
Cmd_AddCommand ("stop", CL_Stop_f);
Cmd_AddCommand ("quit", CL_Quit_f);
Cmd_AddCommand ("connect", CL_Connect_f);
Cmd_AddCommand ("reconnect", CL_Reconnect_f);
Cmd_AddCommand ("rcon", CL_Rcon_f);
// Cmd_AddCommand ("packet", CL_Packet_f); // this is dangerous to leave in
Cmd_AddCommand ("setenv", CL_Setenv_f );
Cmd_AddCommand ("precache", CL_Precache_f);
Cmd_AddCommand ("download", CL_Download_f);
//
// forward to server commands
//
// the only thing this does is allow command completion
// to work -- all unknown commands are automatically
// forwarded to the server
Cmd_AddCommand ("wave", NULL);
Cmd_AddCommand ("inven", NULL);
Cmd_AddCommand ("kill", NULL);
Cmd_AddCommand ("use", NULL);
Cmd_AddCommand ("drop", NULL);
Cmd_AddCommand ("say", NULL);
Cmd_AddCommand ("say_team", NULL);
Cmd_AddCommand ("info", NULL);
Cmd_AddCommand ("prog", NULL);
Cmd_AddCommand ("give", NULL);
Cmd_AddCommand ("god", NULL);
Cmd_AddCommand ("notarget", NULL);
Cmd_AddCommand ("noclip", NULL);
Cmd_AddCommand ("invuse", NULL);
Cmd_AddCommand ("invprev", NULL);
Cmd_AddCommand ("invnext", NULL);
Cmd_AddCommand ("invdrop", NULL);
Cmd_AddCommand ("weapnext", NULL);
Cmd_AddCommand ("weapprev", NULL);
}
/*
===============
CL_WriteConfiguration
Writes key bindings and archived cvars to config.cfg
===============
*/
void CL_WriteConfiguration (void)
{
FILE *f;
char path[MAX_QPATH];
if (cls.state == ca_uninitialized)
return;
Com_sprintf (path, sizeof(path),"%s/config.cfg",FS_Gamedir());
f = fopen (path, "w");
if (!f)
{
Com_Printf ("Couldn't write config.cfg.\n");
return;
}
fprintf (f, "// generated by quake, do not modify\n");
Key_WriteBindings (f);
fclose (f);
Cvar_WriteVariables (path);
}
/*
==================
CL_FixCvarCheats
==================
*/
typedef struct
{
char *name;
char *value;
cvar_t *var;
} cheatvar_t;
cheatvar_t cheatvars[] = {
{"timescale", "1"},
{"timedemo", "0"},
{"r_drawworld", "1"},
{"cl_testlights", "0"},
{"r_fullbright", "0"},
{"r_drawflat", "0"},
{"paused", "0"},
{"fixedtime", "0"},
{"sw_draworder", "0"},
{"gl_lightmap", "0"},
{"gl_saturatelighting", "0"},
{NULL, NULL}
};
int numcheatvars;
void CL_FixCvarCheats (void)
{
int i;
cheatvar_t *var;
if ( !strcmp(cl.configstrings[CS_MAXCLIENTS], "1")
|| !cl.configstrings[CS_MAXCLIENTS][0] )
return; // single player can cheat
// find all the cvars if we haven't done it yet
if (!numcheatvars)
{
while (cheatvars[numcheatvars].name)
{
cheatvars[numcheatvars].var = Cvar_Get (cheatvars[numcheatvars].name,
cheatvars[numcheatvars].value, 0);
numcheatvars++;
}
}
// make sure they are all set to the proper values
for (i=0, var = cheatvars ; i<numcheatvars ; i++, var++)
{
if ( strcmp (var->var->string, var->value) )
{
Cvar_Set (var->name, var->value);
}
}
}
//============================================================================
/*
==================
CL_SendCommand
==================
*/
void CL_SendCommand (void)
{
// get new key events
Sys_SendKeyEvents ();
// allow mice or other external controllers to add commands
IN_Commands ();
// process console commands
Cbuf_Execute ();
// fix any cheating cvars
CL_FixCvarCheats ();
// send intentions now
CL_SendCmd ();
// resend a connection request if necessary
CL_CheckForResend ();
}
/*
==================
CL_Frame
==================
*/
void CL_Frame (int msec)
{
static int extratime;
static int lasttimecalled;
if (dedicated->value)
return;
extratime += msec;
if (!cl_timedemo->value)
{
if (cls.state == ca_connected && extratime < 100)
return; // don't flood packets out while connecting
if (extratime < 1000/cl_maxfps->value)
return; // framerate is too high
}
// let the mouse activate or deactivate
IN_Frame ();
// decide the simulation time
cls.frametime = extratime/1000.0;
cl.time += extratime;
cls.realtime = curtime;
extratime = 0;
/*
if (cls.frametime > (1.0 / cl_minfps->value))
cls.frametime = (1.0 / cl_minfps->value);
*/
if (cls.frametime > (1.0 / 5))
cls.frametime = (1.0 / 5);
// if in the debugger last frame, don't timeout
if (msec > 5000)
cls.netchan.last_received = Sys_Milliseconds ();
// fetch results from server
CL_ReadPackets ();
// send a new command message to the server
CL_SendCommand ();
// predict all unacknowledged movements
CL_PredictMovement ();
// allow rendering DLL change
if (!cl.refresh_prepped && cls.state == ca_active)
CL_PrepRefresh ();
// update the screen
if (host_speeds->value)
time_before_ref = Sys_Milliseconds ();
SCR_UpdateScreen ();
if (host_speeds->value)
time_after_ref = Sys_Milliseconds ();
// update audio
S_Update (cl.refdef.vieworg, cl.v_forward, cl.v_right, cl.v_up);
CDAudio_Update();
// advance local effects for next frame
CL_RunDLights ();
CL_RunLightStyles ();
SCR_RunCinematic ();
SCR_RunConsole ();
cls.framecount++;
if ( log_stats->value )
{
if ( cls.state == ca_active )
{
if ( !lasttimecalled )
{
lasttimecalled = Sys_Milliseconds();
if ( log_stats_file )
fprintf( log_stats_file, "0\n" );
}
else
{
int now = Sys_Milliseconds();
if ( log_stats_file )
fprintf( log_stats_file, "%d\n", now - lasttimecalled );
lasttimecalled = now;
}
}
}
}
//============================================================================
/*
====================
CL_Init
====================
*/
void CL_Init (void)
{
if(dedicated->value){
IN_Init();
return; // nothing running on the client
}
// all archived variables will now be loaded
Con_Init ();
initsnd();
initfb();
IN_Init();
V_Init ();
net_message.data = net_message_buffer;
net_message.maxsize = sizeof(net_message_buffer);
M_Init ();
SCR_Init ();
cls.disable_screen = true; // don't draw yet
CDAudio_Init ();
CL_InitLocal ();
// Cbuf_AddText ("exec autoexec.cfg\n");
FS_ExecAutoexec ();
Cbuf_Execute ();
}
/*
===============
CL_Shutdown
FIXME: this is a callback from Sys_Quit and Com_Error. It would be better
to run quit through here before the final handoff to the sys code.
===============
*/
void CL_Shutdown(void)
{
static qboolean isdown = false;
if (isdown)
{
printf ("recursive shutdown\n");
return;
}
isdown = true;
CL_WriteConfiguration ();
IN_Shutdown ();
CDAudio_Shutdown ();
shutsnd();
R_Shutdown();
}