]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/server/world.qc
g_maplist: refactor initialisation
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / world.qc
index c61147842155d59359d566e08981a424cd01f4e9..04706f615444d6dd93799473a4ef066b94b30c15 100644 (file)
@@ -118,12 +118,7 @@ void GotoFirstMap(entity this)
        {
                // cvar_set("_sv_init", "0");
                // we do NOT set this to 0 any more, so someone "accidentally" changing
-               // to this "init" map on a dedicated server will cause no permanent
-               // harm
-               if(autocvar_g_maplist_shuffle)
-                       ShuffleMaplist();
-               n = tokenizebyseparator(autocvar_g_maplist, " ");
-               cvar_set("g_maplist_index", ftos(n - 1)); // jump to map 0 in GotoNextMap
+               // to this "init" map on a dedicated server will cause no permanent harm
 
                MapInfo_Enumerate();
                MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags(), 0);
@@ -166,10 +161,14 @@ void cvar_changes_init()
        {
                k = bufstr_get(h, i);
 
-#define BADPREFIX(p) if(substring(k, 0, strlen(p)) == p) continue
-#define BADPRESUFFIX(p,s) if(substring(k, 0, strlen(p)) == p && substring(k, -strlen(s), -1) == s) continue
+#define BADPREFIX_COND(p) (substring(k, 0, strlen(p)) == p)
+#define BADSUFFIX_COND(s) (substring(k, -strlen(s), -1) == s)
+
+#define BADPREFIX(p) if(BADPREFIX_COND(p)) continue
+#define BADPRESUFFIX(p, s) if(BADPREFIX_COND(p) && BADSUFFIX_COND(s)) continue
 #define BADCVAR(p) if(k == p) continue
 #define BADVALUE(p, val) if (k == p && v == val) continue
+#define BADPRESUFFIXVALUE(p, s, val) if(BADPREFIX_COND(p) && BADSUFFIX_COND(s) && v == val) continue
 
                // general excludes and namespaces for server admin used cvars
                BADPREFIX("help_"); // PN's server has this listed as changed, let's not rat him out for THAT
@@ -232,6 +231,10 @@ void cvar_changes_init()
                BADCVAR("timeformat");
                BADCVAR("timestamps");
                BADCVAR("g_require_stats");
+               BADCVAR("g_chatban_list");
+               BADCVAR("g_playban_list");
+               BADCVAR("g_playban_minigames");
+               BADCVAR("g_voteban_list");
                BADPREFIX("developer_");
                BADPREFIX("g_ban_");
                BADPREFIX("g_banned_list");
@@ -255,8 +258,6 @@ void cvar_changes_init()
 
                // these can contain player IDs, so better hide
                BADPREFIX("g_forced_team_");
-               BADCVAR("sv_muteban_list");
-               BADCVAR("sv_voteban_list");
                BADCVAR("sv_allow_customplayermodels_idlist");
                BADCVAR("sv_allow_customplayermodels_speciallist");
 
@@ -285,6 +286,7 @@ void cvar_changes_init()
                BADCVAR("g_keyhunt");
                BADCVAR("g_keyhunt_teams");
                BADCVAR("g_lms");
+               BADCVAR("g_mayhem");
                BADCVAR("g_nexball");
                BADCVAR("g_onslaught");
                BADCVAR("g_race");
@@ -299,6 +301,12 @@ void cvar_changes_init()
                BADCVAR("g_tdm");
                BADCVAR("g_tdm_on_dm_maps");
                BADCVAR("g_tdm_teams");
+               BADCVAR("g_tka");
+               BADCVAR("g_tka_on_ka_maps");
+               BADCVAR("g_tka_on_tdm_maps");
+               BADCVAR("g_tka_teams");
+               BADCVAR("g_tmayhem");
+               BADCVAR("g_tmayhem_teams");
                BADCVAR("g_vip");
                BADCVAR("leadlimit");
                BADCVAR("nextmap");
@@ -376,10 +384,15 @@ void cvar_changes_init()
                BADCVAR("g_spawn_alloweffects");
                BADCVAR("g_tdm_point_leadlimit");
                BADCVAR("g_tdm_point_limit");
+               BADCVAR("g_mayhem_point_limit");
+               BADCVAR("g_mayhem_point_leadlimit");
+               BADCVAR("g_tmayhem_point_limit");
+               BADCVAR("g_tmayhem_point_leadlimit");
                BADCVAR("leadlimit_and_fraglimit");
                BADCVAR("leadlimit_override");
                BADCVAR("pausable");
                BADCVAR("sv_announcer");
+               BADCVAR("sv_autopause");
                BADCVAR("sv_checkforpacketsduringsleep");
                BADCVAR("sv_damagetext");
                BADCVAR("sv_db_saveasdump");
@@ -396,7 +409,6 @@ void cvar_changes_init()
                BADCVAR("w_prop_interval");
                BADPREFIX("chat_");
                BADPREFIX("crypto_");
-               BADPREFIX("gameversion");
                BADPREFIX("g_chat_");
                BADPREFIX("g_ctf_captimerecord_");
                BADPREFIX("g_hats_");
@@ -440,6 +452,7 @@ void cvar_changes_init()
                BADCVAR("g_ban_sync_uri");
                BADCVAR("g_buffs");
                BADCVAR("g_ca_teams_override");
+               BADCVAR("g_ca_prevent_stalemate");
                BADCVAR("g_ctf_fullbrightflags");
                BADCVAR("g_ctf_ignore_frags");
                BADCVAR("g_ctf_leaderboard");
@@ -453,6 +466,7 @@ void cvar_changes_init()
                BADCVAR("g_keyhunt_point_limit");
                BADCVAR("g_keyhunt_teams_override");
                BADCVAR("g_lms_lives_override");
+               BADCVAR("g_mayhem_powerups");
                BADCVAR("g_maplist");
                BADCVAR("g_maxplayers");
                BADCVAR("g_mirrordamage");
@@ -469,6 +483,8 @@ void cvar_changes_init()
                BADCVAR("g_start_delay");
                BADCVAR("g_superspectate");
                BADCVAR("g_tdm_teams_override");
+               BADCVAR("g_tmayhem_teams_override");
+               BADCVAR("g_tmayhem_powerups");
                BADCVAR("g_weapon_stay"); BADPRESUFFIX("g_", "_weapon_stay");
                BADCVAR("hostname");
                BADCVAR("log_file");
@@ -512,6 +528,9 @@ void cvar_changes_init()
                BADPREFIX("sv_info_");
                BADPREFIX("sv_ready_restart_");
 
+               BADPRESUFFIXVALUE("g_", "_weaponarena", "most");
+               BADPRESUFFIXVALUE("g_", "_weaponarena", "most_available");
+
                // mutators that announce themselves properly to the server browser
                BADCVAR("g_instagib");
                BADCVAR("g_new_toys");
@@ -523,6 +542,7 @@ void cvar_changes_init()
 #undef BADPREFIX
 #undef BADCVAR
 #undef BADVALUE
+#undef BADPRESUFFIXVALUE
 
                if(pureadding)
                {
@@ -591,15 +611,17 @@ spawnfunc(__init_dedicated_server)
 
        e = new(info_player_deathmatch);  // safeguard against player joining
 
-    // assign reflectively to avoid "assignment to world" warning
-    for (int i = 0, n = numentityfields(); i < n; ++i) {
-        string k = entityfieldname(i);
-        if (k == "classname") {
-            // safeguard against various stuff ;)
-            putentityfieldstring(i, this, "worldspawn");
-            break;
-        }
-    }
+       // assign reflectively to avoid "assignment to world" warning
+       for (int i = 0, n = numentityfields(); i < n; ++i)
+       {
+               string k = entityfieldname(i);
+               if (k == "classname")
+               {
+                       // safeguard against various stuff ;)
+                       putentityfieldstring(i, this, "worldspawn");
+                       break;
+               }
+       }
 
        // needs to be done so early because of the constants they create
        static_init();
@@ -636,14 +658,18 @@ void GameplayMode_DelayedInit(entity this)
        if (!g_duel)
                MapReadSizes(mapname);
 
-       if (autocvar_g_maxplayers < 0 && teamplay)
+       if (autocvar_g_maxplayers < 0)
        {
-               // automatic maxplayers should be a multiple of team count
-               if (map_maxplayers == 0 || map_maxplayers > maxclients)
+               if (map_maxplayers <= 0)
                        map_maxplayers = maxclients; // unlimited, but may need rounding
-               int d = map_maxplayers % AVAILABLE_TEAMS;
-               int u = AVAILABLE_TEAMS - d;
-               map_maxplayers += (u <= d && u + map_maxplayers <= maxclients) ? u : -d;
+               map_maxplayers = bound(max(2, AVAILABLE_TEAMS * 2), map_maxplayers, maxclients);
+               if (teamplay)
+               {
+                       // automatic maxplayers should be a multiple of team count
+                       int down = map_maxplayers % AVAILABLE_TEAMS;
+                       int up = AVAILABLE_TEAMS - down;
+                       map_maxplayers += (up < down && up + map_maxplayers <= maxclients) ? up : -down;
+               }
        }
 
        if (warmup_stage < 0)
@@ -654,29 +680,31 @@ void GameplayMode_DelayedInit(entity this)
                if (teamplay)
                {
                        // automatic minplayers should be a multiple of team count
-                       int d = map_minplayers % AVAILABLE_TEAMS;
-                       int u = AVAILABLE_TEAMS - d;
-                       map_minplayers += (u < d && u + map_minplayers <= m) ? u : -d;
+                       int down = map_minplayers % AVAILABLE_TEAMS;
+                       int up = AVAILABLE_TEAMS - down;
+                       map_minplayers += (up < down && up + map_minplayers <= m) ? up : -down;
                }
-               warmup_limit = -1;
        }
        else
-               map_minplayers = 0; // don't display a minimum if it's not used
+               map_minplayers = 0; // don't display a minimum if it's not used (g_maxplayers < 0 && g_warmup >= 0)
 }
 
 void InitGameplayMode()
 {
-       VoteReset();
+       VoteReset(false);
 
        // find out good world mins/maxs bounds, either the static bounds found by looking for solid, or the mapinfo specified bounds
        get_mi_min_max(1);
        // assign reflectively to avoid "assignment to world" warning
-       int done = 0; for (int i = 0, n = numentityfields(); i < n; ++i) {
-           string k = entityfieldname(i); vector v = (k == "mins") ? mi_min : (k == "maxs") ? mi_max : '0 0 0';
-           if (v) {
-            putentityfieldstring(i, world, sprintf("%v", v));
-            if (++done == 2) break;
-        }
+       for (int i = 0, done = 0, n = numentityfields(); i < n; ++i)
+       {
+               string k = entityfieldname(i);
+               vector v = (k == "mins") ? mi_min : (k == "maxs") ? mi_max : '0 0 0';
+               if (v)
+               {
+                       putentityfieldstring(i, world, sprintf("%v", v));
+                       if (++done == 2) break;
+               }
        }
        // currently, NetRadiant's limit is 131072 qu for each side
        // distance from one corner of a 131072qu cube to the opposite corner is approx. 227023 qu
@@ -730,8 +758,7 @@ spawnfunc(worldspawn)
        {
                if (!server_is_dedicated)
                {
-                       // force unloading of server pk3 files when starting a listen server
-                       // localcmd("\nfs_rescan\n"); // FIXME: does more harm than good, has unintended side effects. What we really want is to unload temporary pk3s only
+                       // DP unloads dlcache pk3s before starting a listen server since https://gitlab.com/xonotic/darkplaces/-/merge_requests/134
                        // restore csqc_progname too
                        string expect = "csprogs.dat";
                        wantrestart = cvar_string("csqc_progname") != expect;
@@ -860,9 +887,6 @@ spawnfunc(worldspawn)
 
        GameRules_limit_fallbacks();
 
-       if(warmup_limit == 0)
-               warmup_limit = autocvar_timelimit * 60;
-
        player_count = 0;
        bot_waypoints_for_items = autocvar_g_waypoints_for_items;
        if(bot_waypoints_for_items == 1)
@@ -1033,6 +1057,10 @@ spawnfunc(worldspawn)
 
        WinningConditionHelper(this); // set worldstatus
 
+       if (autocvar_sv_autopause && server_is_dedicated && !wantrestart)
+               // INITPRIO_LAST is too soon: bots either didn't join yet or didn't leave yet, see: bot_fixcount()
+               defer(this, 5, Pause_TryPause_Dedicated);
+
        world_initialized = 1;
        __spawnfunc_spawn_all();
 }
@@ -1045,59 +1073,59 @@ spawnfunc(light)
 
 bool MoveToRandomLocationWithinBounds(entity e, vector boundmin, vector boundmax, float goodcontents, float badcontents, float badsurfaceflags, int attempts, float maxaboveground, float minviewdistance, bool frompos)
 {
-    float m = e.dphitcontentsmask;
-    e.dphitcontentsmask = goodcontents | badcontents;
-
-    vector org = boundmin;
-    vector delta = boundmax - boundmin;
-
-    vector start, end;
-    start = end = org;
-    int j; // used after the loop
-    for(j = 0; j < attempts; ++j)
-    {
-        start.x = org.x + random() * delta.x;
-        start.y = org.y + random() * delta.y;
-        start.z = org.z + random() * delta.z;
-
-        // rule 1: start inside world bounds, and outside
-        // solid, and don't start from somewhere where you can
-        // fall down to evil
-        tracebox(start, e.mins, e.maxs, start - '0 0 1' * delta.z, MOVE_NORMAL, e);
-        if (trace_fraction >= 1)
-            continue;
-        if (trace_startsolid)
-            continue;
-        if (trace_dphitcontents & badcontents)
-            continue;
-        if (trace_dphitq3surfaceflags & badsurfaceflags)
-            continue;
-
-        // rule 2: if we are too high, lower the point
-        if (trace_fraction * delta.z > maxaboveground)
-            start = trace_endpos + '0 0 1' * maxaboveground;
-        vector enddown = trace_endpos;
-
-        // rule 3: make sure we aren't outside the map. This only works
-        // for somewhat well formed maps. A good rule of thumb is that
-        // the map should have a convex outside hull.
-        // these can be traceLINES as we already verified the starting box
-        vector mstart = start + 0.5 * (e.mins + e.maxs);
-        traceline(mstart, mstart + '1 0 0' * delta.x, MOVE_NORMAL, e);
-        if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
-            continue;
-        traceline(mstart, mstart - '1 0 0' * delta.x, MOVE_NORMAL, e);
-        if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
-            continue;
-        traceline(mstart, mstart + '0 1 0' * delta.y, MOVE_NORMAL, e);
-        if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
-            continue;
-        traceline(mstart, mstart - '0 1 0' * delta.y, MOVE_NORMAL, e);
-        if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
-            continue;
-        traceline(mstart, mstart + '0 0 1' * delta.z, MOVE_NORMAL, e);
-        if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
-            continue;
+       float m = e.dphitcontentsmask;
+       e.dphitcontentsmask = goodcontents | badcontents;
+
+       vector org = boundmin;
+       vector delta = boundmax - boundmin;
+
+       vector start, end;
+       start = end = org;
+       int j; // used after the loop
+       for(j = 0; j < attempts; ++j)
+       {
+               start.x = org.x + random() * delta.x;
+               start.y = org.y + random() * delta.y;
+               start.z = org.z + random() * delta.z;
+
+               // rule 1: start inside world bounds, and outside
+               // solid, and don't start from somewhere where you can
+               // fall down to evil
+               tracebox(start, e.mins, e.maxs, start - '0 0 1' * delta.z, MOVE_NORMAL, e);
+               if (trace_fraction >= 1)
+                       continue;
+               if (trace_startsolid)
+                       continue;
+               if (trace_dphitcontents & badcontents)
+                       continue;
+               if (trace_dphitq3surfaceflags & badsurfaceflags)
+                       continue;
+
+               // rule 2: if we are too high, lower the point
+               if (trace_fraction * delta.z > maxaboveground)
+                       start = trace_endpos + '0 0 1' * maxaboveground;
+               vector enddown = trace_endpos;
+
+               // rule 3: make sure we aren't outside the map. This only works
+               // for somewhat well formed maps. A good rule of thumb is that
+               // the map should have a convex outside hull.
+               // these can be traceLINES as we already verified the starting box
+               vector mstart = start + 0.5 * (e.mins + e.maxs);
+               traceline(mstart, mstart + '1 0 0' * delta.x, MOVE_NORMAL, e);
+               if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
+                       continue;
+               traceline(mstart, mstart - '1 0 0' * delta.x, MOVE_NORMAL, e);
+               if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
+                       continue;
+               traceline(mstart, mstart + '0 1 0' * delta.y, MOVE_NORMAL, e);
+               if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
+                       continue;
+               traceline(mstart, mstart - '0 1 0' * delta.y, MOVE_NORMAL, e);
+               if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
+                       continue;
+               traceline(mstart, mstart + '0 0 1' * delta.z, MOVE_NORMAL, e);
+               if (trace_fraction >= 1 || trace_dphittexturename == "common/caulk")
+                       continue;
 
                // rule 4: we must "see" some spawnpoint or item
                entity sp = NULL;
@@ -1137,40 +1165,40 @@ bool MoveToRandomLocationWithinBounds(entity e, vector boundmin, vector boundmax
                                continue;
                }
 
-        // find a random vector to "look at"
-        end.x = org.x + random() * delta.x;
-        end.y = org.y + random() * delta.y;
-        end.z = org.z + random() * delta.z;
-        end = start + normalize(end - start) * vlen(delta);
-
-        // rule 4: start TO end must not be too short
-        tracebox(start, e.mins, e.maxs, end, MOVE_NORMAL, e);
-        if(trace_startsolid)
-            continue;
-        if(trace_fraction < minviewdistance / vlen(delta))
-            continue;
-
-        // rule 5: don't want to look at sky
-        if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY)
-            continue;
-
-        // rule 6: we must not end up in trigger_hurt
-        if(tracebox_hits_trigger_hurt(start, e.mins, e.maxs, enddown))
-            continue;
-
-        break;
-    }
-
-    e.dphitcontentsmask = m;
-
-    if(j < attempts)
-    {
-        setorigin(e, start);
-        e.angles = vectoangles(end - start);
-        LOG_DEBUG("Needed ", ftos(j + 1), " attempts");
-        return true;
-    }
-    return false;
+               // find a random vector to "look at"
+               end.x = org.x + random() * delta.x;
+               end.y = org.y + random() * delta.y;
+               end.z = org.z + random() * delta.z;
+               end = start + normalize(end - start) * vlen(delta);
+
+               // rule 4: start TO end must not be too short
+               tracebox(start, e.mins, e.maxs, end, MOVE_NORMAL, e);
+               if(trace_startsolid)
+                       continue;
+               if(trace_fraction < minviewdistance / vlen(delta))
+                       continue;
+
+               // rule 5: don't want to look at sky
+               if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY)
+                       continue;
+
+               // rule 6: we must not end up in trigger_hurt
+               if(tracebox_hits_trigger_hurt(start, e.mins, e.maxs, enddown))
+                       continue;
+
+               break;
+       }
+
+       e.dphitcontentsmask = m;
+
+       if(j < attempts)
+       {
+               setorigin(e, start);
+               e.angles = vectoangles(end - start);
+               LOG_DEBUG("Needed ", ftos(j + 1), " attempts");
+               return true;
+       }
+       return false;
 }
 
 float MoveToRandomMapLocation(entity e, float goodcontents, float badcontents, float badsurfaceflags, float attempts, float maxaboveground, float minviewdistance)
@@ -1313,7 +1341,7 @@ void NextLevel()
 
        //pos = FindIntermission ();
 
-       VoteReset();
+       VoteReset(true);
 
        DumpStats(true);
 
@@ -2091,30 +2119,55 @@ void readplayerstartcvars()
 
 void readlevelcvars()
 {
+       serverflags &= ~SERVERFLAG_ALLOW_FULLBRIGHT;
        if(cvar("sv_allow_fullbright"))
                serverflags |= SERVERFLAG_ALLOW_FULLBRIGHT;
 
-       sv_ready_restart_after_countdown = cvar("sv_ready_restart_after_countdown");
+       serverflags &= ~SERVERFLAG_FORBID_PICKUPTIMER;
+       if(cvar("sv_forbid_pickuptimer"))
+               serverflags |= SERVERFLAG_FORBID_PICKUPTIMER;
 
-       warmup_stage = cvar("g_warmup");
-       warmup_limit = cvar("g_warmup_limit");
+       sv_ready_restart_after_countdown = cvar("sv_ready_restart_after_countdown");
 
        if(cvar("g_campaign"))
                warmup_stage = 0; // no warmup during campaign
+       else
+       {
+               warmup_stage = autocvar_g_warmup;
+               if (warmup_stage < 0 || warmup_stage > 1)
+                       warmup_limit = -1; // don't start until there's enough players
+               else if (warmup_stage == 1)
+               {
+                       // this code is duplicated in ReadyCount()
+                       warmup_limit = cvar("g_warmup_limit");
+                       if(warmup_limit == 0)
+                               warmup_limit = autocvar_timelimit * 60;
+               }
+       }
 
        g_pickup_respawntime_weapon = cvar("g_pickup_respawntime_weapon");
        g_pickup_respawntime_superweapon = cvar("g_pickup_respawntime_superweapon");
        g_pickup_respawntime_ammo = cvar("g_pickup_respawntime_ammo");
-       g_pickup_respawntime_short = cvar("g_pickup_respawntime_short");
-       g_pickup_respawntime_medium = cvar("g_pickup_respawntime_medium");
-       g_pickup_respawntime_long = cvar("g_pickup_respawntime_long");
+       g_pickup_respawntime_armor_small = cvar("g_pickup_respawntime_armor_small");
+       g_pickup_respawntime_armor_medium = cvar("g_pickup_respawntime_armor_medium");
+       g_pickup_respawntime_armor_big = cvar("g_pickup_respawntime_armor_big");
+       g_pickup_respawntime_armor_mega = cvar("g_pickup_respawntime_armor_mega");
+       g_pickup_respawntime_health_small = cvar("g_pickup_respawntime_health_small");
+       g_pickup_respawntime_health_medium = cvar("g_pickup_respawntime_health_medium");
+       g_pickup_respawntime_health_big = cvar("g_pickup_respawntime_health_big");
+       g_pickup_respawntime_health_mega = cvar("g_pickup_respawntime_health_mega");
        g_pickup_respawntime_powerup = cvar("g_pickup_respawntime_powerup");
        g_pickup_respawntimejitter_weapon = cvar("g_pickup_respawntimejitter_weapon");
        g_pickup_respawntimejitter_superweapon = cvar("g_pickup_respawntimejitter_superweapon");
        g_pickup_respawntimejitter_ammo = cvar("g_pickup_respawntimejitter_ammo");
-       g_pickup_respawntimejitter_short = cvar("g_pickup_respawntimejitter_short");
-       g_pickup_respawntimejitter_medium = cvar("g_pickup_respawntimejitter_medium");
-       g_pickup_respawntimejitter_long = cvar("g_pickup_respawntimejitter_long");
+       g_pickup_respawntimejitter_armor_small = cvar("g_pickup_respawntimejitter_armor_small");
+       g_pickup_respawntimejitter_armor_medium = cvar("g_pickup_respawntimejitter_armor_medium");
+       g_pickup_respawntimejitter_armor_big = cvar("g_pickup_respawntimejitter_armor_big");
+       g_pickup_respawntimejitter_armor_mega = cvar("g_pickup_respawntimejitter_armor_mega");
+       g_pickup_respawntimejitter_health_small = cvar("g_pickup_respawntimejitter_health_small");
+       g_pickup_respawntimejitter_health_medium = cvar("g_pickup_respawntimejitter_health_medium");
+       g_pickup_respawntimejitter_health_big = cvar("g_pickup_respawntimejitter_health_big");
+       g_pickup_respawntimejitter_health_mega = cvar("g_pickup_respawntimejitter_health_mega");
        g_pickup_respawntimejitter_powerup = cvar("g_pickup_respawntimejitter_powerup");
 
        g_pickup_shells = cvar("g_pickup_shells");
@@ -2158,11 +2211,11 @@ void readlevelcvars()
        g_pickup_ammo_anyway = cvar("g_pickup_ammo_anyway");
        g_pickup_weapons_anyway = cvar("g_pickup_weapons_anyway");
 
-    g_weapon_stay = cvar(strcat("g_", GetGametype(), "_weapon_stay"));
-    if(!g_weapon_stay)
-        g_weapon_stay = cvar("g_weapon_stay");
+       g_weapon_stay = cvar(strcat("g_", GetGametype(), "_weapon_stay"));
+       if(!g_weapon_stay)
+               g_weapon_stay = cvar("g_weapon_stay");
 
-    MUTATOR_CALLHOOK(ReadLevelCvars);
+       MUTATOR_CALLHOOK(ReadLevelCvars);
 
        if (!warmup_stage && !autocvar_g_campaign)
                game_starttime = time + cvar("g_start_delay");
@@ -2174,73 +2227,73 @@ void readlevelcvars()
 
 void InitializeEntity(entity e, void(entity this) func, int order)
 {
-    entity prev, cur;
-
-    if (!e || e.initialize_entity)
-    {
-        // make a proxy initializer entity
-        entity e_old = e;
-        e = new(initialize_entity);
-        e.enemy = e_old;
-    }
-
-    e.initialize_entity = func;
-    e.initialize_entity_order = order;
-
-    cur = initialize_entity_first;
-    prev = NULL;
-    for (;;)
-    {
-        if (!cur || cur.initialize_entity_order > order)
-        {
-            // insert between prev and cur
-            if (prev)
-                prev.initialize_entity_next = e;
-            else
-                initialize_entity_first = e;
-            e.initialize_entity_next = cur;
-            return;
-        }
-        prev = cur;
-        cur = cur.initialize_entity_next;
-    }
+       entity prev, cur;
+
+       if (!e || e.initialize_entity)
+       {
+               // make a proxy initializer entity
+               entity e_old = e;
+               e = new(initialize_entity);
+               e.enemy = e_old;
+       }
+
+       e.initialize_entity = func;
+       e.initialize_entity_order = order;
+
+       cur = initialize_entity_first;
+       prev = NULL;
+       for (;;)
+       {
+               if (!cur || cur.initialize_entity_order > order)
+               {
+                       // insert between prev and cur
+                       if (prev)
+                               prev.initialize_entity_next = e;
+                       else
+                               initialize_entity_first = e;
+                       e.initialize_entity_next = cur;
+                       return;
+               }
+               prev = cur;
+               cur = cur.initialize_entity_next;
+       }
 }
 void InitializeEntitiesRun()
 {
-    entity startoflist = initialize_entity_first;
-    initialize_entity_first = NULL;
-    delete_fn = remove_except_protected;
-    for (entity e = startoflist; e; e = e.initialize_entity_next)
-    {
+       entity startoflist = initialize_entity_first;
+       initialize_entity_first = NULL;
+       delete_fn = remove_except_protected;
+       for (entity e = startoflist; e; e = e.initialize_entity_next)
+       {
                e.remove_except_protected_forbidden = 1;
-    }
-    for (entity e = startoflist; e; )
-    {
+       }
+       for (entity e = startoflist; e; )
+       {
                e.remove_except_protected_forbidden = 0;
-        e.initialize_entity_order = 0;
-       entity next = e.initialize_entity_next;
-        e.initialize_entity_next = NULL;
-        var void(entity this) func = e.initialize_entity;
-        e.initialize_entity = func_null;
-        if (e.classname == "initialize_entity")
-        {
-            entity wrappee = e.enemy;
-            builtin_remove(e);
-            e = wrappee;
-        }
-        //dprint("Delayed initialization: ", e.classname, "\n");
-        if (func)
-        {
-               func(e);
-        }
-        else
-        {
-            eprint(e);
-            backtrace(strcat("Null function in: ", e.classname, "\n"));
-        }
-        e = next;
-    }
-    delete_fn = remove_unsafely;
+               e.initialize_entity_order = 0;
+               entity next = e.initialize_entity_next;
+               e.initialize_entity_next = NULL;
+               var void(entity this) func = e.initialize_entity;
+               e.initialize_entity = func_null;
+               if (e.classname == "initialize_entity")
+               {
+                       entity wrappee = e.enemy;
+                       builtin_remove(e);
+                       e = wrappee;
+               }
+               //dprint("Delayed initialization: ", e.classname, "\n");
+               if (func)
+               {
+                       func(e);
+               }
+               else
+               {
+                       eprint(e);
+                       backtrace(strcat("Null function in: ", e.classname, "\n"));
+               }
+               e = next;
+       }
+       delete_fn = remove_unsafely;
 }
 
 // deferred dropping
@@ -2253,11 +2306,20 @@ void DropToFloor_Handler(entity this)
                return;
        }
 
-       vector end = this.origin - '0 0 256';
+       vector end = this.origin;
+       if (autocvar_sv_mapformat_is_quake3)
+               end.z -= 4096;
+       else if (autocvar_sv_mapformat_is_quake2)
+               end.z -= 128;
+       else
+               end.z -= 256; // Quake, QuakeWorld
 
        // NOTE: SV_NudgeOutOfSolid is used in the engine here
        if(autocvar_sv_gameplayfix_droptofloorstartsolid_nudgetocorrect)
+       {
+               _Movetype_UnstickEntity(this);
                move_out_of_solid(this);
+       }
 
        tracebox(this.origin, this.mins, this.maxs, end, MOVE_NORMAL, this);
 
@@ -2281,7 +2343,10 @@ void DropToFloor_Handler(entity this)
                        LOG_DEBUGF("DropToFloor_Handler: %v fixed badly placed entity", this.origin);
                        setorigin(this, trace_endpos);
                        if(autocvar_sv_gameplayfix_droptofloorstartsolid_nudgetocorrect)
+                       {
+                               _Movetype_UnstickEntity(this);
                                move_out_of_solid(this);
+                       }
                        SET_ONGROUND(this);
                        this.groundentity = trace_ent;
                        // if support is destroyed, keep suspended (gross hack for floating items in various maps)