]> git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - sv_main.c
physics: fix and refactor unsticking
[xonotic/darkplaces.git] / sv_main.c
index d60e98e6b516a914e1452175ea0ffeb51161793d..1ba1b34e3a591fe9f40e631fef57d3be63064429 100644 (file)
--- 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,27 +72,29 @@ 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"};
 cvar_t sv_clmovement_minping = {CF_SERVER, "sv_clmovement_minping", "0", "if client ping is below this time in milliseconds, then their ability to use cl_movement prediction is disabled for a while (as they don't need it)"};
 cvar_t sv_clmovement_minping_disabletime = {CF_SERVER, "sv_clmovement_minping_disabletime", "1000", "when client falls below minping, disable their prediction for this many milliseconds (should be at least 1000 or else their prediction may turn on/off frequently)"};
-cvar_t sv_clmovement_inputtimeout = {CF_SERVER, "sv_clmovement_inputtimeout", "0.2", "when a client does not send input for this many seconds, force them to move anyway (unlike QuakeWorld)"};
+cvar_t sv_clmovement_inputtimeout = {CF_SERVER, "sv_clmovement_inputtimeout", "0.1", "when a client does not send input for this many seconds (max 0.1), force them to move anyway (unlike QuakeWorld)"};
 cvar_t sv_cullentities_nevercullbmodels = {CF_SERVER, "sv_cullentities_nevercullbmodels", "0", "if enabled the clients are always notified of moving doors and lifts and other submodels of world (warning: eats a lot of network bandwidth on some levels!)"};
 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)"};
