]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/server/client.qc
Improve end of warmup countdown abort (when player count drops too low)
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / client.qc
index 2c3bdc25ccf07e90931e1c0704cd09c44a0ffc71..5b7509e484d5fecec7e6f49a852de6ea218e39e9 100644 (file)
@@ -46,6 +46,7 @@
 #include <server/antilag.qh>
 #include <server/bot/api.qh>
 #include <server/bot/default/cvars.qh>
+#include <server/bot/default/waypoints.qh>
 #include <server/campaign.qh>
 #include <server/chat.qh>
 #include <server/cheats.qh>
@@ -137,7 +138,10 @@ bool ClientData_Send(entity this, entity to, int sf)
        if (CS(e).race_completed)       sf |= BIT(0); // forced scoreboard
        if (CS(to).spectatee_status)    sf |= BIT(1); // spectator ent number follows
        if (CS(e).zoomstate)            sf |= BIT(2); // zoomed
-       if (autocvar_sv_showspectators) sf |= BIT(4); // show spectators
+       if (observe_blocked_if_eliminated && INGAME(to))
+                                       sf |= BIT(3); // observing blocked
+       if (autocvar_sv_showspectators == 1 || (autocvar_sv_showspectators && IS_SPEC(to)))
+                                       sf |= BIT(4); // show spectators
 
        WriteHeader(MSG_ENTITY, ENT_CLIENT_CLIENTDATA);
        WriteByte(MSG_ENTITY, sf);
@@ -254,7 +258,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);
        }
@@ -305,7 +309,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);
@@ -400,7 +404,7 @@ void PutObserverInServer(entity this, bool is_forced, bool use_spawnpoint)
        }
        else
        {
-               SetPlayerTeam(this, -1, TEAM_CHANGE_SPECTATOR);
+               SetPlayerTeam(this, -1, TEAM_CHANGE_SPECTATOR); // clears scores too in game modes without teams
                this.frags = FRAGS_SPECTATOR;
        }
 
@@ -532,6 +536,19 @@ void FixPlayermodel(entity player)
                                setcolor(player, stof(autocvar_sv_defaultplayercolors));
 }
 
