]> git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - sv_main.c
movement packet loss tracking
[xonotic/darkplaces.git] / sv_main.c
index 0453ec3afddc9fca565c1c9351443b83128afb0d..160fb56c45b99e00ecbb6220e4edcf977e84d807 100644 (file)
--- a/sv_main.c
+++ b/sv_main.c
@@ -27,7 +27,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 static void SV_SaveEntFile_f(void);
 static void SV_StartDownload_f(void);
 static void SV_Download_f(void);
-static void SV_VM_Setup();
+static void SV_VM_Setup(void);
 extern cvar_t net_connecttimeout;
 
 void VM_CustomStats_Clear (void);
@@ -63,7 +63,6 @@ cvar_t sv_areagrid_mingridsize = {CVAR_NOTIFY, "sv_areagrid_mingridsize", "64",
 cvar_t sv_checkforpacketsduringsleep = {0, "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 = {0, "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 = {0, "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_maxnetfps = {0, "sv_clmovement_maxnetfps", "0", "max amount of movement packets to accept per second"};
 cvar_t sv_clmovement_minping_disabletime = {0, "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 = {0, "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_cullentities_nevercullbmodels = {0, "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!)"};
@@ -74,7 +73,9 @@ cvar_t sv_cullentities_trace_delay = {0, "sv_cullentities_trace_delay", "1", "nu
 cvar_t sv_cullentities_trace_delay_players = {0, "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 = {0, "sv_cullentities_trace_enlarge", "0", "box enlargement for entity culling"};
 cvar_t sv_cullentities_trace_prediction = {0, "sv_cullentities_trace_prediction", "1", "also trace from the predicted player position"};
-cvar_t sv_cullentities_trace_samples = {0, "sv_cullentities_trace_samples", "1", "number of samples to test for entity culling"};
+cvar_t sv_cullentities_trace_prediction_time = {0, "sv_cullentities_trace_prediction_time", "0.2", "how many seconds of prediction to use"};
+cvar_t sv_cullentities_trace_entityocclusion = {0, "sv_cullentities_trace_entityocclusion", "0", "also check if doors and other bsp models are in the way"};
+cvar_t sv_cullentities_trace_samples = {0, "sv_cullentities_trace_samples", "2", "number of samples to test for entity culling"};
 cvar_t sv_cullentities_trace_samples_extra = {0, "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_players = {0, "sv_cullentities_trace_samples_players", "8", "number of samples to test for entity culling when the entity is a player entity"};
 cvar_t sv_debugmove = {CVAR_NOTIFY, "sv_debugmove", "0", "disables collision detection optimizations for debugging purposes"};
@@ -85,21 +86,27 @@ cvar_t sv_fixedframeratesingleplayer = {0, "sv_fixedframeratesingleplayer", "1",
 cvar_t sv_freezenonclients = {CVAR_NOTIFY, "sv_freezenonclients", "0", "freezes time, except for players, allowing you to walk around and take screenshots of explosions"};
 cvar_t sv_friction = {CVAR_NOTIFY, "sv_friction","4", "how fast you slow down"};
 cvar_t sv_gameplayfix_blowupfallenzombies = {0, "sv_gameplayfix_blowupfallenzombies", "1", "causes findradius to detect SOLID_NOT entities such as zombies and corpses on the floor, allowing splash damage to apply to them"};
+cvar_t sv_gameplayfix_consistentplayerprethink = {0, "sv_gameplayfix_consistentplayerprethink", "1", "improves fairness in multiplayer by running all PlayerPreThink functions (which fire weapons) before performing physics, then runing all PlayerPostThink functions"};
 cvar_t sv_gameplayfix_delayprojectiles = {0, "sv_gameplayfix_delayprojectiles", "1", "causes entities to not move on the same frame they are spawned, meaning that projectiles wait until the next frame to perform their first move, giving proper interpolation and rocket trails, but making weapons harder to use at low framerates"};
 cvar_t sv_gameplayfix_droptofloorstartsolid = {0, "sv_gameplayfix_droptofloorstartsolid", "1", "prevents items and monsters that start in a solid area from falling out of the level (makes droptofloor treat trace_startsolid as an acceptable outcome)"};
 cvar_t sv_gameplayfix_droptofloorstartsolid_nudgetocorrect = {0, "sv_gameplayfix_droptofloorstartsolid_nudgetocorrect", "1", "tries to nudge stuck items and monsters out of walls before droptofloor is performed"};
 cvar_t sv_gameplayfix_easierwaterjump = {0, "sv_gameplayfix_easierwaterjump", "1", "changes water jumping to make it easier to get out of water (exactly like in QuakeWorld)"};
 cvar_t sv_gameplayfix_findradiusdistancetobox = {0, "sv_gameplayfix_findradiusdistancetobox", "1", "causes findradius to check the distance to the corner of a box rather than the center of the box, makes findradius detect bmodels such as very large doors that would otherwise be unaffected by splash damage"};
+cvar_t sv_gameplayfix_gravityunaffectedbyticrate = {0, "sv_gameplayfix_gravityunaffectedbyticrate", "0", "fix some ticrate issues in physics."};
 cvar_t sv_gameplayfix_grenadebouncedownslopes = {0, "sv_gameplayfix_grenadebouncedownslopes", "1", "prevents MOVETYPE_BOUNCE (grenades) from getting stuck when fired down a downward sloping surface"};
 cvar_t sv_gameplayfix_multiplethinksperframe = {0, "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_slidemoveprojectiles = {0, "sv_gameplayfix_slidemoveprojectiles", "1", "allows MOVETYPE_FLY/FLYMISSILE/TOSS/BOUNCE/BOUNCEMISSILE entities to finish their move in a frame even if they hit something, fixes 'gravity accumulation' bug for grenades on steep slopes"};
-cvar_t sv_gameplayfix_noairborncorpse = {0, "sv_gameplayfix_noairborncorpse", "1", "causes entities (corpses) sitting ontop of moving entities (players) to fall when the moving entity (player) is no longer supporting them"};
+cvar_t sv_gameplayfix_noairborncorpse = {0, "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 = {0, "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 = {0, "sv_gameplayfix_nudgeoutofsolid", "1", "attempts to fix physics errors (where an object ended up in solid for some reason)"};
+cvar_t sv_gameplayfix_nudgeoutofsolid_bias = {0, "sv_gameplayfix_nudgeoutofsolid_bias", "0", "over-correction on nudgeoutofsolid logic, to prevent constant contact"};
+cvar_t sv_gameplayfix_q2airaccelerate = {0, "sv_gameplayfix_q2airaccelerate", "0", "Quake2-style air acceleration"};
 cvar_t sv_gameplayfix_setmodelrealbox = {0, "sv_gameplayfix_setmodelrealbox", "1", "fixes a bug in Quake that made setmodel always set the entity box to ('-16 -16 -16', '16 16 16') rather than properly checking the model box, breaks some poorly coded mods"};
+cvar_t sv_gameplayfix_slidemoveprojectiles = {0, "sv_gameplayfix_slidemoveprojectiles", "1", "allows MOVETYPE_FLY/FLYMISSILE/TOSS/BOUNCE/BOUNCEMISSILE entities to finish their move in a frame even if they hit something, fixes 'gravity accumulation' bug for grenades on steep slopes"};
 cvar_t sv_gameplayfix_stepdown = {0, "sv_gameplayfix_stepdown", "0", "attempts to step down stairs, not just up them (prevents the familiar thud..thud..thud.. when running down stairs and slopes)"};
 cvar_t sv_gameplayfix_stepwhilejumping = {0, "sv_gameplayfix_stepwhilejumping", "1", "applies step-up onto a ledge even while airborn, useful if you would otherwise just-miss the floor when running across small areas with gaps (for instance running across the moving platforms in dm2, or jumping to the megahealth and red armor in dm2 rather than using the bridge)"};
 cvar_t sv_gameplayfix_swiminbmodels = {0, "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 = {0, "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_gravityunaffectedbyticrate = {0, "sv_gameplayfix_gravityunaffectedbyticrate", "0", "fix some ticrate issues in physics."};
+cvar_t sv_gameplayfix_downtracesupportsongroundflag = {0, "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_gravity = {CVAR_NOTIFY, "sv_gravity","800", "how fast you fall (512 = roughly earth gravity)"};
 cvar_t sv_idealpitchscale = {0, "sv_idealpitchscale","0.8", "how much to look up/down slopes and stairs when not using freelook"};
 cvar_t sv_jumpstep = {CVAR_NOTIFY, "sv_jumpstep", "0", "whether you can step up while jumping (sv_gameplayfix_stepwhilejumping must also be 1)"};
@@ -127,6 +134,7 @@ cvar_t sv_warsowbunny_accel = {0, "sv_warsowbunny_accel", "0.1585", "how fast yo
 cvar_t sv_warsowbunny_topspeed = {0, "sv_warsowbunny_topspeed", "925", "soft speed limit (can get faster with rjs and on ramps)"};
 cvar_t sv_warsowbunny_turnaccel = {0, "sv_warsowbunny_turnaccel", "0", "max sharpness of turns (also master switch for the sv_warsowbunny_* mode; set this to 9 to enable)"};
 cvar_t sv_warsowbunny_backtosideratio = {0, "sv_warsowbunny_backtosideratio", "0.8", "lower values make it easier to change direction without losing speed; the drawback is \"understeering\" in sharp turns"};
+cvar_t sv_onlycsqcnetworking = {0, "sv_onlycsqcnetworking", "0", "disables legacy entity networking code for higher performance (except on clients, which can still be legacy)"};
 cvar_t sys_ticrate = {CVAR_SAVE, "sys_ticrate","0.0138889", "how long a server frame is in seconds, 0.05 is 20fps server rate, 0.1 is 10fps (can not be set higher than 0.1), 0 runs as many server frames as possible (makes games against bots a little smoother, overwhelms network players), 0.0138889 matches QuakeWorld physics"};
 cvar_t teamplay = {CVAR_NOTIFY, "teamplay","0", "teamplay mode, values depend on mod but typically 0 = no teams, 1 = no team damage no self damage, 2 = team damage and self damage, some mods support 3 = no team damage but can damage self"};
 cvar_t timelimit = {CVAR_NOTIFY, "timelimit","0", "ends level at this time (in minutes)"};
@@ -288,8 +296,24 @@ prvm_required_field_t reqfields[] =
        {ev_vector, "cursor_screen"},
        {ev_vector, "cursor_trace_endpos"},
        {ev_vector, "cursor_trace_start"},
+       {ev_vector, "glowmod"},
        {ev_vector, "movement"},
        {ev_vector, "punchvector"},
+
+       // physics
+       //{ev_float, "solid"},
+       //{ev_float, "movetype"},
+       //{ev_float, "modelindex"},
+       {ev_vector, "mass"},
+       //{ev_vector, "origin"},
+       //{ev_vector, "velocity"},
+       //{ev_vector, "axis_forward"},
+       //{ev_vector, "axis_left"},
+       //{ev_vector, "axis_up"},
+       //{ev_vector, "spinvelocity"},
+       //{ev_vector, "angles"},
+       //{ev_vector, "avelocity"},
+
 };
 
 
@@ -350,7 +374,6 @@ void SV_Init (void)
        Cvar_RegisterVariable (&sv_areagrid_mingridsize);
        Cvar_RegisterVariable (&sv_checkforpacketsduringsleep);
        Cvar_RegisterVariable (&sv_clmovement_enable);
-       Cvar_RegisterVariable (&sv_clmovement_maxnetfps);
        Cvar_RegisterVariable (&sv_clmovement_minping);
        Cvar_RegisterVariable (&sv_clmovement_minping_disabletime);
        Cvar_RegisterVariable (&sv_clmovement_inputtimeout);
@@ -361,7 +384,9 @@ void SV_Init (void)
        Cvar_RegisterVariable (&sv_cullentities_trace_delay);
        Cvar_RegisterVariable (&sv_cullentities_trace_delay_players);
        Cvar_RegisterVariable (&sv_cullentities_trace_enlarge);
+       Cvar_RegisterVariable (&sv_cullentities_trace_entityocclusion);
        Cvar_RegisterVariable (&sv_cullentities_trace_prediction);
+       Cvar_RegisterVariable (&sv_cullentities_trace_prediction_time);
        Cvar_RegisterVariable (&sv_cullentities_trace_samples);
        Cvar_RegisterVariable (&sv_cullentities_trace_samples_extra);
        Cvar_RegisterVariable (&sv_cullentities_trace_samples_players);
@@ -373,21 +398,27 @@ void SV_Init (void)
        Cvar_RegisterVariable (&sv_freezenonclients);
        Cvar_RegisterVariable (&sv_friction);
        Cvar_RegisterVariable (&sv_gameplayfix_blowupfallenzombies);
+       Cvar_RegisterVariable (&sv_gameplayfix_consistentplayerprethink);
        Cvar_RegisterVariable (&sv_gameplayfix_delayprojectiles);
        Cvar_RegisterVariable (&sv_gameplayfix_droptofloorstartsolid);
        Cvar_RegisterVariable (&sv_gameplayfix_droptofloorstartsolid_nudgetocorrect);
        Cvar_RegisterVariable (&sv_gameplayfix_easierwaterjump);
        Cvar_RegisterVariable (&sv_gameplayfix_findradiusdistancetobox);
+       Cvar_RegisterVariable (&sv_gameplayfix_gravityunaffectedbyticrate);
        Cvar_RegisterVariable (&sv_gameplayfix_grenadebouncedownslopes);
        Cvar_RegisterVariable (&sv_gameplayfix_multiplethinksperframe);
-       Cvar_RegisterVariable (&sv_gameplayfix_slidemoveprojectiles);
        Cvar_RegisterVariable (&sv_gameplayfix_noairborncorpse);
+       Cvar_RegisterVariable (&sv_gameplayfix_noairborncorpse_allowsuspendeditems);
+       Cvar_RegisterVariable (&sv_gameplayfix_nudgeoutofsolid);
+       Cvar_RegisterVariable (&sv_gameplayfix_nudgeoutofsolid_bias);
+       Cvar_RegisterVariable (&sv_gameplayfix_q2airaccelerate);
        Cvar_RegisterVariable (&sv_gameplayfix_setmodelrealbox);
+       Cvar_RegisterVariable (&sv_gameplayfix_slidemoveprojectiles);
        Cvar_RegisterVariable (&sv_gameplayfix_stepdown);
        Cvar_RegisterVariable (&sv_gameplayfix_stepwhilejumping);
        Cvar_RegisterVariable (&sv_gameplayfix_swiminbmodels);
        Cvar_RegisterVariable (&sv_gameplayfix_upwardvelocityclearsongroundflag);
-       Cvar_RegisterVariable (&sv_gameplayfix_gravityunaffectedbyticrate);
+       Cvar_RegisterVariable (&sv_gameplayfix_downtracesupportsongroundflag);
        Cvar_RegisterVariable (&sv_gravity);
        Cvar_RegisterVariable (&sv_idealpitchscale);
        Cvar_RegisterVariable (&sv_jumpstep);
@@ -415,6 +446,7 @@ void SV_Init (void)
        Cvar_RegisterVariable (&sv_warsowbunny_topspeed);
        Cvar_RegisterVariable (&sv_warsowbunny_turnaccel);
        Cvar_RegisterVariable (&sv_warsowbunny_backtosideratio);
+       Cvar_RegisterVariable (&sv_onlycsqcnetworking);
        Cvar_RegisterVariable (&sys_ticrate);
        Cvar_RegisterVariable (&teamplay);
        Cvar_RegisterVariable (&timelimit);
@@ -475,12 +507,19 @@ void SV_Init (void)
                Cvar_SetValueQuick (&sv_gameplayfix_blowupfallenzombies, 0);
                // hipnotic mission pack has issues with bobbing water entities 'jittering' between different heights on alternate frames at the default 0.0138889 ticrate, 0.02 avoids this issue
                Cvar_SetValueQuick (&sys_ticrate, 0.02);
+               // hipnotic mission pack has issues in their proximity mine sticking code, which causes them to bounce off.                                                                                     
+               Cvar_SetValueQuick (&sv_gameplayfix_slidemoveprojectiles, 0);                                                                                                                                   
        }
        if (gamemode == GAME_ROGUE)
        {
                // rogue mission pack has a guardian boss that does not wake up if findradius returns one of the entities around its spawn area
                Cvar_SetValueQuick (&sv_gameplayfix_findradiusdistancetobox, 0);
        }
+       if (gamemode == GAME_NEXUIZ)
+       {
+               // rogue mission pack has a guardian boss that does not wake up if findradius returns one of the entities around its spawn area
+               Cvar_SetValueQuick (&sv_gameplayfix_q2airaccelerate, 1);
+       }
 
        sv_mempool = Mem_AllocPool("server", 0, NULL);
 }
@@ -899,6 +938,8 @@ void SV_SendServerinfo (client_t *client)
        // clear movement info until client enters the new level properly
        memset(&client->cmd, 0, sizeof(client->cmd));
        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;
@@ -1001,11 +1042,18 @@ static qboolean SV_PrepareEntityForSending (prvm_edict_t *ent, entity_state_t *c
        unsigned int version;
        unsigned int modelindex, effects, flags, glowsize, lightstyle, lightpflags, light[4], specialvisibilityradius;
        unsigned int customizeentityforclient;
+       unsigned int sendentity;
        float f;
        vec3_t cullmins, cullmaxs;
        dp_model_t *model;
        prvm_eval_t *val, *val2;
 
+       // fast path for games that do not use legacy entity networking
+       // note: still networks clients even if they are legacy
+       sendentity = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.SendEntity)->function;
+       if (sv_onlycsqcnetworking.integer && !sendentity && enumber > svs.maxclients)
+               return false;
+
        // this 2 billion unit check is actually to detect NAN origins
        // (we really don't want to send those)
        if (!(VectorLength2(ent->fields.server->origin) < 2000000000.0*2000000000.0))
@@ -1019,7 +1067,7 @@ static qboolean SV_PrepareEntityForSending (prvm_edict_t *ent, entity_state_t *c
        // LordHavoc: this could kill tags attached to an invisible entity, I
        // just hope we never have to support that case
        i = (int)ent->fields.server->modelindex;
-       modelindex = (i >= 1 && i < MAX_MODELS && ent->fields.server->model && *PRVM_GetString(ent->fields.server->model)) ? i : 0;
+       modelindex = (i >= 1 && i < MAX_MODELS && ent->fields.server->model && *PRVM_GetString(ent->fields.server->model) && sv.models[i]) ? i : 0;
 
        flags = 0;
        i = (int)(PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.glow_size)->_float * 0.25f);
@@ -1094,7 +1142,7 @@ static qboolean SV_PrepareEntityForSending (prvm_edict_t *ent, entity_state_t *c
                return false;
 
        *cs = defaultstate;
-       cs->active = true;
+       cs->active = ACTIVE_NETWORK;
        cs->number = enumber;
        VectorCopy(ent->fields.server->origin, cs->origin);
        VectorCopy(ent->fields.server->angles, cs->angles);
@@ -1123,6 +1171,16 @@ static qboolean SV_PrepareEntityForSending (prvm_edict_t *ent, entity_state_t *c
                i = (int)(val->vector[2] * 32.0f);cs->colormod[2] = bound(0, i, 255);
        }
 
+       // don't need to init cs->glowmod because the defaultstate did that for us
+       //cs->glowmod[0] = cs->glowmod[1] = cs->glowmod[2] = 32;
+       val = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.glowmod);
+       if (val->vector[0] || val->vector[1] || val->vector[2])
+       {
+               i = (int)(val->vector[0] * 32.0f);cs->glowmod[0] = bound(0, i, 255);
+               i = (int)(val->vector[1] * 32.0f);cs->glowmod[1] = bound(0, i, 255);
+               i = (int)(val->vector[2] * 32.0f);cs->glowmod[2] = bound(0, i, 255);
+       }
+
        cs->modelindex = modelindex;
 
        cs->alpha = 255;
@@ -1181,7 +1239,7 @@ static qboolean SV_PrepareEntityForSending (prvm_edict_t *ent, entity_state_t *c
        // calculate the visible box of this entity (don't use the physics box
        // as that is often smaller than a model, and would not count
        // specialvisibilityradius)
-       if ((model = sv.models[modelindex]) && (model->type != mod_null))
+       if ((model = SV_GetModelByIndex(modelindex)) && (model->type != mod_null))
        {
                float scale = cs->scale * (1.0f / 16.0f);
                if (cs->angles[0] || cs->angles[2]) // pitch and roll
@@ -1189,7 +1247,7 @@ static qboolean SV_PrepareEntityForSending (prvm_edict_t *ent, entity_state_t *c
                        VectorMA(cs->origin, scale, model->rotatedmins, cullmins);
                        VectorMA(cs->origin, scale, model->rotatedmaxs, cullmaxs);
                }
-               else if (cs->angles[1])
+               else if (cs->angles[1] || ((effects | model->effects) & EF_ROTATE))
                {
                        VectorMA(cs->origin, scale, model->yawmins, cullmins);
                        VectorMA(cs->origin, scale, model->yawmaxs, cullmaxs);
@@ -1240,8 +1298,7 @@ static qboolean SV_PrepareEntityForSending (prvm_edict_t *ent, entity_state_t *c
        // we need to do some csqc entity upkeep here
        // get self.SendFlags and clear them
        // (to let the QC know that they've been read)
-       val = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.SendEntity);
-       if (val->function)
+       if (sendentity)
        {
                val = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.SendFlags);
                sendflags = (unsigned int)val->_float;
@@ -1259,6 +1316,8 @@ static qboolean SV_PrepareEntityForSending (prvm_edict_t *ent, entity_state_t *c
                if (sendflags)
                        for (i = 0;i < svs.maxclients;i++)
                                svs.clients[i].csqcentitysendflags[enumber] |= sendflags;
+               // mark it as inactive for non-csqc networking
+               cs->active = ACTIVE_SHARED;
        }
 
        return true;
@@ -1282,6 +1341,123 @@ void SV_PrepareEntitiesForSending(void)
        }
 }
 
+#define MAX_LINEOFSIGHTTRACES 64
+
+qboolean SV_CanSeeBox(int numtraces, vec_t enlarge, vec3_t eye, vec3_t entboxmins, vec3_t entboxmaxs)
+{
+       float pitchsign;
+       float alpha;
+       float starttransformed[3], endtransformed[3];
+       int blocked = 0;
+       int traceindex;
+       int originalnumtouchedicts;
+       int numtouchedicts = 0;
+       int touchindex;
+       matrix4x4_t matrix, imatrix;
+       dp_model_t *model;
+       prvm_edict_t *touch;
+       static prvm_edict_t *touchedicts[MAX_EDICTS];
+       vec3_t boxmins, boxmaxs;
+       vec3_t clipboxmins, clipboxmaxs;
+       vec3_t endpoints[MAX_LINEOFSIGHTTRACES];
+
+       numtraces = min(numtraces, MAX_LINEOFSIGHTTRACES);
+
+       // expand the box a little
+       boxmins[0] = (enlarge+1) * entboxmins[0] - enlarge * entboxmaxs[0];
+       boxmaxs[0] = (enlarge+1) * entboxmaxs[0] - enlarge * entboxmins[0];
+       boxmins[1] = (enlarge+1) * entboxmins[1] - enlarge * entboxmaxs[1];
+       boxmaxs[1] = (enlarge+1) * entboxmaxs[1] - enlarge * entboxmins[1];
+       boxmins[2] = (enlarge+1) * entboxmins[2] - enlarge * entboxmaxs[2];
+       boxmaxs[2] = (enlarge+1) * entboxmaxs[2] - enlarge * entboxmins[2];
+
+       VectorMAM(0.5f, boxmins, 0.5f, boxmaxs, endpoints[0]);
+       for (traceindex = 1;traceindex < numtraces;traceindex++)
+               VectorSet(endpoints[traceindex], lhrandom(boxmins[0], boxmaxs[0]), lhrandom(boxmins[1], boxmaxs[1]), lhrandom(boxmins[2], boxmaxs[2]));
+
+       // calculate sweep box for the entire swarm of traces
+       VectorCopy(eye, clipboxmins);
+       VectorCopy(eye, clipboxmaxs);
+       for (traceindex = 0;traceindex < numtraces;traceindex++)
+       {
+               clipboxmins[0] = min(clipboxmins[0], endpoints[traceindex][0]);
+               clipboxmins[1] = min(clipboxmins[1], endpoints[traceindex][1]);
+               clipboxmins[2] = min(clipboxmins[2], endpoints[traceindex][2]);
+               clipboxmaxs[0] = max(clipboxmaxs[0], endpoints[traceindex][0]);
+               clipboxmaxs[1] = max(clipboxmaxs[1], endpoints[traceindex][1]);
+               clipboxmaxs[2] = max(clipboxmaxs[2], endpoints[traceindex][2]);
+       }
+
+       // get the list of entities in the sweep box
+       if (sv_cullentities_trace_entityocclusion.integer)
+               numtouchedicts = World_EntitiesInBox(&sv.world, clipboxmins, clipboxmaxs, MAX_EDICTS, touchedicts);
+       if (numtouchedicts > MAX_EDICTS)
+       {
+               // this never happens
+               Con_Printf("SV_EntitiesInBox returned %i edicts, max was %i\n", numtouchedicts, MAX_EDICTS);
+               numtouchedicts = MAX_EDICTS;
+       }
+       // iterate the entities found in the sweep box and filter them
+       originalnumtouchedicts = numtouchedicts;
+       numtouchedicts = 0;
+       for (touchindex = 0;touchindex < originalnumtouchedicts;touchindex++)
+       {
+               touch = touchedicts[touchindex];
+               if (touch->fields.server->solid != SOLID_BSP)
+                       continue;
+               model = SV_GetModelFromEdict(touch);
+               if (!model || !model->brush.TraceLineOfSight)
+                       continue;
+               // skip obviously transparent entities
+               alpha = PRVM_EDICTFIELDVALUE(touch, prog->fieldoffsets.alpha)->_float;
+               if (alpha && alpha < 1)
+                       continue;
+               if ((int)touch->fields.server->effects & EF_ADDITIVE)
+                       continue;
+               touchedicts[numtouchedicts++] = touch;
+       }
+
+       // now that we have a filtered list of "interesting" entities, fire each
+       // ray against all of them, this gives us an early-out case when something
+       // is visible (which it often is)
+
+       for (traceindex = 0;traceindex < numtraces;traceindex++)
+       {
+               // check world occlusion
+               if (sv.worldmodel && sv.worldmodel->brush.TraceLineOfSight)
+                       if (!sv.worldmodel->brush.TraceLineOfSight(sv.worldmodel, eye, endpoints[traceindex]))
+                               continue;
+               for (touchindex = 0;touchindex < numtouchedicts;touchindex++)
+               {
+                       touch = touchedicts[touchindex];
+                       model = SV_GetModelFromEdict(touch);
+                       if(model && model->brush.TraceLineOfSight)
+                       {
+                               // get the entity matrix
+                               pitchsign = SV_GetPitchSign(touch);
+                               Matrix4x4_CreateFromQuakeEntity(&matrix, touch->fields.server->origin[0], touch->fields.server->origin[1], touch->fields.server->origin[2], pitchsign * touch->fields.server->angles[0], touch->fields.server->angles[1], touch->fields.server->angles[2], 1);
+                               Matrix4x4_Invert_Simple(&imatrix, &matrix);
+                               // see if the ray hits this entity
+                               Matrix4x4_Transform(&imatrix, eye, starttransformed);
+                               Matrix4x4_Transform(&imatrix, endpoints[traceindex], endtransformed);
+                               if (!model->brush.TraceLineOfSight(model, starttransformed, endtransformed))
+                               {
+                                       blocked++;
+                                       break;
+                               }
+                       }
+               }
+               // check if the ray was blocked
+               if (touchindex < numtouchedicts)
+                       continue;
+               // return if the ray was not blocked
+               return true;
+       }
+
+       // no rays survived
+       return false;
+}
+
 void SV_MarkWriteEntityStateToClient(entity_state_t *s)
 {
        int isbmodel;
@@ -1315,7 +1491,7 @@ void SV_MarkWriteEntityStateToClient(entity_state_t *s)
                if (!s->modelindex && s->specialvisibilityradius == 0)
                        return;
 
-               isbmodel = (model = sv.models[s->modelindex]) != NULL && model->name[0] == '*';
+               isbmodel = (model = SV_GetModelByIndex(s->modelindex)) != NULL && model->name[0] == '*';
                // viewmodels don't have visibility checking
                if (s->viewmodelforclient)
                {
@@ -1378,42 +1554,13 @@ void SV_MarkWriteEntityStateToClient(entity_state_t *s)
                                                : sv_cullentities_trace_samples.integer;
                                float enlarge = sv_cullentities_trace_enlarge.value;
 
-                               qboolean visible = TRUE;
-
                                if(samples > 0)
                                {
-                                       do
-                                       {
-                                               if(Mod_CanSeeBox_Trace(samples, enlarge, sv.worldmodel, sv.writeentitiestoclient_testeye, ed->priv.server->cullmins, ed->priv.server->cullmaxs))
-                                                       break; // directly visible from the server's view
-
-                                               if(sv_cullentities_trace_prediction.integer)
-                                               {
-                                                       vec3_t predeye;
-
-                                                       // get player velocity
-                                                       float predtime = bound(0, host_client->ping, 0.2); // / 2
-                                                               // sorry, no wallhacking by high ping please, and at 200ms
-                                                               // ping a FPS is annoying to play anyway and a player is
-                                                               // likely to have changed his direction
-                                                       VectorMA(sv.writeentitiestoclient_testeye, predtime, host_client->edict->fields.server->velocity, predeye);
-                                                       if(sv.worldmodel->brush.TraceLineOfSight(sv.worldmodel, sv.writeentitiestoclient_testeye, predeye)) // must be able to go there...
-                                                       {
-                                                               if(Mod_CanSeeBox_Trace(samples, enlarge, sv.worldmodel, predeye, ed->priv.server->cullmins, ed->priv.server->cullmaxs))
-                                                                       break; // directly visible from the predicted view
-                                                       }
-                                                       else
-                                                       {
-                                                               //Con_DPrintf("Trying to walk into solid in a pingtime... not predicting for culling\n");
-                                                       }
-                                               }
-
-                                               // when we get here, we can't see the entity
-                                               visible = false;
-                                       }
-                                       while(0);
-
-                                       if(visible)
+                                       int eyeindex;
+                                       for (eyeindex = 0;eyeindex < sv.writeentitiestoclient_numeyes;eyeindex++)
+                                               if(SV_CanSeeBox(samples, enlarge, sv.writeentitiestoclient_eyes[eyeindex], ed->priv.server->cullmins, ed->priv.server->cullmaxs))
+                                                       break;
+                                       if(eyeindex < sv.writeentitiestoclient_numeyes)
                                                svs.clients[sv.writeentitiestoclient_clientnumber].visibletime[s->number] =
                                                        realtime + (
                                                                s->number <= svs.maxclients
@@ -1440,9 +1587,11 @@ void SV_MarkWriteEntityStateToClient(entity_state_t *s)
 void SV_WriteEntitiesToClient(client_t *client, prvm_edict_t *clent, sizebuf_t *msg, int maxsize)
 {
        qboolean need_empty = false;
-       int i, numsendstates;
+       int i, numsendstates, numcsqcsendstates;
        entity_state_t *s;
        prvm_edict_t *camera;
+       qboolean success;
+       vec3_t eye;
 
        // if there isn't enough space to accomplish anything, skip it
        if (msg->cursize + 25 > maxsize)
@@ -1455,16 +1604,37 @@ void SV_WriteEntitiesToClient(client_t *client, prvm_edict_t *clent, sizebuf_t *
        sv.writeentitiestoclient_stats_culled_trace = 0;
        sv.writeentitiestoclient_stats_visibleentities = 0;
        sv.writeentitiestoclient_stats_totalentities = 0;
+       sv.writeentitiestoclient_numeyes = 0;
 
-// find the client's PVS
-       // the real place being tested from
+       // get eye location
+       sv.writeentitiestoclient_cliententitynumber = PRVM_EDICT_TO_PROG(clent); // LordHavoc: for comparison purposes
        camera = PRVM_EDICT_NUM( client->clientcamera );
-       VectorAdd(camera->fields.server->origin, clent->fields.server->view_ofs, sv.writeentitiestoclient_testeye);
+       VectorAdd(camera->fields.server->origin, clent->fields.server->view_ofs, eye);
        sv.writeentitiestoclient_pvsbytes = 0;
+       // get the PVS values for the eye location, later FatPVS calls will merge
        if (sv.worldmodel && sv.worldmodel->brush.FatPVS)
-               sv.writeentitiestoclient_pvsbytes = sv.worldmodel->brush.FatPVS(sv.worldmodel, sv.writeentitiestoclient_testeye, 8, sv.writeentitiestoclient_pvs, sizeof(sv.writeentitiestoclient_pvs), false);
+               sv.writeentitiestoclient_pvsbytes = sv.worldmodel->brush.FatPVS(sv.worldmodel, eye, 8, sv.writeentitiestoclient_pvs, sizeof(sv.writeentitiestoclient_pvs), sv.writeentitiestoclient_pvsbytes != 0);
 
-       sv.writeentitiestoclient_cliententitynumber = PRVM_EDICT_TO_PROG(clent); // LordHavoc: for comparison purposes
+       // add the eye to a list for SV_CanSeeBox tests
+       VectorCopy(eye, sv.writeentitiestoclient_eyes[sv.writeentitiestoclient_numeyes]);
+       sv.writeentitiestoclient_numeyes++;
+
+       // calculate predicted eye origin for SV_CanSeeBox tests
+       if (sv_cullentities_trace_prediction.integer)
+       {
+               vec_t predtime = bound(0, host_client->ping, sv_cullentities_trace_prediction_time.value);
+               vec3_t predeye;
+               VectorMA(eye, predtime, camera->fields.server->velocity, predeye);
+               if (SV_CanSeeBox(1, 0, eye, predeye, predeye))
+               {
+                       VectorCopy(predeye, sv.writeentitiestoclient_eyes[sv.writeentitiestoclient_numeyes]);
+                       sv.writeentitiestoclient_numeyes++;
+               }
+               //if (!sv.writeentitiestoclient_useprediction)
+               //      Con_DPrintf("Trying to walk into solid in a pingtime... not predicting for culling\n");
+       }
+
+       // TODO: check line of sight to portal entities and add them to PVS
 
        sv.sententitiesmark++;
 
@@ -1472,14 +1642,22 @@ void SV_WriteEntitiesToClient(client_t *client, prvm_edict_t *clent, sizebuf_t *
                SV_MarkWriteEntityStateToClient(sv.sendentities + i);
 
        numsendstates = 0;
+       numcsqcsendstates = 0;
        for (i = 0;i < sv.numsendentities;i++)
        {
                if (sv.sententities[sv.sendentities[i].number] == sv.sententitiesmark)
                {
-                       s = &sv.writeentitiestoclient_sendstates[numsendstates++];
-                       *s = sv.sendentities[i];
-                       if (s->exteriormodelforclient && s->exteriormodelforclient == sv.writeentitiestoclient_cliententitynumber)
-                               s->flags |= RENDER_EXTERIORMODEL;
+                       if(sv.sendentities[i].active == ACTIVE_NETWORK)
+                       {
+                               s = &sv.writeentitiestoclient_sendstates[numsendstates++];
+                               *s = sv.sendentities[i];
+                               if (s->exteriormodelforclient && s->exteriormodelforclient == sv.writeentitiestoclient_cliententitynumber)
+                                       s->flags |= RENDER_EXTERIORMODEL;
+                       }
+                       else if(sv.sendentities[i].active == ACTIVE_SHARED)
+                               sv.writeentitiestoclient_csqcsendstates[numcsqcsendstates++] = sv.sendentities[i].number;
+                       else
+                               Con_Printf("entity %d is in sv.sendentities and marked, but not active, please breakpoint me\n", sv.sendentities[i].number);
                }
        }
 
@@ -1487,27 +1665,35 @@ void SV_WriteEntitiesToClient(client_t *client, prvm_edict_t *clent, sizebuf_t *
                Con_Printf("client \"%s\" entities: %d total, %d visible, %d culled by: %d pvs %d trace\n", client->name, sv.writeentitiestoclient_stats_totalentities, sv.writeentitiestoclient_stats_visibleentities, sv.writeentitiestoclient_stats_culled_pvs + sv.writeentitiestoclient_stats_culled_trace, sv.writeentitiestoclient_stats_culled_pvs, sv.writeentitiestoclient_stats_culled_trace);
 
        if(client->entitydatabase5)
-               need_empty = EntityFrameCSQC_WriteFrame(msg, maxsize, numsendstates, sv.writeentitiestoclient_sendstates, client->entitydatabase5->latestframenum + 1);
+               need_empty = EntityFrameCSQC_WriteFrame(msg, maxsize, numcsqcsendstates, sv.writeentitiestoclient_csqcsendstates, client->entitydatabase5->latestframenum + 1);
        else
-               EntityFrameCSQC_WriteFrame(msg, maxsize, numsendstates, sv.writeentitiestoclient_sendstates, 0);
+               EntityFrameCSQC_WriteFrame(msg, maxsize, numcsqcsendstates, sv.writeentitiestoclient_csqcsendstates, 0);
+
+       if(client->num_skippedentityframes >= 10)
+               need_empty = true; // force every 10th frame to be not empty (or cl_movement replay takes too long)
 
        if (client->entitydatabase5)
-               EntityFrame5_WriteFrame(msg, maxsize, client->entitydatabase5, numsendstates, sv.writeentitiestoclient_sendstates, client - svs.clients + 1, client->movesequence, need_empty);
+               success = EntityFrame5_WriteFrame(msg, maxsize, client->entitydatabase5, numsendstates, sv.writeentitiestoclient_sendstates, client - svs.clients + 1, client->movesequence, need_empty);
        else if (client->entitydatabase4)
        {
-               EntityFrame4_WriteFrame(msg, maxsize, client->entitydatabase4, numsendstates, sv.writeentitiestoclient_sendstates);
+               success = EntityFrame4_WriteFrame(msg, maxsize, client->entitydatabase4, numsendstates, sv.writeentitiestoclient_sendstates);
                Protocol_WriteStatsReliable();
        }
        else if (client->entitydatabase)
        {
-               EntityFrame_WriteFrame(msg, maxsize, client->entitydatabase, numsendstates, sv.writeentitiestoclient_sendstates, client - svs.clients + 1);
+               success = EntityFrame_WriteFrame(msg, maxsize, client->entitydatabase, numsendstates, sv.writeentitiestoclient_sendstates, client - svs.clients + 1);
                Protocol_WriteStatsReliable();
        }
        else
        {
-               EntityFrameQuake_WriteFrame(msg, maxsize, numsendstates, sv.writeentitiestoclient_sendstates);
+               success = EntityFrameQuake_WriteFrame(msg, maxsize, numsendstates, sv.writeentitiestoclient_sendstates);
                Protocol_WriteStatsReliable();
        }
+
+       if(success)
+               client->num_skippedentityframes = 0;
+       else
+               ++client->num_skippedentityframes;
 }
 
 /*
@@ -1655,6 +1841,9 @@ void SV_WriteClientdataToMessage (client_t *client, prvm_edict_t *ent, sizebuf_t
 
        // movement settings for prediction
        // note: these are not sent in protocols with lower MAX_CL_STATS limits
+       stats[STAT_MOVEFLAGS] = MOVEFLAG_VALID
+               | (sv_gameplayfix_q2airaccelerate.integer ? MOVEFLAG_Q2AIRACCELERATE : 0)
+       ;
        statsf[STAT_MOVEVARS_TICRATE] = sys_ticrate.value;
        statsf[STAT_MOVEVARS_TIMESCALE] = slowmo.value;
        statsf[STAT_MOVEVARS_GRAVITY] = sv_gravity.value;
@@ -1818,7 +2007,7 @@ void SV_FlushBroadcastMessages(void)
        SZ_Clear(&sv.datagram);
 }
 
-static void SV_WriteUnreliableMessages(client_t *client, sizebuf_t *msg, int maxsize)
+static void SV_WriteUnreliableMessages(client_t *client, sizebuf_t *msg, int maxsize, int maxsize2)
 {
        // scan the splitpoints to find out how many we can fit in
        int numsegments, j, split;
@@ -1827,34 +2016,28 @@ static void SV_WriteUnreliableMessages(client_t *client, sizebuf_t *msg, int max
        // always accept the first one if it's within 1024 bytes, this ensures
        // that very big datagrams which are over the rate limit still get
        // through, just to keep it working
-       j = msg->cursize + client->unreliablemsg_splitpoint[0];
-       if (maxsize < 1024 && j > maxsize && j <= 1024)
-       {
-               numsegments = 1;
-               maxsize = 1024;
-       }
-       else
-               for (numsegments = 0;numsegments < client->unreliablemsg_splitpoints;numsegments++)
-                       if (msg->cursize + client->unreliablemsg_splitpoint[numsegments] > maxsize)
-                               break;
-       if (numsegments > 0)
-       {
-               // some will fit, so add the ones that will fit
-               split = client->unreliablemsg_splitpoint[numsegments-1];
-               // note this discards ones that were accepted by the segments scan but
-               // can not fit, such as a really huge first one that will never ever
-               // fit in a packet...
-               if (msg->cursize + split <= maxsize)
-                       SZ_Write(msg, client->unreliablemsg.data, split);
-               // remove the part we sent, keeping any remaining data
-               client->unreliablemsg.cursize -= split;
-               if (client->unreliablemsg.cursize > 0)
-                       memmove(client->unreliablemsg.data, client->unreliablemsg.data + split, client->unreliablemsg.cursize);
-               // adjust remaining splitpoints
-               client->unreliablemsg_splitpoints -= numsegments;
-               for (j = 0;j < client->unreliablemsg_splitpoints;j++)
-                       client->unreliablemsg_splitpoint[j] = client->unreliablemsg_splitpoint[numsegments + j] - split;
-       }
+       for (numsegments = 1;numsegments < client->unreliablemsg_splitpoints;numsegments++)
+               if (msg->cursize + client->unreliablemsg_splitpoint[numsegments] > maxsize)
+                       break;
+       // the first segment gets an exemption from the rate limiting, otherwise
+       // it could get dropped consistently due to a low rate limit
+       if (numsegments == 1)
+               maxsize = maxsize2;
+       // some will fit, so add the ones that will fit
+       split = client->unreliablemsg_splitpoint[numsegments-1];
+       // note this discards ones that were accepted by the segments scan but
+       // can not fit, such as a really huge first one that will never ever
+       // fit in a packet...
+       if (msg->cursize + split <= maxsize)
+               SZ_Write(msg, client->unreliablemsg.data, split);
+       // remove the part we sent, keeping any remaining data
+       client->unreliablemsg.cursize -= split;
+       if (client->unreliablemsg.cursize > 0)
+               memmove(client->unreliablemsg.data, client->unreliablemsg.data + split, client->unreliablemsg.cursize);
+       // adjust remaining splitpoints
+       client->unreliablemsg_splitpoints -= numsegments;
+       for (j = 0;j < client->unreliablemsg_splitpoints;j++)
+               client->unreliablemsg_splitpoint[j] = client->unreliablemsg_splitpoint[numsegments + j] - split;
 }
 
 /*
@@ -1960,7 +2143,7 @@ static void SV_SendClientDatagram (client_t *client)
                // add as many queued unreliable messages (effects) as we can fit
                // limit effects to half of the remaining space
                if (client->unreliablemsg.cursize)
-                       SV_WriteUnreliableMessages (client, &msg, (msg.cursize + maxsize) / 2);
+                       SV_WriteUnreliableMessages (client, &msg, maxsize/2, maxsize2);
 
                // now write as many entities as we can fit, and also sends stats
                SV_WriteEntitiesToClient (client, client->edict, &msg, maxsize);
@@ -2272,7 +2455,7 @@ static void SV_Download_f(void)
                
                Con_DPrintf("Downloading %s to %s\n", host_client->download_name, host_client->name);
 
-               if(host_client->download_deflate)
+               if(host_client->download_deflate && svs.csqc_progdata_deflated)
                        host_client->download_file = FS_FileFromData(svs.csqc_progdata_deflated, svs.csqc_progsize_deflated, true);
                else
                        host_client->download_file = FS_FileFromData(svs.csqc_progdata, sv.csqc_progsize, true);
@@ -2584,6 +2767,20 @@ int SV_ParticleEffectIndex(const char *name)
        return 0;
 }
 
+dp_model_t *SV_GetModelByIndex(int modelindex)
+{
+       return (modelindex > 0 && modelindex < MAX_MODELS) ? sv.models[modelindex] : NULL;
+}
+
+dp_model_t *SV_GetModelFromEdict(prvm_edict_t *ed)
+{
+       int modelindex;
+       if (!ed || ed->priv.server->free)
+               return NULL;
+       modelindex = (int)ed->fields.server->modelindex;
+       return (modelindex > 0 && modelindex < MAX_MODELS) ? sv.models[modelindex] : NULL;
+}
+
 /*
 ================
 SV_CreateBaseline
@@ -2706,8 +2903,13 @@ void SV_Prepare_CSQC(void)
                //unsigned char *FS_Deflate(const unsigned char *data, size_t size, size_t *deflated_size, int level, mempool_t *mempool);
                svs.csqc_progdata_deflated = FS_Deflate(svs.csqc_progdata, progsize, &deflated_size, -1, sv_mempool);
                svs.csqc_progsize_deflated = (int)deflated_size;
-               Con_Printf("Deflated: %g%%\n", 100.0 - 100.0 * (deflated_size / (float)progsize));
-               Con_DPrintf("Uncompressed: %u\nCompressed:   %u\n", (unsigned)sv.csqc_progsize, (unsigned)svs.csqc_progsize_deflated);
+               if(svs.csqc_progdata_deflated)
+               {
+                       Con_Printf("Deflated: %g%%\n", 100.0 - 100.0 * (deflated_size / (float)progsize));
+                       Con_DPrintf("Uncompressed: %u\nCompressed:   %u\n", (unsigned)sv.csqc_progsize, (unsigned)svs.csqc_progsize_deflated);
+               }
+               else
+                       Con_Printf("Cannot compress - need zlib for this. Using uncompressed progs only.\n");
        }
 }
 
@@ -2773,6 +2975,7 @@ void SV_SpawnServer (const char *server)
        if(sv.active)
        {
                SV_VM_Begin();
+               World_End(&sv.world);
                if(prog->funcoffsets.SV_Shutdown)
                {
                        func_t s = prog->funcoffsets.SV_Shutdown;
@@ -2903,6 +3106,7 @@ void SV_SpawnServer (const char *server)
 // clear world interaction links
 //
        World_SetSize(&sv.world, sv.worldmodel->name, sv.worldmodel->normalmins, sv.worldmodel->normalmaxs);
+       World_Start(&sv.world);
 
        strlcpy(sv.sound_precache[0], "", sizeof(sv.sound_precache[0]));
 
@@ -3042,7 +3246,7 @@ static void SV_VM_CB_EndIncreaseEdicts(void)
        // link every entity except world
        for (i = 1, ent = prog->edicts;i < prog->max_edicts;i++, ent++)
                if (!ent->priv.server->free)
-                       SV_LinkEdict(ent, false);
+                       SV_LinkEdict(ent);
 }
 
 static void SV_VM_CB_InitEdict(prvm_edict_t *e)
@@ -3103,6 +3307,10 @@ static void SV_VM_CB_FreeEdict(prvm_edict_t *ed)
        ed->fields.server->nextthink = -1;
        ed->fields.server->solid = 0;
 
+       VM_RemoveEdictSkeleton(ed);
+       World_Physics_RemoveFromEntity(&sv.world, ed);
+       World_Physics_RemoveJointFromEntity(&sv.world, ed);
+
        // make sure csqc networking is aware of the removed entity
        e = PRVM_NUM_FOR_EDICT(ed);
        sv.csqcentityversion[e] = 0;
@@ -3202,6 +3410,7 @@ static void SV_VM_Setup(void)
        prog->init_cmd = VM_SV_Cmd_Init;
        prog->reset_cmd = VM_SV_Cmd_Reset;
        prog->error_cmd = Host_Error;
+       prog->ExecuteProgram = SVVM_ExecuteProgram;
 
        // TODO: add a requiredfuncs list (ask LH if this is necessary at all)
        PRVM_LoadProgs( sv_progs.string, 0, NULL, REQFIELDS, reqfields, 0, NULL );