]> git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - host.c
physics: fix and refactor unsticking
[xonotic/darkplaces.git] / host.c
diff --git a/host.c b/host.c
index 793fd0eb747a2bef5c1ddcb488d8d2f6a85f56f3..3f06d38e8ead78a23507cd5424575a62bc7e399c 100644 (file)
--- a/host.c
+++ b/host.c
@@ -1,5 +1,6 @@
 /*
 Copyright (C) 1996-1997 Id Software, Inc.
+Copyright (C) 2000-2021 DarkPlaces contributors
 
 This program is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License
@@ -23,14 +24,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 
 #include <time.h>
 #include "libcurl.h"
-#include "cdaudio.h"
-#include "cl_video.h"
-#include "progsvm.h"
-#include "csprogs.h"
-#include "sv_demo.h"
-#include "snd_main.h"
 #include "taskqueue.h"
-#include "thread.h"
 #include "utf8lib.h"
 
 /*
@@ -44,27 +38,27 @@ Memory is cleared / released when a server or client begins, not when they end.
 
 */
 
-host_t host;
+host_static_t host;
 
 // pretend frames take this amount of time (in seconds), 0 = realtime
-cvar_t host_framerate = {CVAR_CLIENT | CVAR_SERVER, "host_framerate","0", "locks frame timing to this value in seconds, 0.05 is 20fps for example, note that this can easily run too fast, use cl_maxfps if you want to limit your framerate instead, or sys_ticrate to limit server speed"};
-cvar_t cl_maxphysicsframesperserverframe = {CVAR_CLIENT, "cl_maxphysicsframesperserverframe","10", "maximum number of physics frames per server frame"};
+cvar_t host_framerate = {CF_CLIENT | CF_SERVER, "host_framerate","0", "locks frame timing to this value in seconds, 0.05 is 20fps for example, note that this can easily run too fast, use cl_maxfps if you want to limit your framerate instead, or sys_ticrate to limit server speed"};
 // shows time used by certain subsystems
-cvar_t host_speeds = {CVAR_CLIENT | CVAR_SERVER, "host_speeds","0", "reports how much time is used in server/graphics/sound"};
-cvar_t host_maxwait = {CVAR_CLIENT | CVAR_SERVER, "host_maxwait","1000", "maximum sleep time requested from the operating system in millisecond. Larger sleeps will be done using multiple host_maxwait length sleeps. Lowering this value will increase CPU load, but may help working around problems with accuracy of sleep times."};
+cvar_t host_speeds = {CF_CLIENT | CF_SERVER, "host_speeds","0", "reports how much time is used in server/graphics/sound"};
 
-cvar_t developer = {CVAR_CLIENT | CVAR_SERVER | CVAR_SAVE, "developer","0", "shows debugging messages and information (recommended for all developers and level designers); the value -1 also suppresses buffering and logging these messages"};
-cvar_t developer_extra = {CVAR_CLIENT | CVAR_SERVER, "developer_extra", "0", "prints additional debugging messages, often very verbose!"};
-cvar_t developer_insane = {CVAR_CLIENT | CVAR_SERVER, "developer_insane", "0", "prints huge streams of information about internal workings, entire contents of files being read/written, etc.  Not recommended!"};
-cvar_t developer_loadfile = {CVAR_CLIENT | CVAR_SERVER, "developer_loadfile","0", "prints name and size of every file loaded via the FS_LoadFile function (which is almost everything)"};
-cvar_t developer_loading = {CVAR_CLIENT | CVAR_SERVER, "developer_loading","0", "prints information about files as they are loaded or unloaded successfully"};
-cvar_t developer_entityparsing = {CVAR_CLIENT, "developer_entityparsing", "0", "prints detailed network entities information each time a packet is received"};
+cvar_t developer = {CF_CLIENT | CF_SERVER | CF_ARCHIVE, "developer","0", "shows debugging messages and information (recommended for all developers and level designers); the value -1 also suppresses buffering and logging these messages"};
+cvar_t developer_extra = {CF_CLIENT | CF_SERVER, "developer_extra", "0", "prints additional debugging messages, often very verbose!"};
+cvar_t developer_insane = {CF_CLIENT | CF_SERVER, "developer_insane", "0", "prints huge streams of information about internal workings, entire contents of files being read/written, etc.  Not recommended!"};
+cvar_t developer_loadfile = {CF_CLIENT | CF_SERVER, "developer_loadfile","0", "prints name and size of every file loaded via the FS_LoadFile function (which is almost everything)"};
+cvar_t developer_loading = {CF_CLIENT | CF_SERVER, "developer_loading","0", "prints information about files as they are loaded or unloaded successfully"};
+cvar_t developer_entityparsing = {CF_CLIENT, "developer_entityparsing", "0", "prints detailed network entities information each time a packet is received"};
 
-cvar_t timestamps = {CVAR_CLIENT | CVAR_SERVER | CVAR_SAVE, "timestamps", "0", "prints timestamps on console messages"};
-cvar_t timeformat = {CVAR_CLIENT | CVAR_SERVER | CVAR_SAVE, "timeformat", "[%Y-%m-%d %H:%M:%S] ", "time format to use on timestamped console messages"};
+cvar_t timestamps = {CF_CLIENT | CF_SERVER | CF_ARCHIVE, "timestamps", "0", "prints timestamps on console messages"};
+cvar_t timeformat = {CF_CLIENT | CF_SERVER | CF_ARCHIVE, "timeformat", "[%Y-%m-%d %H:%M:%S] ", "time format to use on timestamped console messages"};
 
