]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/common/gamemodes/gamemode/clanarena/sv_clanarena.qc
Merge branch 'master' into z411/bai-server
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / gamemodes / gamemode / clanarena / sv_clanarena.qc
index 800c26f121d235c834162f967ee7a858e10b82f0..b62fcfbd1a13a87146140e48179593c551a780bf 100644 (file)
@@ -1,7 +1,7 @@
 #include "sv_clanarena.qh"
 
 float autocvar_g_ca_damage2score = 100;
-bool autocvar_g_ca_spectate_enemies;
+bool autocvar_g_ca_prevent_stalemate;
 
 float autocvar_g_ca_start_health = 200;
 float autocvar_g_ca_start_armor = 200;
@@ -20,15 +20,22 @@ void CA_count_alive_players()
        for (int i = 1; i <= NUM_TEAMS; ++i)
        {
                Team_SetNumberOfAlivePlayers(Team_GetTeamFromIndex(i), 0);
+               Team_SetNumberOfPlayers(Team_GetTeamFromIndex(i), 0);
        }
-       FOREACH_CLIENT(IS_PLAYER(it) && Entity_HasValidTeam(it),
+       FOREACH_CLIENT(Entity_HasValidTeam(it),
        {
                ++total_players;
-               if (IS_DEAD(it))
+               entity team_ = Entity_GetTeam(it);
+               
+               int num_total = Team_GetNumberOfPlayers(team_);
+               ++num_total;
+               Team_SetNumberOfPlayers(team_, num_total);
+               
+               if (IS_DEAD(it) || !IS_PLAYER(it))
                {
                        continue;
                }
-               entity team_ = Entity_GetTeam(it);
+               
                int num_alive = Team_GetNumberOfAlivePlayers(team_);
                ++num_alive;
                Team_SetNumberOfAlivePlayers(team_, num_alive);
@@ -44,42 +51,171 @@ void CA_count_alive_players()
 
 void nades_Clear(entity player);
 
+entity ca_LastPlayer(float tm)
+{
+       entity last_pl = NULL;
+       FOREACH_CLIENT(IS_PLAYER(it) && it.team == tm, {
+               if (!IS_DEAD(it))
+               {
+                       if (!last_pl)
+                               last_pl = it;
+                       else
+                               return NULL;
+               }
+       });
+       return last_pl;
+}
+
+
+int CA_PreventStalemate()
+{
+       //LOG_INFO("PreventStalemate running");
+       int winnerTeam = 0;
+       int secondTeam = 0;
+
+       for(int i = 1; i <= AVAILABLE_TEAMS; i++)
+       {
+               if(!winnerTeam || Team_GetNumberOfAlivePlayers(Team_GetTeamFromIndex(i)) > Team_GetNumberOfAlivePlayers(Team_GetTeam(winnerTeam)))
+               {
+                       secondTeam = winnerTeam;
+                       winnerTeam = Team_IndexToTeam(i);
+               }
+               else
+               {
+                       if(!secondTeam || Team_GetNumberOfAlivePlayers(Team_GetTeamFromIndex(i)) > Team_GetNumberOfAlivePlayers(Team_GetTeam(secondTeam)))
+                               secondTeam = Team_IndexToTeam(i);
+               }
+       }
+
+       if(Team_GetNumberOfAlivePlayers(Team_GetTeam(winnerTeam)) != Team_GetNumberOfAlivePlayers(Team_GetTeam(secondTeam)))
+       {
+               LOG_INFOF("Stalemate broken by alive players. Best team: %s%s (%d)^7 - Trailing team: %s%s (%d)",
+                       Team_ColorCode(winnerTeam), Team_ColorName(winnerTeam), Team_GetNumberOfAlivePlayers(Team_GetTeam(winnerTeam)),
+                       Team_ColorCode(secondTeam), Team_ColorName(secondTeam), Team_GetNumberOfAlivePlayers(Team_GetTeam(secondTeam)));
+               return winnerTeam;
+       }
+
+       // Equality. Let's check which team has more health now
+       //LOG_INFO("Equality. Checking health now.");
+       winnerTeam = 0;
+       secondTeam = 0;
+       int winnerTeamHealth = 0;
+       int secondTeamHealth = 0;
+       int teamIndex, teamHealth;
+
+       for(int i = 1; i <= AVAILABLE_TEAMS; i++)
+       {
+               teamIndex = i;
+               teamHealth = 0;
+
+               // Add up health for the players in this team
+               FOREACH_CLIENT(IS_PLAYER(it) && Entity_HasValidTeam(it) && it.team == Team_IndexToTeam(teamIndex),
+               {
+                       if (IS_DEAD(it))
+                               continue;
+                       teamHealth += GetResource(it, RES_HEALTH) + GetResource(it, RES_ARMOR);
+               });
+
+               // Set the winner teams
+               if(!winnerTeam || teamHealth > winnerTeamHealth)
+               {
+                       secondTeam = winnerTeam;
+                       secondTeamHealth = winnerTeamHealth;
+                       winnerTeam = Team_IndexToTeam(i);
+                       winnerTeamHealth = teamHealth;
+               }
+               else
+               {
+                       if(!secondTeam || teamHealth > secondTeamHealth)
+                       {
+                               secondTeam = Team_IndexToTeam(i);
+                               secondTeamHealth = teamHealth;
+                       }
+               }
+       }
+
+       if(winnerTeamHealth != secondTeamHealth)
+       {
+               LOG_INFOF("Stalemate broken by team health. Best team: %s%s (%d)^7 - Trailing team: %s%s (%d)",
+                       Team_ColorCode(winnerTeam), Team_ColorName(winnerTeam), winnerTeamHealth,
+                       Team_ColorCode(secondTeam), Team_ColorName(secondTeam), secondTeamHealth);
+               return winnerTeam;
+       }
+       else
+               return -2; // Equality. Can't avoid the stalemate.
+}
+
 float CA_CheckWinner()
 {
+       int winner_team = 0;
+
        if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
        {
-               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
-               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
-               FOREACH_CLIENT(IS_PLAYER(it), { nades_Clear(it); });
-
-               allowed_to_spawn = false;
-               game_stopped = true;
-               round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit);
-               return 1;
+               if(autocvar_g_ca_prevent_stalemate)
+                       winner_team = CA_PreventStalemate();
+               else
+                       winner_team = -2;
        }
 
        CA_count_alive_players();
-       int winner_team = Team_GetWinnerAliveTeam();
+       if (!winner_team)
+               winner_team = Team_GetWinnerAliveTeam();
        if (!winner_team)
                return 0;
 
+       bool perfect = false;
        if(winner_team > 0)
        {
+               entity tm = Team_GetTeam(winner_team);
+               entity last_pl = ca_LastPlayer(winner_team);
+               
+               if(last_pl && Team_GetNumberOfPlayers(tm) >= 3) {
+                       Give_Medal(last_pl, DEFENSE);
+               }
+               
                Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
                Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
+               if(fragsleft > 1) Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, APP_TEAM_NUM(winner_team, ANNCE_ROUND_TEAM_WIN));
                TeamScore_AddToTeam(winner_team, ST_CA_ROUNDS, +1);
+               
+               if (Team_GetNumberOfPlayers(tm) >= 3 &&
+                       Team_GetNumberOfAlivePlayers(tm) == Team_GetNumberOfPlayers(tm))
+                               perfect = true;
        }
        else if(winner_team == -1)
        {
                Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
                Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
+               Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, ANNCE_ROUND_TIED);
+       }
+       else if(winner_team == -2)
+       {
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
+               Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, ANNCE_ROUND_OVER);
        }
 
        allowed_to_spawn = false;