@@ -110,7 +113,7 @@ cvar_t sv_gameplayfix_grenadebouncedownslopes = {CF_SERVER, "sv_gameplayfix_gren
 cvar_t sv_gameplayfix_multiplethinksperframe = {CF_SERVER, "sv_gameplayfix_multiplethinksperframe", "1", "allows entities to think more often than the server framerate, primarily useful for very high fire rate weapons"};
 cvar_t sv_gameplayfix_noairborncorpse = {CF_SERVER, "sv_gameplayfix_noairborncorpse", "1", "causes entities (corpses, items, etc) sitting ontop of moving entities (players) to fall when the moving entity (player) is no longer supporting them"};
 cvar_t sv_gameplayfix_noairborncorpse_allowsuspendeditems = {CF_SERVER, "sv_gameplayfix_noairborncorpse_allowsuspendeditems", "1", "causes entities sitting ontop of objects that are instantaneously remove to float in midair (special hack to allow a common level design trick for floating items)"};
-cvar_t sv_gameplayfix_nudgeoutofsolid = {CF_SERVER, "sv_gameplayfix_nudgeoutofsolid", "0", "attempts to fix physics errors (where an object ended up in solid for some reason)"};
+cvar_t sv_gameplayfix_nudgeoutofsolid = {CF_SERVER, "sv_gameplayfix_nudgeoutofsolid", "0", "attempts to fix physics errors where an object ended up in solid for some reason, better than sv_gameplayfix_unstick* but currently has no effect on Q1BSP (unless mod_q1bsp_polygoncollisions is enabled)"};
 cvar_t sv_gameplayfix_nudgeoutofsolid_separation = {CF_SERVER, "sv_gameplayfix_nudgeoutofsolid_separation", "0.03125", "keep objects this distance apart to prevent collision issues on seams"};
 cvar_t sv_gameplayfix_q2airaccelerate = {CF_SERVER, "sv_gameplayfix_q2airaccelerate", "0", "Quake2-style air acceleration"};
 cvar_t sv_gameplayfix_nogravityonground = {CF_SERVER, "sv_gameplayfix_nogravityonground", "0", "turn off gravity when on ground (to get rid of sliding)"};
@@ -121,17 +124,17 @@ 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"};
+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. Quake did something similar."};
+cvar_t sv_gameplayfix_unstickentities = {CF_SERVER, "sv_gameplayfix_unstickentities", "0", "hack to check if entities are crossing world collision hull and try to move them to the right position. Quake didn't do this so maps shouldn't depend on it."};
 cvar_t sv_gameplayfix_fixedcheckwatertransition = {CF_SERVER, "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_gameplayfix_customstats = {CF_SERVER, "sv_gameplayfix_customstats", "0", "Disable stats higher than 220, for use by certain games such as Xonotic"};
 cvar_t sv_gravity = {CF_SERVER | CF_NOTIFY, "sv_gravity","800", "how fast you fall (512 = roughly earth gravity)"};
 cvar_t sv_init_frame_count = {CF_SERVER, "sv_init_frame_count", "2", "number of frames to run to allow everything to settle before letting clients connect"};
 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)"};
@@ -140,6 +143,7 @@ cvar_t sv_nostep = {CF_SERVER | CF_NOTIFY, "sv_nostep","0", "prevents MOVETYPE_S
 cvar_t sv_playerphysicsqc = {CF_SERVER | CF_NOTIFY, "sv_playerphysicsqc", "1", "enables QuakeC function to override player physics"};
 cvar_t sv_progs = {CF_SERVER, "sv_progs", "progs.dat", "selects which quakec progs.dat file to run" };
 cvar_t sv_protocolname = {CF_SERVER, "sv_protocolname", "DP7", "selects network protocol to host for (values include QUAKE, QUAKEDP, NEHAHRAMOVIE, DP1 and up)"};
+cvar_t sv_qcstats = {CF_SERVER, "sv_qcstats", "0", "Disables engine sending of stats 220 and above, for use by certain games such as Xonotic, NOTE: it's strongly recommended that SVQC send correct STAT_MOVEVARS_TICRATE and STAT_MOVEVARS_TIMESCALE"};
 cvar_t sv_random_seed = {CF_SERVER, "sv_random_seed", "", "random seed; when set, on every map start this random seed is used to initialize the random number generator. Don't touch it unless for benchmarking or debugging"};
 cvar_t host_limitlocal = {CF_SERVER, "host_limitlocal", "0", "whether to apply rate limiting to the local player in a listen server (only useful for testing)"};
 cvar_t sv_sound_land = {CF_SERVER, "sv_sound_land", "demon/dland2.wav", "sound to play when MOVETYPE_STEP entity hits the ground at high speed (empty cvar disables the sound)"};
@@ -147,8 +151,8 @@ cvar_t sv_sound_watersplash = {CF_SERVER, "sv_sound_watersplash", "misc/h2ohit1.
 cvar_t sv_stepheight = {CF_SERVER | CF_NOTIFY, "sv_stepheight", "18", "how high you can step up (TW_SV_STEPCONTROL extension)"};
 cvar_t sv_stopspeed = {CF_SERVER | CF_NOTIFY, "sv_stopspeed","100", "how fast you come to a complete stop"};
 cvar_t sv_wallfriction = {CF_SERVER | CF_NOTIFY, "sv_wallfriction", "1", "how much you slow down when sliding along a wall"};
-cvar_t sv_wateraccelerate = {CF_SERVER, "sv_wateraccelerate", "-1", "rate at which a player accelerates to sv_maxspeed while in the air, if less than 0 the sv_accelerate variable is used instead"};
-cvar_t sv_waterfriction = {CF_SERVER | CF_NOTIFY, "sv_waterfriction","-1", "how fast you slow down, if less than 0 the sv_friction variable is used instead"};
+cvar_t sv_wateraccelerate = {CF_SERVER, "sv_wateraccelerate", "-1", "rate at which a player accelerates to sv_maxspeed while in water, if less than 0 the sv_accelerate variable is used instead"};
+cvar_t sv_waterfriction = {CF_SERVER | CF_NOTIFY, "sv_waterfriction","-1", "how fast you slow down in water, if less than 0 the sv_friction variable is used instead"};
 cvar_t sv_warsowbunny_airforwardaccel = {CF_SERVER, "sv_warsowbunny_airforwardaccel", "1.00001", "how fast you accelerate until you reach sv_maxspeed"};
 cvar_t sv_warsowbunny_accel = {CF_SERVER, "sv_warsowbunny_accel", "0.1585", "how fast you accelerate until after reaching sv_maxspeed (it gets harder as you near sv_warsowbunny_topspeed)"};
 cvar_t sv_warsowbunny_topspeed = {CF_SERVER, "sv_warsowbunny_topspeed", "925", "soft speed limit (can get faster with rjs and on ramps)"};
@@ -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,9 @@ 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)"};
 
 server_t sv;
 server_static_t svs;
@@ -254,12 +265,11 @@ static const char *standardeffectnames[EFFECT_TOTAL] =
        "SVC_PARTICLE"
 };
 
-#define SV_REQFUNCS 0
-#define sv_reqfuncs NULL
 
-//#define SV_REQFUNCS (sizeof(sv_reqfuncs) / sizeof(const char *))
-//static const char *sv_reqfuncs[] = {
-//};
+static void SV_CheckRequiredFuncs(prvm_prog_t *prog, const char *filename)
+{
+       // no required funcs?!
+}
 
 #define SV_REQFIELDS (sizeof(sv_reqfields) / sizeof(prvm_required_field_t))
 
@@ -432,30 +442,6 @@ static void SV_AreaStats_f(cmd_state_t *cmd)
        World_PrintAreaStats(&sv.world, "server");
 }
 
