mirror of
https://github.com/yquake2/yquake2remaster.git
synced 2025-01-09 11:21:34 +00:00
4d9d555d8e
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.
1024 lines
20 KiB
C
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();
|
|
}
|
|
|