]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/server/world.qc
Merge branch 'bones_was_here/805' into 'master'
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / world.qc
index c37998545ff8c7dabd7d87152d06dbd2ef313195..41c9705da06f510026176c49d70f787505ad95fa 100644 (file)
@@ -1,49 +1,51 @@
 #include "world.qh"
 
-#include "anticheat.qh"
-#include "antilag.qh"
-#include "bot/api.qh"
-#include "campaign.qh"
-#include "cheats.qh"
-#include "client.qh"
-#include "command/common.qh"
-#include "command/getreplies.qh"
-#include "command/sv_cmd.qh"
-#include "command/vote.qh"
-#include "hook.qh"
-#include <server/gamelog.qh>
-#include <server/damage.qh>
-#include "ipban.qh"
-#include "mapvoting.qh"
-#include <server/mutators/_mod.qh>
-#include "race.qh"
-#include "scores.qh"
-#include "scores_rules.qh"
-#include "spawnpoints.qh"
-#include "teamplay.qh"
-#include "weapons/weaponstats.qh"
-#include <server/weapons/common.qh>
-#include "../common/constants.qh"
-#include <common/net_linked.qh>
-#include "../common/deathtypes/all.qh"
+#include <common/constants.qh>
+#include <common/deathtypes/all.qh>
 #include <common/gamemodes/_mod.qh>
-#include "../common/gamemodes/sv_rules.qh"
-#include "../common/mapinfo.qh"
-#include "../common/monsters/_mod.qh"
-#include "../common/monsters/sv_monsters.qh"
-#include "../common/vehicles/all.qh"
-#include "../common/notifications/all.qh"
-#include "../common/physics/player.qh"
-#include "../common/playerstats.qh"
-#include "../common/stats.qh"
-#include "../common/teams.qh"
+#include <common/gamemodes/sv_rules.qh>
+#include <common/items/_mod.qh>
+#include <common/mapinfo.qh>
+#include <common/mapobjects/target/music.qh>
+#include <common/mapobjects/trigger/hurt.qh>
+#include <common/mapobjects/trigger/secret.qh>
 #include <common/mapobjects/triggers.qh>
-#include "../common/mapobjects/trigger/secret.qh"
-#include "../common/mapobjects/target/music.qh"
-#include "../common/util.qh"
-#include "../common/items/_mod.qh"
+#include <common/monsters/_mod.qh>
+#include <common/monsters/sv_monsters.qh>
+#include <common/net_linked.qh>
+#include <common/notifications/all.qh>
+#include <common/physics/player.qh>
+#include <common/playerstats.qh>
+#include <common/state.qh>
+#include <common/stats.qh>
+#include <common/teams.qh>
+#include <common/util.qh>
+#include <common/vehicles/all.qh>
 #include <common/weapons/_all.qh>
-#include "../common/state.qh"
+#include <server/anticheat.qh>
+#include <server/antilag.qh>
+#include <server/bot/api.qh>
+#include <server/campaign.qh>
+#include <server/cheats.qh>
+#include <server/client.qh>
+#include <server/command/common.qh>
+#include <server/command/getreplies.qh>
+#include <server/command/sv_cmd.qh>
+#include <server/command/vote.qh>
+#include <server/damage.qh>
+#include <server/gamelog.qh>
+#include <server/hook.qh>
+#include <server/ipban.qh>
+#include <server/items/items.qh>
+#include <server/main.qh>
+#include <server/mapvoting.qh>
+#include <server/mutators/_mod.qh>
+#include <server/race.qh>
+#include <server/scores.qh>
+#include <server/scores_rules.qh>
+#include <server/spawnpoints.qh>
+#include <server/teamplay.qh>
+#include <server/weapons/weaponstats.qh>
 
 const float LATENCY_THINKRATE = 10;
 .float latency_sum;
@@ -65,7 +67,7 @@ void PingPLReport_Think(entity this)
        {
                WriteHeader(MSG_BROADCAST, TE_CSQC_PINGPLREPORT);
                WriteByte(MSG_BROADCAST, this.cnt);
-               WriteShort(MSG_BROADCAST, bound(1, CS(e).ping, 65535));
+               WriteShort(MSG_BROADCAST, bound(1, rint(CS(e).ping), 32767));
                WriteByte(MSG_BROADCAST, min(ceil(CS(e).ping_packetloss * 255), 255));
                WriteByte(MSG_BROADCAST, min(ceil(CS(e).ping_movementloss * 255), 255));
 
@@ -96,8 +98,6 @@ void PingPLReport_Spawn()
 }
 
 const float SPAWNFLAG_NO_WAYPOINTS_FOR_ITEMS = 1;
-string redirection_target;
-float world_initialized;
 
 void SetDefaultAlpha()
 {
@@ -168,6 +168,7 @@ void cvar_changes_init()
 #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 BADCVAR(p) if(k == p) continue
+#define BADVALUE(p, val) if (k == p && 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
@@ -276,7 +277,6 @@ void cvar_changes_init()
                BADCVAR("g_duel_not_dm_maps");
                BADCVAR("g_freezetag");
                BADCVAR("g_freezetag_teams");
-               BADCVAR("g_invasion_teams");
                BADCVAR("g_invasion_type");
                BADCVAR("g_jailbreak");
                BADCVAR("g_jailbreak_teams");
@@ -334,7 +334,6 @@ void cvar_changes_init()
                // does nothing gameplay relevant
                BADCVAR("captureleadlimit_override");
                BADCVAR("condump_stripcolors");
-               BADCVAR("gameversion");
                BADCVAR("fs_gamedir");
                BADCVAR("g_allow_oldvortexbeam");
                BADCVAR("g_balance_kill_delay");
@@ -345,10 +344,12 @@ void cvar_changes_init()
                BADCVAR("g_chatsounds");
                BADCVAR("g_ca_point_leadlimit");
                BADCVAR("g_ca_point_limit");
+               BADCVAR("g_ca_spectate_enemies");
                BADCVAR("g_ctf_captimerecord_always");
                BADCVAR("g_ctf_flag_glowtrails");
                BADCVAR("g_ctf_dynamiclights");
                BADCVAR("g_ctf_flag_pickup_verbosename");
+               BADCVAR("g_ctf_flagcarrier_auto_helpme_damage");
                BADPRESUFFIX("g_ctf_flag_", "_model");
                BADPRESUFFIX("g_ctf_flag_", "_skin");
                BADCVAR("g_domination_point_leadlimit");
@@ -384,6 +385,7 @@ void cvar_changes_init()
                BADCVAR("sv_minigames");
                BADCVAR("sv_namechangetimer");
                BADCVAR("sv_precacheplayermodels");
+               BADCVAR("sv_qcphysics");
                BADCVAR("sv_radio");
                BADCVAR("sv_stepheight");
                BADCVAR("sv_timeout");
@@ -391,7 +393,7 @@ void cvar_changes_init()
                BADCVAR("w_prop_interval");
                BADPREFIX("chat_");
                BADPREFIX("crypto_");
-               BADPREFIX("gameversion_");
+               BADPREFIX("gameversion");
                BADPREFIX("g_chat_");
                BADPREFIX("g_ctf_captimerecord_");
                BADPREFIX("g_hats_");
@@ -404,12 +406,13 @@ void cvar_changes_init()
                BADPREFIX("skill_");
                BADPREFIX("sv_allow_");
                BADPREFIX("sv_cullentities_");
-               BADPREFIX("sv_maxidle_");
+               BADPREFIX("sv_maxidle");
                BADPREFIX("sv_minigames_");
                BADPREFIX("sv_radio_");
                BADPREFIX("sv_timeout_");
                BADPREFIX("sv_vote_");
                BADPREFIX("timelimit_");
+               BADPRESUFFIX("g_", "_round_timelimit");
 
                // allowed changes to server admins (please sync this to server.cfg)
                // vi commands:
@@ -439,6 +442,7 @@ void cvar_changes_init()
                BADCVAR("g_ctf_leaderboard");
                BADCVAR("g_domination_point_limit");
                BADCVAR("g_domination_teams_override");
+               BADCVAR("g_freezetag_revive_spawnshield");
                BADCVAR("g_freezetag_teams_override");
                BADCVAR("g_friendlyfire");
                BADCVAR("g_fullbrightitems");
@@ -454,10 +458,11 @@ void cvar_changes_init()
                BADCVAR("g_physics_clientselect");
                BADCVAR("g_pinata");
                BADCVAR("g_powerups");
+               BADCVAR("g_powerups_drop_ondeath");
                BADCVAR("g_player_brightness");
                BADCVAR("g_rocket_flying");
                BADCVAR("g_rocket_flying_disabledelays");
-               BADCVAR("g_spawnshieldtime");
+               BADPREFIX("g_spawnshield");
                BADCVAR("g_start_delay");
                BADCVAR("g_superspectate");
                BADCVAR("g_tdm_teams_override");
@@ -483,11 +488,11 @@ void cvar_changes_init()
                BADCVAR("sv_defaultplayercolors");
                BADCVAR("sv_defaultplayermodel");
                BADCVAR("sv_defaultplayerskin");
-               BADCVAR("sv_maxidle");
                BADCVAR("sv_maxrate");
                BADCVAR("sv_motd");
                BADCVAR("sv_public");
-               BADCVAR("sv_ready_restart");
+               BADCVAR("sv_showfps");
+               BADCVAR("sv_showspectators");
                BADCVAR("sv_status_privacy");
                BADCVAR("sv_taunt");
                BADCVAR("sv_vote_call");
@@ -497,6 +502,8 @@ void cvar_changes_init()
                BADCVAR("sv_vote_master_commands");
                BADCVAR("sv_vote_master_password");
                BADCVAR("sv_vote_simple_majority_factor");
+               BADVALUE("sys_ticrate", "0.0166667");
+               BADVALUE("sys_ticrate", "0.0333333");
                BADCVAR("teamplay_mode");
                BADCVAR("timelimit_override");
                BADPREFIX("g_warmup_");
@@ -510,22 +517,10 @@ void cvar_changes_init()
                BADCVAR("g_grappling_hook");
                BADCVAR("g_jetpack");
 
-               // temporary for testing
-               // TODO remove before 0.8.3 release
-               BADCVAR("g_ca_weaponarena");
-               BADCVAR("g_freezetag_weaponarena");
-               BADCVAR("g_lms_weaponarena");
-               BADCVAR("g_ctf_stalemate_time");
-
-               if(cvar_string("g_mod_balance") == "Testing")
-               {
-                       // (temporary) while using the Testing balance, any weapon balance cvars are allowed to be changed
-                       BADPREFIX("g_balance_");
-               }
-
 #undef BADPRESUFFIX
 #undef BADPREFIX
 #undef BADCVAR
+#undef BADVALUE
 
                if(pureadding)
                {
@@ -588,7 +583,7 @@ spawnfunc(__init_dedicated_server)
 
        delete_fn = remove_unsafely;
 
-       entity e = spawn();
+       entity e = new(GotoFirstMap);
        setthink(e, GotoFirstMap);
        e.nextthink = time; // this is usually 1 at this point
 
@@ -627,7 +622,7 @@ STATIC_INIT_EARLY(maxclients)
        }
 }
 
