/*
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
#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 "utf8lib.h"
*/
-host_t host;
+host_static_t host;
// pretend frames take this amount of time (in seconds), 0 = realtime
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"};
-cvar_t cl_maxphysicsframesperserverframe = {CF_CLIENT, "cl_maxphysicsframesperserverframe","10", "maximum number of physics frames per server frame"};
// shows time used by certain subsystems
cvar_t host_speeds = {CF_CLIENT | CF_SERVER, "host_speeds","0", "reports how much time is used in server/graphics/sound"};
cvar_t host_maxwait = {CF_CLIENT | CF_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."};
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
PRVM_Crash(MVM_prog);
#endif
- cl.csqc_loaded = false;
Cvar_SetValueQuick(&csqc_progcrc, -1);
Cvar_SetValueQuick(&csqc_progsize, -1);
- 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 ();
+ // prevent an endless loop if the error was triggered by a command
+ Cbuf_Clear(cmd_local->cbuf);
+
+ CL_Disconnect();
cls.demonum = -1;
hosterror = false;
Host_AbortCurrentFrame();
}
-static void Host_ServerOptions (void)
-{
- int i;
-
- // general default
- svs.maxclients = 8;
-
-// 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 = Sys_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 (Sys_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 = Sys_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);
-
- svs.clients = (client_t *)Mem_Alloc(sv_mempool, sizeof(client_t) * svs.maxclients);
-
- if (svs.maxclients > 1 && !deathmatch.integer && !coop.integer)
- Cvar_SetValueQuick(&deathmatch, 1);
-}
-
/*
==================
Host_Quit_f
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)
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(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");
- 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 (&host_isclient);
-
- 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 (×tamps);
- 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
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;
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;
Con_Printf("Saving to %s\n", file);
}
- Host_SaveConfig_to(file);
+ Host_SaveConfig(file);
}
static void Host_AddConfigText(cmd_state_t *cmd)
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");
+ Cbuf_InsertText(cmd_local, "\nmenu_restart\n");
#endif
// 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_AddConfigText(cmd_local);
}
/*
-==================
-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_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_maxwait);
+ 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 (×tamps);
+ Cvar_RegisterVariable (&timeformat);
-qbool 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];
}
}
}
+
void Host_LockSession(void)
{
if(locksession_run)
}
}
}
+
void Host_UnlockSession(void)
{
if(!locksession_run)
static void Host_Init (void)
{
int i;
- const char* os;
char vabuf[1024];
- cmd_state_t *cmd = &cmd_client;
host.hook.ConnectLocal = NULL;
host.hook.Disconnect = NULL;
host.hook.ToggleMenu = NULL;
host.hook.CL_Intermission = NULL;
- host.hook.SV_CanSave = NULL;
+ host.hook.SV_Shutdown = NULL;
host.state = host_init;
if (Sys_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();
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);
+ 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
World_Init();
SV_Init();
Host_InitLocal();
- Host_ServerOptions();
Thread_Init();
TaskQueue_Init();
// here comes the not so critical stuff
- Host_AddConfigText(cmd);
+ Host_AddConfigText(cmd_local);
// if quake.rc is missing, use default
if (!FS_FileExists("quake.rc"))
{
- Cbuf_AddText(cmd, "exec default.cfg\nexec " CONFIGFILENAME "\nexec autoexec.cfg\n");
- Cbuf_Execute(cmd->cbuf);
+ Cbuf_AddText(cmd_local, "exec default.cfg\nexec " CONFIGFILENAME "\nexec autoexec.cfg\n");
+ Cbuf_Execute(cmd_local->cbuf);
}
host.state = host_active;
- Host_StartVideo();
+ CL_StartVideo();
Log_Start();
- // put up the loading image so the user doesn't stare at a black screen...
- SCR_BeginLoadingPlaque(true);
-#ifdef CONFIG_MENU
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 = 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
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
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 || 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");
SV_StartThread();
}
-
/*
===============
Host_Shutdown
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 ();
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)
+ {
+ // 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);
+ }
+
+ return;
}