]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge branch 'bones_was_here/playernums' into 'master'
authorterencehill <piuntn@gmail.com>
Fri, 30 Sep 2022 13:55:08 +0000 (13:55 +0000)
committerterencehill <piuntn@gmail.com>
Fri, 30 Sep 2022 13:55:08 +0000 (13:55 +0000)
Automatic per-map min & max player limits, many warmup and player count and Welcome message things

See merge request xonotic/xonotic-data.pk3dir!1022

32 files changed:
hud_luma.cfg
hud_luminos.cfg
notifications.cfg
qcsrc/client/announcer.qc
qcsrc/client/hud/panel/infomessages.qc
qcsrc/client/hud/panel/infomessages.qh
qcsrc/client/hud/panel/scoreboard.qc
qcsrc/client/hud/panel/scoreboard.qh
qcsrc/client/hud/panel/timer.qc
qcsrc/client/main.qc
qcsrc/client/main.qh
qcsrc/common/gamemodes/gamemode/race/cl_race.qc
qcsrc/common/gamemodes/sv_rules.qc
qcsrc/common/mapinfo.qc
qcsrc/common/mapinfo.qh
qcsrc/common/mutators/mutator/hook/cl_hook.qc
qcsrc/common/mutators/mutator/nades/nades.qc
qcsrc/common/mutators/mutator/offhand_blaster/cl_offhand_blaster.qc
qcsrc/common/notifications/all.inc
qcsrc/server/bot/default/bot.qc
qcsrc/server/client.qc
qcsrc/server/command/cmd.qc
qcsrc/server/command/common.qc
qcsrc/server/command/vote.qc
qcsrc/server/command/vote.qh
qcsrc/server/intermission.qc
qcsrc/server/scores_rules.qc
qcsrc/server/scores_rules.qh
qcsrc/server/teamplay.qc
qcsrc/server/world.qc
qcsrc/server/world.qh
xonotic-server.cfg

index 0903a0584b40b7ed482ec29efc79fbff5ae86966..384024e34536a8f91e2cd0941f5f20d32d4c3ef3 100644 (file)
@@ -133,8 +133,8 @@ seta hud_panel_notify_time "10"
 seta hud_panel_notify_fadetime "3"
 seta hud_panel_notify_icon_aspect "1"
 
-seta hud_panel_timer_pos "0.455000 0"
-seta hud_panel_timer_size "0.090000 0.050000"
+seta hud_panel_timer_pos "0.450000 0"
+seta hud_panel_timer_size "0.100000 0.050000"
 seta hud_panel_timer_bg "border_plain_north"
 seta hud_panel_timer_bg_color ""
 seta hud_panel_timer_bg_color_team ""
index a0849e5aabf9c4c55acf5cd00264f77f6830d5c4..82b07077fecd7da6dda4d5cc8bcd50176be1aee0 100644 (file)
@@ -134,7 +134,7 @@ seta hud_panel_notify_fadetime "3"
 seta hud_panel_notify_icon_aspect "2"
 
 seta hud_panel_timer_pos "0.790000 0.040000"
-seta hud_panel_timer_size "0.090000 0.050000"
+seta hud_panel_timer_size "0.100000 0.050000"
 seta hud_panel_timer_bg "border_small_timer"
 seta hud_panel_timer_bg_color ""
 seta hud_panel_timer_bg_color_team ""
index 1919601d57e49a7d0e20ec0181c64ea9a613183b..9088d8eafff13d0ed398fcfb90fac022cbb5b8e7 100644 (file)
@@ -93,12 +93,14 @@ seta notification_ANNCE_VOTE_ACCEPT "2" "0 = disabled, 1 = enabled if gentle mod
 seta notification_ANNCE_VOTE_CALL "2" "0 = disabled, 1 = enabled if gentle mode is off, 2 = always enabled"
 seta notification_ANNCE_VOTE_FAIL "2" "0 = disabled, 1 = enabled if gentle mode is off, 2 = always enabled"
 
-// MSG_INFO notifications (count = 335):
+// MSG_INFO notifications (count = 337):
 seta notification_INFO_CA_JOIN_LATE "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
 seta notification_INFO_CA_LEAVE "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
 seta notification_INFO_CHAT_NOSPECTATORS "2" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
 seta notification_INFO_COINTOSS "2" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
 seta notification_INFO_CONNECTING "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
+seta notification_INFO_COUNTDOWN_RESTART "2" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
+seta notification_INFO_COUNTDOWN_STOP "2" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
 seta notification_INFO_CTF_CAPTURE_BROKEN "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
 seta notification_INFO_CTF_CAPTURE_NEUTRAL "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
 seta notification_INFO_CTF_CAPTURE "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
@@ -343,7 +345,7 @@ seta notification_INFO_WEAPON_TUBA_SUICIDE "1" "0 = off, 1 = print to console, 2
 seta notification_INFO_WEAPON_VAPORIZER_MURDER "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
 seta notification_INFO_WEAPON_VORTEX_MURDER "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
 
-// MSG_CENTER notifications (count = 242):
+// MSG_CENTER notifications (count = 243):
 seta notification_CENTER_ALONE "1" "0 = off, 1 = centerprint"
 seta notification_CENTER_ASSAULT_ATTACKING "1" "0 = off, 1 = centerprint"
 seta notification_CENTER_ASSAULT_DEFENDING "1" "0 = off, 1 = centerprint"
@@ -354,6 +356,7 @@ seta notification_CENTER_COUNTDOWN_BEGIN "1" "0 = off, 1 = centerprint"
 seta notification_CENTER_COUNTDOWN_GAMESTART "1" "0 = off, 1 = centerprint"
 seta notification_CENTER_COUNTDOWN_ROUNDSTART "1" "0 = off, 1 = centerprint"
 seta notification_CENTER_COUNTDOWN_ROUNDSTOP "1" "0 = off, 1 = centerprint"
+seta notification_CENTER_COUNTDOWN_STOP "1" "0 = off, 1 = centerprint"
 seta notification_CENTER_CTF_CAPTURESHIELD_FREE "1" "0 = off, 1 = centerprint"
 seta notification_CENTER_CTF_CAPTURESHIELD_INACTIVE "1" "0 = off, 1 = centerprint"
 seta notification_CENTER_CTF_CAPTURESHIELD_SHIELDED "1" "0 = off, 1 = centerprint"
@@ -539,7 +542,9 @@ seta notification_CENTER_VEHICLE_STEAL "1" "0 = off, 1 = centerprint"
 seta notification_CENTER_VEHICLE_STEAL_SELF "1" "0 = off, 1 = centerprint"
 seta notification_CENTER_WEAPON_MINELAYER_LIMIT "1" "0 = off, 1 = centerprint"
 