-void default_delayedinit(entity this)
+void GameplayMode_DelayedInit(entity this)
 {
        if(!scores_initialized)
                ScoreRules_generic();
@@ -677,15 +672,23 @@ void InitGameplayMode()
        cache_mutatormsg = strzone("");
        cache_lastmutatormsg = strzone("");
 
-       InitializeEntity(NULL, default_delayedinit, INITPRIO_GAMETYPE_FALLBACK);
+       InitializeEntity(NULL, GameplayMode_DelayedInit, INITPRIO_GAMETYPE_FALLBACK);
 }
 
-void Map_MarkAsRecent(string m);
-float world_already_spawned;
+bool world_already_spawned;
 spawnfunc(worldspawn)
 {
        server_is_dedicated = boolean(stof(cvar_defstring("is_dedicated")));
 
+       if (autocvar_sv_termsofservice_url && autocvar_sv_termsofservice_url != "")
+       {
+               strcpy(sv_termsofservice_url_escaped, strreplace(":", "|", autocvar_sv_termsofservice_url));
+       }
+       else
+       {
+               strcpy(sv_termsofservice_url_escaped, "INVALID");
+       }
+
        bool wantrestart = false;
        {
                if (!server_is_dedicated)
@@ -755,6 +758,9 @@ spawnfunc(worldspawn)
 
        cvar_changes_init(); // do this very early now so it REALLY matches the server config
 
+       // default to RACE_RECORD, can be overwritten by gamemodes
+       record_type = RACE_RECORD;
+
        // needs to be done so early because of the constants they create
        static_init();
 
@@ -805,16 +811,15 @@ spawnfunc(worldspawn)
 
        if(autocvar_g_campaign)
                CampaignPreInit();
+       else
+               PlayerStats_GameReport_Init(); // we need this to be initiated before InitGameplayMode
 
        Map_MarkAsRecent(mapname);
 
-       PlayerStats_GameReport_Init(); // we need this to be initiated before InitGameplayMode
-
        InitGameplayMode();
        static_init_late();
        static_init_precache();
        readlevelcvars();
-       GrappleHookInit();
 
        GameRules_limit_fallbacks();
 
@@ -829,51 +834,15 @@ spawnfunc(worldspawn)
 
        WaypointSprite_Init();
 
-       GameLogInit(); // prepare everything
        // NOTE for matchid:
        // changing the logic generating it is okay. But:
        // it HAS to stay <= 64 chars
        // character set: ASCII 33-126 without the following characters: : ; ' " \ $
-       if(autocvar_sv_eventlog)
-       {
-               string s = sprintf("%s.%s.%06d", itos(autocvar_sv_eventlog_files_counter), strftime(false, "%s"), floor(random() * 1000000));
-               matchid = strzone(s);
+       // strftime(false, "%s") isn't reliable, see strftime_s description
+       matchid = strzone(sprintf("%d.%s.%06d", autocvar_sv_eventlog_files_counter, strftime_s(), random() * 1000000));
 
-               GameLogEcho(strcat(":gamestart:", GetGametype(), "_", GetMapname(), ":", s));
-               s = ":gameinfo:mutators:LIST";
-
-               MUTATOR_CALLHOOK(BuildMutatorsString, s);
-               s = M_ARGV(0, string);
-
-               // initialiation stuff, not good in the mutator system
-               if(!autocvar_g_use_ammunition)
-                       s = strcat(s, ":no_use_ammunition");
-
-               // initialiation stuff, not good in the mutator system
-               if(autocvar_g_pickup_items == 0)
-                       s = strcat(s, ":no_pickup_items");
-               if(autocvar_g_pickup_items > 0)
-                       s = strcat(s, ":pickup_items");
-
-               // initialiation stuff, not good in the mutator system
-               if(autocvar_g_weaponarena != "0")
-                       s = strcat(s, ":", autocvar_g_weaponarena, " arena");
-
-               // TODO to mutator system
-               if(autocvar_g_norecoil)
-                       s = strcat(s, ":norecoil");
-
-               // TODO to mutator system
-               if(autocvar_g_powerups == 0)
-                       s = strcat(s, ":no_powerups");
-               if(autocvar_g_powerups > 0)
-                       s = strcat(s, ":powerups");
-
-               GameLogEcho(s);
-               GameLogEcho(":gameinfo:end");
-       }
-       else
-               matchid = strzone(ftos(random()));
+       if(autocvar_sv_eventlog)
+               GameLogInit(); // requires matchid to be set
 
        cvar_set("nextmap", "");
 
@@ -887,11 +856,8 @@ spawnfunc(worldspawn)
        MapInfo_Enumerate();
        MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags(), 1);
 
-       if(fexists(strcat("scripts/", mapname, ".arena")))
-               cvar_settemp("sv_q3acompat_machineshotgunswap", "1");
-
-       if(fexists(strcat("scripts/", mapname, ".defi")))
-               cvar_settemp("sv_q3defragcompat", "1");
+       q3compat = BITSET(q3compat, Q3COMPAT_ARENA, fexists(strcat("scripts/", mapname, ".arena")));
+       q3compat = BITSET(q3compat, Q3COMPAT_DEFI, fexists(strcat("scripts/", mapname, ".defi")));
 
        if(whichpack(strcat("maps/", mapname, ".cfg")) != "")
        {
@@ -943,12 +909,18 @@ spawnfunc(worldspawn)
        maplist_reply = strzone(getmaplist());
        lsmaps_reply = strzone(getlsmaps());
        monsterlist_reply = strzone(getmonsterlist());
+       bool records_available = false;
        for(int i = 0; i < 10; ++i)
        {
                string s = getrecords(i);
-               if (s)
+               if (s != "")
+               {
                        records_reply[i] = strzone(s);
+                       records_available = true;
+               }
        }
+       if (!records_available)
+               records_reply[0] = "No records available for the current game mode.\n";
        ladder_reply = strzone(getladder());
        rankings_reply = strzone(getrankings());
 
@@ -964,7 +936,7 @@ spawnfunc(worldspawn)
        // fill sv_curl_serverpackages from .serverpackage files
        if (autocvar_sv_curl_serverpackages_auto)
        {
-               string s = "csprogs-" WATERMARK ".txt";
+               string s = "csprogs-" WATERMARK ".dat";
                // remove automatically managed files from the list to prevent duplicates
                for (int i = 0, n = tokenize_console(cvar_string("sv_curl_serverpackages")); i < n; ++i)
                {
@@ -1020,413 +992,139 @@ spawnfunc(light)
        delete(this);
 }
 
-string GetGametype()
-{
-       return MapInfo_Type_ToString(MapInfo_LoadedGametype);
-}
-
-string GetMapname()
-{
-       return mapname;
-}
-
-float Map_Count, Map_Current;
-string Map_Current_Name;
-
-// NOTE: this now expects the map list to be already tokenized and the count in Map_Count
-int GetMaplistPosition()
-{
-       string map = GetMapname();
-       int idx = autocvar_g_maplist_index;
-
-       if(idx >= 0)
-       {
-               if(idx < Map_Count)
-               {
-                       if(map == argv(idx))
-                       {
-                               return idx;
-                       }
-               }
-       }
-
-       for(int pos = 0; pos < Map_Count; ++pos)
-       {
-               if(map == argv(pos))
-                       return pos;
-       }
-
-       // resume normal maplist rotation if current map is not in g_maplist
-       return idx;
-}
-
-bool MapHasRightSize(string map)
+bool MoveToRandomLocationWithinBounds(entity e, vector boundmin, vector boundmax, float goodcontents, float badcontents, float badsurfaceflags, int attempts, float maxaboveground, float minviewdistance, bool frompos)
 {
-       int minplayers = max(0, floor(autocvar_minplayers));
-       if (teamplay)
-               minplayers = max(0, floor(autocvar_minplayers_per_team) * AvailableTeams());
-       if (autocvar_g_maplist_check_waypoints
-               && (currentbots || autocvar_bot_number || player_count < minplayers))
-       {
-               string checkwp_msg = strcat("checkwp ", map);
-               if(!fexists(strcat("maps/", map, ".waypoints")))
+    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;
+               if(frompos)
                {
-                       LOG_TRACE(checkwp_msg, ": no waypoints");
-                       return false;
+                       if((traceline(mstart, e.origin, MOVE_NORMAL, e), trace_fraction) >= 1)
+                               sp = e;
                }
-               LOG_TRACE(checkwp_msg, ": has waypoints");
-       }
-
-       if(autocvar_g_maplist_ignore_sizes)
-               return true;
-
-       // open map size restriction file
-       string opensize_msg = strcat("opensize ", map);
-       float fh = fopen(strcat("maps/", map, ".sizes"), FILE_READ);
-       int player_limit = ((autocvar_g_maplist_sizes_count_maxplayers) ? GetPlayerLimit() : 0);
-       int pcount = ((player_limit > 0) ? min(player_count, player_limit) : player_count); // bind it to the player limit so that forced spectators don't influence the limits
-       if(!autocvar_g_maplist_sizes_count_bots)
-               pcount -= currentbots;
-       if(fh >= 0)
-       {
-               opensize_msg = strcat(opensize_msg, ": ok, ");
-               int mapmin = stoi(fgets(fh));
-               int mapmax = stoi(fgets(fh));
-               fclose(fh);
-               if(pcount < mapmin)
+               if(!sp)
                {
-                       LOG_TRACE(opensize_msg, "not enough");
-                       return false;
+                       IL_EACH(g_spawnpoints, checkpvs(mstart, it),
+                       {
+                               if((traceline(mstart, it.origin, MOVE_NORMAL, e), trace_fraction) >= 1)
+                               {
+                                       sp = it;
+                                       break;
+                               }
+                       });
                }
-               if(mapmax && pcount > mapmax)
+               if(!sp)
                {
-                       LOG_TRACE(opensize_msg, "too many");
-                       return false;
-               }
-               LOG_TRACE(opensize_msg, "right size");
-               return true;
-       }
-       LOG_TRACE(opensize_msg, ": not found");
-       return true;
-}
-
-string Map_Filename(float position)
-{
-       return strcat("maps/", argv(position), ".bsp");
-}
-
-void Map_MarkAsRecent(string m)
-{
-       cvar_set("g_maplist_mostrecent", strwords(cons(m, autocvar_g_maplist_mostrecent), max(0, autocvar_g_maplist_mostrecent_count)));
-}
-
-float Map_IsRecent(string m)
-{
-       return strhasword(autocvar_g_maplist_mostrecent, m);
-}
-
-float Map_Check(float position, float pass)
-{
-       string filename;
-       string map_next;
-       map_next = argv(position);
-       if(pass <= 1)
-       {
-               if(Map_IsRecent(map_next))
-                       return 0;
-       }
-       filename = Map_Filename(position);
-       if(MapInfo_CheckMap(map_next))
-       {
-               if(pass == 2)
-                       return 1;
-               if(MapHasRightSize(map_next))
-                       return 1;
-               return 0;
-       }
-       else
-               LOG_DEBUG( "Couldn't select '", filename, "'..." );
-
-       return 0;
-}
-
-void Map_Goto_SetStr(string nextmapname)
-{
-       if(getmapname_stored != "")
-               strunzone(getmapname_stored);
-       if(nextmapname == "")
-               getmapname_stored = "";
-       else
-               getmapname_stored = strzone(nextmapname);
-}
-
-void Map_Goto_SetFloat(float position)
-{
-       cvar_set("g_maplist_index", ftos(position));
-       Map_Goto_SetStr(argv(position));
-}
-
-void Map_Goto(float reinit)
-{
-       MapInfo_LoadMap(getmapname_stored, reinit);
-}
-
-// return codes of map selectors:
-//   -1 = temporary failure (that is, try some method that is guaranteed to succeed)
-//   -2 = permanent failure
-float MaplistMethod_Iterate() // usual method
-{
-       float pass, i;
+                       int items_checked = 0;
+                       IL_EACH(g_items, checkpvs(mstart, it),
+                       {
+                               if((traceline(mstart, it.origin + (it.mins + it.maxs) * 0.5, MOVE_NORMAL, e), trace_fraction) >= 1)
+                               {
+                                       sp = it;
+                                       break;
+                               }
 
-       LOG_TRACE("Trying MaplistMethod_Iterate");
+                               ++items_checked;
+                               if(items_checked >= attempts)
+                                       break; // sanity
+                       });
 
-       for(pass = 1; pass <= 2; ++pass)
-       {
-               for(i = 1; i < Map_Count; ++i)
-               {
-                       float mapindex;
-                       mapindex = (i + Map_Current) % Map_Count;
-                       if(Map_Check(mapindex, pass))
-                               return mapindex;
+                       if(!sp)
+                               continue;
                }
-       }
-       return -1;
-}
 
-float MaplistMethod_Repeat() // fallback method
-{
-       LOG_TRACE("Trying MaplistMethod_Repeat");
+        // 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);
 
-       if(Map_Check(Map_Current, 2))
-               return Map_Current;
-       return -2;
-}
+        // 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;
 
-float MaplistMethod_Random() // random map selection
-{
-       float i, imax;
+        // rule 5: don't want to look at sky
+        if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY)
+            continue;
 
-       LOG_TRACE("Trying MaplistMethod_Random");
-
-       imax = 42;
-
-       for(i = 0; i <= imax; ++i)
-       {
-               float mapindex;
-               mapindex = (Map_Current + floor(random() * (Map_Count - 1) + 1)) % Map_Count; // any OTHER map
-               if(Map_Check(mapindex, 1))
-                       return mapindex;
-       }
-       return -1;
-}
-
-float MaplistMethod_Shuffle(float exponent) // more clever shuffling
-// the exponent sets a bias on the map selection:
-// the higher the exponent, the less likely "shortly repeated" same maps are
-{
-       float i, j, imax, insertpos;
-
-       LOG_TRACE("Trying MaplistMethod_Shuffle");
-
-       imax = 42;
-
-       for(i = 0; i <= imax; ++i)
-       {
-               string newlist;
-
-               // now reinsert this at another position
-               insertpos = (random() ** (1 / exponent));       // ]0, 1]
-               insertpos = insertpos * (Map_Count - 1);       // ]0, Map_Count - 1]
-               insertpos = ceil(insertpos) + 1;               // {2, 3, 4, ..., Map_Count}
-               LOG_TRACE("SHUFFLE: insert pos = ", ftos(insertpos));
-
-               // insert the current map there
-               newlist = "";
-               for(j = 1; j < insertpos; ++j)                 // i == 1: no loop, will be inserted as first; however, i == 1 has been excluded above
-                       newlist = strcat(newlist, " ", argv(j));
-               newlist = strcat(newlist, " ", argv(0));       // now insert the just selected map
-               for(j = insertpos; j < Map_Count; ++j)         // i == Map_Count: no loop, has just been inserted as last
-                       newlist = strcat(newlist, " ", argv(j));
-               newlist = substring(newlist, 1, strlen(newlist) - 1);
-               cvar_set("g_maplist", newlist);
-               Map_Count = tokenizebyseparator(autocvar_g_maplist, " ");
-
-               // NOTE: the selected map has just been inserted at (insertpos-1)th position
-               Map_Current = insertpos - 1; // this is not really valid, but this way the fallback has a chance of working
-               if(Map_Check(Map_Current, 1))
-                       return Map_Current;
-       }
-       return -1;
-}
-
-void Maplist_Init()
-{
-       float i = Map_Count = 0;
-       if(autocvar_g_maplist != "")
-       {
-               Map_Count = tokenizebyseparator(autocvar_g_maplist, " ");
-               for (i = 0; i < Map_Count; ++i)
-               {
-                       if (Map_Check(i, 2))
-                               break;
-               }
-       }
+        // rule 6: we must not end up in trigger_hurt
+        if(tracebox_hits_trigger_hurt(start, e.mins, e.maxs, enddown))
+            continue;
 
-       if (i == Map_Count)
-       {
-               bprint( "Maplist contains no usable maps!  Resetting it to default map list.\n" );
-               cvar_set("g_maplist", MapInfo_ListAllAllowedMaps(MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags() | MAPINFO_FLAG_NOAUTOMAPLIST));
-               if(autocvar_g_maplist_shuffle)
-                       ShuffleMaplist();
-               if(!server_is_dedicated)
-                       localcmd("\nmenu_cmd sync\n");
-               Map_Count = tokenizebyseparator(autocvar_g_maplist, " ");
-       }
-       if(Map_Count == 0)
-               error("empty maplist, cannot select a new map");
-       Map_Current = bound(0, GetMaplistPosition(), Map_Count - 1);
-
-       strcpy(Map_Current_Name, argv(Map_Current)); // will be automatically freed on exit thanks to DP
-       // this may or may not be correct, but who cares, in the worst case a map
-       // isn't chosen in the first pass that should have been
-}
-
-string GetNextMap()
-{
-       Maplist_Init();
-       float nextMap = -1;
-
-       if(nextMap == -1)
-               if(autocvar_g_maplist_shuffle > 0)
-                       nextMap = MaplistMethod_Shuffle(autocvar_g_maplist_shuffle + 1);
-
-       if(nextMap == -1)
-               if(autocvar_g_maplist_selectrandom)
-                       nextMap = MaplistMethod_Random();
-
-       if(nextMap == -1)
-               nextMap = MaplistMethod_Iterate();
-
-       if(nextMap == -1)
-               nextMap = MaplistMethod_Repeat();
-
-       if(nextMap >= 0)
-       {
-               Map_Goto_SetFloat(nextMap);
-               return getmapname_stored;
-       }
-
-       return "";
-}
-
-float DoNextMapOverride(float reinit)
-{
-       if(autocvar_g_campaign)
-       {
-               CampaignPostIntermission();
-               alreadychangedlevel = true;
-               return true;
-       }
-       if(autocvar_quit_when_empty)
-       {
-               if(player_count <= currentbots)
-               {
-                       localcmd("quit\n");
-                       alreadychangedlevel = true;
-                       return true;
-               }
-       }
-       if(autocvar_quit_and_redirect != "")
-       {
-               redirection_target = strzone(autocvar_quit_and_redirect);
-               alreadychangedlevel = true;
-               return true;
-       }
-       if (!reinit && autocvar_samelevel) // if samelevel is set, stay on same level
-       {
-               localcmd("restart\n");
-               alreadychangedlevel = true;
-               return true;
-       }
-       if(autocvar_nextmap != "")
-       {
-               string m;
-               m = GameTypeVote_MapInfo_FixName(autocvar_nextmap);
-               cvar_set("nextmap",m);
-
-               if(!m || gametypevote)
-                       return false;
-               if(autocvar_sv_vote_gametype)
-               {
-                       Map_Goto_SetStr(m);
-                       return false;
-               }
-
-               if(MapInfo_CheckMap(m))
-               {
-                       Map_Goto_SetStr(m);
-                       Map_Goto(reinit);
-                       alreadychangedlevel = true;
-                       return true;
-               }
-       }
-       if(!reinit && autocvar_lastlevel)
-       {
-               cvar_settemp_restore();
-               localcmd("set lastlevel 0\ntogglemenu 1\n");
-               alreadychangedlevel = true;
-               return true;
-       }
-       return false;
-}
+        break;
+    }
 
-void GotoNextMap(float reinit)
-{
-       //string nextmap;
-       //float n, nummaps;
-       //string s;
-       if (alreadychangedlevel)
-               return;
-       alreadychangedlevel = true;
+    e.dphitcontentsmask = m;
 
-       string nextMap = GetNextMap();
-       if(nextMap == "")
-               error("Everything is broken - cannot find a next map. Please report this to the developers.");
-       Map_Goto(reinit);
+    if(j < attempts)
+    {
+        setorigin(e, start);
+        e.angles = vectoangles(end - start);
+        LOG_DEBUG("Needed ", ftos(j + 1), " attempts");
+        return true;
+    }
+    return false;
 }
 
-
-/*
-============
-IntermissionThink
-
-When the player presses attack or jump, change to the next level
-============
-*/
-.float autoscreenshot;
-void IntermissionThink(entity this)
+float MoveToRandomMapLocation(entity e, float goodcontents, float badcontents, float badsurfaceflags, float attempts, float maxaboveground, float minviewdistance)
 {
-       FixIntermissionClient(this);
-
-       float server_screenshot = (autocvar_sv_autoscreenshot && CS(this).cvar_cl_autoscreenshot);
-       float client_screenshot = (CS(this).cvar_cl_autoscreenshot == 2);
-
-       if( (server_screenshot || client_screenshot)
-               && ((this.autoscreenshot > 0) && (time > this.autoscreenshot)) )
-       {
-               this.autoscreenshot = -1;
-               if(IS_REAL_CLIENT(this)) { stuffcmd(this, sprintf("\nscreenshot screenshots/autoscreenshot/%s-%s.jpg; echo \"^5A screenshot has been taken at request of the server.\"\n", GetMapname(), strftime(false, "%s"))); }
-               return;
-       }
-
-       if (time < intermission_exittime)
-               return;
-
-       if(!mapvote_initialized)
-               if (time < intermission_exittime + 10 && !(PHYS_INPUT_BUTTON_ATCK(this) || PHYS_INPUT_BUTTON_JUMP(this) || PHYS_INPUT_BUTTON_ATCK2(this) || PHYS_INPUT_BUTTON_HOOK(this) || PHYS_INPUT_BUTTON_USE(this)))
-                       return;
-
-       MapVote_Start();
+       return MoveToRandomLocationWithinBounds(e, world.mins, world.maxs, goodcontents, badcontents, badsurfaceflags, attempts, maxaboveground, minviewdistance, false);
 }
 
 /*
@@ -1467,7 +1165,7 @@ void DumpStats(float final)
        s = strcat(s, GetGametype(), "_", GetMapname(), ":", ftos(rint(time)));
 
        if(to_console)
-               LOG_INFO(s);
+               LOG_HELP(s);
        if(to_eventlog)
                GameLogEcho(s);
 
@@ -1483,7 +1181,7 @@ void DumpStats(float final)
 
        s = strcat(":labels:player:", GetPlayerScoreString(NULL, 0));
        if(to_console)
-               LOG_INFO(s);
+               LOG_HELP(s);
        if(to_eventlog)
                GameLogEcho(s);
        if(to_file)
@@ -1492,24 +1190,24 @@ void DumpStats(float final)
        FOREACH_CLIENT(IS_REAL_CLIENT(it) || (IS_BOT_CLIENT(it) && autocvar_sv_logscores_bots), {
                s = strcat(":player:see-labels:", GetPlayerScoreString(it, 0), ":");
                s = strcat(s, ftos(rint(time - CS(it).jointime)), ":");
-               if(IS_PLAYER(it) || MUTATOR_CALLHOOK(GetPlayerStatus, it))
+               if(IS_PLAYER(it) || INGAME_JOINED(it))
                        s = strcat(s, ftos(it.team), ":");
                else
                        s = strcat(s, "spectator:");
 
                if(to_console)
-                       LOG_INFO(s, playername(it, false));
+                       LOG_HELP(s, playername(it.netname, it.team, false));
                if(to_eventlog)
-                       GameLogEcho(strcat(s, ftos(it.playerid), ":", playername(it, false)));
+                       GameLogEcho(strcat(s, ftos(it.playerid), ":", playername(it.netname, it.team, false)));
                if(to_file)
-                       fputs(file, strcat(s, playername(it, false), "\n"));
+                       fputs(file, strcat(s, playername(it.netname, it.team, false), "\n"));
        });
 
        if(teamplay)
        {
                s = strcat(":labels:teamscores:", GetTeamScoreString(0, 0));
                if(to_console)
-                       LOG_INFO(s);
+                       LOG_HELP(s);
                if(to_eventlog)
                        GameLogEcho(s);
                if(to_file)
@@ -1520,7 +1218,7 @@ void DumpStats(float final)
                        s = strcat(":teamscores:see-labels:", GetTeamScoreString(i, 0));
                        s = strcat(s, ":", ftos(i));
                        if(to_console)
-                               LOG_INFO(s);
+                               LOG_HELP(s);
                        if(to_eventlog)
                                GameLogEcho(s);
                        if(to_file)
@@ -1529,7 +1227,7 @@ void DumpStats(float final)
        }
 
        if(to_console)
-               LOG_INFO(":end");
+               LOG_HELP(":end");
        if(to_eventlog)
                GameLogEcho(":end");
        if(to_file)
@@ -1539,48 +1237,15 @@ void DumpStats(float final)
        }
 }
 
-void FixIntermissionClient(entity e)
-{
-       if(!e.autoscreenshot) // initial call
-       {
-               e.autoscreenshot = time + 0.8;  // used for autoscreenshot
-               SetResourceExplicit(e, RES_HEALTH, -2342);
-               // first intermission phase; voting phase has positive health (used to decide whether to send SVC_FINALE or not)
-               for (int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
-               {
-                   .entity weaponentity = weaponentities[slot];
-                       if(e.(weaponentity))
-                       {
-                               e.(weaponentity).effects = EF_NODRAW;
-                               if (e.(weaponentity).weaponchild)
-                                       e.(weaponentity).weaponchild.effects = EF_NODRAW;
-                       }
-               }
-               if(IS_REAL_CLIENT(e))
-               {
-                       stuffcmd(e, "\nscr_printspeed 1000000\n");
-                       RandomSelection_Init();
-                       FOREACH_WORD(autocvar_sv_intermission_cdtrack, true, {
-                               RandomSelection_AddString(it, 1, 1);
-                       });
-                       if (RandomSelection_chosen_string != "")
-                       {
-                               stuffcmd(e, sprintf("\ncd loop %s\n", RandomSelection_chosen_string));
-                       }
-                       msg_entity = e;
-                       WriteByte(MSG_ONE, SVC_INTERMISSION);
-               }
-       }
-}
-
 /*
 go to the next level for deathmatch
 only called if a time or frag limit has expired
 */
 void NextLevel()
 {
+       cvar_set("_endmatch", "0");
        game_stopped = true;
-       intermission_running = 1; // game over
+       intermission_running = true; // game over
 
        // enforce a wait time before allowing changelevel
        if(player_count > 0)
@@ -1612,10 +1277,18 @@ void NextLevel()
 
        GameLogClose();
 
-       FOREACH_CLIENT(IS_PLAYER(it), {
+       int winner_team = 0;
+       FOREACH_CLIENT(IS_PLAYER(it) || INGAME(it), {
                FixIntermissionClient(it);
                if(it.winning)
-                       bprint(playername(it, false), " ^7wins.\n");
+               {
+                       if (teamplay && !winner_team)
+                       {
+                               winner_team = it.team;
+                               bprint(Team_ColorCode(winner_team), Team_ColorName_Upper(winner_team), "^7 team wins the match\n");
+                       }
+                       bprint(playername(it.netname, it.team, false), " ^7wins\n");
+               }
        });
 
        target_music_kill();
@@ -1629,7 +1302,7 @@ void NextLevel()
 }
 
 
-float InitiateSuddenDeath()
+int InitiateSuddenDeath()
 {
        // Check first whether normal overtimes could be added before initiating suddendeath mode
        // - for this timelimit_overtime needs to be >0 of course
@@ -1646,9 +1319,14 @@ float InitiateSuddenDeath()
                if(!checkrules_suddendeathend)
                {
                        if(autocvar_g_campaign)
+                       {
                                checkrules_suddendeathend = time; // no suddendeath in campaign
+                       }
                        else
+                       {
                                checkrules_suddendeathend = time + 60 * autocvar_timelimit_suddendeath;
+                               overtimes = -1;
+                       }
                        if(g_race && !g_race_qualifying)
                                race_StartCompleting();
                }
@@ -1659,6 +1337,7 @@ float InitiateSuddenDeath()
 void InitiateOvertime() // ONLY call this if InitiateSuddenDeath returned true
 {
        ++checkrules_overtimesadded;
+       overtimes = checkrules_overtimesadded;
        //add one more overtime by simply extending the timelimit
        cvar_set("timelimit", ftos(autocvar_timelimit + autocvar_timelimit_overtime));
        Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_OVERTIME_TIME, autocvar_timelimit_overtime * 60);
@@ -1695,13 +1374,13 @@ float GetWinningCode(float fraglimitreached, float equality)
 // set the .winning flag for exactly those players with a given field value
 void SetWinners(.float field, float value)
 {
-       FOREACH_CLIENT(IS_PLAYER(it), { it.winning = (it.(field) == value); });
+       FOREACH_CLIENT(IS_PLAYER(it) || INGAME(it), { it.winning = (it.(field) == value); });
 }
 
 // set the .winning flag for those players with a given field value
 void AddWinners(.float field, float value)
 {
-       FOREACH_CLIENT(IS_PLAYER(it), {
+       FOREACH_CLIENT(IS_PLAYER(it) || INGAME(it), {
                if(it.(field) == value)
                        it.winning = 1;
        });
@@ -1710,12 +1389,7 @@ void AddWinners(.float field, float value)
 // clear the .winning flags
 void ClearWinners()
 {
-       FOREACH_CLIENT(IS_PLAYER(it), { it.winning = 0; });
-}
-
-void ShuffleMaplist()
-{
-       cvar_set("g_maplist", shufflewords(autocvar_g_maplist));
+       FOREACH_CLIENT(IS_PLAYER(it) || INGAME(it), { it.winning = 0; });
 }
 
 int fragsleft_last;
@@ -1914,19 +1588,18 @@ void CheckRules_World()
                leadlimit = 0; // no leadlimit for now
        }
 
-       if(timelimit > 0)
-       {
-               timelimit += game_starttime;
-       }
-       else if (timelimit < 0)
+       if (autocvar__endmatch || timelimit < 0)
        {
                // endmatch
                NextLevel();
                return;
        }
 
-       float wantovertime;
-       wantovertime = 0;
+       if(timelimit > 0)
+               timelimit += game_starttime;
+
+       int overtimes_prev = overtimes;
+       int wantovertime = 0;
 
        if(checkrules_suddendeathend)
        {
@@ -1962,7 +1635,7 @@ void CheckRules_World()
                                if(readyplayers || playerswithlaps >= 2)
                                {
                                        checkrules_suddendeathend = 0;
-                                       ReadyRestart(); // go to race
+                                       ReadyRestart(true); // go to race
                                        return;
                                }
                                else
@@ -2012,38 +1685,531 @@ void CheckRules_World()
 
        if(checkrules_status == WINNING_YES)
        {
+               if (overtimes == -1 && overtimes != overtimes_prev)
+               {
+                       // if suddendeathend overtime has just begun, revert it
+                       checkrules_suddendeathend = 0;
+                       overtimes = overtimes_prev;
+               }
                //print("WINNING\n");
                NextLevel();
        }
 }
 
-string GotoMap(string m)
+float want_weapon(entity weaponinfo, float allguns)
 {
-       m = GameTypeVote_MapInfo_FixName(m);
-       if (!m)
-               return "The map you suggested is not available on this server.";
-       if (!autocvar_sv_vote_gametype)
-       if(!MapInfo_CheckMap(m))
-               return "The map you suggested does not support the current game mode.";
-       cvar_set("nextmap", m);
-       cvar_set("timelimit", "-1");
-       if(mapvote_initialized || alreadychangedlevel)
-       {
-               if(DoNextMapOverride(0))
-                       return "Map switch initiated.";
-               else
-                       return "Hm... no. For some reason I like THIS map more.";
+       int d = 0;
+       bool allow_mutatorblocked = false;
+
+       if(!weaponinfo.m_id)
+               return 0;
+
+       bool mutator_returnvalue = MUTATOR_CALLHOOK(WantWeapon, weaponinfo, d, allguns, allow_mutatorblocked);
+       d = M_ARGV(1, float);
+       allguns = M_ARGV(2, bool);
+       allow_mutatorblocked = M_ARGV(3, bool);
+
+       if(allguns)
+               d = boolean((weaponinfo.spawnflags & WEP_FLAG_NORMAL) && !(weaponinfo.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_SPECIALATTACK)));
+       else if(!mutator_returnvalue)
+               d = !(!weaponinfo.weaponstart);
+
+       if(!allow_mutatorblocked && (weaponinfo.spawnflags & WEP_FLAG_MUTATORBLOCKED)) // never default mutator blocked guns
+               d = 0;
+
+       float t = weaponinfo.weaponstartoverride;
+
+       //LOG_INFOF("want_weapon: %s - d: %d t: %d\n", weaponinfo.netname, d, t);
+
+       // bit order in t:
+       // 1: want or not
+       // 2: is default?
+       // 4: is set by default?
+       if(t < 0)
+               t = 4 | (3 * d);
+       else
+               t |= (2 * d);
+
+       return t;
+}
+
+/// Weapons the player normally starts with outside weapon arena.
+WepSet weapons_start()
+{
+       WepSet ret = '0 0 0';
+       FOREACH(Weapons, it != WEP_Null, {
+               int w = want_weapon(it, false);
+               if (w & 1)
+                       ret |= it.m_wepset;
+       });
+       return ret;
+}
+
+WepSet weapons_all()
+{
+       WepSet ret = '0 0 0';
+       FOREACH(Weapons, it != WEP_Null, {
+               if (!(it.spawnflags & (WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK)))
+                       ret |= it.m_wepset;
+       });
+       return ret;
+}
+
+WepSet weapons_devall()
+{
+       WepSet ret = '0 0 0';
+       FOREACH(Weapons, it != WEP_Null,
+       {
+               ret |= it.m_wepset;
+       });
+       return ret;
+}
+
+WepSet weapons_most()
+{
+       WepSet ret = '0 0 0';
+       FOREACH(Weapons, it != WEP_Null, {
+               if ((it.spawnflags & WEP_FLAG_NORMAL) && !(it.spawnflags & (WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_HIDDEN | WEP_FLAG_SPECIALATTACK)))
+                       ret |= it.m_wepset;
+       });
+       return ret;
+}
+
+void weaponarena_available_all_update(entity this)
+{
+       if (weaponsInMapAll)
+       {
+               start_weapons = warmup_start_weapons = g_weaponarena_weapons = weapons_start() | (weaponsInMapAll & weapons_all());
+       }
+       else
+       {
+               // if no weapons are available on the map, just fall back to all weapons arena
+               start_weapons = warmup_start_weapons = g_weaponarena_weapons = weapons_all();
+       }
+}
+
+void weaponarena_available_devall_update(entity this)
+{
+       if (weaponsInMapAll)
+       {
+               start_weapons = warmup_start_weapons = g_weaponarena_weapons = weapons_start() | weaponsInMapAll;
        }
        else
-               return "Map switch will happen after scoreboard.";
+       {
+               // if no weapons are available on the map, just fall back to devall weapons arena
+               start_weapons = warmup_start_weapons = g_weaponarena_weapons = weapons_devall();
+       }
+}
+
+void weaponarena_available_most_update(entity this)
+{
+       if (weaponsInMapAll)
+       {
+               start_weapons = warmup_start_weapons = g_weaponarena_weapons = weapons_start() | (weaponsInMapAll & weapons_most());
+       }
+       else
+       {
+               // if no weapons are available on the map, just fall back to most weapons arena
+               start_weapons = warmup_start_weapons = g_weaponarena_weapons = weapons_most();
+       }
+}
+
+void readplayerstartcvars()
+{
+       // initialize starting values for players
+       start_weapons = '0 0 0';
+       start_weapons_default = '0 0 0';
+       start_weapons_defaultmask = '0 0 0';
+       start_items = 0;
+       start_ammo_shells = 0;
+       start_ammo_nails = 0;
+       start_ammo_rockets = 0;
+       start_ammo_cells = 0;
+       start_ammo_plasma = 0;
+       if (random_start_ammo == NULL)
+       {
+               random_start_ammo = new_pure(random_start_ammo);
+       }
+       start_health = cvar("g_balance_health_start");
+       start_armorvalue = cvar("g_balance_armor_start");
+
+       g_weaponarena = 0;
+       g_weaponarena_weapons = '0 0 0';
+
+       string s = cvar_string("g_weaponarena");
+
+       MUTATOR_CALLHOOK(SetWeaponArena, s);
+       s = M_ARGV(0, string);
+
+       if (s == "0" || s == "")
+       {
+               // no arena
+       }
+       else if (s == "off")
+       {
+               // forcibly turn off weaponarena
+       }
+       else if (s == "all" || s == "1")
+       {
+               g_weaponarena = 1;
+               g_weaponarena_list = "All Weapons Arena";
+               g_weaponarena_weapons = weapons_all();
+       }
+       else if (s == "devall")
+       {
+               g_weaponarena = 1;
+               g_weaponarena_list = "Dev All Weapons Arena";
+               g_weaponarena_weapons = weapons_devall();
+       }
+       else if (s == "most")
+       {
+               g_weaponarena = 1;
+               g_weaponarena_list = "Most Weapons Arena";
+               g_weaponarena_weapons = weapons_most();
+       }
+       else if (s == "all_available")
+       {
+               g_weaponarena = 1;
+               g_weaponarena_list = "All Available Weapons Arena";
+
+               // this needs to run after weaponsInMapAll is initialized
+               InitializeEntity(NULL, weaponarena_available_all_update, INITPRIO_FINDTARGET);
+       }
+       else if (s == "devall_available")
+       {
+               g_weaponarena = 1;
+               g_weaponarena_list = "Dev All Available Weapons Arena";
+
+               // this needs to run after weaponsInMapAll is initialized
+               InitializeEntity(NULL, weaponarena_available_devall_update, INITPRIO_FINDTARGET);
+       }
+       else if (s == "most_available")
+       {
+               g_weaponarena = 1;
+               g_weaponarena_list = "Most Available Weapons Arena";
+
+               // this needs to run after weaponsInMapAll is initialized
+               InitializeEntity(NULL, weaponarena_available_most_update, INITPRIO_FINDTARGET);
+       }
+       else if (s == "none")
+       {
+               g_weaponarena = 1;
+               g_weaponarena_list = "No Weapons Arena";
+       }
+       else
+       {
+               g_weaponarena = 1;
+               float t = tokenize_console(s);
+               g_weaponarena_list = "";
+               for (int j = 0; j < t; ++j)
+               {
+                       s = argv(j);
+                       Weapon wep = Weapon_from_name(s);
+                       if(wep != WEP_Null)
+                       {
+                               g_weaponarena_weapons |= (wep.m_wepset);
+                               g_weaponarena_list = strcat(g_weaponarena_list, wep.netname, " & ");
+                       }
+               }
+               if (g_weaponarena_list != "") // remove trailing " & "
+                       g_weaponarena_list = substring(g_weaponarena_list, 0, strlen(g_weaponarena_list) - 3);
+               else // no valid weapon found
+                       g_weaponarena_list = "No Weapons Arena";
+       }
+
+       if (g_weaponarena)
+       {
+               g_weapon_stay = 0; // incompatible
+               start_weapons = g_weaponarena_weapons;
+               start_items |= IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS;
+               g_weaponarena_list = strzone(g_weaponarena_list);
+       }
+       else
+       {
+               FOREACH(Weapons, it != WEP_Null, {
+                       int w = want_weapon(it, false);
+                       WepSet s = it.m_wepset;
+                       if(w & 1)
+                               start_weapons |= s;
+                       if(w & 2)
+                               start_weapons_default |= s;
+                       if(w & 4)
+                               start_weapons_defaultmask |= s;
+               });
+       }
+
+       if(cvar("g_balance_superweapons_time") < 0)
+               start_items |= IT_UNLIMITED_SUPERWEAPONS;
+
+       if(!cvar("g_use_ammunition"))
+               start_items |= IT_UNLIMITED_AMMO;
+
+       if(start_items & IT_UNLIMITED_AMMO)
+       {
+               start_ammo_shells = 999;
+               start_ammo_nails = 999;
+               start_ammo_rockets = 999;
+               start_ammo_cells = 999;
+               start_ammo_plasma = 999;
+               start_ammo_fuel = 999;
+       }
+       else
+       {
+               start_ammo_shells = cvar("g_start_ammo_shells");
+               start_ammo_nails = cvar("g_start_ammo_nails");
+               start_ammo_rockets = cvar("g_start_ammo_rockets");
+               start_ammo_cells = cvar("g_start_ammo_cells");
+               start_ammo_plasma = cvar("g_start_ammo_plasma");
+               start_ammo_fuel = cvar("g_start_ammo_fuel");
+               random_start_weapons_count = cvar("g_random_start_weapons_count");
+               SetResource(random_start_ammo, RES_SHELLS, cvar("g_random_start_shells"));
+               SetResource(random_start_ammo, RES_BULLETS, cvar("g_random_start_bullets"));
+               SetResource(random_start_ammo, RES_ROCKETS, cvar("g_random_start_rockets"));
+               SetResource(random_start_ammo, RES_CELLS, cvar("g_random_start_cells"));
+               SetResource(random_start_ammo, RES_PLASMA, cvar("g_random_start_plasma"));
+       }
+
+       warmup_start_ammo_shells = start_ammo_shells;
+       warmup_start_ammo_nails = start_ammo_nails;
+       warmup_start_ammo_rockets = start_ammo_rockets;
+       warmup_start_ammo_cells = start_ammo_cells;
+       warmup_start_ammo_plasma = start_ammo_plasma;
+       warmup_start_ammo_fuel = start_ammo_fuel;
+       warmup_start_health = start_health;
+       warmup_start_armorvalue = start_armorvalue;
+       warmup_start_weapons = start_weapons;
+       warmup_start_weapons_default = start_weapons_default;
+       warmup_start_weapons_defaultmask = start_weapons_defaultmask;
+
+       if (!g_weaponarena)
+       {
+               warmup_start_ammo_shells = cvar("g_warmup_start_ammo_shells");
+               warmup_start_ammo_nails = cvar("g_warmup_start_ammo_nails");
+               warmup_start_ammo_rockets = cvar("g_warmup_start_ammo_rockets");
+               warmup_start_ammo_cells = cvar("g_warmup_start_ammo_cells");
+               warmup_start_ammo_plasma = cvar("g_warmup_start_ammo_plasma");
+               warmup_start_ammo_fuel = cvar("g_warmup_start_ammo_fuel");
+               warmup_start_health = cvar("g_warmup_start_health");
+               warmup_start_armorvalue = cvar("g_warmup_start_armor");
+               warmup_start_weapons = '0 0 0';
+               warmup_start_weapons_default = '0 0 0';
+               warmup_start_weapons_defaultmask = '0 0 0';
+               FOREACH(Weapons, it != WEP_Null, {
+                       int w = want_weapon(it, autocvar_g_warmup_allguns);
+                       WepSet s = it.m_wepset;
+                       if(w & 1)
+                               warmup_start_weapons |= s;
+                       if(w & 2)
+                               warmup_start_weapons_default |= s;
+                       if(w & 4)
+                               warmup_start_weapons_defaultmask |= s;
+               });
+       }
+
+       if (autocvar_g_jetpack)
+               start_items |= ITEM_Jetpack.m_itemid;
+
+       MUTATOR_CALLHOOK(SetStartItems);
+
+       if (start_items & ITEM_Jetpack.m_itemid)
+       {
+               start_items |= ITEM_JetpackRegen.m_itemid;
+               start_ammo_fuel = max(start_ammo_fuel, cvar("g_balance_fuel_rotstable"));
+               warmup_start_ammo_fuel = max(warmup_start_ammo_fuel, cvar("g_balance_fuel_rotstable"));
+       }
+
+       start_ammo_shells = max(0, start_ammo_shells);
+       start_ammo_nails = max(0, start_ammo_nails);
+       start_ammo_rockets = max(0, start_ammo_rockets);
+       start_ammo_cells = max(0, start_ammo_cells);
+       start_ammo_plasma = max(0, start_ammo_plasma);
+       start_ammo_fuel = max(0, start_ammo_fuel);
+       SetResource(random_start_ammo, RES_SHELLS, max(0, GetResource(random_start_ammo, RES_SHELLS)));
+       SetResource(random_start_ammo, RES_BULLETS, max(0, GetResource(random_start_ammo, RES_BULLETS)));
+       SetResource(random_start_ammo, RES_ROCKETS, max(0, GetResource(random_start_ammo, RES_ROCKETS)));
+       SetResource(random_start_ammo, RES_CELLS, max(0, GetResource(random_start_ammo, RES_CELLS)));
+       SetResource(random_start_ammo, RES_PLASMA, max(0, GetResource(random_start_ammo, RES_PLASMA)));
+
+       warmup_start_ammo_shells = max(0, warmup_start_ammo_shells);
+       warmup_start_ammo_nails = max(0, warmup_start_ammo_nails);
+       warmup_start_ammo_rockets = max(0, warmup_start_ammo_rockets);
+       warmup_start_ammo_cells = max(0, warmup_start_ammo_cells);
+       warmup_start_ammo_plasma = max(0, warmup_start_ammo_plasma);
+       warmup_start_ammo_fuel = max(0, warmup_start_ammo_fuel);
+}
+
+void readlevelcvars()
+{
+       if(cvar("sv_allow_fullbright"))
+               serverflags |= SERVERFLAG_ALLOW_FULLBRIGHT;
+
+       sv_ready_restart_after_countdown = cvar("sv_ready_restart_after_countdown");
+
+       warmup_stage = cvar("g_warmup");
+       warmup_limit = cvar("g_warmup_limit");
+
+       if(cvar("g_campaign"))
+               warmup_stage = 0; // no warmup during campaign
+
+       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_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_powerup = cvar("g_pickup_respawntimejitter_powerup");
+
+       g_pickup_shells = cvar("g_pickup_shells");
+       g_pickup_shells_max = cvar("g_pickup_shells_max");
+       g_pickup_nails = cvar("g_pickup_nails");
+       g_pickup_nails_max = cvar("g_pickup_nails_max");
+       g_pickup_rockets = cvar("g_pickup_rockets");
+       g_pickup_rockets_max = cvar("g_pickup_rockets_max");
+       g_pickup_cells = cvar("g_pickup_cells");
+       g_pickup_cells_max = cvar("g_pickup_cells_max");
+       g_pickup_plasma = cvar("g_pickup_plasma");
+       g_pickup_plasma_max = cvar("g_pickup_plasma_max");
+       g_pickup_fuel = cvar("g_pickup_fuel");
+       g_pickup_fuel_jetpack = cvar("g_pickup_fuel_jetpack");
+       g_pickup_fuel_max = cvar("g_pickup_fuel_max");
+       g_pickup_armorsmall = cvar("g_pickup_armorsmall");
+       g_pickup_armorsmall_max = cvar("g_pickup_armorsmall_max");
+       g_pickup_armorsmall_anyway = cvar("g_pickup_armorsmall_anyway");
+       g_pickup_armormedium = cvar("g_pickup_armormedium");
+       g_pickup_armormedium_max = cvar("g_pickup_armormedium_max");
+       g_pickup_armormedium_anyway = cvar("g_pickup_armormedium_anyway");
+       g_pickup_armorbig = cvar("g_pickup_armorbig");
+       g_pickup_armorbig_max = cvar("g_pickup_armorbig_max");
+       g_pickup_armorbig_anyway = cvar("g_pickup_armorbig_anyway");
+       g_pickup_armormega = cvar("g_pickup_armormega");
+       g_pickup_armormega_max = cvar("g_pickup_armormega_max");
+       g_pickup_armormega_anyway = cvar("g_pickup_armormega_anyway");
+       g_pickup_healthsmall = cvar("g_pickup_healthsmall");
+       g_pickup_healthsmall_max = cvar("g_pickup_healthsmall_max");
+       g_pickup_healthsmall_anyway = cvar("g_pickup_healthsmall_anyway");
+       g_pickup_healthmedium = cvar("g_pickup_healthmedium");
+       g_pickup_healthmedium_max = cvar("g_pickup_healthmedium_max");
+       g_pickup_healthmedium_anyway = cvar("g_pickup_healthmedium_anyway");
+       g_pickup_healthbig = cvar("g_pickup_healthbig");
+       g_pickup_healthbig_max = cvar("g_pickup_healthbig_max");
+       g_pickup_healthbig_anyway = cvar("g_pickup_healthbig_anyway");
+       g_pickup_healthmega = cvar("g_pickup_healthmega");
+       g_pickup_healthmega_max = cvar("g_pickup_healthmega_max");
+       g_pickup_healthmega_anyway = cvar("g_pickup_healthmega_anyway");
+
+       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");
+
+    MUTATOR_CALLHOOK(ReadLevelCvars);
+
+       if (!warmup_stage && !autocvar_g_campaign)
+               game_starttime = time + cvar("g_start_delay");
+
+       FOREACH(Weapons, it != WEP_Null, { it.wr_init(it); });
+
+       readplayerstartcvars();
+}
+
+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;
+    }
+}
+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)
+    {
+               e.remove_except_protected_forbidden = 1;
+    }
+    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;
+}
+
+// deferred dropping
+void DropToFloor_Handler(entity this)
+{
+       WITHSELF(this, builtin_droptofloor());
+       this.dropped_origin = this.origin;
+}
+
+void droptofloor(entity this)
+{
+       InitializeEntity(this, DropToFloor_Handler, INITPRIO_DROPTOFLOOR);
 }
 
 bool autocvar_sv_gameplayfix_multiplethinksperframe = true;
-void RunThink(entity this)
+void RunThink(entity this, float dt)
 {
        // don't let things stay in the past.
        // it is possible to start that way by a trigger with a local time.
-       if(this.nextthink <= 0 || this.nextthink > time + frametime)
+       if(this.nextthink <= 0 || this.nextthink > time + dt)
                return;
 
        float oldtime = time; // do we need to save this?
@@ -2059,7 +2225,7 @@ void RunThink(entity this)
                // we don't want to loop in that case, so exit if the new nextthink is
                // <= the time the qc was told, also exit if it is past the end of the
                // frame
-               if(this.nextthink <= time || this.nextthink > oldtime + frametime || !autocvar_sv_gameplayfix_multiplethinksperframe)
+               if(this.nextthink <= time || this.nextthink > oldtime + dt || !autocvar_sv_gameplayfix_multiplethinksperframe)
                        break;
        }
 
@@ -2089,8 +2255,8 @@ void Physics_Frame()
                        if(it.move_movetype == MOVETYPE_PUSH || it.move_movetype == MOVETYPE_FAKEPUSH)
                                continue; // these movetypes have no regular think function
                        // handle thinking here
-                       if (getthink(it) && it.nextthink > 0 && it.nextthink <= time + frametime)
-                               RunThink(it);
+                       if (getthink(it) && it.nextthink > 0 && it.nextthink <= time + PHYS_INPUT_TIMELENGTH)
+                               RunThink(it, PHYS_INPUT_TIMELENGTH);
                }
        });
 