-static qbool SV_CanSave(void)
-{
-       prvm_prog_t *prog = SVVM_prog;
-       if(SV_IsLocalServer() == 1)
-       {
-               // singleplayer checks
-               if ((svs.clients[0].active && PRVM_serveredictfloat(svs.clients[0].edict, deadflag)))
-               {
-                       Con_Print("Can't savegame with a dead player\n");
-                       return false;
-               }
-
-               if(host.hook.CL_Intermission && host.hook.CL_Intermission())
-               {
-                       Con_Print("Can't save in intermission.\n");
-                       return false;
-               }
-       }
-       else
-               Con_Print(CON_WARN "Warning: saving a multiplayer game may have strange results when restored (to properly resume, all players must join in the same player slots and then the game can be reloaded).\n");
-       return true;
-       
-}
-
 static void SV_ServerOptions (void)
 {
        int i;
@@ -470,7 +456,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]);
@@ -546,10 +531,11 @@ 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_RegisterAlias (&host_timescale, "slowmo");
-       Cvar_RegisterAlias (&host_timescale, "timescale");
+       Cvar_RegisterVirtual (&host_timescale, "slowmo");
+       Cvar_RegisterVirtual (&host_timescale, "timescale");
        Cvar_RegisterVariable (&sv_accelerate);
        Cvar_RegisterVariable (&sv_aim);
        Cvar_RegisterVariable (&sv_airaccel_qw);
@@ -569,6 +555,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);
@@ -590,6 +577,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);
@@ -624,12 +612,13 @@ void SV_Init (void)
        Cvar_RegisterVariable (&sv_gameplayfix_unstickplayers);
        Cvar_RegisterVariable (&sv_gameplayfix_unstickentities);
        Cvar_RegisterVariable (&sv_gameplayfix_fixedcheckwatertransition);
-       Cvar_RegisterVariable (&sv_gameplayfix_customstats);
+       Cvar_RegisterVariable (&sv_qcstats);
        Cvar_RegisterVariable (&sv_gravity);
        Cvar_RegisterVariable (&sv_init_frame_count);
        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);
@@ -640,7 +629,7 @@ void SV_Init (void)
        Cvar_RegisterVariable (&sv_protocolname);
        Cvar_RegisterVariable (&sv_random_seed);
        Cvar_RegisterVariable (&host_limitlocal);
-       Cvar_RegisterAlias(&host_limitlocal, "sv_ratelimitlocalplayer");
+       Cvar_RegisterVirtual(&host_limitlocal, "sv_ratelimitlocalplayer");
        Cvar_RegisterVariable (&sv_sound_land);
        Cvar_RegisterVariable (&sv_sound_watersplash);
        Cvar_RegisterVariable (&sv_stepheight);
@@ -655,10 +644,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);
@@ -705,12 +699,15 @@ void SV_Init (void)
        Cvar_RegisterVariable (&sv_mapformat_is_quake2);
        Cvar_RegisterVariable (&sv_mapformat_is_quake3);
 
+       Cvar_RegisterVariable (&sv_writepicture_quality);
+
        SV_InitOperatorCommands();
-       host.hook.SV_CanSave = SV_CanSave;
+       host.hook.SV_Shutdown = SV_Shutdown;
 
        sv_mempool = Mem_AllocPool("server", 0, NULL);
 
        SV_ServerOptions();
+       Cvar_Callback(&sv_netport);
 }
 
 static void SV_SaveEntFile_f(cmd_state_t *cmd)
@@ -798,7 +795,7 @@ void SV_SendServerinfo (client_t *client)
 
        SZ_Clear (&client->netconnection->message);
        MSG_WriteByte (&client->netconnection->message, svc_print);
-       dpsnprintf (message, sizeof (message), "\nServer: %s build %s (progs %i crc)\n", gamename, buildstring, prog->filecrc);
+       dpsnprintf (message, sizeof (message), "\nServer: %s (progs %i crc)\n", engineversion, prog->filecrc);
        MSG_WriteString (&client->netconnection->message,message);
 
        SV_StopDemoRecording(client); // to split up demos into different files