-// MSG_MULTI notifications (count = 157):
+// MSG_MULTI notifications (count = 158):
+seta notification_COUNTDOWN_BEGIN "1" "Enable this multiple notification"
+seta notification_COUNTDOWN_STOP "1" "Enable this multiple notification"
 seta notification_DEATH_MURDER_BUFF "1" "Enable this multiple notification"
 seta notification_DEATH_MURDER_CHEAT "1" "Enable this multiple notification"
 seta notification_DEATH_MURDER_DROWN "1" "Enable this multiple notification"
@@ -632,7 +637,6 @@ seta notification_ITEM_WEAPON_NOAMMO "1" "Enable this multiple notification"
 seta notification_ITEM_WEAPON_PRIMORSEC "1" "Enable this multiple notification"
 seta notification_ITEM_WEAPON_UNAVAILABLE "1" "Enable this multiple notification"
 seta notification_MULTI_COINTOSS "1" "Enable this multiple notification"
-seta notification_MULTI_COUNTDOWN_BEGIN "1" "Enable this multiple notification"
 seta notification_MULTI_INSTAGIB_FINDAMMO "1" "Enable this multiple notification"
 seta notification_WEAPON_ACCORDEON_MURDER "1" "Enable this multiple notification"
 seta notification_WEAPON_ACCORDEON_SUICIDE "1" "Enable this multiple notification"
@@ -749,4 +753,4 @@ seta notification_show_sprees_info "3" "Show spree information in MSG_INFO messa
 seta notification_show_sprees_info_newline "1" "Show attacker spree information for MSG_INFO messages on a separate line than the death notification itself"
 seta notification_show_sprees_info_specialonly "1" "Don't show attacker spree information in MSG_INFO messages if it isn't an achievement"
 
