]> git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - sv_main.c
don't crash when .clientcamera is set on a botclient
[xonotic/darkplaces.git] / sv_main.c
index b2514171439a00d599e22b9feebd5ad25904cb12..406bde3e024ac075698ea1a8464e8be91d29be2c 100644 (file)
--- a/sv_main.c
+++ b/sv_main.c
@@ -23,6 +23,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 #include "sv_demo.h"
 #include "libcurl.h"
 #include "csprogs.h"
+#include "thread.h"
 
 static void SV_SaveEntFile_f(void);
 static void SV_StartDownload_f(void);
@@ -38,6 +39,7 @@ cvar_t sv_worldname = {CVAR_READONLY, "sv_worldname", "", "name of current world
 cvar_t sv_worldnamenoextension = {CVAR_READONLY, "sv_worldnamenoextension", "", "name of current worldmodel without extension"};
 cvar_t sv_worldbasename = {CVAR_READONLY, "sv_worldbasename", "", "name of current worldmodel without maps/ prefix or extension"};
 
+cvar_t sv_disablenotify = {0, "sv_disablenotify", "1", "suppress broadcast prints when certain cvars are changed (CVAR_NOTIFY flag in engine code)"};
 cvar_t coop = {0, "coop","0", "coop mode, 0 = no coop, 1 = coop mode, multiple players playing through the singleplayer game (coop mode also shuts off deathmatch)"};
 cvar_t deathmatch = {0, "deathmatch","0", "deathmatch mode, values depend on mod but typically 0 = no deathmatch, 1 = normal deathmatch with respawning weapons, 2 = weapons stay (players can only pick up new weapons)"};
 cvar_t fraglimit = {CVAR_NOTIFY, "fraglimit","0", "ends level if this many frags is reached by any player"};
@@ -123,6 +125,7 @@ cvar_t sv_gameplayfix_downtracesupportsongroundflag = {0, "sv_gameplayfix_downtr
 cvar_t sv_gameplayfix_q1bsptracelinereportstexture = {0, "sv_gameplayfix_q1bsptracelinereportstexture", "1", "enables mods to get accurate trace_texture results on q1bsp by using a surface-hitting traceline implementation rather than the standard solidbsp method, q3bsp always reports texture accurately"};
 cvar_t sv_gameplayfix_unstickplayers = {0, "sv_gameplayfix_unstickplayers", "1", "big hack to try and fix the rare case of MOVETYPE_WALK entities getting stuck in the world clipping hull."};
 cvar_t sv_gameplayfix_unstickentities = {0, "sv_gameplayfix_unstickentities", "1", "hack to check if entities are crossing world collision hull and try to move them to the right position"};
+cvar_t sv_gameplayfix_fixedcheckwatertransition = {0, "sv_gameplayfix_fixedcheckwatertransition", "1", "fix two very stupid bugs in SV_CheckWaterTransition when watertype is CONTENTS_EMPTY (the bugs causes waterlevel to be 1 on first frame, -1 on second frame - the fix makes it 0 on both frames)"};
 cvar_t sv_gravity = {CVAR_NOTIFY, "sv_gravity","800", "how fast you fall (512 = roughly earth gravity)"};
 cvar_t sv_idealpitchscale = {0, "sv_idealpitchscale","0.8", "how much to look up/down slopes and stairs when not using freelook"};
 cvar_t sv_jumpstep = {CVAR_NOTIFY, "sv_jumpstep", "0", "whether you can step up while jumping (sv_gameplayfix_stepwhilejumping must also be 1)"};
@@ -150,9 +153,11 @@ cvar_t sv_warsowbunny_topspeed = {0, "sv_warsowbunny_topspeed", "925", "soft spe
 cvar_t sv_warsowbunny_turnaccel = {0, "sv_warsowbunny_turnaccel", "0", "max sharpness of turns (also master switch for the sv_warsowbunny_* mode; set this to 9 to enable)"};
 cvar_t sv_warsowbunny_backtosideratio = {0, "sv_warsowbunny_backtosideratio", "0.8", "lower values make it easier to change direction without losing speed; the drawback is \"understeering\" in sharp turns"};
 cvar_t sv_onlycsqcnetworking = {0, "sv_onlycsqcnetworking", "0", "disables legacy entity networking code for higher performance (except on clients, which can still be legacy)"};
+cvar_t sv_areadebug = {0, "sv_areadebug", "0", "disables physics culling for debugging purposes (only for development)"};
 cvar_t sys_ticrate = {CVAR_SAVE, "sys_ticrate","0.0138889", "how long a server frame is in seconds, 0.05 is 20fps server rate, 0.1 is 10fps (can not be set higher than 0.1), 0 runs as many server frames as possible (makes games against bots a little smoother, overwhelms network players), 0.0138889 matches QuakeWorld physics"};
 cvar_t teamplay = {CVAR_NOTIFY, "teamplay","0", "teamplay mode, values depend on mod but typically 0 = no teams, 1 = no team damage no self damage, 2 = team damage and self damage, some mods support 3 = no team damage but can damage self"};
 cvar_t timelimit = {CVAR_NOTIFY, "timelimit","0", "ends level at this time (in minutes)"};
+cvar_t sv_threaded = {0, "sv_threaded", "0", "enables a separate thread for server code, improving performance, especially when hosting a game while playing, EXPERIMENTAL, may be crashy"};
 
 cvar_t saved1 = {CVAR_SAVE, "saved1", "0", "unused cvar in quake that is saved to config.cfg on exit, can be used by mods"};
 cvar_t saved2 = {CVAR_SAVE, "saved2", "0", "unused cvar in quake that is saved to config.cfg on exit, can be used by mods"};
@@ -445,6 +450,7 @@ void SV_Init (void)
        Cmd_AddCommand_WithClientCommand("sv_startdownload", NULL, SV_StartDownload_f, "begins sending a file to the client (network protocol use only)");
        Cmd_AddCommand_WithClientCommand("download", NULL, SV_Download_f, "downloads a specified file from the server");
 
+       Cvar_RegisterVariable (&sv_disablenotify);
        Cvar_RegisterVariable (&coop);
        Cvar_RegisterVariable (&deathmatch);
        Cvar_RegisterVariable (&fraglimit);
@@ -529,6 +535,7 @@ void SV_Init (void)
        Cvar_RegisterVariable (&sv_gameplayfix_q1bsptracelinereportstexture);
        Cvar_RegisterVariable (&sv_gameplayfix_unstickplayers);
        Cvar_RegisterVariable (&sv_gameplayfix_unstickentities);
+       Cvar_RegisterVariable (&sv_gameplayfix_fixedcheckwatertransition);
        Cvar_RegisterVariable (&sv_gravity);
        Cvar_RegisterVariable (&sv_idealpitchscale);
        Cvar_RegisterVariable (&sv_jumpstep);
@@ -556,9 +563,11 @@ void SV_Init (void)
        Cvar_RegisterVariable (&sv_warsowbunny_turnaccel);
        Cvar_RegisterVariable (&sv_warsowbunny_backtosideratio);
        Cvar_RegisterVariable (&sv_onlycsqcnetworking);
+       Cvar_RegisterVariable (&sv_areadebug);
        Cvar_RegisterVariable (&sys_ticrate);
        Cvar_RegisterVariable (&teamplay);
        Cvar_RegisterVariable (&timelimit);
+       Cvar_RegisterVariable (&sv_threaded);
 
        Cvar_RegisterVariable (&saved1);
        Cvar_RegisterVariable (&saved2);
@@ -736,7 +745,7 @@ void SV_StartSound (prvm_edict_t *entity, int channel, const char *sample, int v
 
        ent = PRVM_NUM_FOR_EDICT(entity);
 
-       speed4000 = (int)(speed * 40.0f);
+       speed4000 = (int)floor(speed * 4000.0f + 0.5f);
        field_mask = 0;
        if (volume != DEFAULT_SOUND_PACKET_VOLUME)
                field_mask |= SND_VOLUME;
@@ -1511,7 +1520,7 @@ qboolean SV_CanSeeBox(int numtraces, vec_t enlarge, vec3_t eye, vec3_t entboxmin
 
        // get the list of entities in the sweep box
        if (sv_cullentities_trace_entityocclusion.integer)
-               numtouchedicts = World_EntitiesInBox(&sv.world, clipboxmins, clipboxmaxs, MAX_EDICTS, touchedicts);
+               numtouchedicts = SV_EntitiesInBox(clipboxmins, clipboxmaxs, MAX_EDICTS, touchedicts);
        if (numtouchedicts > MAX_EDICTS)
        {
                // this never happens
@@ -2478,7 +2487,7 @@ static void SV_UpdateToReliableMessages (void)
                                clientcamera = PRVM_NUM_FOR_EDICT(host_client->edict);
                        host_client->clientcamera = clientcamera;
 
-                       if (oldclientcamera != host_client->clientcamera)
+                       if (oldclientcamera != host_client->clientcamera && host_client->netconnection)
                        {
                                MSG_WriteByte(&host_client->netconnection->message, svc_setview);
                                MSG_WriteShort(&host_client->netconnection->message, host_client->clientcamera);
@@ -2827,9 +2836,23 @@ int SV_ModelIndex(const char *s, int precachemode)
                                if (precachemode == 1)
                                        Con_Printf("SV_ModelIndex(\"%s\"): not precached (fix your code), precaching anyway\n", filename);
                                strlcpy(sv.model_precache[i], filename, sizeof(sv.model_precache[i]));
-                               sv.models[i] = Mod_ForName (sv.model_precache[i], true, false, s[0] == '*' ? sv.worldname : NULL);
-                               if (sv.state != ss_loading)
+                               if (sv.state == ss_loading)
+                               {
+                                       // running from SV_SpawnServer which is launched from the client console command interpreter
+                                       sv.models[i] = Mod_ForName (sv.model_precache[i], true, false, s[0] == '*' ? sv.worldname : NULL);
+                               }
+                               else
                                {
+                                       if (svs.threaded)
+                                       {
+                                               // this is running on the server thread, we can't load a model here (it would crash on renderer calls), so only look it up, the svc_precache will cause it to be loaded when it reaches the client
+                                               sv.models[i] = Mod_FindName (sv.model_precache[i], s[0] == '*' ? sv.worldname : NULL);
+                                       }
+                                       else
+                                       {
+                                               // running single threaded, so we can load the model here
+                                               sv.models[i] = Mod_ForName (sv.model_precache[i], true, false, s[0] == '*' ? sv.worldname : NULL);
+                                       }
                                        MSG_WriteByte(&sv.reliable_datagram, svc_precache);
                                        MSG_WriteShort(&sv.reliable_datagram, i);
                                        MSG_WriteString(&sv.reliable_datagram, filename);
@@ -3186,6 +3209,8 @@ void SV_SpawnServer (const char *server)
                }
        }
 
+//     SV_LockThreadMutex();
+
        if (cls.state != ca_dedicated)
        {
                SCR_BeginLoadingPlaque();
@@ -3212,6 +3237,7 @@ void SV_SpawnServer (const char *server)
        if (!worldmodel || !worldmodel->TraceBox)
        {
                Con_Printf("Couldn't load map %s\n", modelname);
+               SV_UnlockThreadMutex();
                return;
        }
 
@@ -3459,6 +3485,8 @@ void SV_SpawnServer (const char *server)
        NetConn_Heartbeat (2);
 
        SV_VM_End();
+
+//     SV_UnlockThreadMutex();
 }
 
 /////////////////////////////////////////////////////
@@ -3819,3 +3847,175 @@ void SV_VM_End(void)
 {
        PRVM_End;
 }
+
+extern cvar_t host_maxwait;
+extern cvar_t host_framerate;
+int SV_ThreadFunc(void *voiddata)
+{
+       double sv_timer = 0;
+       double sv_deltarealtime, sv_oldrealtime, sv_realtime;
+       double wait;
+       int i;
+       sv_realtime = Sys_DoubleTime();
+       while (!svs.threadstop)
+       {
+               // FIXME: we need to handle Host_Error in the server thread somehow
+//             if (setjmp(sv_abortframe))
+//                     continue;                       // something bad happened in the server game
+
+               sv_oldrealtime = sv_realtime;
+               sv_realtime = Sys_DoubleTime();
+
+               sv_deltarealtime = sv_realtime - sv_oldrealtime;
+               sv_timer += sv_deltarealtime;
+
+               svs.perf_acc_realtime += sv_deltarealtime;
+
+               // Look for clients who have spawned
+               for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++)
+                       if(host_client->spawned)
+                               if(host_client->netconnection)
+                                       break;
+               if(i == svs.maxclients)
+               {
+                       // Nobody is looking? Then we won't do timing...
+                       // Instead, reset it to zero
+                       svs.perf_acc_realtime = svs.perf_acc_sleeptime = svs.perf_acc_lost = svs.perf_acc_offset = svs.perf_acc_offset_squared = svs.perf_acc_offset_max = svs.perf_acc_offset_samples = 0;
+               }
+               else if(svs.perf_acc_realtime > 5)
+               {
+                       svs.perf_cpuload = 1 - svs.perf_acc_sleeptime / svs.perf_acc_realtime;
+                       svs.perf_lost = svs.perf_acc_lost / svs.perf_acc_realtime;
+                       if(svs.perf_acc_offset_samples > 0)
+                       {
+                               svs.perf_offset_max = svs.perf_acc_offset_max;
+                               svs.perf_offset_avg = svs.perf_acc_offset / svs.perf_acc_offset_samples;
+                               svs.perf_offset_sdev = sqrt(svs.perf_acc_offset_squared / svs.perf_acc_offset_samples - svs.perf_offset_avg * svs.perf_offset_avg);
+                       }
+                       if(svs.perf_lost > 0 && developer_extra.integer)
+                               Con_DPrintf("Server can't keep up: %s\n", Host_TimingReport());
+                       svs.perf_acc_realtime = svs.perf_acc_sleeptime = svs.perf_acc_lost = svs.perf_acc_offset = svs.perf_acc_offset_squared = svs.perf_acc_offset_max = svs.perf_acc_offset_samples = 0;
+               }
+
+               // get new packets
+               if (sv.active)
+                       NetConn_ServerFrame();
+
+               // if the accumulators haven't become positive yet, wait a while
+               wait = sv_timer * -1000000.0;
+               if (wait >= 1)
+               {
+                       double time0;
+                       if (host_maxwait.value <= 0)
+                               wait = min(wait, 1000000.0);
+                       else
+                               wait = min(wait, host_maxwait.value * 1000.0);
+                       if(wait < 1)
+                               wait = 1; // because we cast to int
+                       time0 = Sys_DoubleTime();
+                       Sys_Sleep((int)wait);
+                       svs.perf_acc_sleeptime += Sys_DoubleTime() - time0;
+                       continue;
+               }
+
+               if (sv.active && sv_timer > 0)
+               {
+                       // execute one server frame
+                       double advancetime;
+                       float offset;
+
+                       if (sys_ticrate.value <= 0)
+                               advancetime = min(sv_timer, 0.1); // don't step more than 100ms
+                       else
+                               advancetime = sys_ticrate.value;
+
+                       if(advancetime > 0)
+                       {
+                               offset = sv_timer + (Sys_DoubleTime() - sv_realtime); // LordHavoc: FIXME: I don't understand this line
+                               ++svs.perf_acc_offset_samples;
+                               svs.perf_acc_offset += offset;
+                               svs.perf_acc_offset_squared += offset * offset;
+                               if(svs.perf_acc_offset_max < offset)
+                                       svs.perf_acc_offset_max = offset;
+                       }
+
+                       // at this point we start doing real server work, and must block on any client activity pertaining to the server (such as executing SV_SpawnServer)
+                       SV_LockThreadMutex();
+
+                       // only advance time if not paused
+                       // the game also pauses in singleplayer when menu or console is used
+                       sv.frametime = advancetime * slowmo.value;
+                       if (host_framerate.value)
+                               sv.frametime = host_framerate.value;
+                       if (sv.paused || (cl.islocalgame && (key_dest != key_game || key_consoleactive || cl.csqc_paused)))
+                               sv.frametime = 0;
+
+                       sv_timer -= advancetime;
+
+                       // setup the VM frame
+                       SV_VM_Begin();
+
+                       // move things around and think unless paused
+                       if (sv.frametime)
+                               SV_Physics();
+
+                       // send all messages to the clients
+                       SV_SendClientMessages();
+
+                       if (sv.paused == 1 && sv_realtime > sv.pausedstart && sv.pausedstart > 0)
+                       {
+                               prog->globals.generic[OFS_PARM0] = sv_realtime - sv.pausedstart;
+                               PRVM_ExecuteProgram(PRVM_serverfunction(SV_PausedTic), "QC function SV_PausedTic is missing");
+                       }
+
+                       // end the server VM frame
+                       SV_VM_End();
+
+                       // send an heartbeat if enough time has passed since the last one
+                       NetConn_Heartbeat(0);
+
+                       // at this point we start doing real server work, and must block on any client activity pertaining to the server (such as executing SV_SpawnServer)
+                       SV_UnlockThreadMutex();
+               }
+
+               // if there is some time remaining from this frame, reset the timers
+               if (sv_timer >= 0)
+               {
+                       svs.perf_acc_lost += sv_timer;
+                       sv_timer = 0;
+               }
+       }
+       return 0;
+}
+
+void SV_StartThread(void)
+{
+       if (!sv_threaded.integer || !Thread_HasThreads())
+               return;
+       svs.threaded = true;
+       svs.threadstop = false;
+       svs.threadmutex = Thread_CreateMutex();
+       svs.thread = Thread_CreateThread(SV_ThreadFunc, NULL);
+}
+
+void SV_StopThread(void)
+{
+       if (!svs.threaded)
+               return;
+       svs.threadstop = true;
+       Thread_WaitThread(svs.thread, 0);
+       Thread_DestroyMutex(svs.threadmutex);
+       svs.threaded = false;
+}
+
+void SV_LockThreadMutex(void)
+{
+       if (svs.threaded)
+               Thread_LockMutex(svs.threadmutex);
+}
+
+void SV_UnlockThreadMutex(void)
+{
+       if (svs.threaded)
+               Thread_UnlockMutex(svs.threadmutex);
+}