@@ -910,11 +907,6 @@ void SV_SendServerinfo (client_t *client)
        client->movesequence = 0;
        client->movement_highestsequence_seen = 0;
        memset(&client->movement_count, 0, sizeof(client->movement_count));
-#ifdef NUM_PING_TIMES
-       for (i = 0;i < NUM_PING_TIMES;i++)
-               client->ping_times[i] = 0;
-       client->num_pings = 0;
-#endif
        client->ping = 0;
 
        // allow the client some time to send his keepalives, even if map loading took ages
@@ -966,8 +958,8 @@ void SV_ConnectClient (int clientnum, netconn_t *netconnection)
                                );
        }
 
-       strlcpy(client->name, "unconnected", sizeof(client->name));
-       strlcpy(client->old_name, "unconnected", sizeof(client->old_name));
+       dp_strlcpy(client->name, "unconnected", sizeof(client->name));
+       dp_strlcpy(client->old_name, "unconnected", sizeof(client->old_name));
        client->prespawned = false;
        client->spawned = false;
        client->begun = false;
@@ -1008,14 +1000,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);
 
@@ -1025,15 +1034,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);
@@ -1061,6 +1077,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)
@@ -1216,7 +1236,7 @@ static void SV_Download_f(cmd_state_t *cmd)
 
        Download_CheckExtensions(cmd);
 
-       strlcpy(host_client->download_name, Cmd_Argv(cmd, 1), sizeof(host_client->download_name));
+       dp_strlcpy(host_client->download_name, Cmd_Argv(cmd, 1), sizeof(host_client->download_name));
        extension = FS_FileExtension(host_client->download_name);
 
        // host_client is asking to download a specified file
@@ -1229,7 +1249,7 @@ static void SV_Download_f(cmd_state_t *cmd)
                extensions[0] = '\0';
                
                if(host_client->download_deflate)
-                       strlcat(extensions, " deflate", sizeof(extensions));
+                       dp_strlcat(extensions, " deflate", sizeof(extensions));
                
                Con_DPrintf("Downloading %s to %s\n", host_client->download_name, host_client->name);
 
@@ -1298,7 +1318,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");
@@ -1384,7 +1404,7 @@ int SV_ModelIndex(const char *s, int precachemode)
        // testing
        //if (precachemode == 2)
        //      return 0;
-       strlcpy(filename, s, sizeof(filename));
+       dp_strlcpy(filename, s, sizeof(filename));
        for (i = 2;i < limit;i++)
        {
                if (!sv.model_precache[i][0])
@@ -1398,7 +1418,7 @@ 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]));
+                               dp_strlcpy(sv.model_precache[i], filename, sizeof(sv.model_precache[i]));
                                if (sv.state == ss_loading)
                                {
                                        // running from SV_SpawnServer which is launched from the client console command interpreter
@@ -1447,7 +1467,7 @@ int SV_SoundIndex(const char *s, int precachemode)
        // testing
        //if (precachemode == 2)
        //      return 0;
-       strlcpy(filename, s, sizeof(filename));
+       dp_strlcpy(filename, s, sizeof(filename));
        for (i = 1;i < limit;i++)
        {
                if (!sv.sound_precache[i][0])
@@ -1461,7 +1481,7 @@ int SV_SoundIndex(const char *s, int precachemode)
                                }
                                if (precachemode == 1)
                                        Con_Printf("SV_SoundIndex(\"%s\"): not precached (fix your code), precaching anyway\n", filename);
-                               strlcpy(sv.sound_precache[i], filename, sizeof(sv.sound_precache[i]));
+                               dp_strlcpy(sv.sound_precache[i], filename, sizeof(sv.sound_precache[i]));
                                if (sv.state != ss_loading)
                                {
                                        MSG_WriteByte(&sv.reliable_datagram, svc_precache);
@@ -1502,7 +1522,7 @@ int SV_ParticleEffectIndex(const char *name)
                sv.particleeffectnamesloaded = true;
                memset(sv.particleeffectname, 0, sizeof(sv.particleeffectname));
                for (i = 0;i < EFFECT_TOTAL;i++)
-                       strlcpy(sv.particleeffectname[i], standardeffectnames[i], sizeof(sv.particleeffectname[i]));
+                       dp_strlcpy(sv.particleeffectname[i], standardeffectnames[i], sizeof(sv.particleeffectname[i]));
                for (filepass = 0;;filepass++)
                {
                        if (filepass == 0)
@@ -1526,7 +1546,7 @@ int SV_ParticleEffectIndex(const char *name)
                                                break;
                                        if (argc < 16)
                                        {
-                                               strlcpy(argv[argc], com_token, sizeof(argv[argc]));
+                                               dp_strlcpy(argv[argc], com_token, sizeof(argv[argc]));
                                                argc++;
                                        }
                                }
@@ -1547,7 +1567,7 @@ int SV_ParticleEffectIndex(const char *name)
                                                        }
                                                        else
                                                        {
-                                                               strlcpy(sv.particleeffectname[effectnameindex], argv[1], sizeof(sv.particleeffectname[effectnameindex]));
+                                                               dp_strlcpy(sv.particleeffectname[effectnameindex], argv[1], sizeof(sv.particleeffectname[effectnameindex]));
                                                                break;
                                                        }
                                                }
