]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge branch 'master' into Mario/team_keepaway
authorMario <mario.mario@y7mail.com>
Sat, 26 Nov 2022 14:10:20 +0000 (00:10 +1000)
committerMario <mario.mario@y7mail.com>
Sat, 26 Nov 2022 14:10:20 +0000 (00:10 +1000)
1  2 
gamemodes-server.cfg
qcsrc/common/mutators/mutator/waypoints/all.inc
qcsrc/common/scores.qh
qcsrc/common/stats.qh
qcsrc/menu/xonotic/util.qc
qcsrc/server/world.qc

diff --combined gamemodes-server.cfg
index 548ca34788c70214f46107097d01850175486654,4afd6310fc34bfb6cdabd4fccf7b4efed56a6062..51ab9231ea0b308579d833869b2fef7b2adccf5d
@@@ -29,7 -29,6 +29,7 @@@ alias sv_hook_gamestart_k
  alias sv_hook_gamestart_ft
  alias sv_hook_gamestart_inv
  alias sv_hook_gamestart_duel
 +alias sv_hook_gamestart_tka
  // there is currently no hook for when the match is restarted
  // see sv_hook_readyrestart for previous uses of this hook
  //alias sv_hook_gamerestart
@@@ -59,7 -58,6 +59,7 @@@ alias sv_vote_gametype_hook_on
  alias sv_vote_gametype_hook_rc
  alias sv_vote_gametype_hook_tdm
  alias sv_vote_gametype_hook_duel
 +alias sv_vote_gametype_hook_tka
  
  // Example preset to allow 1v1ctf to be used for the gametype voting screen.
  // Aliases can have max 31 chars so the gametype can have max 9 chars.
@@@ -210,13 -208,6 +210,13 @@@ set g_duel_respawn_delay_large_count 
  set g_duel_respawn_delay_max 0
  set g_duel_respawn_waves 0
  set g_duel_weapon_stay 0
 +set g_tka_respawn_delay_small 0
 +set g_tka_respawn_delay_small_count 0
 +set g_tka_respawn_delay_large 0
 +set g_tka_respawn_delay_large_count 0
 +set g_tka_respawn_delay_max 0
 +set g_tka_respawn_waves 0
 +set g_tka_weapon_stay 0
  
  
  // =========
@@@ -238,6 -229,7 +238,7 @@@ set g_ca_round_timelimit 180 "round tim
  set g_ca_teams_override 0
  set g_ca_team_spawns 0 "when 1, players spawn from the team spawnpoints of the map, if any"
  set g_ca_teams 0
+ set g_ca_prevent_stalemate 0 "when round time ends instead of instant stalemate give round win to the team with most survivors or with the most total health"
  set g_ca_weaponarena "most" "starting weapons - takes the same options as g_weaponarena"
  
  
@@@ -460,6 -452,20 +461,20 @@@ set g_lms_last_join 3    "if g_lms_join_an
  set g_lms_join_anytime 1      "1: new players can join, but get same amount of lives as the worst player; 0: new players can only join if the worst active player has (fraglimit - g_lms_last_join) or more lives"
  set g_lms_items 0 "enables items to spawn, weaponarena still disables weapons and ammo (to force all items to spawn, use g_pickup_items 1 instead)"
  set g_lms_weaponarena "most_available" "starting weapons - takes the same options as g_weaponarena"