-       game_stopped = true;
+       if(autocvar_g_ca_round_stop)
+               game_stopped = true;
        round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit);
 
-       FOREACH_CLIENT(IS_PLAYER(it), { nades_Clear(it); });
+       FOREACH_CLIENT(IS_PLAYER(it), {
+               nades_Clear(it);
+               
+               // Give perfect medal if everyone in the winner team is alive
+               if(perfect && it.team == winner_team) {
+                       Give_Medal(it, PERFECT);
+               }
+               
+               // Give accuracy medal for each weapon above 50%
+               entity ra = it.roundaccuracy;
+               for (int w = 0; w <= WEP_LAST - WEP_FIRST; ++w) {
+                       if(ra.accuracy_fired[w] > 1 && (ra.accuracy_hit[w] / ra.accuracy_fired[w]) > 0.5) {
+                               Give_Medal(it, ACCURACY);
+                       }
+               }
+       });
 
        return 1;
 }
@@ -188,18 +324,21 @@ MUTATOR_HOOKFUNCTION(ca, PutClientInServer)
                        Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_JOIN_LATE);
                }
        }
+
+       if (!warmup_stage)
+               eliminatedPlayers.SendFlags |= 1;
 }
 
 MUTATOR_HOOKFUNCTION(ca, reset_map_players)
 {
+       g_ca_spectate_enemies = autocvar_g_ca_spectate_enemies;
+       observe_blocked_if_eliminated = (g_ca_spectate_enemies == -1);
+       // we can avoid sending observe_blocked_if_eliminated to all clients here (with ClientData_Touch)
+       // since it will get sent whenever the client spectates someone anyway
+
        FOREACH_CLIENT(true, {
                CS(it).killcount = 0;
-               if (!INGAME(it) && IS_BOT_CLIENT(it))
-               {
-                       it.team = -1;
-                       INGAME_STATUS_SET(it, INGAME_STATUS_JOINED);
-               }
-               if (INGAME(it))
+               if (INGAME(it) || IS_BOT_CLIENT(it))
                {
                        TRANSMUTE(Player, it);
                        INGAME_STATUS_SET(it, INGAME_STATUS_JOINED);
@@ -209,6 +348,12 @@ MUTATOR_HOOKFUNCTION(ca, reset_map_players)
        return true;
 }
 
+MUTATOR_HOOKFUNCTION(ca, Scores_CountFragsRemaining)
+{
+       // announce remaining frags
+       return true;
+}
+
 MUTATOR_HOOKFUNCTION(ca, reset_map_global)
 {
        allowed_to_spawn = true;
@@ -241,8 +386,10 @@ void ca_LastPlayerForTeam_Notify(entity this)
        if (!warmup_stage && round_handler_IsActive() && round_handler_IsRoundStarted())
        {
                entity pl = ca_LastPlayerForTeam(this);
-               if (pl)
+               if (pl) {
                        Send_Notification(NOTIF_ONE, pl, MSG_CENTER, CENTER_ALONE);
+                       Send_Notification(NOTIF_ONE, pl, MSG_ANNCE, ANNCE_ALONE);
+               }
        }
 }
 
@@ -263,11 +410,6 @@ MUTATOR_HOOKFUNCTION(ca, PlayerDies)
        return true;
 }
 
-MUTATOR_HOOKFUNCTION(ca, ClientConnect)
-{
-       entity player = M_ARGV(0, entity);
-       player.ca_damage_counter = 0;
-}
 
 MUTATOR_HOOKFUNCTION(ca, ClientDisconnect)
 {
@@ -294,7 +436,10 @@ MUTATOR_HOOKFUNCTION(ca, MakePlayerObserver)
                INGAME_STATUS_CLEAR(player);
        }
        if (INGAME(player))
+       {
                player.frags = FRAGS_PLAYER_OUT_OF_GAME;
+               player.would_spectate = observe_blocked_if_eliminated; // if blocked from observing force to spectate now
+       }
        if (!warmup_stage)
                eliminatedPlayers.SendFlags |= 1;
        if (!INGAME(player))
@@ -409,18 +554,7 @@ MUTATOR_HOOKFUNCTION(ca, PlayerDamage_SplitHealthArmor)
        }
 
        if (scorer)
-       {
-               // assign damage score in units (rounded) to avoid bugs with float score
-               scorer.ca_damage_counter += scorer_damage;
-               float score_counter = scorer.ca_damage_counter / autocvar_g_ca_damage2score;
-               if (score_counter >= -0.5 && score_counter < 0.5)
-                       return;
-               // NOTE: this code works for subtracting score too
-               int points = floor(score_counter + 0.5);
-               GameRules_scoring_add(scorer, SCORE, points);
-
-               scorer.ca_damage_counter -= points * autocvar_g_ca_damage2score;
-       }
+               GameRules_scoring_add_float2int(scorer, SCORE, scorer_damage, ca_damage_counter, autocvar_g_ca_damage2score);
 }
 
 MUTATOR_HOOKFUNCTION(ca, CalculateRespawnTime)