-cvar_t sessionid = {CVAR_CLIENT | CVAR_SERVER | CVAR_READONLY, "sessionid", "", "ID of the current session (use the -sessionid parameter to set it); this is always either empty or begins with a dot (.)"};
-cvar_t locksession = {CVAR_CLIENT | CVAR_SERVER, "locksession", "0", "Lock the session? 0 = no, 1 = yes and abort on failure, 2 = yes and continue on failure"};
+cvar_t sessionid = {CF_CLIENT | CF_SERVER | CF_READONLY, "sessionid", "", "ID of the current session (use the -sessionid parameter to set it); this is always either empty or begins with a dot (.)"};
+cvar_t locksession = {CF_CLIENT | CF_SERVER, "locksession", "0", "Lock the session? 0 = no, 1 = yes and abort on failure, 2 = yes and continue on failure"};
+
+cvar_t host_isclient = {CF_SHARED | CF_READONLY, "_host_isclient", "0", "If 1, clientside is active."};
 
 /*
 ================
@@ -73,7 +67,6 @@ Host_AbortCurrentFrame
 aborts the current host frame and goes on with the next one
 ================
 */
-void Host_AbortCurrentFrame(void) DP_FUNC_NORETURN;
 void Host_AbortCurrentFrame(void)
 {
        // in case we were previously nice, make us mean again
@@ -93,8 +86,12 @@ void Host_Error (const char *error, ...)
 {
        static char hosterrorstring1[MAX_INPUTLINE]; // THREAD UNSAFE
        static char hosterrorstring2[MAX_INPUTLINE]; // THREAD UNSAFE
-       static qboolean hosterror = false;
+       static qbool hosterror = false;
        va_list argptr;
+       int outfd = sys.outfd;
+
+       // set output to stderr
+       sys.outfd = fileno(stderr);
 
        // turn off rcon redirect if it was active when the crash occurred
        // to prevent loops when it is a networking problem
@@ -109,94 +106,38 @@ void Host_Error (const char *error, ...)
        // LadyHavoc: if crashing very early, or currently shutting down, do
        // Sys_Error instead
        if (host.framecount < 3 || host.state == host_shutdown)
-               Sys_Error ("Host_Error: %s", hosterrorstring1);
+               Sys_Error ("Host_Error during %s: %s", host.framecount < 3 ? "startup" : "shutdown", hosterrorstring1);
 
        if (hosterror)
                Sys_Error ("Host_Error: recursively entered (original error was: %s    new error is: %s)", hosterrorstring2, hosterrorstring1);
        hosterror = true;
 
-       strlcpy(hosterrorstring2, hosterrorstring1, sizeof(hosterrorstring2));
+       dp_strlcpy(hosterrorstring2, hosterrorstring1, sizeof(hosterrorstring2));
 
        CL_Parse_DumpPacket();
-
        CL_Parse_ErrorCleanUp();
 
-       //PR_Crash();
-
        // print out where the crash happened, if it was caused by QC (and do a cleanup)
-       PRVM_Crash(SVVM_prog);
-       PRVM_Crash(CLVM_prog);
-#ifdef CONFIG_MENU
-       PRVM_Crash(MVM_prog);
-#endif
-
-       cl.csqc_loaded = false;
-       Cvar_SetValueQuick(&csqc_progcrc, -1);
-       Cvar_SetValueQuick(&csqc_progsize, -1);
+       PRVM_Crash();
 
-       SV_LockThreadMutex();
-       SV_Shutdown ();
-       SV_UnlockThreadMutex();
+       if(host.hook.SV_Shutdown)
+               host.hook.SV_Shutdown();
 
        if (cls.state == ca_dedicated)
-               Sys_Error ("Host_Error: %s",hosterrorstring2);  // dedicated servers exit
-
-       CL_Disconnect ();
-       cls.demonum = -1;
-
-       hosterror = false;
+               Sys_Error("Host_Error: %s", hosterrorstring1);        // dedicated servers exit
 
-       Host_AbortCurrentFrame();
-}
-
-static void Host_ServerOptions (void)
-{
-       int i;
+       // prevent an endless loop if the error was triggered by a command
+       Cbuf_Clear(cmd_local->cbuf);
 
-       // general default
-       svs.maxclients = 8;
+       CL_DisconnectEx(false, "Host_Error: %s", hosterrorstring1);
+       cls.demonum = -1; // stop demo loop
 
-// COMMANDLINEOPTION: Server: -dedicated [playerlimit] starts a dedicated server (with a command console), default playerlimit is 8
-// COMMANDLINEOPTION: Server: -listen [playerlimit] starts a multiplayer server with graphical client, like singleplayer but other players can connect, default playerlimit is 8
-       // if no client is in the executable or -dedicated is specified on
-       // commandline, start a dedicated server
-       i = COM_CheckParm ("-dedicated");
-       if (i || !cl_available)
-       {
-               cls.state = ca_dedicated;
-               // check for -dedicated specifying how many players
-               if (i && i + 1 < sys.argc && atoi (sys.argv[i+1]) >= 1)
-                       svs.maxclients = atoi (sys.argv[i+1]);
-               if (COM_CheckParm ("-listen"))
-                       Con_Printf ("Only one of -dedicated or -listen can be specified\n");
-               // default sv_public on for dedicated servers (often hosted by serious administrators), off for listen servers (often hosted by clueless users)
-               Cvar_SetValue(&cvars_all, "sv_public", 1);
-       }
-       else if (cl_available)
-       {
-               // client exists and not dedicated, check if -listen is specified
-               cls.state = ca_disconnected;
-               i = COM_CheckParm ("-listen");
-               if (i)
-               {
-                       // default players unless specified
-                       if (i + 1 < sys.argc && atoi (sys.argv[i+1]) >= 1)
-                               svs.maxclients = atoi (sys.argv[i+1]);
-               }
-               else
-               {
-                       // default players in some games, singleplayer in most
-                       if (gamemode != GAME_GOODVSBAD2 && !IS_NEXUIZ_DERIVED(gamemode) && gamemode != GAME_BATTLEMECH)
-                               svs.maxclients = 1;
-               }
-       }
-
-       svs.maxclients = svs.maxclients_next = bound(1, svs.maxclients, MAX_SCOREBOARD);
+       hosterror = false;
 
-       svs.clients = (client_t *)Mem_Alloc(sv_mempool, sizeof(client_t) * svs.maxclients);
+       // restore configured outfd
+       sys.outfd = outfd;
 
-       if (svs.maxclients > 1 && !deathmatch.integer && !coop.integer)
-               Cvar_SetValueQuick(&deathmatch, 1);
+       Host_AbortCurrentFrame();
 }
 
 /*
@@ -204,7 +145,7 @@ static void Host_ServerOptions (void)
 Host_Quit_f
 ==================
 */
-void Host_Quit_f(cmd_state_t *cmd)
+static void Host_Quit_f(cmd_state_t *cmd)
 {
        if(host.state == host_shutdown)
                Con_Printf("shutting down already!\n");
@@ -214,7 +155,7 @@ void Host_Quit_f(cmd_state_t *cmd)
 
 static void Host_Version_f(cmd_state_t *cmd)
 {
-       Con_Printf("Version: %s build %s\n", gamename, buildstring);
+       Con_Printf("Version: %s\n", engineversion);
 }
 
 static void Host_Framerate_c(cvar_t *var)
@@ -223,42 +164,21 @@ static void Host_Framerate_c(cvar_t *var)
                Cvar_SetValueQuick(var, 0);
 }
 
-/*
-=======================
-Host_InitLocal
-======================
-*/
-void Host_SaveConfig_f(cmd_state_t *cmd);
-void Host_LoadConfig_f(cmd_state_t *cmd);
-extern cvar_t sv_writepicture_quality;
-extern cvar_t r_texture_jpeg_fastpicmip;
-static void Host_InitLocal (void)
+// TODO: Find a better home for this.
+static void SendCvar_f(cmd_state_t *cmd)
 {
-       Cmd_AddCommand(CMD_SHARED, "quit", Host_Quit_f, "quit the game");
-       Cmd_AddCommand(CMD_SHARED, "version", Host_Version_f, "print engine version");
-       Cmd_AddCommand(CMD_SHARED, "saveconfig", Host_SaveConfig_f, "save settings to config.cfg (or a specified filename) immediately (also automatic when quitting)");
-       Cmd_AddCommand(CMD_SHARED, "loadconfig", Host_LoadConfig_f, "reset everything and reload configs");
-       Cvar_RegisterVariable (&cl_maxphysicsframesperserverframe);
-       Cvar_RegisterVariable (&host_framerate);
-       Cvar_RegisterCallback (&host_framerate, Host_Framerate_c);
-       Cvar_RegisterVariable (&host_speeds);
-       Cvar_RegisterVariable (&host_maxwait);
-
-       Cvar_RegisterVariable (&developer);
-       Cvar_RegisterVariable (&developer_extra);
-       Cvar_RegisterVariable (&developer_insane);
-       Cvar_RegisterVariable (&developer_loadfile);
-       Cvar_RegisterVariable (&developer_loading);
-       Cvar_RegisterVariable (&developer_entityparsing);
-
-       Cvar_RegisterVariable (&timestamps);
-       Cvar_RegisterVariable (&timeformat);
-
-       Cvar_RegisterVariable (&sv_writepicture_quality);
-       Cvar_RegisterVariable (&r_texture_jpeg_fastpicmip);
+       if(cmd->source == src_local && host.hook.SV_SendCvar)
+       {
+               host.hook.SV_SendCvar(cmd);
+               return;
+       }
+       if(cmd->source == src_client && host.hook.CL_SendCvar)
+       {
+               host.hook.CL_SendCvar(cmd);
+               return;
+       }
 }
 
-
 /*
 ===============
 Host_SaveConfig_f
@@ -266,21 +186,23 @@ Host_SaveConfig_f
 Writes key bindings and archived cvars to config.cfg
 ===============
 */
-static void Host_SaveConfig_to(const char *file)
+void Host_SaveConfig(const char *file)
 {
        qfile_t *f;
 
 // dedicated servers initialize the host but don't parse and set the
 // config.cfg cvars
        // LadyHavoc: don't save a config if it crashed in startup
-       if (host.framecount >= 3 && cls.state != ca_dedicated && !COM_CheckParm("-benchmark") && !COM_CheckParm("-capturedemo"))
+       if (host.framecount >= 3 && cls.state != ca_dedicated && !Sys_CheckParm("-benchmark") && !Sys_CheckParm("-capturedemo"))
        {
                f = FS_OpenRealFile(file, "wb", false);
                if (!f)
                {
-                       Con_Printf(CON_ERROR "Couldn't write %s.\n", file);
+                       Con_Printf(CON_ERROR "Couldn't write %s\n", file);
                        return;
                }
+               else
+                       Con_Printf("Saving config to %s ...\n", file);
 
                Key_WriteBindings (f);
                Cvar_WriteVariables (&cvars_all, f);
@@ -288,20 +210,15 @@ static void Host_SaveConfig_to(const char *file)
                FS_Close (f);
        }
 }
-void Host_SaveConfig(void)
-{
-       Host_SaveConfig_to(CONFIGFILENAME);
-}
-void Host_SaveConfig_f(cmd_state_t *cmd)
+
+static void Host_SaveConfig_f(cmd_state_t *cmd)
 {
        const char *file = CONFIGFILENAME;
 
-       if(Cmd_Argc(cmd) >= 2) {
+       if(Cmd_Argc(cmd) >= 2)
                file = Cmd_Argv(cmd, 1);
-               Con_Printf("Saving to %s\n", file);
-       }
 
-       Host_SaveConfig_to(file);
+       Host_SaveConfig(file);
 }
 
 static void Host_AddConfigText(cmd_state_t *cmd)
@@ -316,7 +233,10 @@ static void Host_AddConfigText(cmd_state_t *cmd)
                Cbuf_InsertText(cmd, "alias startmap_sp \"map start\"\nalias startmap_dm \"map start\"\nexec teu.rc\n");
        else
                Cbuf_InsertText(cmd, "alias startmap_sp \"map start\"\nalias startmap_dm \"map start\"\nexec " STARTCONFIGFILENAME "\n");
-       Cbuf_Execute(cmd->cbuf);
+
+       // if quake.rc is missing, use default
+       if (!FS_FileExists(STARTCONFIGFILENAME))
+               Cbuf_InsertText(cmd, "exec default.cfg\nexec " CONFIGFILENAME "\nexec autoexec.cfg\n");
 }
 
 /*
@@ -326,221 +246,57 @@ Host_LoadConfig_f
 Resets key bindings and cvars to defaults and then reloads scripts
 ===============
 */
