/* host.c host init Copyright (C) 1996-1997 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: Free Software Foundation, Inc. 59 Temple Place - Suite 330 Boston, MA 02111-1307, USA $Id$ */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "QF/cdaudio.h" #include "QF/compat.h" #include "QF/cmd.h" #include "QF/console.h" #include "QF/draw.h" #include "QF/gib.h" #include "QF/input.h" #include "QF/keys.h" #include "QF/msg.h" #include "QF/plugin.h" #include "QF/qargs.h" #include "QF/screen.h" #include "QF/sys.h" #include "QF/va.h" #include "QF/vid.h" #include "chase.h" #include "host.h" #include "r_local.h" #include "sbar.h" #include "server.h" #include "sv_progs.h" #include "view.h" extern void R_Particles_Init_Cvars (void); extern void R_Init_Cvars (void); extern void Host_Skin_Init (void); extern void Host_Skin_Init_Cvars (void); /* A server can always be started, even if the system started out as a client to a remote system. A client can NOT be started if the system started as a dedicated server. Memory is cleared / released when a server or client begins, not when they end. */ extern int fps_count; qboolean msg_suppress_1 = 0; quakeparms_t host_parms; qboolean host_initialized; // true if into command execution double host_frametime; double host_time; double realtime; // without any filtering or bounding double oldrealtime; // last frame run int host_framecount; int host_hunklevel; int minimum_memory; client_t *host_client; // current client jmp_buf host_abortserver; byte *vid_basepal; extern cvar_t *cl_writecfg; cvar_t *fs_globalcfg; cvar_t *fs_usercfg; cvar_t *host_framerate; cvar_t *host_speeds; cvar_t *sys_ticrate; cvar_t *serverprofile; cvar_t *fraglimit; cvar_t *timelimit; cvar_t *teamplay; cvar_t *samelevel; cvar_t *noexit; cvar_t *skill; cvar_t *deathmatch; cvar_t *coop; cvar_t *pausable; cvar_t *temp1; void Host_EndGame (char *message, ...) { va_list argptr; char string[1024]; va_start (argptr, message); vsnprintf (string, sizeof (string), message, argptr); va_end (argptr); Con_DPrintf ("Host_EndGame: %s\n", string); if (sv.active) Host_ShutdownServer (false); if (cls.state == ca_dedicated) Sys_Error ("Host_EndGame: %s\n", string); // dedicated servers exit if (cls.demonum != -1) CL_NextDemo (); else CL_Disconnect (); longjmp (host_abortserver, 1); } /* Host_Error This shuts down both the client and server */ void Host_Error (char *error, ...) { va_list argptr; char string[1024]; static qboolean inerror = false; if (inerror) Sys_Error ("Host_Error: recursively entered"); inerror = true; // SCR_EndLoadingPlaque (); // reenable screen updates va_start (argptr, error); vsnprintf (string, sizeof (string), error, argptr); va_end (argptr); Con_Printf ("Host_Error: %s\n", string); if (sv.active) Host_ShutdownServer (false); if (cls.state == ca_dedicated) Sys_Error ("Host_Error: %s\n", string); // dedicated servers exit CL_Disconnect (); cls.demonum = -1; inerror = false; longjmp (host_abortserver, 1); } void Host_FindMaxClients (void) { int i; svs.maxclients = 1; i = COM_CheckParm ("-dedicated"); if (i) { cls.state = ca_dedicated; if (i != (com_argc - 1)) { svs.maxclients = atoi (com_argv[i + 1]); } else svs.maxclients = 8; } else cls.state = ca_disconnected; i = COM_CheckParm ("-listen"); if (i) { if (cls.state == ca_dedicated) Sys_Error ("Only one of -dedicated or -listen can be specified"); if (i != (com_argc - 1)) svs.maxclients = atoi (com_argv[i + 1]); else svs.maxclients = 8; } if (svs.maxclients < 1) svs.maxclients = 8; else if (svs.maxclients > MAX_SCOREBOARD) svs.maxclients = MAX_SCOREBOARD; svs.maxclientslimit = svs.maxclients; if (svs.maxclientslimit < 4) svs.maxclientslimit = 4; svs.clients = Hunk_AllocName (svs.maxclientslimit * sizeof (client_t), "clients"); if (svs.maxclients > 1) Cvar_SetValue (deathmatch, 1.0); else Cvar_SetValue (deathmatch, 0.0); } void Host_InitLocal (void) { Host_InitCommands (); host_framerate = Cvar_Get ("host_framerate", "0", CVAR_NONE, NULL, "set for slow motion"); host_speeds = Cvar_Get ("host_speeds", "0", CVAR_NONE, NULL, "set for running times"); sys_ticrate = Cvar_Get ("sys_ticrate", "0.05", CVAR_NONE, NULL, "None"); serverprofile = Cvar_Get ("serverprofile", "0", CVAR_NONE, NULL, "None"); fraglimit = Cvar_Get ("fraglimit", "0", CVAR_SERVERINFO, Cvar_Info, "None"); timelimit = Cvar_Get ("timelimit", "0", CVAR_SERVERINFO, Cvar_Info, "None"); teamplay = Cvar_Get ("teamplay", "0", CVAR_SERVERINFO, Cvar_Info, "None"); samelevel = Cvar_Get ("samelevel", "0", CVAR_NONE, NULL, "None"); noexit = Cvar_Get ("noexit", "0", CVAR_SERVERINFO, Cvar_Info, "None"); skill = Cvar_Get ("skill", "1", CVAR_NONE, NULL, "0 - 3"); deathmatch = Cvar_Get ("deathmatch", "0", CVAR_NONE, NULL, "0, 1, or 2"); coop = Cvar_Get ("coop", "0", CVAR_NONE, NULL, "0 or 1"); pausable = Cvar_Get ("pausable", "1", CVAR_NONE, NULL, "None"); temp1 = Cvar_Get ("temp1", "0", CVAR_NONE, NULL, "None"); Host_FindMaxClients (); host_time = 1.0; // so a think at time 0 won't get called } /* Host_WriteConfiguration Writes key bindings and archived cvars to config.cfg */ void Host_WriteConfiguration (void) { QFile *f; // dedicated servers initialize the host but don't parse and set the // config.cfg cvars if (cl_writecfg->int_val && (host_initialized & !isDedicated)) { char *path = va ("%s/config.cfg", com_gamedir); f = Qopen (path, "w"); if (!f) { Con_Printf ("Couldn't write config.cfg.\n"); return; } Key_WriteBindings (f); Cvar_WriteVariables (f); Qclose (f); } } /* SV_ClientPrintf Sends text across to be displayed FIXME: make this just a stuffed echo */ void SV_ClientPrintf (char *fmt, ...) { va_list argptr; char string[1024]; va_start (argptr, fmt); vsnprintf (string, sizeof (string), fmt, argptr); va_end (argptr); MSG_WriteByte (&host_client->message, svc_print); MSG_WriteString (&host_client->message, string); } /* SV_BroadcastPrintf Sends text to all active clients */ void SV_BroadcastPrintf (char *fmt, ...) { va_list argptr; char string[1024]; int i; va_start (argptr, fmt); vsnprintf (string, sizeof (string), fmt, argptr); va_end (argptr); for (i = 0; i < svs.maxclients; i++) if (svs.clients[i].active && svs.clients[i].spawned) { MSG_WriteByte (&svs.clients[i].message, svc_print); MSG_WriteString (&svs.clients[i].message, string); } } /* Host_ClientCommands Send text over to the client to be executed */ void Host_ClientCommands (char *fmt, ...) { va_list argptr; char string[1024]; va_start (argptr, fmt); vsnprintf (string, sizeof (string), fmt, argptr); va_end (argptr); MSG_WriteByte (&host_client->message, svc_stufftext); MSG_WriteString (&host_client->message, string); } /* SV_DropClient Called when the player is getting totally kicked off the host if (crash = true), don't bother sending signofs */ void SV_DropClient (qboolean crash) { int saveSelf; int i; client_t *client; if (!crash) { // send any final messages (don't check for errors) if (NET_CanSendMessage (host_client->netconnection)) { MSG_WriteByte (&host_client->message, svc_disconnect); NET_SendMessage (host_client->netconnection, &host_client->message); } if (host_client->edict && host_client->spawned) { // call the prog function for removing a client // this will set the body to a dead frame, among other things saveSelf = *sv_globals.self; *sv_globals.self = EDICT_TO_PROG (&sv_pr_state, host_client->edict); PR_ExecuteProgram (&sv_pr_state, sv_funcs.ClientDisconnect); *sv_globals.self = saveSelf; } Sys_Printf ("Client %s removed\n", host_client->name); } // break the net connection NET_Close (host_client->netconnection); host_client->netconnection = NULL; // free the client (the body stays around) host_client->active = false; host_client->name[0] = 0; host_client->old_frags = -999999; net_activeconnections--; // send notification to all clients for (i = 0, client = svs.clients; i < svs.maxclients; i++, client++) { if (!client->active) continue; MSG_WriteByte (&client->message, svc_updatename); MSG_WriteByte (&client->message, host_client - svs.clients); MSG_WriteString (&client->message, ""); MSG_WriteByte (&client->message, svc_updatefrags); MSG_WriteByte (&client->message, host_client - svs.clients); MSG_WriteShort (&client->message, 0); MSG_WriteByte (&client->message, svc_updatecolors); MSG_WriteByte (&client->message, host_client - svs.clients); MSG_WriteByte (&client->message, 0); } } /* Host_ShutdownServer This only happens at the end of a game, not between levels */ void Host_ShutdownServer (qboolean crash) { int i; int count; sizebuf_t buf; char message[4]; double start; if (!sv.active) return; sv.active = false; // stop all client sounds immediately if (cls.state == ca_connected) CL_Disconnect (); // flush any pending messages - like the score!!! start = Sys_DoubleTime (); do { count = 0; for (i = 0, host_client = svs.clients; i < svs.maxclients; i++, host_client++) { if (host_client->active && host_client->message.cursize) { if (NET_CanSendMessage (host_client->netconnection)) { NET_SendMessage (host_client->netconnection, &host_client->message); SZ_Clear (&host_client->message); } else { NET_GetMessage (host_client->netconnection); count++; } } } if ((Sys_DoubleTime () - start) > 3.0) break; } while (count); // make sure all the clients know we're disconnecting buf.data = message; buf.maxsize = 4; buf.cursize = 0; MSG_WriteByte (&buf, svc_disconnect); count = NET_SendToAll (&buf, 5); if (count) Con_Printf ("Host_ShutdownServer: NET_SendToAll failed for %u clients\n", count); for (i = 0, host_client = svs.clients; i < svs.maxclients; i++, host_client++) if (host_client->active) SV_DropClient (crash); // // clear structures // memset (&sv, 0, sizeof (sv)); memset (svs.clients, 0, svs.maxclientslimit * sizeof (client_t)); } /* Host_ClearMemory This clears all the memory used by both the client and server, but does not reinitialize anything. */ void Host_ClearMemory (void) { Con_DPrintf ("Clearing memory\n"); D_FlushCaches (); Mod_ClearAll (); if (host_hunklevel) Hunk_FreeToLowMark (host_hunklevel); cls.signon = 0; memset (&sv, 0, sizeof (sv)); memset (&cl, 0, sizeof (cl)); } //============================================================================ /* Host_FilterTime Returns false if the time is too short to run a frame */ qboolean Host_FilterTime (float time) { realtime += time; if (!cls.timedemo && realtime - oldrealtime < 1.0 / 72.0) return false; // framerate is too high host_frametime = realtime - oldrealtime; oldrealtime = realtime; if (host_framerate->value > 0) host_frametime = host_framerate->value; else { // don't allow really long or short // frames if (host_frametime > 0.1) host_frametime = 0.1; if (host_frametime < 0.001) host_frametime = 0.001; } return true; } /* Host_GetConsoleCommands Add them exactly as if they had been typed at the console */ void Host_GetConsoleCommands (void) { char *cmd; while (1) { cmd = Sys_ConsoleInput (); if (!cmd) break; Cbuf_AddText (cmd); } } #ifdef FPS_20 void _Host_ServerFrame (void) { // run the world state *sv_globals.frametime = host_frametime; // read client messages SV_RunClients (); // move things around and think // always pause in single player if in console or menus if (!sv.paused && (svs.maxclients > 1 || key_dest == key_game)) SV_Physics (); } void Host_ServerFrame (void) { float save_host_frametime; float temp_host_frametime; // run the world state *sv_globals.frametime = host_frametime; // set the time and clear the general datagram SV_ClearDatagram (); // check for new clients SV_CheckForNewClients (); temp_host_frametime = save_host_frametime = host_frametime; while (temp_host_frametime > (1.0 / 72.0)) { if (temp_host_frametime > 0.05) host_frametime = 0.05; else host_frametime = temp_host_frametime; temp_host_frametime -= host_frametime; _Host_ServerFrame (); } host_frametime = save_host_frametime; // send all messages to the clients SV_SendClientMessages (); } #else void Host_ServerFrame (void) { // run the world state *sv_globals.frametime = host_frametime; // set the time and clear the general datagram SV_ClearDatagram (); // check for new clients SV_CheckForNewClients (); // read client messages SV_RunClients (); // move things around and think // always pause in single player if in console or menus if (!sv.paused && (svs.maxclients > 1 || key_dest == key_game)) SV_Physics (); // send all messages to the clients SV_SendClientMessages (); } #endif /* Host_Frame Runs all active servers */ void _Host_Frame (float time) { static double time1 = 0; static double time2 = 0; static double time3 = 0; int pass1, pass2, pass3; if (setjmp (host_abortserver)) return; // something bad happened, or the // server disconnected // keep the random time dependent rand (); // decide the simulation time if (!Host_FilterTime (time)) return; // don't run too fast, or packets // will flood out // get new key events IN_SendKeyEvents (); // allow mice or other external controllers to add commands IN_Commands (); // process console commands Cbuf_Execute (); NET_Poll (); // if running the server locally, make intentions now if (sv.active) CL_SendCmd (); //------------------- // // server operations // //------------------- // check for commands typed to the host Host_GetConsoleCommands (); if (sv.active) Host_ServerFrame (); //------------------- // // client operations // //------------------- // if running the server remotely, send intentions now after // the incoming messages have been read if (!sv.active) CL_SendCmd (); host_time += host_frametime; // fetch results from server if (cls.state == ca_connected) { CL_ReadFromServer (); } // update video if (host_speeds->int_val) time1 = Sys_DoubleTime (); SCR_UpdateScreen (realtime); if (host_speeds->int_val) time2 = Sys_DoubleTime (); // update audio if (cls.signon == SIGNONS) { S_Update (r_origin, vpn, vright, vup); R_DecayLights (); } else S_Update (vec3_origin, vec3_origin, vec3_origin, vec3_origin); CDAudio_Update (); if (host_speeds->int_val) { pass1 = (time1 - time3) * 1000; time3 = Sys_DoubleTime (); pass2 = (time2 - time1) * 1000; pass3 = (time3 - time2) * 1000; Con_Printf ("%3i tot %3i server %3i gfx %3i snd\n", pass1 + pass2 + pass3, pass1, pass2, pass3); } host_framecount++; fps_count++; } void Host_Frame (float time) { double time1, time2; static double timetotal; static int timecount; int i, c, m; if (!serverprofile->int_val) { _Host_Frame (time); return; } time1 = Sys_DoubleTime (); _Host_Frame (time); time2 = Sys_DoubleTime (); timetotal += time2 - time1; timecount++; if (timecount < 1000) return; m = timetotal * 1000 / timecount; timecount = 0; timetotal = 0; c = 0; for (i = 0; i < svs.maxclients; i++) { if (svs.clients[i].active) c++; } Con_Printf ("serverprofile: %2i clients %2i msec\n", c, m); } //============================================================================ extern QFile *vcrFile; #define VCR_SIGNATURE 0x56435231 // "VCR1" void Host_InitVCR (quakeparms_t *parms) { int i, len, n; char *p; if (COM_CheckParm ("-playback")) { if (com_argc != 2) Sys_Error ("No other parameters allowed with -playback\n"); vcrFile = Qopen ("quake.vcr", "rbz"); if (!vcrFile) Sys_Error ("playback file not found\n"); Qread (vcrFile, &i, sizeof (int)); if (i != VCR_SIGNATURE) Sys_Error ("Invalid signature in vcr file\n"); Qread (vcrFile, &com_argc, sizeof (int)); com_argv = malloc (com_argc * sizeof (char *)); com_argv[0] = parms->argv[0]; for (i = 0; i < com_argc; i++) { Qread (vcrFile, &len, sizeof (int)); p = malloc (len); Qread (vcrFile, p, len); com_argv[i + 1] = p; } com_argc++; /* add one for arg[0] */ parms->argc = com_argc; parms->argv = com_argv; } if ((n = COM_CheckParm ("-record")) != 0) { vcrFile = Qopen ("quake.vcr", "wb"); i = VCR_SIGNATURE; Qwrite (vcrFile, &i, sizeof (int)); i = com_argc - 1; Qwrite (vcrFile, &i, sizeof (int)); for (i = 1; i < com_argc; i++) { if (i == n) { len = 10; Qwrite (vcrFile, &len, sizeof (int)); Qwrite (vcrFile, "-playback", len); continue; } len = strlen (com_argv[i]) + 1; Qwrite (vcrFile, &len, sizeof (int)); Qwrite (vcrFile, com_argv[i], len); } } } static int check_quakerc (void) { QFile *f; char *l, *p; int ret = 1; COM_FOpenFile ("quake.rc", &f); if (!f) return 1; while ((l = Qgetline (f))) { if ((p = strstr (l, "stuffcmds"))) { if (p == l) { // only known case so far ret = 0; break; } } } Qclose (f); return ret; } void Host_Init (quakeparms_t *parms) { if (standard_quake) minimum_memory = MINIMUM_MEMORY; else minimum_memory = MINIMUM_MEMORY_LEVELPAK; if (COM_CheckParm ("-minmemory")) parms->memsize = minimum_memory; host_parms = *parms; if (parms->memsize < minimum_memory) Sys_Error ("Only %4.1fMB of memory available, can't execute game", parms->memsize / (float) 0x100000); com_argc = parms->argc; com_argv = parms->argv; Cvar_Init_Hash (); Cmd_Init_Hash (); Memory_Init (parms->membase, parms->memsize); Cvar_Init (); Cbuf_Init (); Cmd_Init (); // execute +set as early as possible Cmd_StuffCmds_f (); Cbuf_Execute_Sets (); // execute the global configuration file if it exists // would have been nice if Cmd_Exec_f could have been used, but it // only reads from within the quake file system, and changing that is // probably Not A Good Thing (tm). fs_globalcfg = Cvar_Get ("fs_globalcfg", FS_GLOBALCFG, CVAR_ROM, NULL, "global configuration file"); Cmd_Exec_File (fs_globalcfg->string); Cbuf_Execute_Sets (); // execute +set again to override the config file Cmd_StuffCmds_f (); Cbuf_Execute_Sets (); fs_usercfg = Cvar_Get ("fs_usercfg", FS_USERCFG, CVAR_ROM, NULL, "user configuration file"); Cmd_Exec_File (fs_usercfg->string); Cbuf_Execute_Sets (); // execute +set again to override the config file Cmd_StuffCmds_f (); Cbuf_Execute_Sets (); Chase_Init_Cvars (); CL_InitCvars (); IN_Init_Cvars (); VID_Init_Cvars (); S_Init_Cvars (); Key_Init_Cvars (); Con_Init_Cvars (); PR_Init_Cvars (); SV_Progs_Init_Cvars (); R_Init_Cvars (); R_Particles_Init_Cvars (); Mod_Init_Cvars (); Host_Skin_Init_Cvars (); V_Init_Cvars (); Cmd_StuffCmds_f (); Cbuf_Execute_Sets (); PI_Init (); V_Init (); COM_Init (); GIB_Init (); Game_Init (); Host_InitVCR (parms); Host_InitLocal (); W_LoadWadFile ("gfx.wad"); Key_Init (); Con_Init (); // FIXME: MENUCODE // M_Init (); PR_Init (); SV_Progs_Init (); Mod_Init (); NET_Init (); SV_Init (); Host_Skin_Init (); Con_Printf ("Exe: " __TIME__ " " __DATE__ "\n"); Con_Printf ("%4.1f megabyte heap\n", parms->memsize / (1024 * 1024.0)); if (cls.state != ca_dedicated) { vid_basepal = (byte *) COM_LoadHunkFile ("gfx/palette.lmp"); if (!vid_basepal) Sys_Error ("Couldn't load gfx/palette.lmp"); // LordHavoc: force the transparent color to black vid_basepal[765] = vid_basepal[766] = vid_basepal[767] = 0; vid_colormap = (byte *) COM_LoadHunkFile ("gfx/colormap.lmp"); if (!vid_colormap) Sys_Error ("Couldn't load gfx/colormap.lmp"); VID_Init (vid_basepal); IN_Init (); Draw_Init (); SCR_Init (); R_Init (); S_Init (); CDAudio_Init (); Sbar_Init (); CL_Init (); } Cbuf_InsertText ("exec quake.rc\n"); Cmd_Exec_File (fs_usercfg->string); // reparse the command line for + commands other than set // (sets still done, but it doesn't matter) if (check_quakerc ()) { Cmd_StuffCmds_f (); } Hunk_AllocName (0, "-HOST_HUNKLEVEL-"); host_hunklevel = Hunk_LowMark (); host_initialized = true; Sys_Printf ("========Quake Initialized=========\n"); SCR_UpdateScreen (realtime); } /* Host_Shutdown FIXME: this is a callback from Sys_Quit and Sys_Error. It would be better to run quit through here before final handoff to the sys code. */ void Host_Shutdown (void) { static qboolean isdown = false; if (isdown) { printf ("recursive shutdown\n"); return; } isdown = true; // keep Con_Printf from trying to update the screen scr_disabled_for_loading = true; Host_WriteConfiguration (); CDAudio_Shutdown (); NET_Shutdown (); S_Shutdown (); IN_Shutdown (); if (cls.state != ca_dedicated) { VID_Shutdown (); } }