@@ -1580,7 +1600,7 @@ model_t *SV_GetModelFromEdict(prvm_edict_t *ed)
 {
        prvm_prog_t *prog = SVVM_prog;
        int modelindex;
-       if (!ed || ed->priv.server->free)
+       if (!ed || ed->free)
                return NULL;
        modelindex = (int)PRVM_serveredictfloat(ed, modelindex);
        return (modelindex > 0 && modelindex < MAX_MODELS) ? sv.models[modelindex] : NULL;
@@ -1607,7 +1627,7 @@ static void SV_CreateBaseline (void)
                // LadyHavoc: always clear state values, whether the entity is in use or not
                svent->priv.server->baseline = defaultstate;
 
-               if (svent->priv.server->free)
+               if (svent->free)
                        continue;
                if (entnum > svs.maxclients && !PRVM_serveredictfloat(svent, modelindex))
                        continue;
@@ -1700,7 +1720,7 @@ static void SV_Prepare_CSQC(void)
 
                sv.csqc_progsize = (int)progsize;
                sv.csqc_progcrc = CRC_Block(svs.csqc_progdata, progsize);
-               strlcpy(sv.csqc_progname, csqc_progname.string, sizeof(sv.csqc_progname));
+               dp_strlcpy(sv.csqc_progname, csqc_progname.string, sizeof(sv.csqc_progname));
                Con_DPrintf("server detected csqc progs file \"%s\" with size %i and crc %i\n", sv.csqc_progname, sv.csqc_progsize, sv.csqc_progcrc);
 
                Con_DPrint("Compressing csprogs.dat\n");
@@ -1752,6 +1772,21 @@ int SV_IsLocalServer(void)
        return (host_isclient.integer && sv.active ? svs.maxclients : 0);
 }
 
