diff --git a/BalanceLogic/PlayerManagement.cs b/BalanceLogic/PlayerManagement.cs index 74a02d0..1e04126 100644 --- a/BalanceLogic/PlayerManagement.cs +++ b/BalanceLogic/PlayerManagement.cs @@ -7,7 +7,7 @@ namespace Mesharsky_TeamBalance; public partial class Mesharsky_TeamBalance { - private static readonly ConcurrentDictionary playerCache = new(); + private static readonly ConcurrentDictionary playerCache = new(); public static void UpdatePlayerTeamsInCache() { @@ -24,11 +24,15 @@ public static void UpdatePlayerTeamsInCache() } else { - var newPlayer = new Player + var newPlayer = new PlayerStats { PlayerName = player.PlayerName, PlayerSteamID = player.SteamID, Team = (int)player.Team, + Kills = player.ActionTrackingServices!.MatchStats.Kills, + Assists = player.ActionTrackingServices!.MatchStats.Assists, + Deaths = player.ActionTrackingServices.MatchStats.Deaths, + Damage = player.ActionTrackingServices.MatchStats.Damage, Score = player.Score }; @@ -37,11 +41,11 @@ public static void UpdatePlayerTeamsInCache() } } - public static List GetPlayersForRebalance() + public static List GetPlayersForRebalance() { var players = playerCache.Values .Where(p => p.Team == (int)CsTeam.CounterTerrorist || p.Team == (int)CsTeam.Terrorist) - .OrderByDescending(p => Config?.PluginSettings.UsePerformanceScore == true ? p.PerformanceScore : p.Score) + .OrderByDescending(p => p.PerformanceScore) .ToList(); PrintDebugMessage($"Total valid players for rebalance: {players.Count}"); diff --git a/BalanceLogic/TeamBalance.cs b/BalanceLogic/TeamBalance.cs index 12356ca..162623a 100644 --- a/BalanceLogic/TeamBalance.cs +++ b/BalanceLogic/TeamBalance.cs @@ -1,3 +1,4 @@ +using CounterStrikeSharp.API; using CounterStrikeSharp.API.Modules.Utils; namespace Mesharsky_TeamBalance; @@ -5,25 +6,43 @@ namespace Mesharsky_TeamBalance; public partial class Mesharsky_TeamBalance { public bool GlobalBalanceMade = false; + private void AttemptBalanceTeams() { PrintDebugMessage("Attempting to balance teams..."); + // Reset balance flag before attempting balance + GlobalBalanceMade = false; + if (!ShouldTeamsBeRebalanced()) return; PrintDebugMessage("Balancing teams..."); var players = GetPlayersForRebalance(); + if (players == null || players.Count == 0) + { + PrintDebugMessage("No players available for rebalancing."); + return; + } + bool balanceMade = RebalancePlayers(players); + GlobalBalanceMade = balanceMade; - if (balanceMade) + if (GlobalBalanceMade) { - GlobalBalanceMade = true; + var ctPlayerCount = balanceStats.CT.Stats.Count; + var tPlayerCount = balanceStats.T.Stats.Count; + var ctTotalScore = balanceStats.CT.TotalPerformanceScore; + var tTotalScore = balanceStats.T.TotalPerformanceScore; + + Server.PrintToChatAll($" {ChatColors.Red}[Team Balance] {ChatColors.Default}Teams have been balanced.."); + Server.PrintToChatAll($" {ChatColors.Red}[Team Balance] CT: {ctPlayerCount} players, {ctTotalScore} score"); + Server.PrintToChatAll($" {ChatColors.Red}[Team Balance] T: {tPlayerCount} players, {tTotalScore} score."); } else { - GlobalBalanceMade = false; + Server.PrintToChatAll($" {ChatColors.Red}[Team Balance] {ChatColors.Default}No need for team balance at this moment."); } } @@ -33,12 +52,17 @@ private static bool ShouldTeamsBeRebalanced() UpdatePlayerTeamsInCache(); - var players = playerCache.Values.ToList(); + var players = playerCache?.Values.ToList(); + if (players == null || players.Count == 0) + { + PrintDebugMessage("No players found for rebalancing."); + return false; + } int ctPlayerCount = players.Count(p => p.Team == (int)CsTeam.CounterTerrorist); int tPlayerCount = players.Count(p => p.Team == (int)CsTeam.Terrorist); - if (ctPlayerCount + tPlayerCount < Config?.PluginSettings.MinPlayers) + if (ctPlayerCount + tPlayerCount < (Config?.PluginSettings.MinPlayers ?? 0)) { PrintDebugMessage("Not enough players to balance."); return false; @@ -52,13 +76,13 @@ private static bool ShouldTeamsBeRebalanced() ? players.Where(p => p.Team == (int)CsTeam.Terrorist).Sum(p => p.PerformanceScore) : players.Where(p => p.Team == (int)CsTeam.Terrorist).Sum(p => p.Score); - if (ctScore > tScore * Config?.PluginSettings.MaxScoreBalanceRatio || tScore > ctScore * Config?.PluginSettings.MaxScoreBalanceRatio) + if (ctScore > tScore * (Config?.PluginSettings.MaxScoreBalanceRatio ?? 1.0f) || tScore > ctScore * (Config?.PluginSettings.MaxScoreBalanceRatio ?? 1.0f)) { PrintDebugMessage("Score difference is too high. Balance required."); return true; } - if (Math.Abs(ctPlayerCount - tPlayerCount) > Config?.PluginSettings.MaxTeamSizeDifference) + if (Math.Abs(ctPlayerCount - tPlayerCount) > (Config?.PluginSettings.MaxTeamSizeDifference ?? 1)) { PrintDebugMessage("Team sizes are not equal. Balance needed."); return true; diff --git a/BalanceLogic/TeamBalancingLogic.cs b/BalanceLogic/TeamBalancingLogic.cs index e928443..5a7189b 100644 --- a/BalanceLogic/TeamBalancingLogic.cs +++ b/BalanceLogic/TeamBalancingLogic.cs @@ -1,226 +1,51 @@ -using CounterStrikeSharp.API.Modules.Utils; - namespace Mesharsky_TeamBalance; public partial class Mesharsky_TeamBalance { - private static bool RebalancePlayers(List players) - { - PrintDebugMessage("Starting player rebalance..."); - - var currentRound = GetCurrentRound(); - - var ctTeam = players.Where(p => p.Team == (int)CsTeam.CounterTerrorist).ToList(); - var tTeam = players.Where(p => p.Team == (int)CsTeam.Terrorist).ToList(); - - var ctTotalScore = ctTeam.Sum(p => p.PerformanceScore); - var tTotalScore = tTeam.Sum(p => p.PerformanceScore); - - PrintDebugMessage($"Initial CT Score: {ctTotalScore}, T Score: {tTotalScore}, CT Players: {ctTeam.Count}, T Players: {tTeam.Count}"); - - // Determine balance trigger reasons - bool sizeDifferenceTriggered = Math.Abs(ctTeam.Count - tTeam.Count) > Config?.PluginSettings.MaxTeamSizeDifference; - bool scoreDifferenceTriggered = Math.Abs(ctTotalScore - tTotalScore) > (tTotalScore * Config?.PluginSettings.MaxScoreBalanceRatio); + private readonly BalanceStats balanceStats = new BalanceStats(); - PrintDebugMessage($"Balance Triggered by Size Difference: {sizeDifferenceTriggered}, Score Difference: {scoreDifferenceTriggered}"); - - // Step 1: Ensure the teams are within MaxTeamSizeDifference by MOVING players - int attempts = 0; - while (sizeDifferenceTriggered && Math.Abs(ctTeam.Count - tTeam.Count) > Config?.PluginSettings.MaxTeamSizeDifference) - { - attempts++; - var difference = ctTeam.Count - tTeam.Count; - - if (difference > 0) - { - var playerToMove = ctTeam.OrderByDescending(p => p.PerformanceScore).First(); - MovePlayer(playerToMove, tTeam, ctTeam, ref ctTotalScore, ref tTotalScore); - } - else - { - var playerToMove = tTeam.OrderByDescending(p => p.PerformanceScore).First(); - MovePlayer(playerToMove, ctTeam, tTeam, ref tTotalScore, ref ctTotalScore); - } - - if (attempts >= 10) - { - PrintDebugMessage("Maximum move attempts reached. Exiting to prevent infinite loop."); - break; - } - } - - // Step 2: Balance teams by TRADING players to minimize performance score differences - if (scoreDifferenceTriggered || !sizeDifferenceTriggered) - { - SwapPlayersToBalance(ctTeam, tTeam, ref ctTotalScore, ref tTotalScore, currentRound); - } - - // Step 3: Apply changes if any balancing was performed - bool balanceMade = ApplyTeamChanges(ctTeam, tTeam, currentRound); - - PrintDebugMessage($"Rebalance complete || Final CT Score: {ctTotalScore}, Final T Score: {tTotalScore}, Final CT Players: {ctTeam.Count}, Final T Players: {tTeam.Count}"); - return balanceMade; - } - - private static void SwapPlayersToBalance(List ctTeam, List tTeam, ref float ctTotalScore, ref float tTotalScore, int currentRound) + private bool RebalancePlayers(List players) { - int maxAttempts = 10; - int attempts = 0; - - while (Math.Abs(ctTotalScore - tTotalScore) > Config?.PluginSettings.MaxScoreBalanceRatio && attempts < maxAttempts) - { - attempts++; - - var difference = Math.Abs(ctTotalScore - tTotalScore); - PrintDebugMessage($"Attempt {attempts}: Current Score Difference = {difference}, Max Allowed = {tTotalScore * Config?.PluginSettings.MaxScoreBalanceRatio}"); - - // Find the best players to swap - var bestCtPlayerToMove = FindBestPlayerToMove(ctTeam, tTeam, ctTotalScore, tTotalScore, currentRound, difference); - var bestTPlayerToMove = FindBestPlayerToMove(tTeam, ctTeam, tTotalScore, ctTotalScore, currentRound, difference); - - // If both players are found, proceed with the trade - if (bestCtPlayerToMove.HasValue && bestTPlayerToMove.HasValue) - { - var ctPlayer = bestCtPlayerToMove.Value.Item1; - var tPlayer = bestTPlayerToMove.Value.Item1; - - // Ensure both players are valid before proceeding - if (ctPlayer != null && tPlayer != null) - { - TradePlayers(ctPlayer, tPlayer, ctTeam, tTeam, ref ctTotalScore, ref tTotalScore); - } - else - { - PrintDebugMessage("One of the players is null. Skipping trade."); - break; - } - } - else - { - // If no valid players are found for trading, exit the loop - PrintDebugMessage("No valid players found for trading. Exiting swap loop."); - break; - } - - // Safety check: Break out of the loop if no further meaningful trades can be made - if (attempts > 1 && Math.Abs(ctTotalScore - tTotalScore) == difference) - { - PrintDebugMessage("Score difference unchanged after trade attempt. Exiting swap loop."); - break; - } - } - - if (attempts >= maxAttempts) - { - PrintDebugMessage("Maximum attempts reached. Exiting swap loop to prevent infinite loop."); - } - } - - - // Find the best player to move based on minimizing the performance difference - private static (Player, float)? FindBestPlayerToMove(List fromTeam, List toTeam, float fromTeamScore, float toTeamScore, int currentRound, float difference) - { - var potentialPlayers = fromTeam - .Where(p => p != null && CanMovePlayer(fromTeam, toTeam, p, currentRound) && p.PerformanceScore <= difference) - .Select(p => (p, Math.Abs(fromTeamScore - p.PerformanceScore - (toTeamScore + p.PerformanceScore)))) - .OrderBy(result => result.Item2) - .ToList(); - - // Debug logging to understand why no valid players are found - if (potentialPlayers.Count == 0) - { - PrintDebugMessage("No players eligible for moving under current conditions."); - } - else - { - PrintDebugMessage($"Found {potentialPlayers.Count} potential players for trading. Top candidate: {potentialPlayers.First().Item1.PlayerName} with score difference: {potentialPlayers.First().Item2}"); - } + PrintDebugMessage("Starting player rebalance..."); - return potentialPlayers.FirstOrDefault(); - } + // Collect current stats + balanceStats.GetStats(players); - private static void TradePlayers(Player ctPlayer, Player tPlayer, List ctTeam, List tTeam, ref float ctTotalScore, ref float tTotalScore) - { - // Validate that both players are still on their respective teams - if (!ctTeam.Contains(ctPlayer) || !tTeam.Contains(tPlayer)) - { - PrintDebugMessage("Player no longer in the original team. Skipping trade."); - return; - } + // Pre-checks and debug messages + var ctPlayerCount = balanceStats.CT.Stats.Count; + var tPlayerCount = balanceStats.T.Stats.Count; + var ctTotalScore = balanceStats.CT.TotalPerformanceScore; + var tTotalScore = balanceStats.T.TotalPerformanceScore; - // Safeguard: Ensure trade will reduce the score imbalance - float newCtTotalScore = ctTotalScore - ctPlayer.PerformanceScore + tPlayer.PerformanceScore; - float newTTotalScore = tTotalScore - tPlayer.PerformanceScore + ctPlayer.PerformanceScore; + PrintDebugMessage($"Current CT Team size: {ctPlayerCount}, T Team size: {tPlayerCount}"); + PrintDebugMessage($"Current CT Score: {ctTotalScore}, T Score: {tTotalScore}"); - if (Math.Abs(newCtTotalScore - newTTotalScore) >= Math.Abs(ctTotalScore - tTotalScore)) + if (Math.Abs(ctPlayerCount - tPlayerCount) > Config?.PluginSettings.MaxTeamSizeDifference) { - PrintDebugMessage("Trade would worsen score imbalance. Skipping trade."); - return; + PrintDebugMessage($"Team size difference exceeds the allowed max_team_size_difference: {Config?.PluginSettings.MaxTeamSizeDifference}. Correction needed."); } - - // Perform the trade - ctTeam.Remove(ctPlayer); - tTeam.Remove(tPlayer); - - ctTeam.Add(tPlayer); - tTeam.Add(ctPlayer); - - // Adjust the team scores - ctTotalScore = newCtTotalScore; - tTotalScore = newTTotalScore; - - PrintDebugMessage($"Traded {ctPlayer.PlayerName} with {tPlayer.PlayerName}. New scores: CT = {ctTotalScore}, T = {tTotalScore}"); - } - - private static void MovePlayer(Player player, List toTeam, List fromTeam, ref float fromTeamScore, ref float toTeamScore, bool forceMove = false) - { - if (player == null || fromTeam == null || toTeam == null) + + if (Math.Abs(ctTotalScore - tTotalScore) > Config?.PluginSettings.MaxScoreBalanceRatio) { - PrintDebugMessage("Invalid operation. Either player or team is null."); - return; + PrintDebugMessage($"Score difference exceeds the allowed score_balance_ratio: {Config?.PluginSettings.MaxScoreBalanceRatio}. Correction needed."); } - // Safeguard: Do not move if the move would make score differences worse unless forceMove is true - float projectedFromTeamScore = fromTeamScore - player.PerformanceScore; - float projectedToTeamScore = toTeamScore + player.PerformanceScore; - - if (Math.Abs(projectedFromTeamScore - projectedToTeamScore) > Math.Abs(fromTeamScore - toTeamScore) && !forceMove) + // Step 1: Balance team sizes + if (balanceStats.ShouldMoveLowestScorers()) { - PrintDebugMessage("Move would worsen score imbalance. Skipping move."); - return; + balanceStats.MoveLowestScorersFromBiggerTeam(); } - fromTeam.Remove(player); - toTeam.Add(player); - - fromTeamScore -= player.PerformanceScore; - toTeamScore += player.PerformanceScore; - - PrintDebugMessage($"Moved {player.PlayerName} to the opposite team. New scores: CT = {fromTeamScore}, T = {toTeamScore}"); - } - - private static bool ApplyTeamChanges(List ctTeam, List tTeam, int currentRound) - { - bool balanceMade = false; - - foreach (var player in ctTeam) + // Step 2: Balance teams by performance scores + if (!balanceStats.TeamsAreEqualScore()) { - if (player.Team != (int)CsTeam.CounterTerrorist) - { - ChangePlayerTeam(player.PlayerSteamID, CsTeam.CounterTerrorist); - balanceMade = true; - } + balanceStats.BalanceTeamsByPerformance(); } - foreach (var player in tTeam) - { - if (player.Team != (int)CsTeam.Terrorist) - { - ChangePlayerTeam(player.PlayerSteamID, CsTeam.Terrorist); - balanceMade = true; - } - } + // Step 3: Apply the team assignments + balanceStats.AssignPlayerTeams(); - return balanceMade; + PrintDebugMessage($"Rebalance complete || Final CT Score: {balanceStats.CT.TotalPerformanceScore}, Final T Score: {balanceStats.T.TotalPerformanceScore}, Final CT Players: {balanceStats.CT.Stats.Count}, Final T Players: {balanceStats.T.Stats.Count}"); + return true; } -} \ No newline at end of file +} diff --git a/Class/BalanceStats.cs b/Class/BalanceStats.cs new file mode 100644 index 0000000..d246d7b --- /dev/null +++ b/Class/BalanceStats.cs @@ -0,0 +1,143 @@ +using CounterStrikeSharp.API.Modules.Utils; + +namespace Mesharsky_TeamBalance; + +public partial class Mesharsky_TeamBalance +{ + public class BalanceStats + { + public TeamStats CT { get; set; } = new TeamStats(); + public TeamStats T { get; set; } = new TeamStats(); + + public void GetStats(List allPlayers) + { + if (allPlayers == null || allPlayers.Count == 0) + { + PrintDebugMessage("No players to get stats for."); + return; + } + + CT.Reset(); + T.Reset(); + + foreach (var player in allPlayers) + { + if (player.Team == (int)CsTeam.CounterTerrorist) + CT.AddPlayer(player); + else if (player.Team == (int)CsTeam.Terrorist) + T.AddPlayer(player); + } + } + + public bool TeamsAreEqualScore() + { + return Math.Abs(CT.TotalPerformanceScore - T.TotalPerformanceScore) <= (Config?.PluginSettings.MaxScoreBalanceRatio ?? 1.0f); + } + + public bool ShouldMoveLowestScorers() + { + return Math.Abs(CT.Stats.Count - T.Stats.Count) > (Config?.PluginSettings.MaxTeamSizeDifference ?? 1); + } + + public void MoveLowestScorersFromBiggerTeam() + { + int maxAttempts = 20; + int attempts = 0; + + while (CT.Stats.Count != T.Stats.Count && attempts < maxAttempts) + { + attempts++; + + if (CT.Stats.Count > T.Stats.Count) + { + var playerToMove = CT.Stats.OrderBy(p => p.PerformanceScore).FirstOrDefault(); + if (playerToMove != null) + { + T.AddPlayer(playerToMove); + CT.RemovePlayer(playerToMove); + PrintDebugMessage($"Moved player {playerToMove.PlayerName} from CT to T."); + } + } + else if (T.Stats.Count > CT.Stats.Count) + { + var playerToMove = T.Stats.OrderBy(p => p.PerformanceScore).FirstOrDefault(); + if (playerToMove != null) + { + CT.AddPlayer(playerToMove); + T.RemovePlayer(playerToMove); + PrintDebugMessage($"Moved player {playerToMove.PlayerName} from T to CT."); + } + } + + // Check if no meaningful changes are being made, break to avoid infinite loop + if (attempts >= maxAttempts) + { + PrintDebugMessage("Maximum attempts reached while balancing team sizes. Exiting to prevent infinite loop."); + break; + } + } + } + + public void BalanceTeamsByPerformance() + { + int maxAttempts = 30; + int attempts = 0; + + while (!TeamsAreEqualScore() && attempts < maxAttempts) + { + attempts++; + + var ctPlayer = CT.Stats.OrderByDescending(p => p.PerformanceScore).FirstOrDefault(); + var tPlayer = T.Stats.OrderByDescending(p => p.PerformanceScore).FirstOrDefault(); + + if (ctPlayer != null && tPlayer != null) + { + SwapPlayers(ctPlayer, tPlayer); + PrintDebugMessage($"Swapped player {ctPlayer.PlayerName} with {tPlayer.PlayerName}"); + + } + else + { + PrintDebugMessage("No valid players found for swapping. Exiting loop."); + break; + } + + if (attempts >= maxAttempts) + { + PrintDebugMessage("Maximum attempts reached while balancing team performance. Exiting to prevent infinite loop."); + break; + } + } + } + + private void SwapPlayers(PlayerStats ctPlayer, PlayerStats tPlayer) + { + if (ctPlayer == null || tPlayer == null) + { + PrintDebugMessage("Cannot swap null players."); + return; + } + + CT.RemovePlayer(ctPlayer); + T.RemovePlayer(tPlayer); + + CT.AddPlayer(tPlayer); + T.AddPlayer(ctPlayer); + } + + public void AssignPlayerTeams() + { + foreach (var player in CT.Stats) + { + if (player.Team != (int)CsTeam.CounterTerrorist) + ChangePlayerTeam(player.PlayerSteamID, CsTeam.CounterTerrorist); + } + + foreach (var player in T.Stats) + { + if (player.Team != (int)CsTeam.Terrorist) + ChangePlayerTeam(player.PlayerSteamID, CsTeam.Terrorist); + } + } + } +} diff --git a/Class/Player.cs b/Class/Player.cs deleted file mode 100644 index 842cc9a..0000000 --- a/Class/Player.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace Mesharsky_TeamBalance; - -public partial class Mesharsky_TeamBalance -{ - public class Player - { - public string? PlayerName { get; set; } - public ulong PlayerSteamID { get; set; } - public int Team { get; set; } - public int Score { get; set; } - public int Kills { get; set; } - public int Deaths { get; set; } - public int Assists { get; set; } - public int Damage { get; set; } - - // KDA Ratio: (Kills + Assists) / Deaths - public float KDA => Deaths == 0 ? (Kills + Assists) : (float)(Kills + Assists) / Deaths; - - // Consistency Factor: Measures player's consistent impact over the rounds (based on kills and damage) - public float ConsistencyFactor => (Kills * 0.6f + Damage * 0.4f) / (Kills + Deaths + 1); - - // Performance Score - public float PerformanceScore => (Damage * 0.5f) + (KDA * 0.4f) + (ConsistencyFactor * 0.1f); - } -} diff --git a/Class/PlayerStats.cs b/Class/PlayerStats.cs new file mode 100644 index 0000000..6dde310 --- /dev/null +++ b/Class/PlayerStats.cs @@ -0,0 +1,19 @@ +namespace Mesharsky_TeamBalance; + +public class PlayerStats +{ + public string? PlayerName { get; set; } + public ulong PlayerSteamID { get; set; } + public int Team { get; set; } + public int Score { get; set; } + public int Kills { get; set; } + public int Deaths { get; set; } + public int Assists { get; set; } + public int Damage { get; set; } + + public float KDA => Deaths == 0 ? (Kills + Assists) : (float)(Kills + Assists) / Deaths; + + public float ConsistencyFactor => (Kills * 0.6f + Damage * 0.4f) / (Kills + Deaths + 1); + + public float PerformanceScore => (Damage * 0.5f) + (KDA * 0.4f) + (ConsistencyFactor * 0.1f); +} diff --git a/Class/TeamStats.cs b/Class/TeamStats.cs new file mode 100644 index 0000000..887c18a --- /dev/null +++ b/Class/TeamStats.cs @@ -0,0 +1,34 @@ +namespace Mesharsky_TeamBalance; + + +public partial class Mesharsky_TeamBalance +{ + public class TeamStats + { + public List Stats { get; set; } = new List(); + public float TotalPerformanceScore { get; private set; } + + public void CalculatePerformanceScore() + { + TotalPerformanceScore = Stats.Sum(player => player.PerformanceScore); + } + + public void Reset() + { + Stats.Clear(); + TotalPerformanceScore = 0; + } + + public void AddPlayer(PlayerStats player) + { + Stats.Add(player); + CalculatePerformanceScore(); + } + + public void RemovePlayer(PlayerStats player) + { + Stats.Remove(player); + CalculatePerformanceScore(); + } + } +} \ No newline at end of file diff --git a/Config/Config.cs b/Config/Config.cs index d4fbe9e..66b4ce9 100644 --- a/Config/Config.cs +++ b/Config/Config.cs @@ -23,18 +23,16 @@ public void LoadConfiguration() if (model.TryGetValue("PluginSettings", out var pluginSettingsObj) && pluginSettingsObj is TomlTable pluginTable) { var minPlayers = int.Parse(pluginTable["minimum_players"]?.ToString() ?? "4"); - var maxScoreBalanceRatio = float.Parse(pluginTable["score_balance_ratio"]?.ToString() ?? "1.6"); + var maxScoreBalanceRatio = float.Parse(pluginTable["score_balance_ratio"]?.ToString() ?? "2.0"); var usePerformanceScore = bool.Parse(pluginTable["use_performance_score"]?.ToString() ?? "true"); var maxTeamSizeDifference = int.Parse(pluginTable["max_team_size_difference"]?.ToString() ?? "1"); - var minRoundsBetweenMoves = int.Parse(pluginTable["min_rounds_between_moves"]?.ToString() ?? "2"); var pluginSettings = new PluginSettingsConfig { MinPlayers = minPlayers, MaxScoreBalanceRatio = maxScoreBalanceRatio, UsePerformanceScore = usePerformanceScore, - MaxTeamSizeDifference = maxTeamSizeDifference, - MinRoundsBetweenMoves = minRoundsBetweenMoves + MaxTeamSizeDifference = maxTeamSizeDifference }; Config = new BalanceConfig @@ -72,8 +70,8 @@ private static void GenerateDefaultConfigFile(string configPath) .AppendLine("# For example, if set to 1.6, the balance will trigger if one team's score is") .AppendLine("# 60% higher than the other team's score. Adjust this value based on how strict") .AppendLine("# you want the balancing to be.") - .AppendLine("# Default: 1.6") - .AppendLine("score_balance_ratio = 1.6") + .AppendLine("# Default: 2.0") + .AppendLine("score_balance_ratio = 2.0") .AppendLine() .AppendLine("# Whether to use PerformanceScore for balancing.") .AppendLine("# PerformanceScore is a custom metric that considers KDA (Kills, Deaths, Assists),") @@ -89,13 +87,7 @@ private static void GenerateDefaultConfigFile(string configPath) .AppendLine("# of players between the teams is no more than one. This helps prevent one team from") .AppendLine("# having a significant numerical advantage over the other.") .AppendLine("# Default: 1") - .AppendLine("max_team_size_difference = 1") - .AppendLine() - .AppendLine("# The minimum number of rounds that must pass before a player can be moved again.") - .AppendLine("# This setting prevents the same player from being moved back and forth between") - .AppendLine("# teams multiple times in quick succession.") - .AppendLine("# Default: 2") - .AppendLine("min_rounds_between_moves = 2"); + .AppendLine("max_team_size_difference = 1"); File.WriteAllText(configPath, defaultConfig.ToString()); diff --git a/Config/ConfigList.cs b/Config/ConfigList.cs index 5d5a76d..73ca75d 100644 --- a/Config/ConfigList.cs +++ b/Config/ConfigList.cs @@ -10,9 +10,8 @@ public class BalanceConfig public class PluginSettingsConfig { public int MinPlayers { get; set; } = 4; - public float MaxScoreBalanceRatio { get; set; } = 1.6f; + public float MaxScoreBalanceRatio { get; set; } = 2.0f; public bool UsePerformanceScore { get; set; } = true; public int MaxTeamSizeDifference { get; set; } = 1; - public int MinRoundsBetweenMoves { get; set; } = 2; } } diff --git a/Events/Events.cs b/Events/Events.cs index 36c715a..363b9c8 100644 --- a/Events/Events.cs +++ b/Events/Events.cs @@ -17,14 +17,6 @@ public void Initialize_Events() [GameEventHandler] public HookResult OnRoundStart(EventRoundStart @event, GameEventInfo info) { - if (GlobalBalanceMade) - { - Server.PrintToChatAll($" {ChatColors.Red}[Team Balance] {ChatColors.Default}Teams have been balanced."); - } - else - { - Server.PrintToChatAll($" {ChatColors.Red}[Team Balance] {ChatColors.Default}No need for team balance at this moment."); - } if (!IsWarmup()) return HookResult.Continue; @@ -72,6 +64,12 @@ private static void UpdatePlayerStatsInCache() var allPlayers = Utilities.GetPlayers(); + if (allPlayers == null) + { + PrintDebugMessage("No players found."); + return; + } + foreach (var player in allPlayers.Where(p => p != null && p.IsValid && !p.IsBot && !p.IsHLTV && p.Connected == PlayerConnectedState.PlayerConnected)) { if (playerCache.TryGetValue(player.SteamID, out var cachedPlayer)) @@ -84,7 +82,7 @@ private static void UpdatePlayerStatsInCache() } else { - var newPlayer = new Player + var newPlayer = new PlayerStats { PlayerName = player.PlayerName, PlayerSteamID = player.SteamID, @@ -150,7 +148,7 @@ private static int ParseTeamId(CommandInfo info) return info.ArgCount > startIndex && int.TryParse(info.ArgByIndex(startIndex), out int teamId) ? teamId : -1; } - private static bool CanSwitchTeams(Player cachedPlayer, int newTeamId) + private static bool CanSwitchTeams(PlayerStats cachedPlayer, int newTeamId) { int ctPlayerCount = playerCache.Values.Count(p => p.Team == (int)CsTeam.CounterTerrorist); int tPlayerCount = playerCache.Values.Count(p => p.Team == (int)CsTeam.Terrorist); @@ -160,7 +158,7 @@ private static bool CanSwitchTeams(Player cachedPlayer, int newTeamId) return Math.Abs(ctPlayerCount - tPlayerCount) <= Config?.PluginSettings.MaxTeamSizeDifference; } - private static void AdjustPlayerCountForSwitch(Player cachedPlayer, int newTeamId, ref int ctPlayerCount, ref int tPlayerCount) + private static void AdjustPlayerCountForSwitch(PlayerStats cachedPlayer, int newTeamId, ref int ctPlayerCount, ref int tPlayerCount) { if (cachedPlayer.Team == (int)CsTeam.CounterTerrorist) ctPlayerCount--; @@ -173,9 +171,9 @@ private static void AdjustPlayerCountForSwitch(Player cachedPlayer, int newTeamI tPlayerCount++; } - private static void UpdateTeamAssignment(Player cachedPlayer, int newTeamId) + private static void UpdateTeamAssignment(PlayerStats cachedPlayer, int newTeamId) { cachedPlayer.Team = newTeamId; PrintDebugMessage($"Player {cachedPlayer.PlayerName} updated to team {newTeamId} in cache."); } -} \ No newline at end of file +} diff --git a/Helpers/Misc.cs b/Helpers/Misc.cs index fe2b164..6448442 100644 --- a/Helpers/Misc.cs +++ b/Helpers/Misc.cs @@ -28,10 +28,10 @@ private static bool ChangePlayerTeam(ulong steamId, CsTeam newTeam) player.SwitchTeam(newTeam); - playerCache.AddOrUpdate(steamId, + playerCache.AddOrUpdate(steamId, (key) => { - var newPlayer = new Player + var newPlayer = new PlayerStats { PlayerName = player.PlayerName, PlayerSteamID = player.SteamID, @@ -40,7 +40,7 @@ private static bool ChangePlayerTeam(ulong steamId, CsTeam newTeam) }; PrintDebugMessage($"Added {newPlayer.PlayerName} to cache with team {newPlayer.Team}"); return newPlayer; - }, + }, (key, cachedPlayer) => { cachedPlayer.Team = (int)newTeam; @@ -51,23 +51,6 @@ private static bool ChangePlayerTeam(ulong steamId, CsTeam newTeam) return true; } - private static bool CanMovePlayer(List fromTeam, List toTeam, Player player, int currentRound) - { - if (Math.Abs(fromTeam.Count - 1 - (toTeam.Count + 1)) > Config?.PluginSettings.MaxTeamSizeDifference) - return false; - - return true; - } - - public static int GetCurrentRound() - { - var gameRules = Utilities.FindAllEntitiesByDesignerName("cs_gamerules").First().GameRules!; - - int rounds = gameRules.TotalRoundsPlayed; - - return rounds; - } - public static bool IsWarmup() { return Utilities.FindAllEntitiesByDesignerName("cs_gamerules").First().GameRules! diff --git a/Mesharsky_TeamBalance.cs b/Mesharsky_TeamBalance.cs index a159ded..1d4b160 100644 --- a/Mesharsky_TeamBalance.cs +++ b/Mesharsky_TeamBalance.cs @@ -6,7 +6,7 @@ namespace Mesharsky_TeamBalance; public partial class Mesharsky_TeamBalance : BasePlugin { public override string ModuleName => "Mesharsky Team Balance"; - public override string ModuleVersion => "1.0"; + public override string ModuleVersion => "2.0"; public override string ModuleAuthor => "Mesharsky"; public override void Load(bool hotReload) diff --git a/Mesharsky_TeamBalance.generated.sln b/Mesharsky_TeamBalance.generated.sln index 8e3a582..870e388 100644 --- a/Mesharsky_TeamBalance.generated.sln +++ b/Mesharsky_TeamBalance.generated.sln @@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.5.002.0 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mesharsky_TeamBalance", "Mesharsky_TeamBalance.csproj", "{CD90E983-B596-4595-B25B-E0A84C5C4B27}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mesharsky_TeamBalance", "Mesharsky_TeamBalance.csproj", "{23086BC3-0B9E-4208-97C1-CD6A2D56B1DE}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -11,10 +11,10 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {CD90E983-B596-4595-B25B-E0A84C5C4B27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CD90E983-B596-4595-B25B-E0A84C5C4B27}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CD90E983-B596-4595-B25B-E0A84C5C4B27}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CD90E983-B596-4595-B25B-E0A84C5C4B27}.Release|Any CPU.Build.0 = Release|Any CPU + {23086BC3-0B9E-4208-97C1-CD6A2D56B1DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {23086BC3-0B9E-4208-97C1-CD6A2D56B1DE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {23086BC3-0B9E-4208-97C1-CD6A2D56B1DE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {23086BC3-0B9E-4208-97C1-CD6A2D56B1DE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/TeamBalance.toml b/TeamBalance.toml index 1e125ff..4b80b6c 100644 --- a/TeamBalance.toml +++ b/TeamBalance.toml @@ -15,7 +15,7 @@ minimum_players = 4 # 60% higher than the other team's score. Adjust this value based on how strict # you want the balancing to be. # Default: 1.6 -score_balance_ratio = 1.6 +score_balance_ratio = 2.0 # Whether to use PerformanceScore for balancing. # PerformanceScore is a custom metric that considers KDA (Kills, Deaths, Assists), @@ -31,10 +31,4 @@ use_performance_score = true # of players between the teams is no more than one. This helps prevent one team from # having a significant numerical advantage over the other. # Default: 1 -max_team_size_difference = 1 - -# The minimum number of rounds that must pass before a player can be moved again. -# This setting prevents the same player from being moved back and forth between -# teams multiple times in quick succession. -# Default: 2 -min_rounds_between_moves = 2 \ No newline at end of file +max_team_size_difference = 1 \ No newline at end of file