/* 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 the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // host.c -- coordinates spawning and killing of local servers #include "quakedef.h" /* 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. */ 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 *host_basepal; byte *host_colormap; int fps_count; cvar_t host_framerate = {"host_framerate", "0"}; // set for slow motion cvar_t host_speeds = {"host_speeds", "0"}; // set for running times cvar_t sys_ticrate = {"sys_ticrate", "0.05"}; cvar_t serverprofile = {"serverprofile", "0"}; cvar_t fraglimit = {"fraglimit", "0", false, true}; cvar_t timelimit = {"timelimit", "0", false, true}; cvar_t teamplay = {"teamplay", "0", false, true}; cvar_t samelevel = {"samelevel", "0"}; cvar_t noexit = {"noexit", "0", false, true}; cvar_t developer = {"developer", "0"}; cvar_t skill = {"skill", "1"}; // 0 - 3 cvar_t deathmatch = {"deathmatch", "0"}; // 0, 1, or 2 cvar_t coop = {"coop", "0"}; // 0 or 1 cvar_t pausable = {"pausable", "1"}; cvar_t temp1 = {"temp1", "0"}; //QuDos- //enable/disable cmdhist.dat file creation, i don't need this feature cvar_t cmdhist_file = {"cmdhist_file", "0"}; //end //-QuDos /* ================ Host_EndGame ================ */ 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 ("\n===========================\n"); Con_Printf ("Host_Error: %s\n",string); Con_Printf ("===========================\n\n"); 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); } /* ================ Host_FindMaxClients ================ */ void Host_FindMaxClients (void) { int i; svs.maxclients = 1; if ((i = COM_CheckParm ("-dedicated"))) { cls.state = ca_dedicated; if (i != (com_argc - 1)) svs.maxclients = Q_atoi (com_argv[i+1]); else svs.maxclients = 8; } else { cls.state = ca_disconnected; } if ((i = COM_CheckParm ("-listen"))) { if (cls.state == ca_dedicated) Sys_Error ("Only one of -dedicated or -listen can be specified"); if (i != (com_argc - 1)) svs.maxclients = Q_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 = max(4, svs.maxclients); 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); } /* ======================= Host_InitLocal ====================== */ void Host_InitLocal (void) { Host_InitCommands (); Cvar_RegisterVariable (&host_framerate); Cvar_RegisterVariable (&host_speeds); Cvar_RegisterVariable (&sys_ticrate); Cvar_RegisterVariable (&serverprofile); Cvar_RegisterVariable (&fraglimit); Cvar_RegisterVariable (&timelimit); Cvar_RegisterVariable (&teamplay); Cvar_RegisterVariable (&samelevel); Cvar_RegisterVariable (&noexit); Cvar_RegisterVariable (&skill); Cvar_RegisterVariable (&developer); Cvar_RegisterVariable (&deathmatch); Cvar_RegisterVariable (&coop); Cvar_RegisterVariable (&pausable); Cvar_RegisterVariable (&temp1); //QuDos- //enable/disable cmdhist.dat file creation, i don't need this feature Cvar_RegisterVariable (&cmdhist_file); //end //-QuDos Cmd_AddCommand ("writeconfig", Host_WriteConfig_f); // by joe Host_FindMaxClients (); host_time = 1.0; // so a think at time 0 won't get called } /* =============== Host_WriteConfig =============== */ void Host_WriteConfig (char *cfgname) { FILE *f; if (!(f = fopen(va("%s/%s", com_gamedir, cfgname), "w"))) { Con_Printf ("Couldn't write %s\n", cfgname); return; } fprintf (f, "// Generated by DemonQuake\n"); fprintf (f, "\n// Key bindings\n"); Key_WriteBindings (f); fprintf (f, "\n// Variables\n"); Cvar_WriteVariables (f); fclose (f); } /* =============== Host_WriteConfiguration Writes key bindings and archived cvars to config.cfg =============== */ void Host_WriteConfiguration (void) { // dedicated servers initialize the host but don't parse and set the config.cfg cvars if (host_initialized & !isDedicated) Host_WriteConfig ("config.cfg"); } /* =============== Host_WriteConfig_f Writes key bindings and ONLY archived cvars to a custom config file =============== */ void Host_WriteConfig_f (void) { char name[MAX_OSPATH]; if (Cmd_Argc() != 2) { Con_Printf ("Usage: writeconfig \n"); return; } Q_strcpy (name, Cmd_Argv(1)); COM_ForceExtension (name, ".cfg"); Con_Printf ("Writing %s\n", name); Host_WriteConfig (name); } /* ================= 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 ; imessage, 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 i, saveSelf; client_t *client; // joe, ProQuake fix: don't drop a client that's already been dropped! if (!host_client->active) return; 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 = pr_global_struct->self; pr_global_struct->self = EDICT_TO_PROG(host_client->edict); PR_ExecuteProgram (pr_global_struct->ClientDisconnect); pr_global_struct->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 ; iactive) 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, count; sizebuf_t buf; unsigned 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 ; iactive && 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); if ((count = NET_SendToAll (&buf, 5))) Con_Printf ("Host_ShutdownServer: NET_SendToAll failed for %u clients\n", count); for (i=0, host_client=svs.clients ; iactive) 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 (double time) { double fps; realtime += time; fps = (cl.gametype == GAME_DEATHMATCH) ? max(1.0, cl_maxfps.value) : bound(1.0, cl_maxfps.value, 72.0); // only allow the following early return if not capturing if (!cls.capturedemo) if (!cls.timedemo && realtime - oldrealtime < 1.0 / fps) return false; // framerate is too high host_frametime = realtime - oldrealtime; if (cls.demoplayback) host_frametime *= bound(0, cl_demospeed.value, 20); oldrealtime = realtime; if (host_framerate.value > 0) host_frametime = host_framerate.value; else // don't allow really long or short frames host_frametime = bound(0.001, host_frametime, 0.1); return true; } /* =================== Host_GetConsoleCommands Add them exactly as if they had been typed at the console =================== */ void Host_GetConsoleCommands (void) { char *cmd; while (1) { if (!(cmd = Sys_ConsoleInput())) break; Cbuf_AddText (cmd); } } /* ================== Host_ServerFrame ================== */ #ifdef FPS_20 void _Host_ServerFrame (void) { // run the world state pr_global_struct->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, temp_host_frametime; // run the world state pr_global_struct->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) { // joe, from ProQuake: stuff the port number into the server console once every minute static double port_time = 0.0; if (port_time > sv.time + 1 || port_time < sv.time - 60) { port_time = sv.time; Cmd_ExecuteString (va("port %d\n", net_hostport), src_command); } // run the world state pr_global_struct->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 (double time) { static double time1 = 0.0, time2 = 0.0, time3 = 0.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)) { // joe, ProQuake fix: if we're not doing a frame, still check for lagged moves to send if (!sv.active && (cl.movemessages > 2)) CL_SendLagMove (); return; // don't run too fast, or packets will flood out } // get new key events Sys_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 (); if (host_speeds.value) time1 = Sys_DoubleTime (); // update video SCR_UpdateScreen (); if (host_speeds.value) time2 = Sys_DoubleTime (); if (cls.signon == SIGNONS) { // update audio S_Update (r_origin, vpn, vright, vup); CL_DecayLights (); } else { S_Update (vec3_origin, vec3_origin, vec3_origin, vec3_origin); } CDAudio_Update (); if (host_speeds.value) { 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); } if (!cls.demoplayback && cl_demorewind.value) { Cvar_Set ("cl_demorewind", "0"); Con_Printf ("Demorewind is only enabled during playback\n"); } // don't allow cheats in multiplayer if (cl.gametype == GAME_DEATHMATCH) { if (!cls.demorecording) Cvar_Set ("r_fullbright", "0"); Cvar_Set ("r_fullbrightskins", "0"); #ifndef GLQUAKE Cvar_Set ("r_draworder", "0"); Cvar_Set ("r_ambient", "0"); Cvar_Set ("r_drawflat", "0"); #endif } else { // don't allow cheats when recording a demo if (cls.demorecording) Cvar_Set ("cl_truelightning", "0"); // don't allow higher than 72 fps if (cl_maxfps.value > 72) Cvar_Set ("cl_maxfps", "72"); } host_framecount++; fps_count++; } void Host_Frame (double time) { double time1, time2; static double timetotal; static int timecount; int i, c, m; if (!serverprofile.value) { _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 = timetotal = 0; c = 0; for (i=0 ; iargv[0]; for (i=0 ; iargc = com_argc; parms->argv = com_argv; } if ((n = COM_CheckParm("-record"))) { vcrFile = fopen ("quake.vcr", "wb"); i = VCR_SIGNATURE; fwrite (&i, 1, sizeof(int), vcrFile); i = com_argc - 1; fwrite (&i, 1, sizeof(int), vcrFile); for (i = 1; i < com_argc; i++) { if (i == n) { len = 10; fwrite (&len, 1, sizeof(int), vcrFile); fwrite (&"-playback", 1, len, vcrFile); continue; } len = strlen (com_argv[i]) + 1; fwrite (&len, 1, sizeof(int), vcrFile); fwrite (&com_argv[i], 1, len, vcrFile); } } } /* ==================== Host_Init ==================== */ void Host_Init (quakeparms_t *parms) { minimum_memory = (hipnotic || rogue) ? MINIMUM_MEMORY_LEVELPAK : MINIMUM_MEMORY; if (COM_CheckParm ("-minmemory")) parms->memsize = minimum_memory; host_parms = *parms; if (parms->memsize < minimum_memory) Sys_Error ("Only %4.1f megs of memory available, can't execute game", parms->memsize / (float)0x100000); com_argc = parms->argc; com_argv = parms->argv; Memory_Init (parms->membase, parms->memsize); Cbuf_Init (); Cmd_Init (); Cvar_Init (); V_Init (); Chase_Init (); Host_InitVCR (parms); COM_Init (parms->basedir); Host_InitLocal (); W_LoadWadFile ("gfx.wad"); Key_Init (); Con_Init (); M_Init (); PR_Init (); Mod_Init (); NET_Init (); SV_Init (); Con_Printf ("Exe: "__TIME__" "__DATE__"\n"); if (cls.state != ca_dedicated) { if (!(host_basepal = (byte *)COM_LoadHunkFile ("gfx/palette.lmp"))) Sys_Error ("Couldn't load gfx/palette.lmp"); if (!(host_colormap = (byte *)COM_LoadHunkFile ("gfx/colormap.lmp"))) Sys_Error ("Couldn't load gfx/colormap.lmp"); #ifndef _WIN32 // on non win32, mouse comes before video for security reasons IN_Init (); #endif VID_Init (host_basepal); Draw_Init (); SCR_Init (); R_Init (); #ifndef _WIN32 // on Win32, sound initialization has to come before video initialization, so we // can put up a popup if the sound hardware is in use S_Init (); #else #ifdef GLQUAKE // FIXME: doesn't use the new one-window approach yet S_Init (); #endif #endif // _WIN32 CDAudio_Init (); Sbar_Init (); CL_Init (); #ifdef _WIN32 // on non win32, mouse comes before video for security reasons IN_Init (); #endif } #ifdef GLQUAKE if (nehahra) { Neh_Init (); } #endif Hunk_AllocName (0, "-HOST_HUNKLEVEL-"); host_hunklevel = Hunk_LowMark (); host_initialized = true; Con_Printf ("%4.1f megs RAM used.\n", parms->memsize / (1024 * 1024.0)); // modified from Sys_Printf() by joe Con_Printf ("\n\x1d\x1e\x1e\x1e\x1e\x1e\x1e\x1e DemonQuake Initialized \x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1f\n"); if (COM_FindFile("config.cfg")) Cbuf_AddText ("exec config.cfg\n"); if (COM_FindFile("autoexec.cfg")) Cbuf_AddText ("exec autoexec.cfg\n"); Cbuf_InsertText ("exec quake.rc\n"); #ifdef GLQUAKE if (nehahra) { Neh_DoBindings (); } R_CheckParticles (); #endif } /* =============== Host_Shutdown FIXME: this is a callback from Sys_Quit and Sys_Error. It would be better to run quit through here before the final handoff to the sys code. =============== */ void Host_Shutdown (void) { static qboolean isdown = false; // joe: stuff from [sons]Quake int i, j; FILE *cmdhist; cmdhistory_t cmdhistory; extern char key_lines[64][MAXCMDLINE]; extern int history_line; extern int edit_line; extern int key_linepos; 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 (); // joe: same here //QuDos- enable/disable cmdhist.dat file creation, i don't need this feature //if (con_initialized && (cmdhist = fopen("cmdhist.dat", "wb"))) if (con_initialized && (cmdhist = fopen(va("%s/cmdhist.dat", com_gamedir), "wb"))) //QuDos- { for (i=0 ; i<64 ; i++) for (j=0 ; j