-void Host_LoadConfig_f(cmd_state_t *cmd)
+static void Host_LoadConfig_f(cmd_state_t *cmd)
 {
-       // reset all cvars, commands and aliases to init values
-       Cmd_RestoreInitState();
 #ifdef CONFIG_MENU
-       // prepend a menu restart command to execute after the config
-       Cbuf_InsertText(&cmd_client, "\nmenu_restart\n");
+       // Xonotic QC complains/breaks if its cvars are deleted before its m_shutdown() is called
+       if(MR_Shutdown)
+               MR_Shutdown();
+       // append a menu restart command to execute after the config
+       Cbuf_AddText(cmd, "\nmenu_restart\n");
 #endif
+       // reset all cvars, commands and aliases to init values
+       Cmd_RestoreInitState();
        // reset cvars to their defaults, and then exec startup scripts again
-       Host_AddConfigText(&cmd_client);
-}
-
-//============================================================================
-
-/*
-===================
-Host_GetConsoleCommands
-
-Add them exactly as if they had been typed at the console
-===================
-*/
-static void Host_GetConsoleCommands (void)
-{
-       char *line;
-
-       while ((line = Sys_ConsoleInput()))
-       {
-               if (cls.state == ca_dedicated)
-                       Cbuf_AddText(&cmd_server, line);
-               else
-                       Cbuf_AddText(&cmd_client, line);
-       }
-}
-
-/*
-==================
-Host_TimeReport
-
-Returns a time report string, for example for
-==================
-*/
-const char *Host_TimingReport(char *buf, size_t buflen)
-{
-       return va(buf, buflen, "%.1f%% CPU, %.2f%% lost, offset avg %.1fms, max %.1fms, sdev %.1fms", svs.perf_cpuload * 100, svs.perf_lost * 100, svs.perf_offset_avg * 1000, svs.perf_offset_max * 1000, svs.perf_offset_sdev * 1000);
+       Host_AddConfigText(cmd);
 }
 
 /*
-==================
-Host_Frame
-
-Runs all active servers
-==================
+=======================
+Host_InitLocal
+======================
 */
