#include "client.h"
alias_ecs_Instance *client_global_ecs_instance = NULL;
alias_ecs_LayerHandle client_global_ecs_main_layer = 0;
cvar_t *freelook;
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_maxfps;
cvar_t *cl_gun;
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 *lookspring;
cvar_t *lookstrafe;
cvar_t *sensitivity;
cvar_t *m_pitch;
cvar_t *m_yaw;
cvar_t *m_forward;
cvar_t *m_side;
cvar_t *cl_lightlevel;
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_WriteDemoMessage(void) {
int len, swlen;
len = net_message.cursize - 8;
swlen = LittleLong(len);
fwrite(&swlen, 4, 1, cls.demofile);
fwrite(net_message.data + 8, len, 1, cls.demofile);
}
void CL_Stop_f(void) {
int len;
if(!cls.demorecording) {
Com_Printf("Not recording a demo.\n");
return;
}
len = -1;
fwrite(&len, 4, 1, cls.demofile);
fclose(cls.demofile);
cls.demofile = NULL;
cls.demorecording = false;
Com_Printf("Stopped demo.\n");
}
void CL_Record_f(void) {
char name[MAX_OSPATH];
uint8_t 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;
}
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;
cls.demowaiting = true;
SZ_Init(&buf, buf_data, sizeof(buf_data));
MSG_WriteByte(&buf, svc_serverdata);
MSG_WriteLong(&buf, PROTOCOL_VERSION);
MSG_WriteLong(&buf, 0x10000 + cl.servercount);
MSG_WriteByte(&buf, 1); MSG_WriteString(&buf, cl.gamedir);
MSG_WriteShort(&buf, cl.playernum);
MSG_WriteString(&buf, cl.configstrings[CS_NAME]);
for(i = 0; i < MAX_CONFIGSTRINGS; i++) {
if(cl.configstrings[i][0]) {
if(buf.cursize + strlen(cl.configstrings[i]) + 32 > buf.maxsize) { 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]);
}
}
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) { 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");
len = LittleLong(buf.cursize);
fwrite(&len, 4, 1, cls.demofile);
fwrite(buf.data, buf.cursize, 1, cls.demofile);
}
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 argc = Cmd_Argc();
if(argc > 2) {
char buffer[1000];
int i;
strcpy(buffer, Cmd_Argv(1));
strcat(buffer, "=");
for(i = 2; i < argc; i++) {
strcat(buffer, Cmd_Argv(i));
strcat(buffer, " ");
}
putenv(buffer);
} else if(argc == 2) {
char *env = getenv(Cmd_Argv(1));
if(env) {
Com_Printf("%s=%s\n", Cmd_Argv(1), 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;
}
if(Cmd_Argc() > 1) {
MSG_WriteByte(&cls.netchan.message, clc_stringcmd);
SZ_Print(&cls.netchan.message, Cmd_Args());
}
}
void CL_Pause_f(void) {
if(Cvar_VariableValue("maxclients") > 1 || !Com_ServerState()) {
Cvar_SetValue("paused", 0);
return;
}
Cvar_SetValue("paused", !cl_paused->value);
}
void CL_Quit_f(void) {
CL_Disconnect();
Com_Quit();
}
void CL_Drop(void) {
if(cls.state == ca_uninitialized)
return;
if(cls.state == ca_disconnected)
return;
CL_Disconnect();
if(cls.disable_servercount != -1)
SCR_EndLoadingPlaque(); }
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());
}
void CL_CheckForResend(void) {
netadr_t adr;
if(cls.state == ca_disconnected && Com_ServerState()) {
cls.state = ca_connecting;
strncpy(cls.servername, "localhost", sizeof(cls.servername) - 1);
CL_SendConnectPacket();
return;
}
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;
if(Cmd_Argc() != 2) {
Com_Printf("usage: connect <server>\n");
return;
}
if(Com_ServerState()) { SV_Shutdown(va("Server quit\n", msg), false);
} else {
CL_Disconnect();
}
server = Cmd_Argv(1);
NET_Config(true);
CL_Disconnect();
cls.state = ca_connecting;
strncpy(cls.servername, server, sizeof(cls.servername) - 1);
cls.connect_time = -99999; }
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);
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_ClearState(void) {
S_StopAllSounds();
CL_ClearEffects();
CL_ClearTEnts();
memset(&cl, 0, sizeof(cl));
memset(&cl_entities, 0, sizeof(cl_entities));
SZ_Clear(&cls.netchan.message);
}
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();
cls.connect_time = 0;
SCR_StopCinematic();
if(cls.demorecording)
CL_Stop_f();
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();
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"); }
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);
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);
}
void CL_Changing_f(void) {
if(cls.download)
return;
SCR_BeginLoadingPlaque();
cls.state = ca_connected; Com_Printf("\nChanging map...\n");
}
void CL_Reconnect_f(void) {
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;
cls.state = ca_connecting;
Com_Printf("reconnecting...\n");
}
}
void CL_ParseStatusMessage(void) {
char *s;
s = MSG_ReadString(&net_message);
Com_Printf("%s\n", s);
M_AddToServerList(net_from, s);
}
void CL_PingServers_f(void) {
int i;
netadr_t adr;
char name[32];
char *adrstring;
cvar_t *noudp;
NET_Config(true);
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));
}
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));
}
}
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(); CL_ParseClientinfo(i);
}
}
void CL_ConnectionlessPacket(void) {
char *s;
char *c;
MSG_BeginReading(&net_message);
MSG_ReadLong(&net_message);
s = MSG_ReadStringLine(&net_message);
Cmd_TokenizeString(s, false);
c = Cmd_Argv(0);
Com_Printf("%s: %s\n", NET_AdrToString(net_from), c);
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;
}
if(!strcmp(c, "info")) {
CL_ParseStatusMessage();
return;
}
if(!strcmp(c, "cmd")) {
if(!NET_IsLocalAddress(net_from)) {
Com_Printf("Command packet from remote host. Ignored.\n");
return;
}
Sys_AppActivate();
s = MSG_ReadString(&net_message);
Cbuf_AddText(s);
Cbuf_AddText("\n");
return;
}
if(!strcmp(c, "print")) {
s = MSG_ReadString(&net_message);
Com_Printf("%s", s);
return;
}
if(!strcmp(c, "ping")) {
Netchan_OutOfBandPrint(NS_CLIENT, net_from, "ack");
return;
}
if(!strcmp(c, "challenge")) {
cls.challenge = atoi(Cmd_Argv(1));
CL_SendConnectPacket();
return;
}
if(!strcmp(c, "echo")) {
Netchan_OutOfBandPrint(NS_CLIENT, net_from, "%s", Cmd_Argv(1));
return;
}
Com_Printf("Unknown command.\n");
}
void CL_DumpPackets(void) {
while(NET_GetPacket(NS_CLIENT, &net_from, &net_message)) {
Com_Printf("dumnping a packet\n");
}
}
void CL_ReadPackets(void) {
while(NET_GetPacket(NS_CLIENT, &net_from, &net_message)) {
if(*(int *)net_message.data == -1) {
CL_ConnectionlessPacket();
continue;
}
if(cls.state == ca_disconnected || cls.state == ca_connecting)
continue;
if(net_message.cursize < 8) {
Com_Printf("%s: Runt packet\n", NET_AdrToString(net_from));
continue;
}
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; CL_ParseServerMessage();
}
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;
}
void CL_FixUpGender(void) {
char *p;
char sk[80];
if(gender_auto->value) {
if(gender->modified) {
gender->modified = false;
return;
}
strncpy(sk, skin->string, sizeof(sk) - 1);
if((p = strchr(sk, '/')) != NULL)
*p = 0;
if(Q_stricmp(sk, "male") == 0 || Q_stricmp(sk, "cyborg") == 0)
Cvar_Set("gender", "male");
else if(Q_stricmp(sk, "female") == 0 || Q_stricmp(sk, "crackhor") == 0)
Cvar_Set("gender", "female");
else
Cvar_Set("gender", "none");
gender->modified = false;
}
}
void CL_Userinfo_f(void) {
Com_Printf("User info settings:\n");
Info_Print(Cvar_Userinfo());
}
void CL_Snd_Restart_f(void) {
S_Shutdown();
S_Init();
CL_RegisterSounds();
}
int precache_check; int precache_spawncount;
int precache_tex;
int precache_model_skin;
byte *precache_model;
#define PLAYER_MULT 5
#define ENV_CNT (CS_PLAYERSKINS + MAX_CLIENTS * PLAYER_MULT)
#define TEXTURE_CNT (ENV_CNT + 13)
static const char *env_suf[6] = {"rt", "bk", "lf", "ft", "up", "dn"};
void CL_RequestNextDownload(void) {
unsigned map_checksum; 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;
if(precache_check == CS_MODELS) { precache_check = CS_MODELS + CMODEL_COUNT; if(allow_download_maps->value) {
for(int cmodel_index = 0; cmodel_index < CMODEL_COUNT; cmodel_index++) {
char path[MAX_QPATH];
memcpy(path, cl.configstrings[CS_MODELS + cmodel_index + 1], MAX_QPATH);
if(path[0] == 0)
continue;
*strchr(path, ';') = 0;
if(!CL_CheckOrDownloadFile(path))
return; }
}
}
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; }
precache_model_skin = 1;
}
if(!precache_model) {
FS_LoadFile(cl.configstrings[precache_check], (void **)&precache_model);
if(!precache_model) {
precache_model_skin = 0;
precache_check++;
continue; }
if(LittleLong(*(unsigned *)precache_model) != IDALIASHEADER) {
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; }
}
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; }
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++; 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; }
}
precache_check = CS_IMAGES;
}
if(precache_check >= CS_IMAGES && precache_check < CS_IMAGES + MAX_IMAGES) {
if(precache_check == CS_IMAGES)
precache_check++; 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; }
precache_check = CS_PLAYERSKINS;
}
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: Com_sprintf(fn, sizeof(fn), "players/%s/tris.md2", model);
if(!CL_CheckOrDownloadFile(fn)) {
precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 1;
return; }
n++;
case 1: Com_sprintf(fn, sizeof(fn), "players/%s/weapon.md2", model);
if(!CL_CheckOrDownloadFile(fn)) {
precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 2;
return; }
n++;
case 2: Com_sprintf(fn, sizeof(fn), "players/%s/weapon.pcx", model);
if(!CL_CheckOrDownloadFile(fn)) {
precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 3;
return; }
n++;
case 3: Com_sprintf(fn, sizeof(fn), "players/%s/%s.pcx", model, skin);
if(!CL_CheckOrDownloadFile(fn)) {
precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 4;
return; }
n++;
case 4: 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; }
precache_check = CS_PLAYERSKINS + (i + 1) * PLAYER_MULT;
}
}
}
precache_check = ENV_CNT;
}
if(precache_check == ENV_CNT) {
precache_check = ENV_CNT + 1;
for(int cmodel_index = 0; cmodel_index < CMODEL_COUNT; cmodel_index++) {
char path[MAX_QPATH];
memcpy(path, cl.configstrings[CS_MODELS + cmodel_index + 1], MAX_QPATH);
if(path[0] == 0)
continue;
char *sc = strchr(path, ';');
*sc = 0;
CM_LoadMap(cmodel_index, path, true, &map_checksum);
if(map_checksum != atoi(sc + 1)) {
Com_Error(ERR_DROP, "Local map version differs from server: %i != '%s'\n", map_checksum, sc + 1);
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; }
}
precache_check = TEXTURE_CNT;
}
if(precache_check == TEXTURE_CNT) {
precache_check = TEXTURE_CNT + 1;
precache_tex = 0;
}
if(precache_check == TEXTURE_CNT + 1) {
if(allow_download->value && allow_download_maps->value) {
while(precache_tex < CM_NumTextures(CMODEL_A)) {
char fn[MAX_OSPATH];
sprintf(fn, sizeof(fn), "textures/%s.wal", CM_GetTexturePrecacheName(CMODEL_A, precache_tex++));
if(!CL_CheckOrDownloadFile(fn))
return; }
}
precache_check = TEXTURE_CNT + 999;
}
CL_RegisterSounds();
CL_PrepRefresh();
MSG_WriteByte(&cls.netchan.message, clc_stringcmd);
MSG_WriteString(&cls.netchan.message, va("begin %i\n", precache_spawncount));
}
void CL_Precache_f(void) {
if(Cmd_Argc() < 2) {
unsigned map_checksum;
char path[MAX_QPATH];
memcpy(path, cl.configstrings[CS_MODELS + 1], MAX_QPATH);
*strchr(path, ';') = 0;
CM_LoadMap(CMODEL_A, path, 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();
}
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);
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_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_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);
freelook = Cvar_Get("freelook", "0", CVAR_ARCHIVE);
lookspring = Cvar_Get("lookspring", "0", CVAR_ARCHIVE);
lookstrafe = Cvar_Get("lookstrafe", "0", CVAR_ARCHIVE);
sensitivity = Cvar_Get("sensitivity", "3", CVAR_ARCHIVE);
m_pitch = Cvar_Get("m_pitch", "0.022", CVAR_ARCHIVE);
m_yaw = Cvar_Get("m_yaw", "0.022", 0);
m_forward = Cvar_Get("m_forward", "1", 0);
m_side = Cvar_Get("m_side", "1", 0);
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);
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); 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;
cl_vwep = Cvar_Get("cl_vwep", "1", CVAR_ARCHIVE);
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("snd_restart", CL_Snd_Restart_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("setenv", CL_Setenv_f);
Cmd_AddCommand("precache", CL_Precache_f);
Cmd_AddCommand("download", CL_Download_f);
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);
}
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);
}
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;
if(!numcheatvars) {
while(cheatvars[numcheatvars].name) {
cheatvars[numcheatvars].var = Cvar_Get(cheatvars[numcheatvars].name, cheatvars[numcheatvars].value, 0);
numcheatvars++;
}
}
for(i = 0, var = cheatvars; i < numcheatvars; i++, var++) {
if(strcmp(var->var->string, var->value)) {
Cvar_Set(var->name, var->value);
}
}
}
void CL_SendCommand(void) {
Sys_SendKeyEvents();
IN_Commands();
Cbuf_Execute();
CL_FixCvarCheats();
CL_SendCmd();
CL_CheckForResend();
}
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; if(extratime < 1000 / cl_maxfps->value)
return; }
IN_Frame();
cls.frametime = extratime / 1000.0;
cl.time += extratime;
cls.realtime = curtime;
extratime = 0;
if(cls.frametime > (1.0 / 5))
cls.frametime = (1.0 / 5);
if(msec > 5000)
cls.netchan.last_received = Sys_Milliseconds();
CL_ReadPackets();
CL_SendCommand();
CL_PredictMovement();
VID_CheckChanges();
if(!cl.refresh_prepped && cls.state == ca_active)
CL_PrepRefresh();
if(host_speeds->value)
time_before_ref = Sys_Milliseconds();
SCR_UpdateScreen();
if(host_speeds->value)
time_after_ref = Sys_Milliseconds();
S_Update(cl.refdef.vieworg, cl.v_forward, cl.v_right, cl.v_up);
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;
}
}
}
}
void CL_Init(void) {
if(dedicated->value)
return;
Con_Init();
#if defined __linux__ || defined __sgi
S_Init();
VID_Init();
#else
VID_Init();
S_Init(); #endif
V_Init();
net_message.data = net_message_buffer;
net_message.maxsize = sizeof(net_message_buffer);
M_Init();
SCR_Init();
cls.disable_screen = true;
CL_InitLocal();
IN_Init();
FS_ExecAutoexec();
Cbuf_Execute();
}
void CL_Shutdown(void) {
static bool isdown = false;
if(isdown) {
printf("recursive shutdown\n");
return;
}
isdown = true;
CL_WriteConfiguration();
S_Shutdown();
IN_Shutdown();
VID_Shutdown();
}