X-Git-Url: http://git.xonotic.org/?a=blobdiff_plain;f=sv_main.c;h=5b7c4989eac9f0d1da1d8d4c239527da188228fb;hb=bec8e6d8924179fac3cee47e2b75af45b3ead521;hp=d103ffe65c1fcba759f7a02d5f76053e2fd863ce;hpb=66ec69f1c020739371ac074baa7bb652834ecece;p=xonotic%2Fdarkplaces.git diff --git a/sv_main.c b/sv_main.c index d103ffe6..5b7c4989 100644 --- a/sv_main.c +++ b/sv_main.c @@ -50,6 +50,7 @@ cvar_t pausable = {CF_SERVER, "pausable","1", "allow players to pause or not (ot cvar_t pr_checkextension = {CF_SERVER | CF_READONLY, "pr_checkextension", "1", "indicates to QuakeC that the standard quakec extensions system is available (if 0, quakec should not attempt to use extensions)"}; cvar_t samelevel = {CF_SERVER | CF_NOTIFY, "samelevel","0", "repeats same level if level ends (due to timelimit or someone hitting an exit)"}; cvar_t skill = {CF_SERVER, "skill","1", "difficulty level of game, affects monster layouts in levels, 0 = easy, 1 = normal, 2 = hard, 3 = nightmare (same layout as hard but monsters fire twice)"}; +cvar_t campaign = {CF_SERVER, "campaign", "0", "singleplayer mode"}; cvar_t host_timescale = {CF_CLIENT | CF_SERVER, "host_timescale", "1.0", "controls game speed, 0.5 is half speed, 2 is double speed"}; cvar_t sv_accelerate = {CF_SERVER, "sv_accelerate", "10", "rate at which a player accelerates to sv_maxspeed"}; @@ -71,6 +72,7 @@ cvar_t sv_allowdownloads_archive = {CF_SERVER, "sv_allowdownloads_archive", "0", cvar_t sv_allowdownloads_config = {CF_SERVER, "sv_allowdownloads_config", "0", "whether to allow downloads of config files (cfg)"}; cvar_t sv_allowdownloads_dlcache = {CF_SERVER, "sv_allowdownloads_dlcache", "0", "whether to allow downloads of dlcache files (dlcache/)"}; cvar_t sv_allowdownloads_inarchive = {CF_SERVER, "sv_allowdownloads_inarchive", "0", "whether to allow downloads from archives (pak/pk3)"}; +cvar_t sv_areagrid_link_SOLID_NOT = {CF_SERVER | CF_NOTIFY, "sv_areagrid_link_SOLID_NOT", "1", "set to 0 to prevent SOLID_NOT entities from being linked to the area grid, and unlink any that are already linked (in the code paths that would otherwise link them), for better performance"}; cvar_t sv_areagrid_mingridsize = {CF_SERVER | CF_NOTIFY, "sv_areagrid_mingridsize", "128", "minimum areagrid cell size, smaller values work better for lots of small objects, higher values for large objects"}; cvar_t sv_checkforpacketsduringsleep = {CF_SERVER, "sv_checkforpacketsduringsleep", "0", "uses select() function to wait between frames which can be interrupted by packets being received, instead of Sleep()/usleep()/SDL_Sleep() functions which do not check for packets"}; cvar_t sv_clmovement_enable = {CF_SERVER, "sv_clmovement_enable", "1", "whether to allow clients to use cl_movement prediction, which can cause choppy movement on the server which may annoy other players"}; @@ -81,17 +83,18 @@ cvar_t sv_cullentities_nevercullbmodels = {CF_SERVER, "sv_cullentities_nevercull cvar_t sv_cullentities_pvs = {CF_SERVER, "sv_cullentities_pvs", "1", "fast but loose culling of hidden entities"}; cvar_t sv_cullentities_stats = {CF_SERVER, "sv_cullentities_stats", "0", "displays stats on network entities culled by various methods for each client"}; cvar_t sv_cullentities_trace = {CF_SERVER, "sv_cullentities_trace", "0", "somewhat slow but very tight culling of hidden entities, minimizes network traffic and makes wallhack cheats useless"}; -cvar_t sv_cullentities_trace_delay = {CF_SERVER, "sv_cullentities_trace_delay", "1", "number of seconds until the entity gets actually culled"}; +cvar_t sv_cullentities_trace_delay = {CF_SERVER, "sv_cullentities_trace_delay", "1", "number of seconds until the entity gets actually culled (also applies to portal camera eyes even if sv_cullentities_trace is 0)"}; cvar_t sv_cullentities_trace_delay_players = {CF_SERVER, "sv_cullentities_trace_delay_players", "0.2", "number of seconds until the entity gets actually culled if it is a player entity"}; -cvar_t sv_cullentities_trace_enlarge = {CF_SERVER, "sv_cullentities_trace_enlarge", "0", "box enlargement for entity culling"}; -cvar_t sv_cullentities_trace_expand = {CF_SERVER, "sv_cullentities_trace_expand", "0", "box is expanded by this many units for entity culling"}; -cvar_t sv_cullentities_trace_eyejitter = {CF_SERVER, "sv_cullentities_trace_eyejitter", "16", "jitter the eye by this much for each trace"}; +cvar_t sv_cullentities_trace_enlarge = {CF_SERVER, "sv_cullentities_trace_enlarge", "0", "box enlargement for entity culling (also applies to portal camera eyes even if sv_cullentities_trace is 0)"}; +cvar_t sv_cullentities_trace_expand = {CF_SERVER, "sv_cullentities_trace_expand", "0", "box is expanded by this many units for entity culling (also applies to portal camera eyes even if sv_cullentities_trace is 0)"}; +cvar_t sv_cullentities_trace_eyejitter = {CF_SERVER, "sv_cullentities_trace_eyejitter", "16", "jitter the eye by this much for each trace (also applies to portal camera eyes even if sv_cullentities_trace is 0)"}; cvar_t sv_cullentities_trace_prediction = {CF_SERVER, "sv_cullentities_trace_prediction", "1", "also trace from the predicted player position"}; -cvar_t sv_cullentities_trace_prediction_time = {CF_SERVER, "sv_cullentities_trace_prediction_time", "0.2", "how many seconds of prediction to use"}; +cvar_t sv_cullentities_trace_prediction_time = {CF_SERVER, "sv_cullentities_trace_prediction_time", "0.2", "maximum ping time to predict in seconds"}; cvar_t sv_cullentities_trace_entityocclusion = {CF_SERVER, "sv_cullentities_trace_entityocclusion", "0", "also check if doors and other bsp models are in the way"}; cvar_t sv_cullentities_trace_samples = {CF_SERVER, "sv_cullentities_trace_samples", "2", "number of samples to test for entity culling"}; -cvar_t sv_cullentities_trace_samples_extra = {CF_SERVER, "sv_cullentities_trace_samples_extra", "2", "number of samples to test for entity culling when the entity affects its surroundings by e.g. dlight"}; +cvar_t sv_cullentities_trace_samples_extra = {CF_SERVER, "sv_cullentities_trace_samples_extra", "2", "number of samples to test for entity culling when the entity affects its surroundings by e.g. dlight (also applies to portal camera eyes even if sv_cullentities_trace is 0)"}; cvar_t sv_cullentities_trace_samples_players = {CF_SERVER, "sv_cullentities_trace_samples_players", "8", "number of samples to test for entity culling when the entity is a player entity"}; +cvar_t sv_cullentities_trace_spectators = {CF_SERVER, "sv_cullentities_trace_spectators", "0", "enables trace entity culling for clients that are spectating"}; cvar_t sv_debugmove = {CF_SERVER | CF_NOTIFY, "sv_debugmove", "0", "disables collision detection optimizations for debugging purposes"}; cvar_t sv_echobprint = {CF_SERVER | CF_ARCHIVE, "sv_echobprint", "1", "prints gamecode bprint() calls to server console"}; cvar_t sv_edgefriction = {CF_SERVER, "edgefriction", "1", "how much you slow down when nearing a ledge you might fall off, multiplier of sv_friction (Quake used 2, QuakeWorld used 1 due to a bug in physics code)"}; @@ -121,7 +124,7 @@ cvar_t sv_gameplayfix_stepmultipletimes = {CF_SERVER, "sv_gameplayfix_stepmultip cvar_t sv_gameplayfix_nostepmoveonsteepslopes = {CF_SERVER, "sv_gameplayfix_nostepmoveonsteepslopes", "0", "crude fix which prevents MOVETYPE_STEP (not swimming or flying) to move on slopes whose angle is bigger than 45 degree"}; cvar_t sv_gameplayfix_swiminbmodels = {CF_SERVER, "sv_gameplayfix_swiminbmodels", "1", "causes pointcontents (used to determine if you are in a liquid) to check bmodel entities as well as the world model, so you can swim around in (possibly moving) water bmodel entities"}; cvar_t sv_gameplayfix_upwardvelocityclearsongroundflag = {CF_SERVER, "sv_gameplayfix_upwardvelocityclearsongroundflag", "1", "prevents monsters, items, and most other objects from being stuck to the floor when pushed around by damage, and other situations in mods"}; -cvar_t sv_gameplayfix_downtracesupportsongroundflag = {CF_SERVER, "sv_gameplayfix_downtracesupportsongroundflag", "1", "prevents very short moves from clearing onground (which may make the player stick to the floor at high netfps)"}; +cvar_t sv_gameplayfix_downtracesupportsongroundflag = {CF_SERVER, "sv_gameplayfix_downtracesupportsongroundflag", "1", "prevents very short moves from clearing onground (which may make the player stick to the floor at high netfps), fixes groundentity not being set when walking onto a mover with sv_gameplayfix_nogravityonground"}; cvar_t sv_gameplayfix_q1bsptracelinereportstexture = {CF_SERVER, "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 = {CF_SERVER, "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 = {CF_SERVER, "sv_gameplayfix_unstickentities", "1", "hack to check if entities are crossing world collision hull and try to move them to the right position"}; @@ -132,6 +135,7 @@ cvar_t sv_init_frame_count = {CF_SERVER, "sv_init_frame_count", "2", "number of cvar_t sv_idealpitchscale = {CF_SERVER, "sv_idealpitchscale","0.8", "how much to look up/down slopes and stairs when not using freelook"}; cvar_t sv_jumpstep = {CF_SERVER | CF_NOTIFY, "sv_jumpstep", "0", "whether you can step up while jumping"}; cvar_t sv_jumpvelocity = {CF_SERVER, "sv_jumpvelocity", "270", "cvar that can be used by QuakeC code for jump velocity"}; +cvar_t sv_legacy_bbox_expand = {CF_SERVER, "sv_legacy_bbox_expand", "1", "before linking an entity to the area grid, decrease its mins and increase its maxs by '1 1 1', or '15 15 1' if it has flag FL_ITEM (this is the Quake/QuakeWorld behaviour); disable to make SVQC bboxes consistent with CSQC which never does this expansion"}; cvar_t sv_maxairspeed = {CF_SERVER, "sv_maxairspeed", "30", "maximum speed a player can accelerate to when airborn (note that it is possible to completely stop by moving the opposite direction)"}; cvar_t sv_maxrate = {CF_SERVER | CF_ARCHIVE | CF_NOTIFY, "sv_maxrate", "1000000", "upper limit on client rate cvar, should reflect your network connection quality"}; cvar_t sv_maxspeed = {CF_SERVER | CF_NOTIFY, "sv_maxspeed", "320", "maximum speed a player can accelerate to when on ground (can be exceeded by tricks)"}; @@ -156,10 +160,15 @@ cvar_t sv_warsowbunny_turnaccel = {CF_SERVER, "sv_warsowbunny_turnaccel", "0", " cvar_t sv_warsowbunny_backtosideratio = {CF_SERVER, "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 = {CF_SERVER, "sv_onlycsqcnetworking", "0", "disables legacy entity networking code for higher performance (except on clients, which can still be legacy)"}; cvar_t sv_areadebug = {CF_SERVER, "sv_areadebug", "0", "disables physics culling for debugging purposes (only for development)"}; + cvar_t sys_ticrate = {CF_SERVER | CF_ARCHIVE, "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 sv_maxphysicsframesperserverframe = {CF_SERVER, "sv_maxphysicsframesperserverframe","10", "maximum number of physics frames per server frame"}; +cvar_t sv_lagreporting_always = {CF_SERVER, "sv_lagreporting_always", "0", "report lag even in singleplayer, listen, or an empty dedicated server"}; +cvar_t sv_lagreporting_strict = {CF_SERVER, "sv_lagreporting_strict", "0", "log any extra frames run to catch up after a holdup (only applies when sv_maxphysicsframesperserverframe > 1)"}; +cvar_t sv_threaded = {CF_SERVER, "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 teamplay = {CF_SERVER | CF_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 = {CF_SERVER | CF_NOTIFY, "timelimit","0", "ends level at this time (in minutes)"}; -cvar_t sv_threaded = {CF_SERVER, "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 sv_rollspeed = {CF_CLIENT, "sv_rollspeed", "200", "how much strafing is necessary to tilt the view"}; cvar_t sv_rollangle = {CF_CLIENT, "sv_rollangle", "2.0", "how much to tilt the view when strafing"}; @@ -203,7 +212,7 @@ cvar_t sv_autodemo_perclient_discardable = {CF_SERVER | CF_ARCHIVE, "sv_autodemo cvar_t halflifebsp = {CF_SERVER, "halflifebsp", "0", "indicates the current map is hlbsp format (useful to know because of different bounding box sizes)"}; cvar_t sv_mapformat_is_quake2 = {CF_SERVER, "sv_mapformat_is_quake2", "0", "indicates the current map is q2bsp format (useful to know because of different entity behaviors, .frame on submodels and other things)"}; -cvar_t sv_mapformat_is_quake3 = {CF_SERVER, "sv_mapformat_is_quake3", "0", "indicates the current map is q2bsp format (useful to know because of different entity behaviors)"}; +cvar_t sv_mapformat_is_quake3 = {CF_SERVER, "sv_mapformat_is_quake3", "0", "indicates the current map is q3bsp format (useful to know because of different entity behaviors)"}; cvar_t sv_writepicture_quality = {CF_SERVER | CF_ARCHIVE, "sv_writepicture_quality", "10", "WritePicture quality offset (higher means better quality, but slower)"}; @@ -448,7 +457,6 @@ static void SV_ServerOptions (void) 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]); @@ -524,6 +532,7 @@ void SV_Init (void) Cvar_RegisterVariable (&pr_checkextension); Cvar_RegisterVariable (&samelevel); Cvar_RegisterVariable (&skill); + Cvar_RegisterVariable (&campaign); Cvar_RegisterVariable (&host_timescale); Cvar_RegisterCallback (&host_timescale, Host_Timescale_c); Cvar_RegisterVirtual (&host_timescale, "slowmo"); @@ -547,6 +556,7 @@ void SV_Init (void) Cvar_RegisterVariable (&sv_allowdownloads_config); Cvar_RegisterVariable (&sv_allowdownloads_dlcache); Cvar_RegisterVariable (&sv_allowdownloads_inarchive); + Cvar_RegisterVariable (&sv_areagrid_link_SOLID_NOT); Cvar_RegisterVariable (&sv_areagrid_mingridsize); Cvar_RegisterVariable (&sv_checkforpacketsduringsleep); Cvar_RegisterVariable (&sv_clmovement_enable); @@ -568,6 +578,7 @@ void SV_Init (void) Cvar_RegisterVariable (&sv_cullentities_trace_samples); Cvar_RegisterVariable (&sv_cullentities_trace_samples_extra); Cvar_RegisterVariable (&sv_cullentities_trace_samples_players); + Cvar_RegisterVariable (&sv_cullentities_trace_spectators); Cvar_RegisterVariable (&sv_debugmove); Cvar_RegisterVariable (&sv_echobprint); Cvar_RegisterVariable (&sv_edgefriction); @@ -608,6 +619,7 @@ void SV_Init (void) Cvar_RegisterVariable (&sv_idealpitchscale); Cvar_RegisterVariable (&sv_jumpstep); Cvar_RegisterVariable (&sv_jumpvelocity); + Cvar_RegisterVariable (&sv_legacy_bbox_expand); Cvar_RegisterVariable (&sv_maxairspeed); Cvar_RegisterVariable (&sv_maxrate); Cvar_RegisterVariable (&sv_maxspeed); @@ -633,10 +645,15 @@ void SV_Init (void) Cvar_RegisterVariable (&sv_warsowbunny_backtosideratio); Cvar_RegisterVariable (&sv_onlycsqcnetworking); Cvar_RegisterVariable (&sv_areadebug); + Cvar_RegisterVariable (&sys_ticrate); + Cvar_RegisterVariable (&sv_maxphysicsframesperserverframe); + Cvar_RegisterVariable (&sv_lagreporting_always); + Cvar_RegisterVariable (&sv_lagreporting_strict); + Cvar_RegisterVariable (&sv_threaded); + Cvar_RegisterVariable (&teamplay); Cvar_RegisterVariable (&timelimit); - Cvar_RegisterVariable (&sv_threaded); Cvar_RegisterVariable (&sv_rollangle); Cvar_RegisterVariable (&sv_rollspeed); @@ -984,14 +1001,31 @@ void SV_ConnectClient (int clientnum, netconn_t *netconnection) SV_DropClient Called when the player is getting totally kicked off the host -if (crash = true), don't bother sending signofs +if (leaving = true), don't bother sending signofs ===================== */ -void SV_DropClient(qbool crash) +void SV_DropClient(qbool leaving, const char *fmt, ... ) { prvm_prog_t *prog = SVVM_prog; int i; - Con_Printf("Client \"%s\" dropped\n", host_client->name); + + va_list argptr; + char reason[512] = ""; + + Con_Printf("Client \"%s\" dropped", host_client->name); + + if(fmt) + { + va_start(argptr, fmt); + dpvsnprintf(reason, sizeof(reason), fmt, argptr); + va_end(argptr); + + Con_Printf(" (%s)\n", reason); + } + else + { + Con_Printf(" \n"); + } SV_StopDemoRecording(host_client); @@ -1001,15 +1035,22 @@ void SV_DropClient(qbool crash) if (host_client->netconnection) { // tell the client to be gone - if (!crash) + if (!leaving) { // LadyHavoc: no opportunity for resending, so use unreliable 3 times - unsigned char bufdata[8]; + unsigned char bufdata[520]; // Disconnect reason string can be 512 characters sizebuf_t buf; memset(&buf, 0, sizeof(buf)); buf.data = bufdata; buf.maxsize = sizeof(bufdata); MSG_WriteByte(&buf, svc_disconnect); + if(fmt) + { + if(sv.protocol == PROTOCOL_DARKPLACES8) + MSG_WriteString(&buf, reason); + else + SV_ClientPrintf("%s\n", reason); + } NetConn_SendUnreliableMessage(host_client->netconnection, &buf, sv.protocol, 10000, 0, false); NetConn_SendUnreliableMessage(host_client->netconnection, &buf, sv.protocol, 10000, 0, false); NetConn_SendUnreliableMessage(host_client->netconnection, &buf, sv.protocol, 10000, 0, false); @@ -1037,6 +1078,10 @@ void SV_DropClient(qbool crash) NetConn_Close(host_client->netconnection); host_client->netconnection = NULL; } + if(fmt) + SV_BroadcastPrintf("\003^3%s left the game (%s)\n", host_client->name, reason); + else + SV_BroadcastPrintf("\003^3%s left the game\n", host_client->name); // if a download is active, close it if (host_client->download_file) @@ -1274,7 +1319,7 @@ static void SV_Download_f(cmd_state_t *cmd) if (!sv_allowdownloads_archive.integer) { - if (!strcasecmp(extension, "pak") || !strcasecmp(extension, "pk3")) + if (!strcasecmp(extension, "pak") || !strcasecmp(extension, "pk3") || !strcasecmp(extension, "dpk")) { SV_ClientPrintf("Download rejected: file \"%s\" is an archive\nYou must separately download or purchase the data archives for this game/mod to get this file\n", host_client->download_name); SV_ClientCommands("\nstopdownload\n"); @@ -1835,8 +1880,16 @@ void SV_SpawnServer (const char *map) // // make cvars consistant // + if (coop.integer) + { Cvar_SetValueQuick(&deathmatch, 0); + Cvar_SetValueQuick(&campaign, 0); + } + else if(!deathmatch.integer) + Cvar_SetValueQuick(&campaign, 1); + else + Cvar_SetValueQuick(&campaign, 0); // LadyHavoc: it can be useful to have skills outside the range 0-3... //current_skill = bound(0, (int)(skill.value + 0.5), 3); //Cvar_SetValue ("skill", (float)current_skill); @@ -1846,6 +1899,10 @@ void SV_SpawnServer (const char *map) // set up the new server // memset (&sv, 0, sizeof(sv)); + + // tell SV_Frame() to reset its timers + sv.spawnframe = host.framecount; + // if running a local client, make sure it doesn't try to access the last // level's data which is no longer valiud cls.signon = 0; @@ -2083,7 +2140,7 @@ void SV_Shutdown(void) } for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++) if (host_client->active) - SV_DropClient(false); // server shutdown + SV_DropClient(false, "Server shutting down"); // server shutdown NetConn_CloseServerPorts(); @@ -2113,7 +2170,7 @@ static void SVVM_end_increase_edicts(prvm_prog_t *prog) // link every entity except world for (i = 1, ent = prog->edicts;i < prog->num_edicts;i++, ent++) - if (!ent->free && !VectorCompare(PRVM_serveredictvector(ent, absmin), PRVM_serveredictvector(ent, absmax))) + if (!ent->free) SV_LinkEdict(ent); } @@ -2189,9 +2246,10 @@ static void SVVM_free_edict(prvm_prog_t *prog, prvm_edict_t *ed) PRVM_serveredictfloat(ed, solid) = 0; VM_RemoveEdictSkeleton(prog, ed); +#ifdef USEODE World_Physics_RemoveFromEntity(&sv.world, ed); World_Physics_RemoveJointFromEntity(&sv.world, ed); - +#endif // make sure csqc networking is aware of the removed entity e = PRVM_NUM_FOR_EDICT(ed); sv.csqcentityversion[e] = 0; @@ -2445,17 +2503,8 @@ static void SV_CheckTimeouts(void) // never timeout loopback connections for (i = (host_isclient.integer ? 1 : 0), host_client = &svs.clients[i]; i < svs.maxclients; i++, host_client++) - { if (host_client->netconnection && host.realtime > host_client->netconnection->timeout) - { - if (host_client->begun) - SV_BroadcastPrintf("Client \"%s\" connection timed out\n", host_client->name); - else - Con_Printf("Client \"%s\" connection timed out\n", host_client->name); - - SV_DropClient(false); - } - } + SV_DropClient(false, "Timed out"); } /* @@ -2467,55 +2516,61 @@ Returns a time report string, for example for */ const char *SV_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); + return va(buf, buflen, "%.1f%% CPU, %.2f%% lost, offset avg %.1fms, max %.1fms, sdev %.1fms", sv.perf_cpuload * 100, sv.perf_lost * 100, sv.perf_offset_avg * 1000, sv.perf_offset_max * 1000, sv.perf_offset_sdev * 1000); } extern cvar_t host_maxwait; extern cvar_t host_framerate; -extern cvar_t cl_maxphysicsframesperserverframe; double SV_Frame(double time) { static double sv_timer; int i; char vabuf[1024]; - qbool playing = false; + qbool reporting = false; + + // reset timer after level change + if (host.framecount == sv.spawnframe || host.framecount == sv.spawnframe + 1) + sv_timer = time = host.sleeptime = 0; if (!svs.threaded) { - svs.perf_acc_sleeptime = host.sleeptime; - svs.perf_acc_realtime += time; + sv.perf_acc_sleeptime += host.sleeptime; + sv.perf_acc_realtime += time; - // Look for clients who have spawned - for (i = 0, host_client = svs.clients; i < svs.maxclients; i++, host_client++) - if(host_client->begun && host_client->netconnection) - playing = true; + if (sv_lagreporting_always.integer) + reporting = true; + else if (cls.state == ca_dedicated) + { + // Report lag if there's players, so they know it wasn't the network or their machine + for (i = 0; i < svs.maxclients; ++i) + { + if (svs.clients[i].begun && svs.clients[i].netconnection) + { + reporting = true; + break; + } + } + } - if(svs.perf_acc_realtime > 5) + if(sv.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; + sv.perf_cpuload = 1 - sv.perf_acc_sleeptime / sv.perf_acc_realtime; + sv.perf_lost = sv.perf_acc_lost / sv.perf_acc_realtime; - if(svs.perf_acc_offset_samples > 0) + if(sv.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); + sv.perf_offset_max = sv.perf_acc_offset_max; + sv.perf_offset_avg = sv.perf_acc_offset / sv.perf_acc_offset_samples; + sv.perf_offset_sdev = sqrt(sv.perf_acc_offset_squared / sv.perf_acc_offset_samples - sv.perf_offset_avg * sv.perf_offset_avg); } - if(svs.perf_lost > 0 && developer_extra.integer && playing) // only complain if anyone is looking - Con_DPrintf("Server can't keep up: %s\n", SV_TimingReport(vabuf, sizeof(vabuf))); - } + if (sv.perf_lost > 0 && reporting) + SV_BroadcastPrintf("\003" CON_WARN "Server lag report: %s\n", SV_TimingReport(vabuf, sizeof(vabuf))); - if(svs.perf_acc_realtime > 5 || sv.time < 10) - { - /* - * Don't accumulate time for the first 10 seconds of a match - * so things can settle - */ - 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 = host.sleeptime = 0; + sv.perf_acc_realtime = sv.perf_acc_sleeptime = + sv.perf_acc_lost = sv.perf_acc_offset = + sv.perf_acc_offset_squared = sv.perf_acc_offset_max = + sv.perf_acc_offset_samples = 0; } /* @@ -2541,7 +2596,7 @@ double SV_Frame(double time) if (sv_timer > 0.1) { if (!svs.threaded) - svs.perf_acc_lost += (sv_timer - 0.1); + sv.perf_acc_lost += (sv_timer - 0.1); sv_timer = 0.1; } @@ -2568,7 +2623,8 @@ double SV_Frame(double time) { advancetime = sys_ticrate.value; // listen servers can run multiple server frames per client frame - framelimit = cl_maxphysicsframesperserverframe.integer; + if (sv_maxphysicsframesperserverframe.integer > 0) + framelimit = sv_maxphysicsframesperserverframe.integer; aborttime = Sys_DirtyTime() + 0.1; } @@ -2584,12 +2640,12 @@ double SV_Frame(double time) offset = 0; offset += sv_timer; - ++svs.perf_acc_offset_samples; - svs.perf_acc_offset += offset; - svs.perf_acc_offset_squared += offset * offset; + ++sv.perf_acc_offset_samples; + sv.perf_acc_offset += offset; + sv.perf_acc_offset_squared += offset * offset; - if(svs.perf_acc_offset_max < offset) - svs.perf_acc_offset_max = offset; + if(sv.perf_acc_offset_max < offset) + sv.perf_acc_offset_max = offset; } // only advance time if not paused @@ -2613,6 +2669,9 @@ double SV_Frame(double time) break; } + if (framecount > 1 && sv_lagreporting_strict.integer && reporting) + SV_BroadcastPrintf(CON_WARN "Server lag report: caught up %.1fms by running %d extra frames\n", advancetime * (framecount - 1) * 1000, framecount - 1); + R_TimeReport("serverphysics"); // send all messages to the clients @@ -2639,7 +2698,7 @@ double SV_Frame(double time) if (sv_timer >= 0) { if (!svs.threaded) - svs.perf_acc_lost += sv_timer; + sv.perf_acc_lost += sv_timer; sv_timer = 0; } @@ -2669,7 +2728,7 @@ static int SV_ThreadFunc(void *voiddata) sv_timer += sv_deltarealtime; - svs.perf_acc_realtime += sv_deltarealtime; + sv.perf_acc_realtime += sv_deltarealtime; // 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(); @@ -2685,22 +2744,22 @@ static int SV_ThreadFunc(void *voiddata) { // don't accumulate time for the first 10 seconds of a match // so things can settle - 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; + sv.perf_acc_realtime = sv.perf_acc_sleeptime = sv.perf_acc_lost = sv.perf_acc_offset = sv.perf_acc_offset_squared = sv.perf_acc_offset_max = sv.perf_acc_offset_samples = 0; } - else if(svs.perf_acc_realtime > 5) + else if(sv.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) + sv.perf_cpuload = 1 - sv.perf_acc_sleeptime / sv.perf_acc_realtime; + sv.perf_lost = sv.perf_acc_lost / sv.perf_acc_realtime; + if(sv.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); + sv.perf_offset_max = sv.perf_acc_offset_max; + sv.perf_offset_avg = sv.perf_acc_offset / sv.perf_acc_offset_samples; + sv.perf_offset_sdev = sqrt(sv.perf_acc_offset_squared / sv.perf_acc_offset_samples - sv.perf_offset_avg * sv.perf_offset_avg); } - if(svs.perf_lost > 0 && developer_extra.integer) + if(sv.perf_lost > 0 && developer_extra.integer) if(playing) Con_DPrintf("Server can't keep up: %s\n", SV_TimingReport(vabuf, sizeof(vabuf))); - 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; + sv.perf_acc_realtime = sv.perf_acc_sleeptime = sv.perf_acc_lost = sv.perf_acc_offset = sv.perf_acc_offset_squared = sv.perf_acc_offset_max = sv.perf_acc_offset_samples = 0; } // get new packets @@ -2725,7 +2784,7 @@ static int SV_ThreadFunc(void *voiddata) time0 = Sys_DirtyTime(); Sys_Sleep((int)wait); delta = Sys_DirtyTime() - time0;if (delta < 0 || delta >= 1800) delta = 0; - svs.perf_acc_sleeptime += delta; + sv.perf_acc_sleeptime += delta; continue; } @@ -2743,11 +2802,11 @@ static int SV_ThreadFunc(void *voiddata) if(advancetime > 0) { offset = sv_timer + (Sys_DirtyTime() - sv_realtime); // LadyHavoc: 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; + ++sv.perf_acc_offset_samples; + sv.perf_acc_offset += offset; + sv.perf_acc_offset_squared += offset * offset; + if(sv.perf_acc_offset_max < offset) + sv.perf_acc_offset_max = offset; } // only advance time if not paused @@ -2785,7 +2844,7 @@ static int SV_ThreadFunc(void *voiddata) // if there is some time remaining from this frame, reset the timers if (sv_timer >= 0) { - svs.perf_acc_lost += sv_timer; + sv.perf_acc_lost += sv_timer; sv_timer = 0; } }