+static void SV_VM_Shutdown(qbool prog_reset)
+{
+       prvm_prog_t *prog = SVVM_prog;
+
+       if(prog->loaded && PRVM_serverfunction(SV_Shutdown))
+       {
+               func_t s = PRVM_serverfunction(SV_Shutdown);
+               PRVM_serverglobalfloat(time) = sv.time;
+               PRVM_serverfunction(SV_Shutdown) = 0; // prevent it from getting called again
+               prog->ExecuteProgram(prog, s,"SV_Shutdown() required");
+       }
+       if (prog_reset)
+               PRVM_Prog_Reset(prog);
+}
+
 /*
 ================
 SV_SpawnServer
@@ -1768,21 +1803,26 @@ void SV_SpawnServer (const char *map)
        char *entities;
        model_t *worldmodel;
        char modelname[sizeof(sv.worldname)];
+       const char *canonicalname;
        char vabuf[1024];
 
        Con_Printf("SpawnServer: %s\n", map);
 
        dpsnprintf (modelname, sizeof(modelname), "maps/%s.bsp", map);
 
-       if (!FS_FileExists(modelname))
+       if (!(canonicalname = FS_FileExists(modelname)))
        {
                dpsnprintf (modelname, sizeof(modelname), "maps/%s", map);
-               if (!FS_FileExists(modelname))
+               if (!(canonicalname = FS_FileExists(modelname)))
                {
-                       Con_Printf("SpawnServer: no map file named %s\n", modelname);
+                       Con_Printf(CON_ERROR "SpawnServer: no map file named %s.bsp\n", modelname);
                        return;
                }
        }
+       // if it's not in a pak canonicalname will be the same pointer as modelname
+       // if it's in a pak canonicalname may differ by case
+       if (modelname != canonicalname)
+               dp_strlcpy(modelname, canonicalname, sizeof(modelname));
 
 //     SV_LockThreadMutex();
 
@@ -1795,25 +1835,7 @@ void SV_SpawnServer (const char *map)
        }
 
        if(sv.active)
-       {
-               client_t *client;
-               for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++)
-               {
-                       if (client->netconnection)
-                       {
-                               MSG_WriteByte(&client->netconnection->message, svc_stufftext);
-                               MSG_WriteString(&client->netconnection->message, "reconnect\n");
-                       }
-               }
-               World_End(&sv.world);
-               if(PRVM_serverfunction(SV_Shutdown))
-               {
-                       func_t s = PRVM_serverfunction(SV_Shutdown);
-                       PRVM_serverglobalfloat(time) = sv.time;
-                       PRVM_serverfunction(SV_Shutdown) = 0; // prevent it from getting called again
-                       prog->ExecuteProgram(prog, s,"SV_Shutdown() required");
-               }
-       }
+               SV_VM_Shutdown(false);
 
        // free q3 shaders so that any newly downloaded shaders will be active
        Mod_FreeQ3Shaders();
@@ -1821,7 +1843,7 @@ void SV_SpawnServer (const char *map)
        worldmodel = Mod_ForName(modelname, false, developer.integer > 0, NULL);
        if (!worldmodel || !worldmodel->TraceBox)
        {
-               Con_Printf("Couldn't load map %s\n", modelname);
+               Con_Printf(CON_ERROR "Couldn't load map %s\n", modelname);
 
                if(!host_isclient.integer)
                        Sys_MakeProcessMean();
@@ -1847,14 +1869,37 @@ void SV_SpawnServer (const char *map)
 //
 // tell all connected clients that we are going to a new level
 //
-       if (!sv.active)
+       if (sv.active)
+       {
+               client_t *client;
+               for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++)
+               {
+                       if (client->netconnection)
+                       {
+                               MSG_WriteByte(&client->netconnection->message, svc_stufftext);
+                               MSG_WriteString(&client->netconnection->message, "reconnect\n");
+                       }
+               }
+       }
+       else
+       {
+               // open server port
                NetConn_OpenServerPorts(true);
+       }
 
 //
 // 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);
@@ -1864,6 +1909,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;
@@ -1883,10 +1932,10 @@ void SV_SpawnServer (const char *map)
        sv.active = true;
 
        // set level base name variables for later use
-       strlcpy (sv.name, map, sizeof (sv.name));
-       strlcpy(sv.worldname, modelname, sizeof(sv.worldname));
+       dp_strlcpy(sv.worldname, modelname, sizeof(sv.worldname));
        FS_StripExtension(sv.worldname, sv.worldnamenoextension, sizeof(sv.worldnamenoextension));
-       strlcpy(sv.worldbasename, !strncmp(sv.worldnamenoextension, "maps/", 5) ? sv.worldnamenoextension + 5 : sv.worldnamenoextension, sizeof(sv.worldbasename));
+       dp_strlcpy(sv.worldbasename, !strncasecmp(sv.worldnamenoextension, "maps/", 5) ? sv.worldnamenoextension + 5 : sv.worldnamenoextension, sizeof(sv.worldbasename));
+//     dp_strlcpy(sv.name, sv.worldbasename, sizeof (sv.name)); // TODO can we just remove this now?
        //Cvar_SetQuick(&sv_worldmessage, sv.worldmessage); // set later after QC is spawned
        Cvar_SetQuick(&sv_worldname, sv.worldname);
        Cvar_SetQuick(&sv_worldnamenoextension, sv.worldnamenoextension);
@@ -1937,17 +1986,17 @@ void SV_SpawnServer (const char *map)
        World_SetSize(&sv.world, sv.worldname, sv.worldmodel->normalmins, sv.worldmodel->normalmaxs, prog);
        World_Start(&sv.world);
 
-       strlcpy(sv.sound_precache[0], "", sizeof(sv.sound_precache[0]));
+       dp_strlcpy(sv.sound_precache[0], "", sizeof(sv.sound_precache[0]));
 
-       strlcpy(sv.model_precache[0], "", sizeof(sv.model_precache[0]));
-       strlcpy(sv.model_precache[1], sv.worldname, sizeof(sv.model_precache[1]));
+       dp_strlcpy(sv.model_precache[0], "", sizeof(sv.model_precache[0]));
+       dp_strlcpy(sv.model_precache[1], sv.worldname, sizeof(sv.model_precache[1]));
        for (i = 1;i < sv.worldmodel->brush.numsubmodels && i+1 < MAX_MODELS;i++)
        {
                dpsnprintf(sv.model_precache[i+1], sizeof(sv.model_precache[i+1]), "*%i", i);
                sv.models[i+1] = Mod_ForName (sv.model_precache[i+1], false, false, sv.worldname);
        }
        if(i < sv.worldmodel->brush.numsubmodels)
-               Con_Printf("Too many submodels (MAX_MODELS is %i)\n", MAX_MODELS);
+               Con_Printf(CON_WARN "Too many submodels (MAX_MODELS is %i)\n", MAX_MODELS);
 
 //
 // load the rest of the entities
@@ -1955,7 +2004,7 @@ void SV_SpawnServer (const char *map)
        // AK possible hack since num_edicts is still 0
        ent = PRVM_EDICT_NUM(0);
        memset (ent->fields.fp, 0, prog->entityfields * sizeof(prvm_vec_t));
-       ent->priv.server->free = false;
+       ent->free = false;
        PRVM_serveredictstring(ent, model) = PRVM_SetEngineString(prog, sv.worldname);
        PRVM_serveredictfloat(ent, modelindex) = 1;             // world model
        PRVM_serveredictfloat(ent, solid) = SOLID_BSP;
@@ -1970,7 +2019,7 @@ void SV_SpawnServer (const char *map)
        else
                PRVM_serverglobalfloat(deathmatch) = deathmatch.integer;
 
-       PRVM_serverglobalstring(mapname) = PRVM_SetEngineString(prog, sv.name);
+       PRVM_serverglobalstring(mapname) = PRVM_SetEngineString(prog, sv.worldbasename);
 
 // serverflags are for cross level information (sigils)
        PRVM_serverglobalfloat(serverflags) = svs.serverflags;
@@ -2052,9 +2101,8 @@ void SV_SpawnServer (const char *map)
                }
        }
 
-       // update the map title cvar
-       strlcpy(sv.worldmessage, PRVM_GetString(prog, PRVM_serveredictstring(prog->edicts, message)), sizeof(sv.worldmessage)); // map title (not related to filename)
-       Cvar_SetQuick(&sv_worldmessage, sv.worldmessage);
+       // update the map title cvar (not related to filename)
+       Cvar_SetQuick(&sv_worldmessage, PRVM_GetString(prog, PRVM_serveredictstring(prog->edicts, message)));
 
        Con_Printf("Server spawned.\n");
        NetConn_Heartbeat (2);
@@ -2074,32 +2122,24 @@ This only happens at the end of a game, not between levels
 */
 void SV_Shutdown(void)
 {
-       prvm_prog_t *prog = SVVM_prog;
        int i;
 
-       Con_DPrintf("SV_Shutdown\n");
+       SV_LockThreadMutex();
 
        if (!sv.active)
-               return;
+               goto end;
+
+       Con_DPrintf("SV_Shutdown\n");
 
        NetConn_Heartbeat(2);
        NetConn_Heartbeat(2);
 
-// make sure all the clients know we're disconnecting
-       World_End(&sv.world);
-       if(prog->loaded)
-       {
-               if(PRVM_serverfunction(SV_Shutdown))
-               {
-                       func_t s = PRVM_serverfunction(SV_Shutdown);
-                       PRVM_serverglobalfloat(time) = sv.time;
-                       PRVM_serverfunction(SV_Shutdown) = 0; // prevent it from getting called again
-                       prog->ExecuteProgram(prog, s,"SV_Shutdown() required");
-               }
-       }
+       // make sure all the clients know we're disconnecting
        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
+
+       SV_VM_Shutdown(true);
 
        NetConn_CloseServerPorts();
 
@@ -2109,8 +2149,8 @@ void SV_Shutdown(void)
 //
        memset(&sv, 0, sizeof(sv));
        memset(svs.clients, 0, svs.maxclients*sizeof(client_t));
-
-       cl.islocalgame = false;
+end:
+       SV_UnlockThreadMutex();
 }
 
 /////////////////////////////////////////////////////