+ set g_lms_leader_lives_diff 2 "players leading by at least this number of lives are considered leaders and are more visible"
+ set g_lms_leader_minpercent 0.5 "leading players are not considered leaders only if they are more than this percentage of total players"
+ set g_lms_leader_wp_time 5 "show waypoints for leaders only for this amount of time"
+ set g_lms_leader_wp_interval 25 "periodically show again waypoints for leaders after this amount of time"
+ set g_lms_leader_wp_interval_jitter 10 "jitter waypoint interval by this amount of time"
+ set g_lms_dynamic_respawn_delay 1 "increase player respawn delay based on the number of lives less than the leader (NOTE: delay doesn't increase when only 2 players are left alive)"
+ set g_lms_dynamic_respawn_delay_base 2 "base player respawn delay"
+ set g_lms_dynamic_respawn_delay_increase 3 "increase base player respawn delay by this amount of time for each life less than the leader"
+ set g_lms_dynamic_respawn_delay_max 20 "max player respawn delay"
+ set g_lms_dynamic_vampire 1 "attackers receive a fraction of health removed from enemies based on the number of lives less than the enemy"
+ set g_lms_dynamic_vampire_factor_base 0.1 "base vampire factor"
+ set g_lms_dynamic_vampire_factor_increase 0.1 "increase vampire factor by this fraction for each life less than the enemy"
+ set g_lms_dynamic_vampire_factor_max 0.5 "max vampire factor"
+ set g_lms_dynamic_vampire_min_lives_diff 2 "number of lives the attacker must have less than the enemy to receive the base fraction of health"
  set g_lms_forfeit_min_match_time 30 "end the match early if at least this many seconds have elapsed and less than 2 players are playing due to forfeits"
  
  
@@@ -569,35 -575,3 +584,35 @@@ set g_duel 0 "Duel: frag the opponent m
  //set g_duel_warmup 180 "Have a short warmup period before beginning the actual duel"
  set g_duel_with_powerups 0 "Enable powerups to spawn in the duel gamemode"
  set g_duel_not_dm_maps 0 "when this is set, DM maps will NOT be listed in duel"
 +
 +// ===============
 +//  team keepaway
 +// ===============
 +set g_tka 0 "another game mode which focuses around a ball"
 +set g_tka_on_ka_maps 1 "when this is set, all KA maps automatically support TKA"
 +set g_tka_on_tdm_maps 0 "when this is set, all TDM maps automatically support TKA"
 +set g_tka_teams 2 "how many teams are in team keepaway (set by mapinfo)"
 +set g_tka_team_spawns 0 "when 1, players spawn from the team spawnpoints of the map, if any"
 +set g_tka_teams_override 0    "how many teams are in team keepaway"
 +set g_tka_point_limit -1 "TKA point limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
 +set g_tka_point_leadlimit -1 "TKA point lead limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
 +set g_tka_score_team 1 "allow points to be awarded to teammates for any kill when the ball is in your team's possession"
 +set g_tka_score_bckill 1 "points for killing the ball barrier (Ball Carrier Kill)"
 +set g_tka_score_killac 1 "points for kills while holding the ball (Kill As Carrier)"
 +set g_tka_score_timeinterval 1 "amount of time it takes between intervals for timepoints to be added to the score"
 +set g_tka_score_timepoints 0 "points to add to score per timeinterval, 0 for no points"
 +set g_tka_ballcarrier_effects 8 "Add together the numbers you want: EF_ADDITIVE (32) / EF_NODEPTHTEST (8192) / EF_DIMLIGHT (8)"
 +set g_tka_ballcarrier_highspeed 1 "speed multiplier done to the person holding the ball (recommended when used with some mutators)"
 +set g_tka_ballcarrier_damage  1       "damage multiplier while holding the ball"
 +set g_tka_ballcarrier_force   1       "force multiplier while holding the ball"
 +set g_tka_ballcarrier_selfdamage      1       "self damage multiplier while holding the ball"
 +set g_tka_ballcarrier_selfforce       1       "self force multiplier while holding the ball"
 +set g_tka_noncarrier_warn     1       "warn players when they kill without holding the ball"
 +set g_tka_noncarrier_damage   1       "damage done to other players if both you and they don't have the ball"
 +set g_tka_noncarrier_force    1       "force done to other players if both you and they don't have the ball"
 +set g_tka_noncarrier_selfdamage       1       "self damage if you don't have the ball"
 +set g_tka_noncarrier_selfforce        1       "self force if you don't have the ball"
 +set g_tkaball_effects 0 "Add together the numbers you want: EF_ADDITIVE (32) / EF_NODEPTHTEST (8192) / EF_DIMLIGHT (8)"
 +set g_tkaball_trail_color     254     "particle trail color from player/ball"
 +set g_tkaball_damageforcescale        3 "Scale of force which is applied to the ball by weapons/explosions/etc"
 +set g_tkaball_respawntime     10      "if no one picks up the ball, how long to wait until the ball respawns"
