/* * 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 * 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 \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", "1", 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; // USERINFO cvars are special, they just need to be registered Cvar_Get("password", "", CVAR_USERINFO); Cvar_Get("spectator", "0", CVAR_USERINFO); 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); } } } int GLimp_GetRefreshRate(void); qboolean R_IsVSyncActive(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 (R_IsVSyncActive()) { 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) // FIXME: why > ?? { 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(); 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(); }