-static void Host_Init(void);
-double Host_Frame(double time)
-{
-       double cl_timer = 0;
-       double sv_timer = 0;
-       static double wait;
-
-       TaskQueue_Frame(false);
-
-       // keep the random time dependent, but not when playing demos/benchmarking
-       if(!*sv_random_seed.string && !host.restless)
-               rand();
-
-       NetConn_UpdateSockets();
-
-       Log_DestBuffer_Flush();
-
-       Curl_Run();
-
-       // check for commands typed to the host
-       Host_GetConsoleCommands();
-
-       // process console commands
-//             R_TimeReport("preconsole");
-
-       Cbuf_Frame(host.cbuf);
-
-//             R_TimeReport("console");
-
-       //Con_Printf("%6.0f %6.0f\n", cl_timer * 1000000.0, sv_timer * 1000000.0);
-
-       R_TimeReport("---");
-
-       sv_timer = SV_Frame(time);
-       cl_timer = CL_Frame(time);
-
-       Mem_CheckSentinelsGlobal();
-
-       // if the accumulators haven't become positive yet, wait a while
-       if (cls.state == ca_dedicated)
-               wait = sv_timer * -1000000.0; // dedicated
-       else if (!sv.active || svs.threaded)
-               wait = cl_timer * -1000000.0; // connected to server, main menu, or server is on different thread
-       else
-               wait = max(cl_timer, sv_timer) * -1000000.0; // listen server or singleplayer
-
-       if (!host.restless && wait >= 1)
-               return wait;
-       else
-               return 0;
-}
-
-static inline void Host_Sleep(double time)
-{
-       static double delta;
-       double time0;
-
-       if(host_maxwait.value <= 0)
-               time = min(time, 1000000.0);
-       else
-               time = min(time, host_maxwait.value * 1000.0);
-       if(time < 1)
-               time = 1; // because we cast to int
-
-       time0 = Sys_DirtyTime();
-       if (sv_checkforpacketsduringsleep.integer && !sys_usenoclockbutbenchmark.integer && !svs.threaded) {
-               NetConn_SleepMicroseconds((int)time);
-               if (cls.state != ca_dedicated)
-                       NetConn_ClientFrame(); // helps server browser get good ping values
-               // TODO can we do the same for ServerFrame? Probably not.
-       }
-       else
-               Sys_Sleep((int)time);
-       delta = Sys_DirtyTime() - time0;
-       if (delta < 0 || delta >= 1800) 
-               delta = 0;
-       host.sleeptime += delta;
-//                     R_TimeReport("sleep");
-       return;
-}
-
-// Cloudwalk: Most overpowered function declaration...
-static inline double Host_UpdateTime (double newtime, double oldtime)
-{
-       double time = newtime - oldtime;
-
-       if (time < 0)
-       {
-               // warn if it's significant
-               if (time < -0.01)
-                       Con_Printf(CON_WARN "Host_UpdateTime: time stepped backwards (went from %f to %f, difference %f)\n", oldtime, newtime, time);
-               time = 0;
-       }
-       else if (time >= 1800)
-       {
-               Con_Printf(CON_WARN "Host_UpdateTime: time stepped forward (went from %f to %f, difference %f)\n", oldtime, newtime, time);
-               time = 0;
-       }
-
-       return time;
-}
-
-void Host_Main(void)
+extern cvar_t r_texture_jpeg_fastpicmip;
+static void Host_InitLocal (void)
 {
-       double time, newtime, oldtime, sleeptime;
-
-       Host_Init(); // Start!
-
-       host.realtime = 0;
-       oldtime = Sys_DirtyTime();
-
-       // Main event loop
-       while(host.state != host_shutdown)
-       {
-               // Something bad happened, or the server disconnected
-               if (setjmp(host.abortframe))
-               {
-                       host.state = host_active; // In case we were loading
-                       continue;
-               }
-
-               newtime = host.dirtytime = Sys_DirtyTime();
-               host.realtime += time = Host_UpdateTime(newtime, oldtime);
-
-               sleeptime = Host_Frame(time);
-               oldtime = newtime;
-
-               if (sleeptime)
-               {
-                       Host_Sleep(sleeptime);
-                       continue;
-               }
-
-               host.framecount++;
-       }
+       Cmd_AddCommand(CF_SHARED, "quit", Host_Quit_f, "quit the game");
+       Cmd_AddCommand(CF_SHARED, "version", Host_Version_f, "print engine version");
+       Cmd_AddCommand(CF_SHARED, "saveconfig", Host_SaveConfig_f, "save settings to config.cfg (or a specified filename) immediately (also automatic when quitting)");
+       Cmd_AddCommand(CF_SHARED, "loadconfig", Host_LoadConfig_f, "reset everything and reload configs");
+       Cmd_AddCommand(CF_SHARED, "sendcvar", SendCvar_f, "sends the value of a cvar to the server as a sentcvar command, for use by QuakeC");
+       Cvar_RegisterVariable (&host_framerate);
+       Cvar_RegisterCallback (&host_framerate, Host_Framerate_c);
+       Cvar_RegisterVariable (&host_speeds);
+       Cvar_RegisterVariable (&host_isclient);
 
-       return;
-}
+       Cvar_RegisterVariable (&developer);
+       Cvar_RegisterVariable (&developer_extra);
+       Cvar_RegisterVariable (&developer_insane);
+       Cvar_RegisterVariable (&developer_loadfile);
+       Cvar_RegisterVariable (&developer_loading);
+       Cvar_RegisterVariable (&developer_entityparsing);
 