+void GiveWarmupResources(entity this)
+{
+       SetResource(this, RES_SHELLS, warmup_start_ammo_shells);
+       SetResource(this, RES_BULLETS, warmup_start_ammo_nails);
+       SetResource(this, RES_ROCKETS, warmup_start_ammo_rockets);
+       SetResource(this, RES_CELLS, warmup_start_ammo_cells);
+       SetResource(this, RES_PLASMA, warmup_start_ammo_plasma);
+       SetResource(this, RES_FUEL, warmup_start_ammo_fuel);
+       SetResource(this, RES_HEALTH, warmup_start_health);
+       SetResource(this, RES_ARMOR, warmup_start_armorvalue);
+       STAT(WEAPONS, this) = WARMUP_START_WEAPONS;
+}
+
 void PutPlayerInServer(entity this)
 {
        if (this.vehicle) vehicles_exit(this.vehicle, VHEF_RELEASE);
@@ -539,6 +556,9 @@ void PutPlayerInServer(entity this)
        PlayerState_attach(this);
        accuracy_resend(this);
 
+       if (teamplay && this.bot_forced_team)
+               SetPlayerTeam(this, this.bot_forced_team, TEAM_CHANGE_MANUAL);
+
        if (this.team < 0)
                TeamBalance_JoinBestTeam(this);
 
@@ -571,17 +591,10 @@ void PutPlayerInServer(entity this)
        this.takedamage = DAMAGE_AIM;
        this.effects = EF_TELEPORT_BIT | EF_RESTARTANIM_BIT;
 
-       if (warmup_stage) {
-               SetResource(this, RES_SHELLS, warmup_start_ammo_shells);
-               SetResource(this, RES_BULLETS, warmup_start_ammo_nails);
-               SetResource(this, RES_ROCKETS, warmup_start_ammo_rockets);
-               SetResource(this, RES_CELLS, warmup_start_ammo_cells);
-               SetResource(this, RES_PLASMA, warmup_start_ammo_plasma);
-               SetResource(this, RES_FUEL, warmup_start_ammo_fuel);
-               SetResource(this, RES_HEALTH, warmup_start_health);
-               SetResource(this, RES_ARMOR, warmup_start_armorvalue);
-               STAT(WEAPONS, this) = WARMUP_START_WEAPONS;
-       } else {
+       if (warmup_stage)
+               GiveWarmupResources(this);
+       else
+       {
                SetResource(this, RES_SHELLS, start_ammo_shells);
                SetResource(this, RES_BULLETS, start_ammo_nails);
                SetResource(this, RES_ROCKETS, start_ammo_rockets);
@@ -760,7 +773,6 @@ void PutPlayerInServer(entity this)
        Unfreeze(this, false);
 
        MUTATOR_CALLHOOK(PlayerSpawn, this, spot);
-
        {
                string s = spot.target;
                if(g_assault || g_race) // TODO: make targeting work in assault & race without this hack
@@ -805,14 +817,15 @@ void PutPlayerInServer(entity this)
                this.alivetime = time;
 
        antilag_clear(this, CS(this));
+
+       if (warmup_stage < 0 || warmup_stage > 1)
+               ReadyCount();
 }
 
 /** Called when a client spawns in the server */
 void PutClientInServer(entity this)
 {
-       if (IS_BOT_CLIENT(this)) {
-               TRANSMUTE(Player, this);
-       } else if (IS_REAL_CLIENT(this)) {
+       if (IS_REAL_CLIENT(this)) {
                msg_entity = this;
                WriteByte(MSG_ONE, SVC_SETVIEW);
                WriteEntity(MSG_ONE, this);
@@ -1018,29 +1031,10 @@ void ClientPreConnect(entity this)
 }
 #endif
 
-string GetClientVersionMessage(entity this)
-{
-       if (CS(this).version_mismatch) {
-               if(CS(this).version < autocvar_gameversion) {
-                       return strcat("This is Xonotic ", autocvar_g_xonoticversion,
-                               "\n^3Your client version is outdated.\n\n\n### YOU WON'T BE ABLE TO PLAY ON THIS SERVER ###\n\n\nPlease update!!!^8");
-               } else {
-                       return strcat("This is Xonotic ", autocvar_g_xonoticversion,
-                               "\n^3This server is using an outdated Xonotic version.\n\n\n ### THIS SERVER IS INCOMPATIBLE AND THUS YOU CANNOT JOIN ###.^8");
-               }
-       } else {
-               return strcat("Welcome to Xonotic ", autocvar_g_xonoticversion);
-       }
-}
-
-void SendWelcomemessage(entity this, bool force_centerprint)
-{
-       msg_entity = this;
-       WriteHeader(MSG_ONE, TE_CSQC_SERVERWELCOME);
-       SendWelcomemessage_msg_type(this, force_centerprint, MSG_ONE);
-}
-
-void SendWelcomemessage_msg_type(entity this, bool force_centerprint, int msg_type)
+// NOTE csqc uses the active mutators list sent by this function
+// to understand which mutators are enabled
+// also note that they aren't all registered mutators, e.g. jetpack, low gravity
+void SendWelcomeMessage(entity this, int msg_type)
 {
        WriteByte(msg_type, boolean(autocvar_g_campaign));
        if (boolean(autocvar_g_campaign))
@@ -1050,32 +1044,30 @@ void SendWelcomemessage_msg_type(entity this, bool force_centerprint, int msg_ty
                WriteString(msg_type, Campaign_GetMessage());
                return;
        }
-       WriteByte(msg_type, force_centerprint);
        WriteString(msg_type, autocvar_hostname);
-       WriteString(msg_type, GetClientVersionMessage(this));
+       WriteString(msg_type, autocvar_g_xonoticversion);
+       WriteByte(msg_type, CS(this).version_mismatch);
+       WriteByte(msg_type, (CS(this).version < autocvar_gameversion));
+       WriteByte(msg_type, autocvar_g_warmup > 1 ? autocvar_g_warmup : map_minplayers);
+       WriteByte(msg_type, GetPlayerLimit());
 
        MUTATOR_CALLHOOK(BuildMutatorsPrettyString, "");
        string modifications = M_ARGV(0, string);
 
-       if(g_weaponarena)
-       {
-               if(g_weaponarena_random)
-                       modifications = strcat(modifications, ", ", ftos(g_weaponarena_random), " of ", g_weaponarena_list, " Arena");
-               else
-                       modifications = strcat(modifications, ", ", g_weaponarena_list, " Arena");
-       }
-       else if(cvar("g_balance_blaster_weaponstartoverride") == 0)
+       if (!g_weaponarena && cvar("g_balance_blaster_weaponstartoverride") == 0)
                modifications = strcat(modifications, ", No start weapons");
        if(cvar("sv_gravity") < stof(cvar_defstring("sv_gravity")))
                modifications = strcat(modifications, ", Low gravity");
        if(g_weapon_stay && !g_cts)
                modifications = strcat(modifications, ", Weapons stay");
        if(autocvar_g_jetpack)
-               modifications = strcat(modifications, ", Jet pack");
+               modifications = strcat(modifications, ", Jetpack");
        modifications = substring(modifications, 2, strlen(modifications) - 2);
 
        WriteString(msg_type, modifications);
 
+       WriteString(msg_type, g_weaponarena_list);
+
        if(cache_lastmutatormsg != autocvar_g_mutatormsg)
        {
                strcpy(cache_lastmutatormsg, autocvar_g_mutatormsg);
@@ -1084,11 +1076,6 @@ void SendWelcomemessage_msg_type(entity this, bool force_centerprint, int msg_ty
 
        WriteString(msg_type, cache_mutatormsg);
 
-       string mutator_msg = "";
-       MUTATOR_CALLHOOK(BuildGameplayTipsString, mutator_msg);
-       mutator_msg = M_ARGV(0, string);
-
-       WriteString(msg_type, mutator_msg); // trust that the mutator will do proper formatting
        WriteString(msg_type, strreplace("\\n", "\n", autocvar_sv_motd));
 }
 
@@ -1165,12 +1152,18 @@ void ClientConnect(entity this)
        {
                if (g_weaponarena_weapons == WEPSET(TUBA))
                        stuffcmd(this, "cl_cmd settemp chase_active 1\n");
+               // quickmenu file must be put in a subfolder with an unique name
+               // to reduce chances of overriding custom client quickmenus
+               if (waypointeditor_enabled)
+                       stuffcmd(this, sprintf("cl_cmd settemp _hud_panel_quickmenu_file_from_server %s\n", "wpeditor.txt"));
+               else if (autocvar_sv_quickmenu_file != "" && strstrofs(autocvar_sv_quickmenu_file, "/", 0) && fexists(autocvar_sv_quickmenu_file))
+                       stuffcmd(this, sprintf("cl_cmd settemp _hud_panel_quickmenu_file_from_server %s\n", autocvar_sv_quickmenu_file));
        }
 
        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();
 
@@ -1181,7 +1174,7 @@ void ClientConnect(entity this)
        if (IS_REAL_CLIENT(this))
                sv_notice_join(this);
 
-       this.move_qcphysics = autocvar_sv_qcphysics;
+       this.move_qcphysics = true;
 
        // update physics stats (players can spawn before physics runs)
        Physics_UpdateStats(this);
@@ -1196,9 +1189,6 @@ void ClientConnect(entity this)
 
        if (player_count == 1)
                localcmd("\nsv_hook_firstjoin\n");
-
-       if (IS_REAL_CLIENT(this) && !IS_PLAYER(this) && !autocvar_g_campaign)
-               CS(this).motd_actived_time = -1; // the welcome message is shown by the client
 }
 /*
 =============
@@ -1259,7 +1249,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
@@ -1968,11 +1958,15 @@ bool ShowTeamSelection(entity this)
 {
        if (!teamplay || autocvar_g_campaign || autocvar_g_balance_teams || this.team_selected || (CS(this).wasplayer && autocvar_g_changeteam_banned) || Player_HasRealForcedTeam(this))
                return false;
-       stuffcmd(this, "menu_showteamselect\n");
+       if (frametime) // once per frame is more than enough
+               stuffcmd(this, "_scoreboard_team_selection 1\n");
        return true;
 }
 void Join(entity this)
 {
+       if (autocvar_g_campaign && !campaign_bots_may_start && !game_stopped && time >= game_starttime)
+               ReadyRestart(true);
+
        TRANSMUTE(Player, this);
 
        if(!this.team_selected)
@@ -1999,10 +1993,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;
 }
 
 /**
@@ -2047,69 +2042,13 @@ 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;
        }
 
        return free_slots;
 }
 
-void PrintWelcomeMessage(entity this)
-{
-       if(CS(this).motd_actived_time == 0)
-       {
-               if (autocvar_g_campaign) {
-                       if ((IS_PLAYER(this) && PHYS_INPUT_BUTTON_INFO(this)) || (!IS_PLAYER(this))) {
-                               CS(this).motd_actived_time = time;
-                               SendWelcomemessage(this, false);
-                       }
-               } else {
-                       if (PHYS_INPUT_BUTTON_INFO(this)) {
-                               CS(this).motd_actived_time = time;
-                               SendWelcomemessage(this, true);
-                       }
-               }
-       }
-       else if(CS(this).motd_actived_time > 0) // showing MOTD or campaign message
-       {
-               if (autocvar_g_campaign) {
-                       if (PHYS_INPUT_BUTTON_INFO(this))
-                               CS(this).motd_actived_time = time;
-                       else if ((time - CS(this).motd_actived_time > 2) && IS_PLAYER(this)) { // hide it some seconds after BUTTON_INFO has been released
-                               CS(this).motd_actived_time = 0;
-                               Kill_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CPID_MOTD);
-                       }
-               } else {
-                       if (PHYS_INPUT_BUTTON_INFO(this))
-                               CS(this).motd_actived_time = time;
-                       else if (time - CS(this).motd_actived_time > 2) { // hide it some seconds after BUTTON_INFO has been released
-                               CS(this).motd_actived_time = 0;
-                               Kill_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CPID_MOTD);
-                       }
-               }
-       }
-       else //if(CS(this).motd_actived_time < 0) // just connected, motd is active
-       {
-               if(PHYS_INPUT_BUTTON_INFO(this)) // BUTTON_INFO hides initial MOTD
-                       CS(this).motd_actived_time = -2; // wait until BUTTON_INFO gets released
-               else if (CS(this).motd_actived_time == -2)
-               {
-                       // instantly hide MOTD
-                       CS(this).motd_actived_time = 0;
-                       Kill_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CPID_MOTD);
-               }
-               else if (IS_PLAYER(this) || IS_SPEC(this))
-               {
-                       // FIXME occasionally for some reason MOTD never goes away
-                       // delay MOTD removal a little bit in the hope it fixes this bug
-                       if (CS(this).motd_actived_time == -1) // MOTD marked to fade away as soon as client becomes player or spectator
-                               CS(this).motd_actived_time = -(5 + floor(random() * 10)); // add small delay
-                       else //if (CS(this).motd_actived_time < -2)
-                               CS(this).motd_actived_time++;
-               }
-       }
-}
-
 bool joinAllowed(entity this)
 {
        if (CS(this).version_mismatch) return false;
@@ -2286,6 +2225,14 @@ void ObserverOrSpectatorThink(entity this)
                }
        }
 
+       if (IS_BOT_CLIENT(this) && !CS(this).autojoin_checked)
+       {
+               CS(this).autojoin_checked = true;
+               TRANSMUTE(Player, this);
+               PutClientInServer(this);
+               return;
+       }
+
        if (this.flags & FL_JUMPRELEASED) {
                if (PHYS_INPUT_BUTTON_JUMP(this) && (joinAllowed(this) || time < CS(this).jointime + MIN_SPEC_TIME)) {
                        this.flags &= ~FL_JUMPRELEASED;
@@ -2314,10 +2261,12 @@ void ObserverOrSpectatorThink(entity this)
                                }
                                CS(this).impulse = 0;
                        } else if(PHYS_INPUT_BUTTON_ATCK2(this)) {
-                               this.would_spectate = false;
-                               this.flags &= ~FL_JUMPRELEASED;
-                               TRANSMUTE(Observer, this);
-                               PutClientInServer(this);
+                               if(!observe_blocked_if_eliminated || !INGAME(this)) {
+                                       this.would_spectate = false;
+                                       this.flags &= ~FL_JUMPRELEASED;
+                                       TRANSMUTE(Observer, this);
+                                       PutClientInServer(this);
+                               }
                        } else if(!SpectateUpdate(this) && !SpectateNext(this)) {
                                PutObserverInServer(this, false, true);
                                this.would_spectate = true;
@@ -2533,9 +2482,6 @@ void PlayerPreThink (entity this)
                PlayerUseKey(this);
        CS(this).usekeypressed = PHYS_INPUT_BUTTON_USE(this);
 
-       if (IS_REAL_CLIENT(this))
-               PrintWelcomeMessage(this);
-
        if (IS_PLAYER(this)) {
                if (IS_REAL_CLIENT(this) && time < CS(this).jointime + MIN_SPEC_TIME)
                        error("Client can't be spawned as player on connection!");
@@ -2557,7 +2503,6 @@ void PlayerPreThink (entity this)
                        || (!(autocvar_sv_spectate || autocvar_g_campaign || (Player_GetForcedTeamIndex(this) == TEAM_FORCE_SPECTATOR))
                                && (!teamplay || autocvar_g_balance_teams)))
                {
-                       campaign_bots_may_start = true;
                        if(joinAllowed(this))
                                Join(this);
                        return;