@@ -2129,7 +2169,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->priv.server->free && !VectorCompare(PRVM_serveredictvector(ent, absmin), PRVM_serveredictvector(ent, absmax)))
+               if (!ent->free)
                        SV_LinkEdict(ent);
 }
 
@@ -2205,9 +2245,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;
@@ -2225,7 +2266,7 @@ static void SVVM_count_edicts(prvm_prog_t *prog)
        for (i=0 ; i<prog->num_edicts ; i++)
        {
                ent = PRVM_EDICT_NUM(i);
-               if (ent->priv.server->free)
+               if (ent->free)
                        continue;
                active++;
                if (PRVM_serveredictfloat(ent, solid))
@@ -2268,7 +2309,7 @@ static qbool SVVM_load_edict(prvm_prog_t *prog, prvm_edict_t *ent)
 static void SV_VM_Setup(void)
 {
        prvm_prog_t *prog = SVVM_prog;
-       PRVM_Prog_Init(prog, &cmd_server);
+       PRVM_Prog_Init(prog, cmd_local);
 
        // allocate the mempools
        // TODO: move the magic numbers/constants into #defines [9/13/2006 Black]
@@ -2304,7 +2345,7 @@ static void SV_VM_Setup(void)
        prog->error_cmd             = Host_Error;
        prog->ExecuteProgram        = SVVM_ExecuteProgram;
 
-       PRVM_Prog_Load(prog, sv_progs.string, NULL, 0, SV_REQFUNCS, sv_reqfuncs, SV_REQFIELDS, sv_reqfields, SV_REQGLOBALS, sv_reqglobals);
+       PRVM_Prog_Load(prog, sv_progs.string, NULL, 0, SV_CheckRequiredFuncs, SV_REQFIELDS, sv_reqfields, SV_REQGLOBALS, sv_reqglobals);
 
        // some mods compiled with scrambling compilers lack certain critical
        // global names and field names such as "self" and "time" and "nextthink"
@@ -2461,72 +2502,85 @@ 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)
-               {
-                       Con_Printf("Client \"%s\" connection timed out\n", host_client->name);
-                       SV_DropClient(false);
-               }
-       }
+                       SV_DropClient(false, "Timed out");
+}
+
+/*
+==================
+SV_TimeReport
+
+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", 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;
 
-       if(!sv.active)
-               return 0;
+       // 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 = sv.perf_acc_offset_squared / sv.perf_acc_offset_samples - sv.perf_offset_avg * sv.perf_offset_avg;
+                               sv.perf_offset_sdev = sv.perf_offset_sdev > 0 ? sqrt(sv.perf_offset_sdev) : 0;
                        }
 
-                       if(svs.perf_lost > 0 && developer_extra.integer && playing) // only complain if anyone is looking
-                               Con_DPrintf("Server can't keep up: %s\n", Host_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;
                }
 
                /*
                 * Receive packets on each main loop iteration, as the main loop may
                 * be undersleeping due to select() detecting a new packet
                 */