index 617194fbf6d3f7659eadc5c2ca46a2765d6e4b4f,aa0ca250fdaede1d595fa6fd5f4113a022bba0d8..98b1f4d3ce8b5f78bee76adc87a0e057690ef0cd
@@@ -47,11 -47,9 +47,13 @@@ REGISTER_WAYPOINT(KeyCarrierPink, _("Ke
  
  REGISTER_WAYPOINT(KaBall, _("Ball"), "notify_ballpickedup", '0 1 1', 1);
  REGISTER_WAYPOINT(KaBallCarrier, _("Ball carrier"), "keepawayball_carrying", '1 0 0', 1);
 +REGISTER_WAYPOINT(TkaBallCarrierRed, _("Ball carrier"), "tka_taken_red", '0 1 1', 1);
 +REGISTER_WAYPOINT(TkaBallCarrierBlue, _("Ball carrier"), "tka_taken_blue", '0 1 1', 1);
 +REGISTER_WAYPOINT(TkaBallCarrierYellow, _("Ball carrier"), "tka_taken_yellow", '0 1 1', 1);
 +REGISTER_WAYPOINT(TkaBallCarrierPink, _("Ball carrier"), "tka_taken_pink", '0 1 1', 1);
  
+ REGISTER_WAYPOINT(LmsLeader, _("Leader"), "", '0 1 1', 1);
  REGISTER_WAYPOINT(NbBall, _("Ball"), "", '0.91 0.85 0.62', 1);
  REGISTER_WAYPOINT(NbGoal, _("Goal"), "", '1 0.5 0', 1);
  
diff --combined qcsrc/common/scores.qh
index f5889cfb305d3602572b2072ce358b6946541c7e,3449f2b53ee5b950b2d2916d8e593509decdece3..241d29bc3db5cf8d0ceaad41e1e973ca2901893b
@@@ -5,7 -5,8 +5,8 @@@
  #define REGISTER_SP(id) REGISTER(Scores, SP, id, m_id, new_pure(PlayerScoreField))
  REGISTRY(Scores, MAX_SCORE);
  REGISTER_REGISTRY(Scores)
- REGISTRY_SORT(Scores);
+ // do not sort alphabetically, player sort priority is based on score registration order
+ //REGISTRY_SORT(Scores);
  REGISTRY_CHECK(Scores);
  
  REGISTRY_DEFINE_GET(Scores, NULL)
@@@ -14,70 -15,41 +15,41 @@@ STATIC_INIT(Scores_renumber) { FOREACH(
  /*
   * Score indices
   */
  #ifdef GAMEQC
- // fields not networked via the score system
- REGISTER_SP(END);
- REGISTER_SP(PING);
- REGISTER_SP(PL);
- REGISTER_SP(NAME);
- REGISTER_SP(SEPARATOR);
- REGISTER_SP(KDRATIO); // kills / deaths
- REGISTER_SP(SUM); // kills - deaths
- REGISTER_SP(FRAGS); // kills - suicides
  // networked fields
+ // NOTE: score registration order is used as player sort priority (after primary and secondary)
  
- REGISTER_SP(SCORE);
- REGISTER_SP(DMG);
- REGISTER_SP(DMGTAKEN);
+ // TODO: move gamemode scores to gamemode files
+ // TODO: allow gamemodes to fully customize player sorting priority, even the common ones
  
- REGISTER_SP(KILLS);
- REGISTER_SP(DEATHS);
- REGISTER_SP(SUICIDES);
- REGISTER_SP(TEAMKILLS);
- REGISTER_SP(ELO);
- REGISTER_SP(FPS);
- // TODO: move to common mutators
- REGISTER_SP(RACE_TIME);
  REGISTER_SP(RACE_LAPS);
+ REGISTER_SP(RACE_TIME);
  REGISTER_SP(RACE_FASTEST);
  
- //REGISTER_SP(CTS_TIME);
- //REGISTER_SP(CTS_LAPS);
- //REGISTER_SP(CTS_FASTEST);
  REGISTER_SP(ASSAULT_OBJECTIVES);
  
- REGISTER_SP(CTF_PICKUPS);
+ REGISTER_SP(CTF_CAPS);
  REGISTER_SP(CTF_FCKILLS);
  REGISTER_SP(CTF_RETURNS);
- REGISTER_SP(CTF_CAPS);
- REGISTER_SP(CTF_CAPTIME);
  REGISTER_SP(CTF_DROPS);
+ REGISTER_SP(CTF_PICKUPS);
+ REGISTER_SP(CTF_CAPTIME);
  
  REGISTER_SP(DOM_TAKES);
  REGISTER_SP(DOM_TICKS);
  
  REGISTER_SP(FREEZETAG_REVIVALS);
  
- REGISTER_SP(KEEPAWAY_PICKUPS);
  REGISTER_SP(KEEPAWAY_BCTIME);
  REGISTER_SP(KEEPAWAY_CARRIERKILLS);
+ REGISTER_SP(KEEPAWAY_PICKUPS);
  
- REGISTER_SP(KH_PICKUPS);
  REGISTER_SP(KH_CAPS);
  REGISTER_SP(KH_KCKILLS);
- REGISTER_SP(KH_PUSHES);
- REGISTER_SP(KH_DESTROYS);
  REGISTER_SP(KH_LOSSES);
+ REGISTER_SP(KH_DESTROYS);
+ REGISTER_SP(KH_PUSHES);
+ REGISTER_SP(KH_PICKUPS);
  
  REGISTER_SP(LMS_RANK);
  REGISTER_SP(LMS_LIVES);
  REGISTER_SP(NEXBALL_GOALS);
  REGISTER_SP(NEXBALL_FAULTS);
  
- REGISTER_SP(ONS_TAKES);
  REGISTER_SP(ONS_CAPS);
+ REGISTER_SP(ONS_TAKES);
  
 +REGISTER_SP(TKA_PICKUPS);
 +REGISTER_SP(TKA_BCTIME);
 +REGISTER_SP(TKA_CARRIERKILLS);
++
+ REGISTER_SP(SCORE);
+ REGISTER_SP(KILLS);
+ REGISTER_SP(DEATHS);
+ REGISTER_SP(TEAMKILLS);
+ REGISTER_SP(SUICIDES);
+ REGISTER_SP(DMG);
+ REGISTER_SP(DMGTAKEN);
+ REGISTER_SP(ELO); // not sortable
+ REGISTER_SP(FPS); // not sortable
+ // fields not networked via the score system
+ REGISTER_SP(END);
+ REGISTER_SP(PING);
+ REGISTER_SP(PL);
+ REGISTER_SP(NAME);
+ REGISTER_SP(SEPARATOR);
+ REGISTER_SP(KDRATIO); // kills / deaths
+ REGISTER_SP(SUM); // kills - deaths
+ REGISTER_SP(FRAGS); // kills - suicides
  #endif
  
  
@@@ -121,15 -112,18 +116,18 @@@ const int SFL_RANK = BIT(5)
   */
  const int SFL_TIME = BIT(6);
  
+ const int SFL_NOT_SORTABLE = BIT(7); // don't sort by this field
  // not an extra constant yet
  #define SFL_ZERO_IS_WORST SFL_TIME
  
  /**
   * Scoring priority (NOTE: PRIMARY is used for fraglimit)
+  * NOTE: SFL_SORT_PRIO_SECONDARY value must be lower than SFL_SORT_PRIO_PRIMARY's
   */
- const int SFL_SORT_PRIO_SECONDARY = 4;
- const int SFL_SORT_PRIO_PRIMARY = 8;
- const int SFL_SORT_PRIO_MASK = 12;
+ const int SFL_SORT_PRIO_SECONDARY = BIT(2);
+ const int SFL_SORT_PRIO_PRIMARY = BIT(3);
+ const int SFL_SORT_PRIO_MASK = SFL_SORT_PRIO_PRIMARY | SFL_SORT_PRIO_SECONDARY;
  
  #define IS_INCREASING(x) ( (x) & SFL_LOWER_IS_BETTER )
  #define IS_DECREASING(x) ( !((x) & SFL_LOWER_IS_BETTER) )
diff --combined qcsrc/common/stats.qh
index 36678050166e41b100d47757237e0f5703171036,53f78cc5ad2d8b3b077441f9f0d8f5291c63a5e0..fa780ee801b7e2d7ed0eb1e652e3984137d92ff8
@@@ -125,20 -125,13 +125,14 @@@ REGISTER_STAT(MONSTERS_KILLED, int
  REGISTER_STAT(NADE_BONUS, float)
  REGISTER_STAT(NADE_BONUS_TYPE, int)
  REGISTER_STAT(NADE_BONUS_SCORE, float)
- REGISTER_STAT(HEALING_ORB, float)
- REGISTER_STAT(HEALING_ORB_ALPHA, float)
  REGISTER_STAT(PLASMA, int)
  REGISTER_STAT(FROZEN, int)
  REGISTER_STAT(REVIVE_PROGRESS, float)
  REGISTER_STAT(ROUNDLOST, int)
  REGISTER_STAT(CAPTURE_PROGRESS, float)
- REGISTER_STAT(ENTRAP_ORB, float)
- REGISTER_STAT(ENTRAP_ORB_ALPHA, float)
  REGISTER_STAT(ITEMSTIME, int, autocvar_sv_itemstime)
  REGISTER_STAT(KILL_TIME, float)
- REGISTER_STAT(VEIL_ORB, float)
- REGISTER_STAT(VEIL_ORB_ALPHA, float)
 +REGISTER_STAT(TKA_BALLSTATUS, int)
  
  #ifdef SVQC
  float autocvar_sv_showfps = 0;
@@@ -209,7 -202,7 +203,7 @@@ int autocvar_sv_gameplayfix_easierwater
  int autocvar_sv_gameplayfix_stepdown = 2;
  float autocvar_sv_gameplayfix_stepdown_maxspeed = 0;
  int autocvar_sv_gameplayfix_stepmultipletimes = 1;
- int autocvar_sv_gameplayfix_unstickplayers = 2;
+ int autocvar_sv_gameplayfix_unstickplayers = 0;
  int autocvar_sv_gameplayfix_fixedcheckwatertransition = 1;
  int autocvar_sv_gameplayfix_slidemoveprojectiles = 1;
  int autocvar_sv_gameplayfix_grenadebouncedownslopes = 1;
index 2a9c25a5a880a530eecfcecb1f2ee25a647785be,d8640a501c3650344fa836af31d26a38d60192f8..30b9e2d193c3885b167895eda62d1dd4af980e8e
@@@ -492,16 -492,11 +492,11 @@@ void updateCheck(
  
  }
  
- bool show_propermenu = false;
  float preMenuInit()
  {
        vector sz;
        vector boxA, boxB;
  
-       if(random() < 0.1)
-               show_propermenu = true;
        updateCheck();
  
        MapInfo_Cache_Create();
@@@ -546,10 -541,7 +541,7 @@@ void preMenuDraw(
                fs = ((1/draw_scale.x) * eX + (1/draw_scale.y) * eY) * 12;
                line = eY * fs.y;
                string l1, l2;
-               if(show_propermenu)
-                       l1 = sprintf("Jeff pay 4 new weapons for %s", _Nex_ExtResponseSystem_UpdateTo);
-               else
-                       l1 = sprintf(_("Update to %s now!"), _Nex_ExtResponseSystem_UpdateTo);
+               l1 = sprintf(_("Update to %s now!"), _Nex_ExtResponseSystem_UpdateTo);
                l2 = "http://www.xonotic.org/";
                if(_Nex_ExtResponseSystem_UpdateToURL)
                        l2 = _Nex_ExtResponseSystem_UpdateToURL;
@@@ -662,7 -654,6 +654,7 @@@ float updateCompression(
        GAMETYPE(MAPINFO_TYPE_CA) \
        GAMETYPE(MAPINFO_TYPE_FREEZETAG) \
        GAMETYPE(MAPINFO_TYPE_KEEPAWAY) \
 +      GAMETYPE(MAPINFO_TYPE_TEAM_KEEPAWAY) \
        GAMETYPE(MAPINFO_TYPE_KEYHUNT) \
        GAMETYPE(MAPINFO_TYPE_LMS) \
        GAMETYPE(MAPINFO_TYPE_DOMINATION) \
diff --combined qcsrc/server/world.qc
index d86f597b30074830cdea2c0292eefff1eb884b4f,a54ad9aa792ebbfd07ab3c85d0ed24ce052e0ae3..2bbe27d81fcc51c02ff6927117eacb078973ae50
@@@ -298,15 -298,12 +298,16 @@@ 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_vip");
                BADCVAR("leadlimit");
                BADCVAR("nextmap");
                BADCVAR("teamplay");
                BADCVAR("timelimit");
+               BADCVAR("g_mapinfo_q3compat");
                BADCVAR("g_mapinfo_settemp_acl");
                BADCVAR("g_mapinfo_ignore_warnings");
                BADCVAR("g_maplist_ignore_sizes");
                BADCVAR("g_maplist");
                BADCVAR("g_maplist_mostrecent");
                BADCVAR("sv_motd");
+               BADCVAR("sv_termsofservice_url");
  
                v = cvar_string(k);
                d = cvar_defstring(k);
                BADCVAR("g_start_delay");
                BADCVAR("g_superspectate");
                BADCVAR("g_tdm_teams_override");
-               BADCVAR("g_warmup");
                BADCVAR("g_weapon_stay"); BADPRESUFFIX("g_", "_weapon_stay");
                BADCVAR("hostname");
                BADCVAR("log_file");
                BADVALUE("sys_ticrate", "0.0333333");
                BADCVAR("teamplay_mode");
                BADCVAR("timelimit_override");
-               BADPREFIX("g_warmup_");
+               BADPREFIX("g_warmup");
                BADPREFIX("sv_info_");
                BADPREFIX("sv_ready_restart_");
  
@@@ -593,15 -590,17 +594,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();
@@@ -628,8 -627,42 +631,42 @@@ STATIC_INIT_EARLY(maxclients
  
  void GameplayMode_DelayedInit(entity this)
  {
+       // at this stage team entities are spawned, teamplay contains the number of them
        if(!scores_initialized)
                ScoreRules_generic();
+       if (warmup_stage >= 0 && autocvar_g_maxplayers >= 0)
+               return;
+       if (!g_duel)
+               MapReadSizes(mapname);
+       if (autocvar_g_maxplayers < 0 && teamplay)
+       {
+               // automatic maxplayers should be a multiple of team count
+               if (map_maxplayers == 0 || map_maxplayers > maxclients)
+                       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;
+       }
+       if (warmup_stage < 0)
+       {
+               int m = GetPlayerLimit();
+               if (m <= 0) m = maxclients;
+               map_minplayers = bound(max(2, AVAILABLE_TEAMS * 2), map_minplayers, m);
+               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;
+               }
+               warmup_limit = -1;
+       }
+       else
+               map_minplayers = 0; // don't display a minimum if it's not used
  }
  
  void InitGameplayMode()
        // 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
  bool world_already_spawned;
  spawnfunc(worldspawn)
  {
+       cvar_set("_endmatch", "0");
        server_is_dedicated = boolean(stof(cvar_defstring("is_dedicated")));
  
        if (autocvar_sv_termsofservice_url && autocvar_sv_termsofservice_url != "")
        GameRules_limit_fallbacks();
  
        if(warmup_limit == 0)
-               warmup_limit = (autocvar_timelimit > 0) ? autocvar_timelimit * 60 : autocvar_timelimit;
+               warmup_limit = autocvar_timelimit * 60;
  
        player_count = 0;
        bot_waypoints_for_items = autocvar_g_waypoints_for_items;
        MapInfo_Enumerate();
        MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags(), 1);
  
-       q3compat = BITSET(q3compat, Q3COMPAT_ARENA, fexists(strcat("scripts/", mapname, ".arena")));
-       q3compat = BITSET(q3compat, Q3COMPAT_DEFI, fexists(strcat("scripts/", mapname, ".defi")));
+       q3compat = BITSET(q3compat, Q3COMPAT_ARENA, _MapInfo_FindArenaFile(mapname, ".arena") != "");
+       q3compat = BITSET(q3compat, Q3COMPAT_DEFI, _MapInfo_FindArenaFile(mapname, ".defi") != "");
+       // quake 3 music support
+       if(world.music || world.noise)
+       {
+               // prefer .music over .noise
+               string chosen_music;
+               if(world.music)
+                       chosen_music = world.music;
+               else
+                       chosen_music = world.noise;
+               string newstuff = strcat(clientstuff, "cd loop \"", chosen_music, "\"\n");
+               strcpy(clientstuff, newstuff);
+       }
  
        if(whichpack(strcat("maps/", mapname, ".cfg")) != "")
        {
@@@ -998,59 -1049,59 +1053,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;
                                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)
@@@ -2044,6 -2095,7 +2099,7 @@@ void readplayerstartcvars(
  
  void readlevelcvars()
  {
+       serverflags &= ~SERVERFLAG_ALLOW_FULLBRIGHT;
        if(cvar("sv_allow_fullbright"))
                serverflags |= SERVERFLAG_ALLOW_FULLBRIGHT;
  
        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");
  
  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
+ // ported from VM_SV_droptofloor TODO: make a common function for the client-side?
  void DropToFloor_Handler(entity this)
  {
-       WITHSELF(this, builtin_droptofloor());
+       if(!this || wasfreed(this))
+       {
+               // no modifying free entities
+               return;
+       }
+       vector end = this.origin - '0 0 256';
+       // NOTE: NudgeOutOfSolid support is not added as Xonotic's physics do not use it!
+       //if(autocvar_sv_gameplayfix_droptofloorstartsolid_nudgetocorrect)
+               //SV_NudgeOutOfSolid(this);
+       tracebox(this.origin, this.mins, this.maxs, end, MOVE_NORMAL, this);
+       if(trace_startsolid && autocvar_sv_gameplayfix_droptofloorstartsolid)
+       {
+               vector offset, org;
+               offset = 0.5 * (this.mins + this.maxs);
+               offset.z = this.mins.z;
+               org = this.origin + offset;
+               traceline(org, end, MOVE_NORMAL, this);
+               trace_endpos = trace_endpos - offset;
+               if(trace_startsolid)
+               {
+                       LOG_DEBUGF("DropToFloor_Handler: %v could not fix badly placed entity", this.origin);
+                       _Movetype_LinkEdict(this, false);
+                       SET_ONGROUND(this);
+                       this.groundentity = NULL;
+               }
+               else if(trace_fraction < 1)
+               {
+                       LOG_DEBUGF("DropToFloor_Handler: %v fixed badly placed entity", this.origin);
+                       //if(autocvar_sv_gameplayfix_droptofloorstartsolid_nudgetocorrect)
+                               //SV_NudgeOutOfSolid(this);
+                       setorigin(this, trace_endpos);
+                       SET_ONGROUND(this);
+                       this.groundentity = trace_ent;
+                       // if support is destroyed, keep suspended (gross hack for floating items in various maps)
+                       this.move_suspendedinair = true;
+               }
+       }
+       else
+       {
+               if(!trace_allsolid && trace_fraction < 1)
+               {
+                       setorigin(this, trace_endpos);
+                       SET_ONGROUND(this);
+                       this.groundentity = trace_ent;
+                       // if support is destroyed, keep suspended (gross hack for floating items in various maps)
+                       this.move_suspendedinair = true;
+               }
+       }
        this.dropped_origin = this.origin;
  }