@@ -2099,6 +2265,7 @@ void Physics_Frame()
 
        // make a second pass to see if any ents spawned this frame and make
        // sure they run their move/think. this is verified by checking .move_time, which will never be 0 if the entity has moved
+       // MOVETYPE_NONE is also checked as .move_time WILL be 0 with that movetype
        IL_EACH(g_moveables, it.move_qcphysics,
        {
                if(IS_CLIENT(it) || it.move_time || it.move_movetype == MOVETYPE_NONE || it.move_movetype == MOVETYPE_PHYSICS)
@@ -2120,9 +2287,10 @@ void EndFrame()
                        STAT(TYPEHIT_TIME, it) = time;
                } else if (e.killsound) {
                        STAT(KILL_TIME, it) = time;
-               } else if (e.damage_dealt) {
+               } else if (e.hitsound_damage_dealt) {
                        STAT(HIT_TIME, it) = time;
-                       STAT(DAMAGE_DEALT_TOTAL, it) += ceil(e.damage_dealt);
+                       // NOTE: this is not accurate as client code doesn't need so much accuracy for its purposes
+                       STAT(HITSOUND_DAMAGE_DEALT_TOTAL, it) += ceil(e.hitsound_damage_dealt);
                }
        });
        // add 1 frametime because after this, engine SV_Physics
@@ -2133,7 +2301,7 @@ void EndFrame()
        float altime = time + frametime * (1 + autocvar_g_antilag_nudge);
        FOREACH_CLIENT(true, {
                it.typehitsound = false;
-               it.damage_dealt = 0;
+               it.hitsound_damage_dealt = 0;
                it.killsound = false;
                antilag_record(it, CS(it), altime);
        });
@@ -2255,6 +2423,8 @@ void Shutdown()
 
                WeaponStats_Shutdown();
                MapInfo_Shutdown();
+
+               strfree(sv_termsofservice_url_escaped);
        }
        else if(world_initialized == 0)
        {