-// Notification counts (total = 842): MSG_ANNCE = 80, MSG_INFO = 335, MSG_CENTER = 242, MSG_MULTI = 157, MSG_CHOICE = 28
+// Notification counts (total = 846): MSG_ANNCE = 80, MSG_INFO = 337, MSG_CENTER = 243, MSG_MULTI = 158, MSG_CHOICE = 28
index e15bc23b7e23c62251f9af84547d070bb3accc5a..70834c509b3c32dcf3d4393f279d0e3386c811c3 100644 (file)
@@ -78,7 +78,7 @@ void Announcer_Countdown(entity this)
        if(countdown <= 0) // countdown has finished, starttime is now
        {
                Local_Notification(MSG_CENTER, CENTER_COUNTDOWN_BEGIN);
-               Local_Notification(MSG_MULTI, MULTI_COUNTDOWN_BEGIN);
+               Local_Notification(MSG_MULTI, COUNTDOWN_BEGIN);
                delete(this);
                announcer_countdown = NULL;
                Announcer_ClearTitle();
@@ -130,7 +130,7 @@ void Announcer_Gamestart()
        float roundstarttime = STAT(ROUNDSTARTTIME);
        if(roundstarttime > startTime)
                startTime = roundstarttime;
-       if(intermission)
+       if(intermission || warmup_stage)
        {
                Announcer_ClearTitle();
                if(announcer_countdown)
index 554a05a475259ec38ef52300bcf81649e4f77af2..0f0325eeba18bfd3b1a429422c7cbd41071120e2 100644 (file)
@@ -150,7 +150,16 @@ void HUD_InfoMessages()
                else
                        blinkcolor = "^3";
 
-               if(ready_waiting && !spectatee_status)
+               if(warmup_stage && STAT(WARMUP_TIMELIMIT) <= 0 && srv_minplayers)
+               {
+                       Scoreboard_UpdatePlayerTeams(); // ensure numplayers is current
+                       if(srv_minplayers - numplayers == 1)
+                               s = _("^31^2 more player is needed for the match to start.");
+                       else
+                               s = sprintf(_("^3%d^2 more players are needed for the match to start."), srv_minplayers - numplayers);
+                       InfoMessage(s);
+               }
+               else if(ready_waiting && !spectatee_status)
                {
                        if(ready_waiting_for_me)
                        {
index 197662640dc24a9994ac5d02c5251f4a89be88fd..880f5930c8c4d27250c6b2fb224072dc40507893 100644 (file)
@@ -1,5 +1,6 @@
 #pragma once
 #include "../panel.qh"
+#include "scoreboard.qh" // for Scoreboard_UpdatePlayerTeams()
 
 bool autocvar_hud_panel_infomessages;
 bool autocvar_hud_panel_infomessages_dynamichud = false;
index 83c487419b7fe00a35b053594abbec9fca64a095..727fc411be32e111647544076b574e410d2baf2b 100644 (file)
@@ -547,9 +547,11 @@ void Scoreboard_UpdatePlayerTeams()
        update_time = time;
 
        entity pl, tmp;
+       numplayers = 0;
        //int num = 0;
        for(pl = players.sort_next; pl; pl = pl.sort_next)
        {
+               numplayers += pl.team != NUM_SPECTATOR;
                //num += 1;
                int Team = entcs_GetScoreTeam(pl.sv_entnum);
                if(SetTeam(pl, Team))
@@ -2330,8 +2332,9 @@ void Scoreboard_Draw()
                        }
                }
                drawcolorcodedstring(pos + '1 0 0' * (panel_size.x - stringwidth(str, true, sb_gameinfo_detail_fontsize)), str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align right
-               // map name
-               str = sprintf(_("^7Map: ^2%s"), shortmapname);
+               // map name and player count
+               str = sprintf(_("^5%d^7/^5%d ^7players"), numplayers, srv_maxplayers ? srv_maxplayers : maxclients);
+               str = strcat("^7", _("Map:"), " ^2", mi_shortname, "    ", str); // reusing "Map:" translatable string
                drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
        }
        // End of Game Info Section
index dbf54e4b2eed0bf05076695dae330f22c7156602..2e13de65ba91c5cd1007bee5f1b9f685198cc89b 100644 (file)
@@ -17,6 +17,8 @@ float scoreboard_bottom;
 float scoreboard_left;
 float scoreboard_right;
 
+int numplayers;
+
 void Cmd_Scoreboard_SetFields(int argc);
 void Scoreboard_Draw();
 void Scoreboard_InitScores();
index 68e7312c7a5ba881ee1f45a000e91f64219ea7fb..00a231b7cf44607d70fd56e338784f2fe3fdbdb5 100644 (file)
@@ -1,4 +1,5 @@
 #include "timer.qh"
+#include "scoreboard.qh"
 
 #include <client/draw.qh>
 #include <client/view.qh>
@@ -73,15 +74,9 @@ void HUD_Timer()
        // Use real or frozen time and get the time limit
        curtime = (intermission_time ? intermission_time : time);
        if(warmup_stage)
-       {
                timelimit = STAT(WARMUP_TIMELIMIT);
-               if(timelimit == 0)
-                       timelimit = STAT(TIMELIMIT) * 60;
-       }
        else
-       {
                timelimit = STAT(TIMELIMIT) * 60;
-       }
 
        // Calculate time left
        timeleft = HUD_Timer_TimeLeft(curtime, STAT(GAMESTARTTIME), timelimit);
@@ -129,7 +124,12 @@ void HUD_Timer()
        int overtimes = STAT(OVERTIMES);
 
        if(warmup_stage || autocvar__hud_configure)
-               subtext = _("Warmup");
+       {
+               if (STAT(WARMUP_TIMELIMIT) > 0)
+                       subtext = _("Warmup");
+               else
+                       subtext = srv_minplayers ? _("Warmup: too few players") : _("Warmup: no time limit");
+       }
        else if(STAT(TIMEOUT_STATUS) == 2)
                subtext = _("Timeout");
        else if (overtimes == -1)
index 22f0438f411fd76aa4dda07953d3e09c3f1345ef..ff51fb54c5d3d2208c792280356e410ef4806776 100644 (file)
@@ -129,7 +129,6 @@ void CSQC_Init()
        {
                get_mi_min_max_texcoords(1); // try the CLEVER way first
                minimapname = strcat("gfx/", mi_shortname, "_radar");
-               shortmapname = mi_shortname;
 
                if (precache_pic(minimapname) == "")
                {
@@ -1420,34 +1419,56 @@ bool net_handle_ServerWelcome()
        }
 
        strcpy(hostname, ReadString());
-
        string hostversion = ReadString();
        bool version_mismatch = ReadByte();
        bool version_check = ReadByte();
-       string ver = GetVersionMessage(hostversion, version_mismatch, version_check);
-
+       srv_minplayers = ReadByte();
+       srv_maxplayers = ReadByte();
        string modifications = translate_modifications(ReadString());
        string weaponarena_list = translate_weaponarena(ReadString());
        string cache_mutatormsg = ReadString();
        string motd = ReadString();
 
-       string msg = "";
-       msg = strcat(msg, ver);
-       msg = strcat(msg, "^8\n\n", strcat(_("Gametype:"), " ^1", MapInfo_Type_ToText(gametype)), "^8\n");
+       string msg = GetVersionMessage(hostversion, version_mismatch, version_check);
+
+       msg = strcat(msg, "\n\n", _("Gametype:"), " ^1", MapInfo_Type_ToText(gametype), "\n");
+
+       msg = strcat(msg, "\n", _("Map:"), " ^2");
+       if (world.message == "")
+               msg = strcat(msg, mi_shortname, "\n");
+       else
+       {
+               int i = strstrofs(world.message, " by ", 0); // matches _MapInfo_Generate()
+               string longname = i >= 0 ? substring(world.message, 0, i) : world.message;
+               msg = strcat(msg, (strcasecmp(longname, mi_shortname) ? strcat(mi_shortname, " ^7// ^2") : ""), longname, "\n");
+       }
+
+       if (srv_minplayers || srv_maxplayers)
+       {
+               msg = strcat(msg, "\n", _("This match supports"), " ^5");
+               if (srv_minplayers == srv_maxplayers)
+                       msg = strcat(msg, sprintf(_("%d players"), srv_maxplayers), "\n");
+               else if (srv_minplayers && srv_maxplayers)
+                       msg = strcat(msg, sprintf(_("%d to %d players"), srv_minplayers, srv_maxplayers), "\n");
+               else if (srv_maxplayers)
+                       msg = strcat(msg, sprintf(_("%d players maximum"), srv_maxplayers), "\n");
+               else
+                       msg = strcat(msg, sprintf(_("%d players minimum"), srv_minplayers), "\n");
+       }
 
        modifications = cons_mid(modifications, ", ", weaponarena_list);
        if(modifications != "")
-               msg = strcat(msg, "^8\n", _("Active modifications:"), " ^3", modifications, "^8\n");
+               msg = strcat(msg, "\n", _("Active modifications:"), " ^3", modifications, "\n");
 
        if (cache_mutatormsg != "")
-               msg = strcat(msg, "\n\n^8", _("Special gameplay tips:"), " ^7", cache_mutatormsg);
+               msg = strcat(msg, "\n", _("Special gameplay tips:"), " ^7", cache_mutatormsg, "\n");
        string mutator_msg = "";
        MUTATOR_CALLHOOK(BuildGameplayTipsString, mutator_msg);
        mutator_msg = M_ARGV(0, string);
        msg = strcat(msg, mutator_msg); // trust that the mutator will do proper formatting
 
        if (motd != "")
-               msg = strcat(msg, "\n\n^8", _("MOTD:"), " ^7", motd);
+               msg = strcat(msg, "\n^9↓ ", _("Server's message"), " â†“\n", motd);
 
        strcpy(welcome_msg, msg);
        welcome_msg_menu_check_maxtime = time + 1; // wait for menu to load before showing the welcome dialog
index 4726fe8ad6c3bb3fb42ff27108e4ba6e57db7c59..1f1c29002dfcc3df1b4224c8566e350a007a8c60 100644 (file)
@@ -105,6 +105,8 @@ bool warmup_stage;
 
 string hostname;
 string welcome_msg;
+int srv_minplayers;
+int srv_maxplayers;
 float welcome_msg_menu_check_maxtime;
 void Welcome_Message_Show_Try();
 
@@ -175,9 +177,6 @@ float spectatee_status_changed_time;
 
 #define player_currententnum (spectatee_status > 0 ? spectatee_status : player_localnum + 1)
 
-// short mapname
-string shortmapname;
-
 // database for misc stuff
 int tempdb;
 int ClientProgsDB;
index a551b1546b6c5355e443d7dcc360c1fb22b75b88..fa9ecafedaaf220de23b2acf939615b1123d3157 100644 (file)
@@ -57,10 +57,10 @@ void HUD_Mod_Race(vector pos, vector mySize)
                rr = CTS_RECORD;
        else
                rr = RACE_RECORD;
-       float t = stof(db_get(ClientProgsDB, strcat(shortmapname, rr, "time")));
+       float t = stof(db_get(ClientProgsDB, strcat(mi_shortname, rr, "time")));
 
        if(score && (score < t || !t)) {
-               db_put(ClientProgsDB, strcat(shortmapname, rr, "time"), ftos(score));
+               db_put(ClientProgsDB, strcat(mi_shortname, rr, "time"), ftos(score));
                if(autocvar_cl_autodemo_delete_keeprecords)
                {
                        float f = autocvar_cl_autodemo_delete;
index 84d89909fe98274f47eb44e51999758f9371dbb6..d7f5006d45886d4677d75a13b80cb1aada70fb11 100644 (file)
@@ -7,7 +7,7 @@ void GameRules_teams(bool value)
 {
     if (value) {
         serverflags |= SERVERFLAG_TEAMPLAY;
-        teamplay = 1;
+        teamplay = 1;  // aka AVAILABLE_TEAMS, updated by ScoreRules_basics() after team ents spawn
         cvar_set("teamplay", "2");  // DP needs this for sending proper getstatus replies.
         Team_InitTeams();
         GameRules_spawning_teams(true);
index c0b67ff4d535e97e9752132fafd7134c28c9ae6d..23c8d8704e6438817176307e53076e779703c237 100644 (file)
@@ -1083,6 +1083,23 @@ int MapInfo_Get_ByName(string pFilename, float pAllowGenerate, Gametype pGametyp
        return r;
 }
 
+bool MapReadSizes(string map)
+{
+       // TODO: implement xonotic#28 / xonvote 172 (sizes in mapinfo)
+       string readsize_msg = strcat("MapReadSizes ", map);
+       float fh = fopen(strcat("maps/", map, ".sizes"), FILE_READ);
+       if(fh >= 0)
+       {
+               map_minplayers = stoi(fgets(fh));
+               map_maxplayers = stoi(fgets(fh));
+               fclose(fh);
+               LOG_TRACEF(readsize_msg, ": ok, min %d max %d", map_minplayers, map_maxplayers);
+               return true;
+       }
+       LOG_TRACE(readsize_msg, ": not found");
+       return false;
+}
+
 float MapInfo_FindName(string s)
 {
        // if there is exactly one map of prefix s, return it
index a7bf6ce0aab77fdcb4718c0f084998c30da9f517..c2bb5cf449cf2e930941aec58747ad31b9c3e27e 100644 (file)
@@ -168,6 +168,11 @@ string MapInfo_BSPName_ByID(float i);
 // load info about a map by name into the MapInfo_Map_* globals
 int MapInfo_Get_ByName(string s, float allowGenerate, Gametype gametypeToSet); // 1 on success, 0 on failure, 2 if it autogenerated a mapinfo file
 
+// load map-specific player limits
+int map_minplayers;
+int map_maxplayers;
+bool MapReadSizes(string map);
+
 // look for a map by a prefix, returns the actual map name on success, string_null on failure or ambigous match
 string MapInfo_FindName_match; // the name of the map that was found
 float MapInfo_FindName_firstResult; // -1 if none were found, index of first one if not unique but found (FindName then returns -1)
index be88853bbf37e2e2cb312367ff86ed90becd8c45..aa055666b2c052453ee85226084f130e89bae1dc 100644 (file)
@@ -9,7 +9,7 @@ MUTATOR_HOOKFUNCTION(cl_hook, BuildGameplayTipsString)
        {
                string key = getcommandkey(_("off-hand hook"), "+hook");
                M_ARGV(0, string) = strcat(M_ARGV(0, string),
-                       "\n\n", sprintf(_("^3grappling hook^8 is enabled, press ^3%s^8 to use it"), key), "\n");
+                       "\n", sprintf(_("^3grappling hook^8 is enabled, press ^3%s^8 to use it"), key), "\n");
        }
 }
 
index 7f0c8b94ad6d2c3d7924230e818f367860232103..1707e168b3af7163cd3c6f2fd39fde4efab2309d 100644 (file)
@@ -101,7 +101,7 @@ MUTATOR_HOOKFUNCTION(cl_nades, BuildGameplayTipsString)
        {
                string key = getcommandkey(_("drop weapon / throw nade"), "dropweapon");
                M_ARGV(0, string) = strcat(M_ARGV(0, string),
-                       "\n\n", sprintf(_("^3nades^8 are enabled, press ^3%s^8 to use them"), key), "\n");
+                       "\n", sprintf(_("^3nades^8 are enabled, press ^3%s^8 to use them"), key), "\n");
        }
 }
 
index f6b15aeda92f279e9e6172039205972eb9fd3e19..dd30807720d4fc40b392fbf2d3377bfc3083a9ec 100644 (file)
@@ -8,6 +8,6 @@ MUTATOR_HOOKFUNCTION(cl_offhand_blaster, BuildGameplayTipsString)
        {
                string key = getcommandkey(_("off-hand hook"), "+hook");
                M_ARGV(0, string) = strcat(M_ARGV(0, string),
-                       "\n\n", sprintf(_("^3offhand blaster^8 is enabled, press ^3%s^8 to use it"), key), "\n");
+                       "\n", sprintf(_("^3offhand blaster^8 is enabled, press ^3%s^8 to use it"), key), "\n");
        }
 }
index 73c858fcfb8987e83a37357dd44db6aa605c4320..360a3439a61478ac7d649a0c2e26ad247239c37f 100644 (file)
@@ -258,6 +258,9 @@ string multiteam_info_sprintf(string input, string teamname) { return ((input !=
     MSG_INFO_NOTIF(CA_JOIN_LATE,                            N_CONSOLE,  0, 0, "", "",       "",     _("^F1Round already started, you will join the game in the next round"), "")
     MSG_INFO_NOTIF(CA_LEAVE,                                N_CONSOLE,  0, 0, "", "",       "",     _("^F2You will spectate in the next round"), "")
 
+    MSG_INFO_NOTIF(COUNTDOWN_RESTART,                       N_CHATCON,  0, 0, "", "",       "",     _("^F2Match is restarting..."), "")
+    MSG_INFO_NOTIF(COUNTDOWN_STOP,                          N_CHATCON,  0, 0, "", "",       "",     _("^F4Countdown stopped!"), "")
+
     MSG_INFO_NOTIF(DEATH_MURDER_BUFF,                       N_CONSOLE,  3, 3, "spree_inf s1 s2 f3buffname s3loc spree_end", "s2 s1",    "notify_death",         _("^BG%s%s^K1 was killed by ^BG%s^K1's ^BG%s^K1 buff ^K1%s%s"), _("^BG%s%s^K1 was scored against by ^BG%s^K1's ^BG%s^K1 buff ^K1%s%s"))
     MSG_INFO_NOTIF(DEATH_MURDER_CHEAT,                      N_CONSOLE,  3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1",               "notify_death",         _("^BG%s%s^K1 was unfairly eliminated by ^BG%s^K1%s%s"), "")
     MSG_INFO_NOTIF(DEATH_MURDER_DROWN,                      N_CONSOLE,  3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1",               "notify_water",         _("^BG%s%s^K1 was drowned by ^BG%s^K1%s%s"), "")
@@ -527,6 +530,7 @@ string multiteam_info_sprintf(string input, string teamname) { return ((input !=
     MSG_CENTER_NOTIF(ASSAULT_DEFENDING,                 N_ENABLE,    0, 0, "",               CPID_ASSAULT_ROLE,      "0 0",  _("^BGYou are defending!"), "")
     MSG_CENTER_NOTIF(ASSAULT_OBJ_DESTROYED,             N_ENABLE,    0, 1, "f1time",         CPID_ASSAULT_ROLE,      "0 0",  _("^BGObjective destroyed in ^F4%s^BG!"), "")
 
+    MSG_CENTER_NOTIF(COUNTDOWN_STOP,                    N_ENABLE,    0, 1, "f1",             CPID_MISSING_PLAYERS,   "4 0",  strcat(BOLD(_("^F4Countdown stopped!")), "\n^BG", _("%s players are needed for this match.")), "")
     MSG_CENTER_NOTIF(COUNTDOWN_BEGIN,                   N_ENABLE,    0, 0, "",               CPID_ROUND,             "2 0",  BOLD(_("^BGBegin!")), "")
     MSG_CENTER_NOTIF(COUNTDOWN_GAMESTART,               N_ENABLE,    0, 1, "",               CPID_ROUND,             "1 f1", strcat(_("^BGGame starts in"), "\n", BOLD("^COUNT")), "")
     MSG_CENTER_NOTIF(COUNTDOWN_ROUNDSTART,              N_ENABLE,    0, 2, "f1",             CPID_ROUND,             "1 f2", strcat(_("^BGRound %s starts in"), "\n", BOLD("^COUNT")), "")
@@ -689,7 +693,7 @@ string multiteam_info_sprintf(string input, string teamname) { return ((input !=
     MSG_CENTER_NOTIF(ITEM_WEAPON_UNAVAILABLE,           N_ENABLE,    0, 1, "item_wepname",                       CPID_ITEM, "item_centime 0", _("^F1%s^BG is ^F4not available^BG on this map"), "")
 
     MSG_CENTER_NOTIF(JOIN_NOSPAWNS,                     N_ENABLE,    0, 0, "",               CPID_PREVENT_JOIN,      "0 0",  _("^K1No spawnpoints available!\nHope your team can fix it..."), "")
-    MSG_CENTER_NOTIF(JOIN_PREVENT,                      N_ENABLE,    0, 0, "",               CPID_PREVENT_JOIN,      "0 0",  _("^K1You may not join the game at this time.\nThe player limit reached maximum capacity."), "")
+    MSG_CENTER_NOTIF(JOIN_PREVENT,                      N_ENABLE,    0, 1, "f1",             CPID_PREVENT_JOIN,      "0 0",  _("^K1You may not join the game at this time.\nThis match is limited to ^F2%s^BG players."), "")
 
     MSG_CENTER_NOTIF(KEEPAWAY_DROPPED,                  N_ENABLE,    1, 0, "s1",             CPID_KEEPAWAY,          "0 0",  _("^BG%s^BG has dropped the ball!"), "")
     MSG_CENTER_NOTIF(KEEPAWAY_PICKUP,                   N_ENABLE,    1, 0, "s1",             CPID_KEEPAWAY,          "0 0",  _("^BG%s^BG has picked up the ball!"), "")
@@ -793,6 +797,9 @@ string multiteam_info_sprintf(string input, string teamname) { return ((input !=
     MSG_MULTI_NOTIF(prefix##_PINK, defaultvalue, anncepre##PINK, infopre##PINK, centerpre##PINK)
 
 // MSG_MULTI_NOTIFICATIONS
+    MSG_MULTI_NOTIF(COUNTDOWN_BEGIN,                    N_ENABLE,  ANNCE_BEGIN,    NULL,                                   CENTER_COUNTDOWN_BEGIN)
+    MSG_MULTI_NOTIF(COUNTDOWN_STOP,                     N_ENABLE,  NULL,           INFO_COUNTDOWN_STOP,                    CENTER_COUNTDOWN_STOP)
+
     MSG_MULTI_NOTIF(DEATH_MURDER_BUFF,                  N_ENABLE,  NULL,           INFO_DEATH_MURDER_BUFF,                 NULL)
     MSG_MULTI_NOTIF(DEATH_MURDER_CHEAT,                 N_ENABLE,  NULL,           INFO_DEATH_MURDER_CHEAT,                NULL)
     MSG_MULTI_NOTIF(DEATH_MURDER_DROWN,                 N_ENABLE,  NULL,           INFO_DEATH_MURDER_DROWN,                NULL)
@@ -888,7 +895,6 @@ string multiteam_info_sprintf(string input, string teamname) { return ((input !=
     MSG_MULTI_NOTIF(ITEM_WEAPON_UNAVAILABLE,            N_ENABLE,  NULL,           INFO_ITEM_WEAPON_UNAVAILABLE,           CENTER_ITEM_WEAPON_UNAVAILABLE)
 
     MSG_MULTI_NOTIF(MULTI_COINTOSS,                     N_ENABLE,  NULL,           INFO_COINTOSS,                          CENTER_COINTOSS)
-    MSG_MULTI_NOTIF(MULTI_COUNTDOWN_BEGIN,              N_ENABLE,  ANNCE_BEGIN,    NULL,                                   CENTER_COUNTDOWN_BEGIN)
     MSG_MULTI_NOTIF(MULTI_INSTAGIB_FINDAMMO,            N_ENABLE,  ANNCE_NUM_10,   NULL,                                   CENTER_INSTAGIB_FINDAMMO_FIRST)
 
     MSG_MULTI_NOTIF(WEAPON_ACCORDEON_MURDER,            N_ENABLE,  NULL,           INFO_WEAPON_ACCORDEON_MURDER,           NULL)
index 79b3a96924754bf066ef762b7d602c03faa9ca8b..01a66b2c12587764d8bd1f94f1050b46db0ba9f7 100644 (file)
@@ -170,7 +170,7 @@ void bot_setnameandstuff(entity this)
                int smallest_count = -1;
                if (teamplay)
                {
-                       for (int i = 1; i <= AvailableTeams(); ++i)
+                       for (int i = 1; i <= AVAILABLE_TEAMS; ++i)
                        {
                                // NOTE if (autocvar_g_campaign && autocvar_g_campaign_forceteam == i)
                                // TeamBalance_GetNumberOfPlayers(balance, i); returns the number of players + 1
@@ -210,7 +210,7 @@ void bot_setnameandstuff(entity this)
                        });
                        if (!conflict)
                                prio += 1;
-                       if (teamplay && !(autocvar_bot_vs_human && AvailableTeams() == 2))
+                       if (teamplay && !(autocvar_bot_vs_human && AVAILABLE_TEAMS == 2))
                        {
                                int forced_team = stof(argv(5));
                                if (!Team_IsValidIndex(forced_team))
@@ -242,7 +242,7 @@ void bot_setnameandstuff(entity this)
        if(argv(4) != "" && stof(argv(4)) >= 0) bot_pants = argv(4);
        else bot_pants = ftos(floor(random() * 15));
 
-       if (teamplay && !(autocvar_bot_vs_human && AvailableTeams() == 2))
+       if (teamplay && !(autocvar_bot_vs_human && AVAILABLE_TEAMS == 2))
        {
                this.bot_forced_team = stof(argv(5));
                if (!Team_IsValidIndex(this.bot_forced_team))
@@ -611,13 +611,13 @@ bool bot_fixcount(bool multiple_per_frame)
        // But don't remove bots immediately on level change, as the real players
        // usually haven't rejoined yet
        bots_would_leave = false;
-       if (teamplay && autocvar_bot_vs_human && AvailableTeams() == 2)
+       if (autocvar_bot_vs_human && AVAILABLE_TEAMS == 2)
                bots = min(ceil(fabs(autocvar_bot_vs_human) * activerealplayers), maxclients - realplayers);
        else if ((realplayers || autocvar_bot_join_empty || (currentbots > 0 && time < 5)))
        {
                int minplayers = max(0, floor(autocvar_minplayers));
                if (teamplay)
-                       minplayers = max(0, floor(autocvar_minplayers_per_team) * AvailableTeams());
+                       minplayers = max(0, floor(autocvar_minplayers_per_team) * AVAILABLE_TEAMS);
                int minbots = max(0, floor(autocvar_bot_number));
 
                // add bots to reach minplayers if needed
index a1195d52cfb9ae579ec684b215e21aa3c6d60b21..eb2ba1dcf7234831965879998894cedc3b0d13d6 100644 (file)
@@ -256,7 +256,7 @@ void PutObserverInServer(entity this, bool is_forced, bool use_spawnpoint)
                {
                        if (vote_called) { VoteCount(false); }
                        this.ready = false;
-                       recount_ready = true;
+                       if (warmup_stage || game_starttime > time) recount_ready = true;
                }
                entcs_update_players(this);
        }
@@ -307,7 +307,7 @@ void PutObserverInServer(entity this, bool is_forced, bool use_spawnpoint)
 
        TRANSMUTE(Observer, this);
 
-       if(recount_ready) ReadyCount();
+       if(recount_ready) ReadyCount(); // FIXME: please add comment about why this is delayed
 
        WaypointSprite_PlayerDead(this);
        accuracy_resend(this);
@@ -809,6 +809,9 @@ void PutPlayerInServer(entity this)
                this.alivetime = time;
 
        antilag_clear(this, CS(this));
+
+       if (warmup_stage == -1)
+               ReadyCount();
 }
 
 /** Called when a client spawns in the server */
@@ -1037,6 +1040,8 @@ void SendWelcomeMessage(entity this, int msg_type)
        WriteString(msg_type, autocvar_g_xonoticversion);
        WriteByte(msg_type, CS(this).version_mismatch);
        WriteByte(msg_type, (CS(this).version < autocvar_gameversion));
+       WriteByte(msg_type, map_minplayers);
+       WriteByte(msg_type, GetPlayerLimit());
 
        MUTATOR_CALLHOOK(BuildMutatorsPrettyString, "");
        string modifications = M_ARGV(0, string);
@@ -1150,7 +1155,7 @@ void ClientConnect(entity this)
        if (!autocvar_sv_foginterval && world.fog != "")
                stuffcmd(this, strcat("\nfog ", world.fog, "\nr_fog_exp2 0\nr_drawfog 1\n"));
 
-       if (autocvar_sv_teamnagger && !(autocvar_bot_vs_human && AvailableTeams() == 2))
+       if (autocvar_sv_teamnagger && !(autocvar_bot_vs_human && AVAILABLE_TEAMS == 2))
                if(!MUTATOR_CALLHOOK(HideTeamNagger, this))
                        send_CSQC_teamnagger();
 
@@ -1236,7 +1241,7 @@ void ClientDisconnect(entity this)
        if (this.personal) delete(this.personal);
 
        this.playerid = 0;
-       ReadyCount();
+       if (warmup_stage || game_starttime > time) ReadyCount();
        if (vote_called && IS_REAL_CLIENT(this)) VoteCount(false);
 
        player_powerups_remove_all(this); // stop powerup sound
@@ -1980,10 +1985,11 @@ int GetPlayerLimit()
 {
        if(g_duel)
                return 2; // TODO: this workaround is needed since the mutator hook from duel can't be activated before the gametype is loaded (e.g. switching modes via gametype vote screen)
-       int player_limit = autocvar_g_maxplayers;
+       // don't return map_maxplayers during intermission as it would interfere with MapHasRightSize()
+       int player_limit = (autocvar_g_maxplayers >= 0 || intermission_running) ? autocvar_g_maxplayers : map_maxplayers;
        MUTATOR_CALLHOOK(GetPlayerLimit, player_limit);
        player_limit = M_ARGV(0, int);
-       return player_limit;
+       return player_limit < maxclients ? player_limit : 0;
 }
 
 /**
@@ -2028,7 +2034,7 @@ int nJoinAllowed(entity this, entity ignore)
        static float msg_time = 0;
        if(this && !INGAME(this) && ignore && !free_slots && time > msg_time)
        {
-               Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_JOIN_PREVENT);
+               Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_JOIN_PREVENT, player_limit);
                msg_time = time + 0.5;
        }
 
index 552d870e6bc742a8d07615054b7c47f154aa5522..e2d71597f44ae1f20369a8b6c7e2e2c59f38c90e 100644 (file)
@@ -393,9 +393,7 @@ void ClientCommand_ready(entity caller, int request)
                                        }
 
                                        caller.last_ready = time;
-
-                                       // cannot reset the game while a timeout is active!
-                                       if (!timeout_status) ReadyCount();
+                                       ReadyCount();
                                }
                        }
                        return;  // never fall through to usage
index af7aeb059a63f9c438a65b06dd7cb8375a833b75..6138bd8b5b4734ed0222d0eedbbcade4b2904aeb 100644 (file)
@@ -187,6 +187,10 @@ void timeout_handler_reset(entity this)
        timeout_leadtime = 0;
 
        delete(this);
+
+       // ReadyCount() does nothing when a timeout is active or pending
+       // so check readiness now to support g_warmup_allow_timeout
+       if (warmup_stage) ReadyCount();
 }
 
 void timeout_handler_think(entity this)
index 12e130c9e80ddf25aa17ba4da4583762ddcb95bd..de5524526c9585b00d347a246431c6e41c2d7d0a 100644 (file)
@@ -429,7 +429,7 @@ void ReadyRestart_force(bool is_fake_round_start)
        if (time <= game_starttime && game_stopped)
                return;
        if (!is_fake_round_start)
-               bprint("^1Match is restarting...\n");
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_COUNTDOWN_RESTART);
 
        VoteReset();
 
@@ -503,23 +503,53 @@ void ReadyRestart(bool forceWarmupEnd)
        ReadyRestart_force(false);
 }
 
-// Count the players who are ready and determine whether or not to restart the match
+/* Count the players who are ready and determine whether or not to restart the match when:
+ * a player presses F4                      server/command/cmd.qc  ClientCommand_ready()
+ * a player switches from players to specs  server/client.qc       PutObserverInServer()
+ * a player joins (from specs or directly)  server/client.qc       PutPlayerInServer()
+ * a player disconnects                     server/client.qc       ClientDisconnect()  */
 void ReadyCount()
 {
+       // cannot reset the game while a timeout is active or pending
+       if (timeout_status) return;
+
        float ready_needed_factor, ready_needed_count;
-       float t_ready = 0, t_players = 0;
+       float t_players = 0;
+       readycount = 0;
 
        FOREACH_CLIENT(IS_REAL_CLIENT(it) && (IS_PLAYER(it) || INGAME_JOINED(it)), {
                ++t_players;
-               if (it.ready) ++t_ready;
+               if (it.ready) ++readycount;
        });
 
-       readycount = t_ready;
-
        Nagger_ReadyCounted();
 
-       ready_needed_factor = bound(0.5, cvar("g_warmup_majority_factor"), 0.999);
-       ready_needed_count = floor(t_players * ready_needed_factor) + 1;
+       if (t_players < map_minplayers) // map_minplayers will only be set if g_warmup -1 at worldspawn
+       {
+               if (game_starttime > time) // someone bailed during countdown, back to warmup
+               {
+                       warmup_stage = -1; // CAN change it AFTER calling Nagger_ReadyCounted() this frame
+                       game_starttime = time;
+                       Send_Notification(NOTIF_ALL, NULL, MSG_MULTI, COUNTDOWN_STOP, map_minplayers);
+               }
+               if (warmup_limit > 0)
+                       warmup_limit = -1;
+               return; // don't ReadyRestart if players are ready but too few
+       }
+       else if (map_minplayers && warmup_limit <= 0)
+       {
+               // there's enough players now but we're still in infinite warmup
+               warmup_limit = cvar("g_warmup_limit");
+               if (warmup_limit == 0)
+                       warmup_limit = autocvar_timelimit * 60;
+               if (warmup_limit > 0)
+                       game_starttime = time;
+               // implicit else: g_warmup -1 && g_warmup_limit -1 means
+               // warmup continues until enough players AND enough RUPs (no time limit)
+       }
+
+       ready_needed_factor = bound(0.5, cvar("g_warmup_majority_factor"), 1);
+       ready_needed_count = ceil(t_players * ready_needed_factor);
 
        if (readycount >= ready_needed_count) ReadyRestart(true);
 }
index 7c96223a71318d736040fa6fa5d091275843c564..88e311beb670eb4581deec4f2ff2b12a72656af5 100644 (file)
@@ -62,11 +62,11 @@ void VoteCommand(int request, entity caller, int argc, string vote_command);
 // warmup and nagger stuff
 const float RESTART_COUNTDOWN = 10;
 entity nagger;
-float readycount;                  // amount of players who are ready
+int readycount;                    // amount of players who are ready
 .float ready;                      // flag for if a player is ready
 .float last_ready;                 // last ready time for anti-spam
 .int team_saved;                   // team number to restore upon map reset
-.void(entity this) reset;             // if set, an entity is reset using this
+.void(entity this) reset;          // if set, an entity is reset using this
 .void(entity this) reset2;         // if set, an entity is reset using this (after calling ALL the reset functions for other entities)
 void reset_map(float dorespawn, bool is_fake_round_start);
 void ReadyCount();
index c1a5d33d8feec8551c493fa78272fd832b946b62..b2652f7d8d9b27cc0da41d128d31bccf88383603 100644 (file)
@@ -54,7 +54,7 @@ bool MapHasRightSize(string map)
 {
        int minplayers = max(0, floor(autocvar_minplayers));
        if (teamplay)
-               minplayers = max(0, floor(autocvar_minplayers_per_team) * AvailableTeams());
+               minplayers = max(0, floor(autocvar_minplayers_per_team) * AVAILABLE_TEAMS);
        if (autocvar_g_maplist_check_waypoints
                && (currentbots || autocvar_bot_number || player_count < minplayers))
        {
@@ -71,32 +71,32 @@ bool MapHasRightSize(string map)
                return true;
 
        // open map size restriction file
-       string opensize_msg = strcat("opensize ", map);
-       float fh = fopen(strcat("maps/", map, ".sizes"), FILE_READ);
+       if(!MapReadSizes(map))
+               return true; // map has no size restrictions
+
+       string checksize_msg = strcat("MapHasRightSize ", map);
        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)
+       pcount -= rint(cvar("g_maplist_sizes_specparty") * pcount);
+
+       // ensure small maps can be selected when pcount is low
+       if(map_minplayers <= (_MapInfo_GetTeamPlayBool(MapInfo_CurrentGametype()) ? 4 : 2))
+               map_minplayers = 0;
+
+       if(pcount < map_minplayers)
        {
-               opensize_msg = strcat(opensize_msg, ": ok, ");
-               int mapmin = stoi(fgets(fh));
-               int mapmax = stoi(fgets(fh));
-               fclose(fh);
-               if(pcount < mapmin)
-               {
-                       LOG_TRACE(opensize_msg, "not enough");
-                       return false;
-               }
-               if(mapmax && pcount > mapmax)
-               {
-                       LOG_TRACE(opensize_msg, "too many");
-                       return false;
-               }
-               LOG_TRACE(opensize_msg, "right size");
-               return true;
+               LOG_TRACE(checksize_msg, ": not enough");
+               return false;
+       }
+       if(map_maxplayers && pcount > map_maxplayers)
+       {
+               LOG_TRACE(checksize_msg, ": too many");
+               return false;
        }
-       LOG_TRACE(opensize_msg, ": not found");
+       LOG_TRACE(checksize_msg, ": right size");
        return true;
 }
 
index 2749db38e63c081aa3aa9425c0e674c4e10c270e..64dfbb03b20529ceebfd1f7bbf1140fbe71e00b6 100644 (file)
@@ -19,11 +19,6 @@ int NumTeams(int teams)
        return boolean(teams & BIT(0)) + boolean(teams & BIT(1)) + boolean(teams & BIT(2)) + boolean(teams & BIT(3));
 }
 
-int AvailableTeams()
-{
-       return NumTeams(ScoreRules_teams);
-}
-
 // NOTE: ST_constants may not be >= MAX_TEAMSCORE
 // scores that should be in all modes:
 void ScoreRules_basics(int teams, float sprio, float stprio, float score_enabled)
@@ -35,6 +30,7 @@ void ScoreRules_basics(int teams, float sprio, float stprio, float score_enabled
                ScoreInfo_SetLabel_TeamScore(i, "", 0);
 
        ScoreRules_teams = teams;
+       AVAILABLE_TEAMS = NumTeams(teams);
 
        if(score_enabled)
                ScoreInfo_SetLabel_TeamScore(ST_SCORE, "score", stprio);
index 921864d2622bca71412fcff9bcc3cd186ac8a0d6..2978c217ef4d18ccceffdaaf8c846f8f4dc89911 100644 (file)
@@ -2,7 +2,8 @@
 
 bool IsTeamAvailable(int team_num);
 int NumTeams(int teams);
-int AvailableTeams();
 void ScoreRules_basics(int teams, float sprio, float stprio, float score_enabled);
 void ScoreRules_basics_end();
 void ScoreRules_generic();
+
+#define AVAILABLE_TEAMS teamplay
index 0d248ca1382290ab28885e07fa73a0c47ad3eaee..c7c939d74d6e4f3c553f958b611f8008aec2c1a3 100644 (file)
@@ -527,7 +527,7 @@ entity TeamBalance_CheckAllowedTeams(entity for_whom)
        }
 
        // TODO: Balance quantity of bots across > 2 teams when bot_vs_human is set (and remove next line)
-       if (autocvar_bot_vs_human && AvailableTeams() == 2 && for_whom)
+       if (autocvar_bot_vs_human && AVAILABLE_TEAMS == 2 && for_whom)
        {
                if (autocvar_bot_vs_human > 0)
                {
index bd713ecd09cea6562c5f41e6eef7b06455eb7641..7f651fa7748291ff5e7090192793cf39c206afa9 100644 (file)
@@ -467,7 +467,6 @@ void cvar_changes_init()
                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");
@@ -507,7 +506,7 @@ void cvar_changes_init()
                BADVALUE("sys_ticrate", "0.0333333");
                BADCVAR("teamplay_mode");
                BADCVAR("timelimit_override");
-               BADPREFIX("g_warmup_");
+               BADPREFIX("g_warmup");
                BADPREFIX("sv_info_");
                BADPREFIX("sv_ready_restart_");
 
@@ -625,8 +624,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()
@@ -826,7 +859,7 @@ spawnfunc(worldspawn)
        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;
index e74ab2dc7589733b4ca9e4034ab587c49daa78d1..d82469c11ef1ca7573752897113a614c673e01f3 100644 (file)
@@ -114,7 +114,7 @@ float g_weapon_stay;
 float want_weapon(entity weaponinfo, float allguns); // WEAPONTODO: what still needs done?
 
 float g_grappling_hook;
-float warmup_stage;
+int warmup_stage;
 
 bool sv_ready_restart_after_countdown;
 
index 334f749a2552baf0ab69165c106bbbb2aad469c8..cd0a845d1f3eba5e77d44c2a0dd292b3fd3ffa8c 100644 (file)
@@ -26,15 +26,15 @@ alias sv_hook_readyrestart
 //nifreks lockonrestart feature, used in team-based game modes, if set to 1 and all players readied up no other player can then join the game anymore, useful to block spectators from joining
 set teamplay_lockonrestart 0 "lock teams once all players readied up and the game restarted (no new players can join after restart unless using the server-command unlockteams)"
 
-set g_maxplayers 0 "maximum number of players allowed to play at the same time, set to 0 to allow all players to join the game"
+set g_maxplayers 0 "maximum number of players allowed to play at the same time, 0 means unlimited, -1 uses the map setting or unlimited if not set (rounded to multiple of team number)"
 set g_maxplayers_spectator_blocktime 5 "if the players voted for the \"nospectators\" command, this setting defines the number of seconds a observer/spectator has time to join the game before he gets kicked"
 
 // tournament mod
-set g_warmup 0 "split the game into a warmup- and match-stage"
+set g_warmup 0 "split the game into a warmup- and match-stage, -1 means stay in warmup until enough (set by map, lower bound of 2 or 2 per team) players join, then g_warmup_limit and readiness apply"
 set g_warmup_limit 180 "limit warmup-stage to this time (in seconds); if set to -1 the warmup-stage is not affected by any timelimit, if set to 0 the usual timelimit also affects warmup-stage"
 set g_warmup_allow_timeout 0 "allow calling timeouts in the warmup-stage (if sv_timeout is set to 1)"
 set g_warmup_allguns 1 "provide more weapons on start while in warmup: 0 = normal start weapons, 1 = all guns available on the map, 2 = all normal weapons"
-set g_warmup_majority_factor 0.8 "minimum percentage of players ready needed for warmup to end"
+set g_warmup_majority_factor 0.8 "fraction of joined players sufficient to end warmup before g_warmup_limit by readying up"
 
 alias sv_hook_warmupend
 
@@ -231,6 +231,7 @@ set g_maplist_check_waypoints 0 "when 1, maps are skipped if there currently are
 set g_maplist_ignore_sizes 0 "when 1, all maps are shown in the map list regardless of player count"
 set g_maplist_sizes_count_maxplayers 1 "check the player limit when getting the player count so forced spectators don't affect the size restrictions"
 set g_maplist_sizes_count_bots 1 "include the number of bots currently in the server when counting the number of players for size restrictions"
+set g_maplist_sizes_specparty 0 "this fraction of people are expected to only spectate, reduces player count used to select voting GUI maps"
 
 set g_items_mindist 4000 "starting distance for the fading of items"
 set g_items_maxdist 4500 "maximum distance at which an item can be viewed, after which it will be invisible"