yquake2remaster/src/client/cl_main.c
Yamagi Burmeister 4d9d555d8e When vsync is enabled, cap the desired rfps.
With vsync enabled the render times of consecutive frames can diverge.
The first frame arrives right at the next display frame and is rendered
without waiting time. The next frame has to wait 20ms. The leads to some
problems with the move prediction if the client is asynchronous. Fix
this by capping the desired frame rate at the display refresh rate. Also
make sure that the network framerate is never higher then the renderer
framerate.

With the commit the timing is always correct:
* With no limit as much frames as possible are rendered. In this case
  rfps > nfps and everything's good.
* With vsync enabled rfps > nfps or rfps == nfps is given. Also rfps
  will never exceed the display refresh rate.
* On slow hardware either rfps > nfps or an implicit rfpc == nfps is
  given.
2016-08-14 16:35:48 +02:00

1024 lines
20 KiB
C

/*
* Copyright (C) 1997-2001 Id Software, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
* =======================================================================
*
* This is the clients main loop as well as some miscelangelous utility
* and support functions
*
* =======================================================================
*/
#include "header/client.h"
#include "../backends/generic/header/input.h"
void CL_ForwardToServer_f(void);
void CL_Changing_f(void);
void CL_Reconnect_f(void);
void CL_Connect_f(void);
void CL_Rcon_f(void);
void CL_CheckForResend(void);
cvar_t *freelook;
cvar_t *rcon_client_password;
cvar_t *rcon_address;
cvar_t *cl_noskins;
cvar_t *cl_footsteps;
cvar_t *cl_timeout;
cvar_t *cl_predict;
cvar_t *cl_maxfps;
cvar_t *cl_drawfps;
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_async;
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;
/* userinfo */
cvar_t *name;
cvar_t *skin;
cvar_t *rate;
cvar_t *fov;
cvar_t *horplus;
cvar_t *windowed_mouse;
cvar_t *msg;
cvar_t *hand;
cvar_t *gender;
cvar_t *gender_auto;
cvar_t *gl_maxfps;
cvar_t *gl_stereo;
cvar_t *gl_stereo_separation;
cvar_t *gl_stereo_convergence;
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;
/*
* 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);
}
/*
* stop recording a demo
*/
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");
}
/*
* record <demoname>
* Begins recording a demo from the current position
*/
void
CL_Record_f(void)
{
char name[MAX_OSPATH];
byte 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;
/* 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, 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)
{
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)
{
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);
}
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));
}
}
}
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);
}
void
CL_Quit_f(void)
{
CL_Disconnect();
Com_Quit();
}
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);
}
/*
* 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);
}
/*
* 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);
}
}
/* This fixes some problems with wrong tagged models and skins */
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;
}
Q_strlcpy(sk, skin->string, sizeof(sk));
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());
}
/*
* Restart the sound subsystem so it can pick up
* new parameters and flush all sounds
*/
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;
/*
* 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 */
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 = (int)strtol(Cmd_Argv(1), (char **)NULL, 10);
precache_model = 0;
precache_model_skin = 0;
CL_RequestNextDownload();
}
void
CL_InitLocal(void)
{
cls.state = ca_disconnected;
cls.realtime = Sys_Milliseconds();
CL_InitInput();
/* register our variables */
cin_force43 = Cvar_Get("cin_force43", "1", 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", "2", CVAR_ARCHIVE);
cl_footsteps = Cvar_Get("cl_footsteps", "1", 0);
cl_noskins = Cvar_Get("cl_noskins", "0", 0);
cl_predict = Cvar_Get("cl_predict", "1", 0);
cl_maxfps = Cvar_Get("cl_maxfps", "60", CVAR_ARCHIVE);
cl_drawfps = Cvar_Get("cl_drawfps", "0", CVAR_ARCHIVE);
cl_async = Cvar_Get("cl_async", "0", CVAR_ARCHIVE);
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", "1", 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);
gl_maxfps = Cvar_Get("gl_maxfps", "95", CVAR_ARCHIVE);
gl_stereo = Cvar_Get( "gl_stereo", "0", CVAR_ARCHIVE );
gl_stereo_separation = Cvar_Get( "gl_stereo_separation", "1", CVAR_ARCHIVE );
gl_stereo_convergence = Cvar_Get( "gl_stereo_convergence", "1.4", CVAR_ARCHIVE );
rcon_client_password = Cvar_Get("rcon_password", "", 0);
rcon_address = Cvar_Get("rcon_address", "", 0);
cl_lightlevel = Cvar_Get("gl_lightlevel", "0", 0);
/* userinfo */
name = Cvar_Get("name", "unnamed", CVAR_USERINFO | CVAR_ARCHIVE);
skin = Cvar_Get("skin", "male/grunt", CVAR_USERINFO | CVAR_ARCHIVE);
rate = Cvar_Get("rate", "8000", 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);
horplus = Cvar_Get("horplus", "1", CVAR_ARCHIVE);
windowed_mouse = Cvar_Get("windowed_mouse", "1", 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);
/* 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("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);
/* 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);
}
/*
* Writes key bindings and archived cvars to config.cfg
*/
void
CL_WriteConfiguration(void)
{
FILE *f;
char path[MAX_OSPATH];
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);
fflush(f);
fclose(f);
Cvar_WriteVariables(path);
}
typedef struct
{
char *name;
char *value;
cvar_t *var;
} cheatvar_t;
cheatvar_t cheatvars[] = {
{"timescale", "1"},
{"timedemo", "0"},
{"gl_drawworld", "1"},
{"cl_testlights", "0"},
{"gl_fullbright", "0"},
{"gl_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);
}
}
}
void
CL_UpdateWindowedMouse(void)
{
if (cls.disable_screen)
{
return;
}
if (cls.key_dest == key_menu || cls.key_dest == key_console ||
(cls.key_dest == key_game && (cls.state != ca_active || !cl.refresh_prepped)))
{
if (windowed_mouse->value)
{
Cvar_SetValue("windowed_mouse", 0);
}
}
else
{
if (!windowed_mouse->value)
{
Cvar_SetValue("windowed_mouse", 1);
}
}
}
qboolean GLimp_VsyncEnabled(void);
int GLimp_GetRefreshRate(void);
void
CL_Frame(int msec)
{
int nfps;
int rfps;
static int lasttimecalled;
static int packetdelta = 1000;
static int renderdelta = 1000;
static int miscdelta = 1000;
qboolean packetframe = true;
qboolean renderframe = true;
qboolean miscframe = true;
if (dedicated->value)
{
return;
}
// Target render frame rate
if (GLimp_VsyncEnabled())
{
rfps = GLimp_GetRefreshRate();
if (rfps > gl_maxfps->value)
{
rfps = (int)gl_maxfps->value;
}
}
else
{
rfps = (int)gl_maxfps->value;
}
// The network framerate must not be higher then the render framerate
nfps = (cl_maxfps->value > rfps) ? rfps : cl_maxfps->value;
// Adjust deltas
packetdelta += msec;
renderdelta += msec;
miscdelta += msec;
// Calculate simulation time
cls.nframetime = packetdelta * 0.001f;
cls.rframetime = renderdelta * 0.001f;
cls.realtime = curtime;
cl.time += msec;
// Don't extrapolate too far ahead
if (cls.nframetime > 0.5f)
{
cls.nframetime = 0.5f;
}
if (cls.rframetime > 0.5f)
{
cls.rframetime = 0.5f;
}
/* if in the debugger last frame, don't timeout */
if (msec > 5000)
{
cls.netchan.last_received = Sys_Milliseconds();
}
if (!cl_timedemo->value)
{
// Don't flood while connecting
if ((cls.state == ca_connected) && (packetdelta < 100))
{
packetframe = false;
}
if (cl_async->value)
{
// Network frames
if (packetdelta < (1000.0f / nfps))
{
packetframe = false;
}
else if (cls.nframetime == cls.rframetime)
{
packetframe = false;
}
// Render frames
if (renderdelta < (1000.0f / rfps))
{
renderframe = false;
}
// Misc. stuff at 10 FPS
if (miscdelta < 100.0f)
{
miscframe = false;
}
}
else
{
// Cap frames at gl_maxfps
if (renderdelta < (1000.0f / rfps))
{
renderframe = false;
packetframe = false;
miscframe = false;
}
}
// Throttle the game a little bit. 1000 FPS are enough.
if (!packetframe && !renderframe && !cls.forcePacket && !userinfo_modified)
{
double frametime = (1000.0 / cl_maxfps->value - packetdelta) <= (1000.0 / gl_maxfps->value - renderdelta) ?
(1000.0 / cl_maxfps->value - packetdelta) : (1000.0 / gl_maxfps->value - renderdelta);
if (frametime > 1)
{
Sys_Sleep(1);
}
return;
}
}
else if (msec < 1)
{
return;
}
// Update input stuff
if (packetframe || renderframe)
{
CL_ReadPackets();
CL_UpdateWindowedMouse();
Sys_SendKeyEvents();
Cbuf_Execute();
CL_FixCvarCheats();
if (cls.state > ca_connecting)
{
CL_RefreshCmd();
}
else
{
CL_RefreshMove();
}
}
if (cls.forcePacket || userinfo_modified)
{
packetframe = true;
cls.forcePacket = false;
}
if (packetframe)
{
packetdelta = 0;
CL_SendCmd();
CL_CheckForResend();
}
if (renderframe)
{
renderdelta = 0;
if (miscframe)
{
miscdelta = 0;
VID_CheckChanges();
}
CL_PredictMovement();
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);
#ifdef CDA
if (miscframe)
{
CDAudio_Update();
}
#endif
/* advance local effects for next frame */
CL_RunDLights();
CL_RunLightStyles();
SCR_RunCinematic();
SCR_RunConsole();
/* Update framecounter */
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; /* nothing running on the client */
}
/* all archived variables will now be loaded */
Con_Init();
S_Init();
SCR_Init();
VID_Init();
IN_Init();
V_Init();
net_message.data = net_message_buffer;
net_message.maxsize = sizeof(net_message_buffer);
M_Init();
cls.disable_screen = true; /* don't draw yet */
#ifdef CDA
CDAudio_Init();
#endif
CL_InitLocal();
FS_ExecAutoexec();
Cbuf_Execute();
Key_ReadConsoleHistory();
}
void
CL_Shutdown(void)
{
static qboolean isdown = false;
if (isdown)
{
printf("recursive shutdown\n");
return;
}
isdown = true;
CL_WriteConfiguration();
Key_WriteConsoleHistory();
#ifdef CDA
CDAudio_Shutdown();
#endif
#ifdef OGG
OGG_Stop();
#endif
S_Shutdown();
IN_Shutdown();
VID_Shutdown();
}