-//============================================================================
+       Cvar_RegisterVariable (&timestamps);
+       Cvar_RegisterVariable (&timeformat);
 
-qboolean vid_opened = false;
-void Host_StartVideo(void)
-{
-       if (!vid_opened && cls.state != ca_dedicated)
-       {
-               vid_opened = true;
-#ifdef WIN32
-               // make sure we open sockets before opening video because the Windows Firewall "unblock?" dialog can screw up the graphics context on some graphics drivers
-               NetConn_UpdateSockets();
-#endif
-               VID_Start();
-               CDAudio_Startup();
-       }
+       Cvar_RegisterVariable (&r_texture_jpeg_fastpicmip);
 }
 
 char engineversion[128];
 
-qboolean sys_nostdout = false;
 
 static qfile_t *locksession_fh = NULL;
-static qboolean locksession_run = false;
+static qbool locksession_run = false;
 static void Host_InitSession(void)
 {
        int i;
@@ -549,7 +305,7 @@ static void Host_InitSession(void)
        Cvar_RegisterVariable(&locksession);
 
        // load the session ID into the read-only cvar
-       if ((i = COM_CheckParm("-sessionid")) && (i + 1 < sys.argc))
+       if ((i = Sys_CheckParm("-sessionid")) && (i + 1 < sys.argc))
        {
                if(sys.argv[i+1][0] == '.')
                        Cvar_SetQuick(&sessionid, sys.argv[i+1]);
@@ -561,12 +317,13 @@ static void Host_InitSession(void)
                }
        }
 }