-               NetConn_ServerFrame();
-               SV_CheckTimeouts();
+               if (sv.active)
+               {
+                       NetConn_ServerFrame();
+                       SV_CheckTimeouts();
+               }
        }
 
        /*
@@ -2541,11 +2595,11 @@ 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;
        }
 
-       if (sv_timer > 0 && !svs.threaded)
+       if (sv.active && sv_timer > 0 && !svs.threaded)
        {
                /*
                 * Execute one or more server frames, with an upper limit on how much
@@ -2568,7 +2622,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 +2639,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 +2668,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 +2697,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;
        }
 
@@ -2652,7 +2710,6 @@ static int SV_ThreadFunc(void *voiddata)
        qbool playing = false;
        double sv_timer = 0;
        double sv_deltarealtime, sv_oldrealtime, sv_realtime;
-       double wait;
        int i;
        char vabuf[1024];
        sv_realtime = Sys_DirtyTime();
@@ -2669,7 +2726,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 +2742,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", Host_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;
+                                       Con_DPrintf("Server can't keep up: %s\n", SV_TimingReport(vabuf, sizeof(vabuf)));
+                       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
@@ -2711,21 +2768,10 @@ static int SV_ThreadFunc(void *voiddata)
                }
 
                // if the accumulators haven't become positive yet, wait a while
-               wait = sv_timer * -1000000.0;
-               if (wait >= 1)
+               if (sv_timer < 0)
                {
-                       double time0, delta;
                        SV_UnlockThreadMutex(); // don't keep mutex locked while sleeping
-                       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_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 += Sys_Sleep(-sv_timer);
                        continue;
                }
 
@@ -2743,11 +2789,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 +2831,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;
                }
        }