@@ -435,18 +569,12 @@ MUTATOR_HOOKFUNCTION(ca, PlayerRegen)
        return true;
 }
 
-MUTATOR_HOOKFUNCTION(ca, Scores_CountFragsRemaining)
-{
-       // announce remaining frags
-       return true;
-}
-
 MUTATOR_HOOKFUNCTION(ca, SpectateSet)
 {
        entity client = M_ARGV(0, entity);
        entity targ = M_ARGV(1, entity);
 
-       if (!autocvar_g_ca_spectate_enemies && INGAME(client))
+       if (g_ca_spectate_enemies != 1 && INGAME(client))
        if (DIFF_TEAM(targ, client))
                return true;
 }
@@ -455,7 +583,7 @@ MUTATOR_HOOKFUNCTION(ca, SpectateNext)
 {
        entity client = M_ARGV(0, entity);
 
-       if (!autocvar_g_ca_spectate_enemies && INGAME(client)
+       if (g_ca_spectate_enemies != 1 && INGAME(client)
                && Team_GetNumberOfAlivePlayers(Entity_GetTeam(client)))
        {
                entity targ = M_ARGV(1, entity);
@@ -470,7 +598,7 @@ MUTATOR_HOOKFUNCTION(ca, SpectatePrev)
        entity targ = M_ARGV(1, entity);
        entity first = M_ARGV(2, entity);
 
-       if (!autocvar_g_ca_spectate_enemies && INGAME(client)
+       if (g_ca_spectate_enemies != 1 && INGAME(client)
                && Team_GetNumberOfAlivePlayers(Entity_GetTeam(client)))
        {
                do { targ = targ.chain; }