+
 void Host_LockSession(void)
 {
        if(locksession_run)
                return;
        locksession_run = true;
-       if(locksession.integer != 0 && !COM_CheckParm("-readonly"))
+       if(locksession.integer != 0 && !Sys_CheckParm("-readonly"))
        {
                char vabuf[1024];
                char *p = va(vabuf, sizeof(vabuf), "%slock%s", *fs_userdir ? fs_userdir : fs_basedir, sessionid.string);
@@ -586,6 +343,7 @@ void Host_LockSession(void)
                }
        }
 }
+
 void Host_UnlockSession(void)
 {
        if(!locksession_run)
@@ -610,17 +368,28 @@ Host_Init
 static void Host_Init (void)
 {
        int i;
-       const char* os;
        char vabuf[1024];
-       cmd_state_t *cmd = &cmd_client;
+
+       Sys_SDL_Init();
+
+       Memory_Init();
+
+       host.hook.ConnectLocal = NULL;
+       host.hook.Disconnect = NULL;
+       host.hook.ToggleMenu = NULL;
+       host.hook.CL_Intermission = NULL;
+       host.hook.SV_Shutdown = NULL;
 
        host.state = host_init;
 
-       if (COM_CheckParm("-profilegameonly"))
+       if (setjmp(host.abortframe)) // Huh?!
+               Sys_Error("Engine initialization failed. Check the console (if available) for additional information.\n");
+
+       if (Sys_CheckParm("-profilegameonly"))
                Sys_AllowProfiling(false);
 
        // LadyHavoc: quake never seeded the random number generator before... heh
-       if (COM_CheckParm("-benchmark"))
+       if (Sys_CheckParm("-benchmark"))
                srand(0); // predictable random sequence for -benchmark
        else
                srand((unsigned int)time(NULL));
@@ -629,13 +398,13 @@ static void Host_Init (void)
        // LadyHavoc: doesn't seem very temporary...
        // LadyHavoc: made this a saved cvar
 // COMMANDLINEOPTION: Console: -developer enables warnings and other notices (RECOMMENDED for mod developers)
-       if (COM_CheckParm("-developer"))
+       if (Sys_CheckParm("-developer"))
        {
                developer.value = developer.integer = 1;
                developer.string = "1";
        }
 
-       if (COM_CheckParm("-developer2") || COM_CheckParm("-developer3"))
+       if (Sys_CheckParm("-developer2") || Sys_CheckParm("-developer3"))
        {
                developer.value = developer.integer = 1;
                developer.string = "1";
@@ -649,15 +418,15 @@ static void Host_Init (void)
                developer_memorydebug.string = "1";
        }
 
-       if (COM_CheckParm("-developer3"))
+       if (Sys_CheckParm("-developer3"))
        {
                gl_paranoid.integer = 1;gl_paranoid.string = "1";
                gl_printcheckerror.integer = 1;gl_printcheckerror.string = "1";
        }
 
-// COMMANDLINEOPTION: Console: -nostdout disables text output to the terminal the game was launched from
-       if (COM_CheckParm("-nostdout"))
-               sys_nostdout = 1;
+       // -dedicated is checked in SV_ServerOptions() but that's too late for Cvar_RegisterVariable() to skip all the client-only cvars
+       if (Sys_CheckParm ("-dedicated") || !cl_available)
+               cls.state = ca_dedicated;
 
        // initialize console command/cvar/alias/command execution systems
        Cmd_Init();
@@ -666,6 +435,7 @@ static void Host_Init (void)
        Memory_Init_Commands();
 
        // initialize console and logging and its cvars/commands
+       // this enables Con_Printf() messages to be coloured
        Con_Init();
 
        // initialize various cvars that could not be initialized earlier
@@ -674,12 +444,11 @@ static void Host_Init (void)
        Sys_Init_Commands();
        COM_Init_Commands();
 
-       // initialize filesystem (including fs_basedir, fs_gamedir, -game, scr_screenshot_name)
+       // initialize filesystem (including fs_basedir, fs_gamedir, -game, scr_screenshot_name, gamename)
        FS_Init();
 
-       // construct a version string for the corner of the console
-       os = DP_OS_NAME;
-       dpsnprintf (engineversion, sizeof (engineversion), "%s %s %s", gamename, os, buildstring);
+       // ASAP! construct a version string for the corner of the console and for crash messages
+       dpsnprintf (engineversion, sizeof (engineversion), "%s %s%s, buildstring: %s", gamename, DP_OS_NAME, cls.state == ca_dedicated ? " dedicated" : "", buildstring);
        Con_Printf("%s\n", engineversion);
 
        // initialize process nice level
@@ -702,7 +471,6 @@ static void Host_Init (void)
        World_Init();
        SV_Init();
        Host_InitLocal();
-       Host_ServerOptions();
 
        Thread_Init();
        TaskQueue_Init();
@@ -717,76 +485,70 @@ static void Host_Init (void)
        // without crashing the whole game, so this should just be a short-time solution
 
        // here comes the not so critical stuff
-       if (setjmp(host.abortframe)) {
-               return;
-       }
 
-       Host_StartVideo();
-
-       Host_AddConfigText(cmd);
-
-       // if quake.rc is missing, use default
-       if (!FS_FileExists("quake.rc"))
-       {
-               Cbuf_InsertText(cmd, "exec default.cfg\nexec " CONFIGFILENAME "\nexec autoexec.cfg\n");
-               Cbuf_Execute(cmd->cbuf);
-       }
+       Host_AddConfigText(cmd_local);
+       Cbuf_Execute(cmd_local->cbuf); // cannot be in Host_AddConfigText as that would cause Host_LoadConfig_f to loop!
 
        host.state = host_active;
 
-       // run stuffcmds now, deferred previously because it can crash if a server starts that early
-       Cbuf_AddText(cmd,"stuffcmds\n");
-       Cbuf_Execute(cmd->cbuf);
+       CL_StartVideo();
 
        Log_Start();
 
-       // put up the loading image so the user doesn't stare at a black screen...
-       SCR_BeginLoadingPlaque(true);
+       if (cls.state != ca_dedicated)
+       {
+               // put up the loading image so the user doesn't stare at a black screen...
+               SCR_BeginLoadingPlaque(true);
+               S_Startup();
+#ifdef CONFIG_MENU
+               MR_Init();
+#endif
+       }
 
        // check for special benchmark mode
 // COMMANDLINEOPTION: Client: -benchmark <demoname> runs a timedemo and quits, results of any timedemo can be found in gamedir/benchmark.log (for example id1/benchmark.log)
-       i = COM_CheckParm("-benchmark");
+       i = Sys_CheckParm("-benchmark");
        if (i && i + 1 < sys.argc)
        if (!sv.active && !cls.demoplayback && !cls.connect_trying)
        {
-               Cbuf_AddText(&cmd_client, va(vabuf, sizeof(vabuf), "timedemo %s\n", sys.argv[i + 1]));
-               Cbuf_Execute((&cmd_client)->cbuf);
+               Cbuf_AddText(cmd_local, va(vabuf, sizeof(vabuf), "timedemo %s\n", sys.argv[i + 1]));
+               Cbuf_Execute(cmd_local->cbuf);
        }
 
        // check for special demo mode
 // COMMANDLINEOPTION: Client: -demo <demoname> runs a playdemo and quits
-       i = COM_CheckParm("-demo");
+       i = Sys_CheckParm("-demo");
        if (i && i + 1 < sys.argc)
        if (!sv.active && !cls.demoplayback && !cls.connect_trying)
        {
-               Cbuf_AddText(&cmd_client, va(vabuf, sizeof(vabuf), "playdemo %s\n", sys.argv[i + 1]));
-               Cbuf_Execute((&cmd_client)->cbuf);
+               Cbuf_AddText(cmd_local, va(vabuf, sizeof(vabuf), "playdemo %s\n", sys.argv[i + 1]));
+               Cbuf_Execute(cmd_local->cbuf);
        }
 
 #ifdef CONFIG_VIDEO_CAPTURE
 // COMMANDLINEOPTION: Client: -capturedemo <demoname> captures a playdemo and quits
-       i = COM_CheckParm("-capturedemo");
+       i = Sys_CheckParm("-capturedemo");
        if (i && i + 1 < sys.argc)
        if (!sv.active && !cls.demoplayback && !cls.connect_trying)
        {
-               Cbuf_AddText(&cmd_client, va(vabuf, sizeof(vabuf), "playdemo %s\ncl_capturevideo 1\n", sys.argv[i + 1]));
-               Cbuf_Execute((&cmd_client)->cbuf);
+               Cbuf_AddText(cmd_local, va(vabuf, sizeof(vabuf), "playdemo %s\ncl_capturevideo 1\n", sys.argv[i + 1]));
+               Cbuf_Execute((cmd_local)->cbuf);
        }
 #endif
 
-       if (cls.state == ca_dedicated || COM_CheckParm("-listen"))
+       if (cls.state == ca_dedicated || Sys_CheckParm("-listen"))
        if (!sv.active && !cls.demoplayback && !cls.connect_trying)
        {
-               Cbuf_AddText(&cmd_client, "startmap_dm\n");
-               Cbuf_Execute((&cmd_client)->cbuf);
+               Cbuf_AddText(cmd_local, "startmap_dm\n");
+               Cbuf_Execute(cmd_local->cbuf);
        }
 
        if (!sv.active && !cls.demoplayback && !cls.connect_trying)
        {
 #ifdef CONFIG_MENU
-               Cbuf_AddText(&cmd_client, "togglemenu 1\n");
+               Cbuf_AddText(cmd_local, "togglemenu 1\n");
 #endif
-               Cbuf_Execute((&cmd_client)->cbuf);
+               Cbuf_Execute(cmd_local->cbuf);
        }
 
        Con_DPrint("========Initialized=========\n");
@@ -795,30 +557,17 @@ static void Host_Init (void)
                SV_StartThread();
 }
 
-
 /*
 ===============
 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.
+Cleanly shuts down after the main loop exits.
 ===============
 */
-void Host_Shutdown(void)
+static void Host_Shutdown(void)
 {
-       static qboolean isdown = false;
-
-       if (isdown)
-       {
-               Con_Print("recursive shutdown\n");
-               return;
-       }
-       if (setjmp(host.abortframe))
-       {
-               Con_Print("aborted the quitting frame?!?\n");
-               return;
-       }
-       isdown = true;
+       if (Sys_CheckParm("-profilegameonly"))
+               Sys_AllowProfiling(false);
 
        if(cls.state != ca_dedicated)
                CL_Shutdown();
@@ -828,14 +577,13 @@ void Host_Shutdown(void)
                SV_StopThread();
 
        // shut down local server if active
-       SV_LockThreadMutex();
-       SV_Shutdown ();
-       SV_UnlockThreadMutex();
+       if(host.hook.SV_Shutdown)
+               host.hook.SV_Shutdown();
 
        // AK shutdown PRVM
        // AK hmm, no PRVM_Shutdown(); yet
 
-       Host_SaveConfig();
+       Host_SaveConfig(CONFIGFILENAME);
 
        Curl_Shutdown ();
        NetConn_Shutdown ();
@@ -844,7 +592,7 @@ void Host_Shutdown(void)
        TaskQueue_Shutdown();
        Thread_Shutdown();
        Cmd_Shutdown();
-       Sys_Shutdown();
+       Sys_SDL_Shutdown();
        Log_Close();
        Crypto_Shutdown();
 
@@ -854,6 +602,103 @@ void Host_Shutdown(void)
        Memory_Shutdown();
 }
 
-void Host_NoOperation_f(cmd_state_t *cmd)
+//============================================================================
+
+/*
+==================
+Host_Frame
+
+Runs all active servers
+==================
+*/
+static double Host_Frame(double time)
 {
+       double cl_wait, sv_wait;
+
+       TaskQueue_Frame(false);
+
+       // keep the random time dependent, but not when playing demos/benchmarking
+       if(!*sv_random_seed.string && !host.restless)
+               rand();
+
+       NetConn_UpdateSockets();
+
+       Log_DestBuffer_Flush();
+
+       // Run any downloads
+       Curl_Frame();
+
+       // get new SDL events and add commands from keybindings to the cbuf
+       Sys_SDL_HandleEvents();
+
+       // process console commands
+       Cbuf_Frame(host.cbuf);
+
+       R_TimeReport("---");
+
+       // if the accumulators haven't become positive yet, wait a while
+       sv_wait = - SV_Frame(time);
+       cl_wait = - CL_Frame(time);
+
+       Mem_CheckSentinelsGlobal();
+
+       if (cls.state == ca_dedicated)
+               return sv_wait; // dedicated
+       else if (!sv.active || svs.threaded)
+               return cl_wait; // connected to server, main menu, or server is on different thread
+       else
+               return min(cl_wait, sv_wait); // listen server or singleplayer
+}
+
+// Cloudwalk: Most overpowered function declaration...
+static inline double Host_UpdateTime (double newtime, double oldtime)
+{
+       double time = newtime - oldtime;
+
+       if (time < 0)
+       {
+               // warn if it's significant
+               if (time < -0.01)
+                       Con_Printf(CON_WARN "Host_UpdateTime: time stepped backwards (went from %f to %f, difference %f)\n", oldtime, newtime, time);
+               time = 0;
+       }
+       else if (time >= 1800)
+       {
+               Con_Printf(CON_WARN "Host_UpdateTime: time stepped forward (went from %f to %f, difference %f)\n", oldtime, newtime, time);
+               time = 0;
+       }
+
+       return time;
+}
+
+void Host_Main(void)
+{
+       double time, oldtime, sleeptime;
+
+       Host_Init(); // Start!
+
+       host.realtime = 0;
+       oldtime = Sys_DirtyTime();
+
+       // Main event loop
+       while(host.state < host_shutdown) // see Sys_HandleCrash() comments
+       {
+               // Something bad happened, or the server disconnected
+               if (setjmp(host.abortframe))
+               {
+                       host.state = host_active; // In case we were loading
+                       continue;
+               }
+
+               host.dirtytime = Sys_DirtyTime();
+               host.realtime += time = Host_UpdateTime(host.dirtytime, oldtime);
+               oldtime = host.dirtytime;
+
+               sleeptime = Host_Frame(time);
+               ++host.framecount;
+               sleeptime -= Sys_DirtyTime() - host.dirtytime; // execution time
+               host.sleeptime = Sys_Sleep(sleeptime);
+       }
+
+       Host_Shutdown();
 }