Skip to content

Commit

Permalink
Improvments + Enhancements. New config value - Rebalance logic. Warmu…
Browse files Browse the repository at this point in the history
…p rebalance
  • Loading branch information
Mesharsky committed Aug 23, 2024
1 parent 75ba900 commit 8bb2860
Show file tree
Hide file tree
Showing 9 changed files with 212 additions and 75 deletions.
4 changes: 4 additions & 0 deletions Class/Player.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,9 @@ public class Player

// Performance score based on KDA, Damage, and Score
public float PerformanceScore => KDA * 0.5f + Damage * 0.3f + Score * 0.2f;

// Track the round number when the player was last moved
public int LastMovedRound { get; set; } = 0;
}
}

13 changes: 11 additions & 2 deletions Config/Config.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,15 @@ public void LoadConfiguration()
var maxScoreBalanceRatio = float.Parse(pluginTable["score_balance_ratio"]?.ToString() ?? "1.6");
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
MaxTeamSizeDifference = maxTeamSizeDifference,
MinRoundsBetweenMoves = minRoundsBetweenMoves
};

Config = new BalanceConfig
Expand Down Expand Up @@ -87,10 +89,17 @@ 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("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");

File.WriteAllText(configPath, defaultConfig.ToString());

PrintDebugMessage("Default configuration file created successfully.");
}

}
1 change: 1 addition & 0 deletions Config/ConfigList.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ public class PluginSettingsConfig
public float MaxScoreBalanceRatio { get; set; } = 1.6f;
public bool UsePerformanceScore { get; set; } = true;
public int MaxTeamSizeDifference { get; set; } = 1;
public int MinRoundsBetweenMoves { get; set; } = 2;
}
}
88 changes: 88 additions & 0 deletions Events/Events.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes.Registration;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Cvars;
using CounterStrikeSharp.API.Modules.Utils;

Expand All @@ -17,6 +18,20 @@ public void Initialize_Events()
Event_PlayerDisconnect();
}

[GameEventHandler]
public HookResult OnRoundStart(EventRoundStart @event, GameEventInfo info)
{
if (!IsWarmup())
return HookResult.Continue;

var endTime = ConVar.Find("mp_warmuptime")?.GetPrimitiveValue<float>();
var delay = endTime == null ? 1 : (endTime - 1);

AddTimer((float)delay, AttemptBalanceTeams);

return HookResult.Continue;
}

[GameEventHandler]
public HookResult OnRoundEnd(EventRoundEnd @event, GameEventInfo info)
{
Expand Down Expand Up @@ -116,4 +131,77 @@ private static void UpdatePlayerStatsInCache()
}
}
}

private HookResult Command_JoinTeam(CCSPlayerController? player, CommandInfo info)
{
if (!IsWarmup())
return HookResult.Continue;

if (player != null && player.IsValid)
{
int startIndex = 0;
if (info.ArgCount > 0 && info.ArgByIndex(0).ToLower() == "jointeam")
{
startIndex = 1;
}

if (info.ArgCount > startIndex)
{
string teamArg = info.ArgByIndex(startIndex);

if (int.TryParse(teamArg, out int teamId))
{
if (teamId >= (int)CsTeam.Spectator && teamId <= (int)CsTeam.CounterTerrorist)
{
if (playerCache.TryGetValue(player.SteamID, out var cachedPlayer))
{
var currentTeam = cachedPlayer.Team;
if (currentTeam == teamId)
{
PrintDebugMessage($"Player {cachedPlayer.PlayerName} is already on team {teamId}. No change needed.");
return HookResult.Continue;
}

int ctPlayerCount = playerCache.Values.Count(p => p.Team == (int)CsTeam.CounterTerrorist);
int tPlayerCount = playerCache.Values.Count(p => p.Team == (int)CsTeam.Terrorist);

if (currentTeam == (int)CsTeam.CounterTerrorist)
{
ctPlayerCount--;
}
else if (currentTeam == (int)CsTeam.Terrorist)
{
tPlayerCount--;
}

if (teamId == (int)CsTeam.CounterTerrorist)
{
ctPlayerCount++;
}
else if (teamId == (int)CsTeam.Terrorist)
{
tPlayerCount++;
}

if (Math.Abs(ctPlayerCount - tPlayerCount) <= Config?.PluginSettings.MaxTeamSizeDifference)
{
cachedPlayer.Team = teamId;
PrintDebugMessage($"Player {cachedPlayer.PlayerName} updated to team {teamId} in cache.");
}
else
{
PrintDebugMessage($"Player {cachedPlayer.PlayerName} cannot switch to team {teamId} as it would violate the team balance.");
//player.PrintToChat($" {ChatColors.Red}[Csowicze] {ChatColors.Default} Nie możesz zmienić drużyny. Ilość graczy nie będzie równa");
player.PrintToChat($" {ChatColors.Red}[Team Balance] {ChatColors.Default} you cannot switch to this team as it would violate the team balance");
return HookResult.Handled;
}
}
}
}
}
}

return HookResult.Continue;
}

}
98 changes: 64 additions & 34 deletions Helpers/BalanceFunctions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,13 @@ private void AttemptBalanceTeams()

if (balanceMade)
{
BalanceHasBeenMade = true;
Server.PrintToChatAll($"{ChatColors.Red}[Team Balance] {ChatColors.Default}Teams has been balanced");
Server.PrintToChatAll($" {ChatColors.Red}[Team Balance] {ChatColors.Default}Teams has been balanced");
//Server.PrintToChatAll($" {ChatColors.Red}[Csowicze] {ChatColors.Default}Drużyny zostały zbalansowane.");
}
else
{
BalanceHasBeenMade = false;
Server.PrintToChatAll($" {ChatColors.Red}[Team Balance] {ChatColors.Default}No need for team balance at this moment");
//Server.PrintToChatAll($" {ChatColors.Red}[Csowicze] {ChatColors.Default}System nie wykrył potrzeby balansu drużyn. Brak zmian.");
}
}

Expand All @@ -42,15 +43,18 @@ private static List<Player> GetPlayersForRebalance()
return players;
}

// This is ASS, but no other idea at this moment.
private static bool RebalancePlayers(List<Player> players)
{
PrintDebugMessage("Starting player rebalance...");

int totalPlayers = players.Count;
int maxPerTeam = totalPlayers / 2 + (totalPlayers % 2);
int currentRound = GetCurrentRound();

List<Player> ctTeam = new List<Player>();
List<Player> tTeam = new List<Player>();
HashSet<ulong> movedPlayers = new HashSet<ulong>();

float ctTotalScore = 0f;
float tTotalScore = 0f;
Expand All @@ -59,45 +63,35 @@ private static bool RebalancePlayers(List<Player> players)

PrintDebugMessage($"RebalancePlayers: totalPlayers={totalPlayers}, maxPerTeam={maxPerTeam}");

// Step 1: Balance team sizes first
foreach (var player in players)
{
bool ctValidChoice = (tTeam.Count >= maxPerTeam || ctTotalScore <= tTotalScore) && ctTeam.Count < maxPerTeam;
bool tValidChoice = (ctTeam.Count >= maxPerTeam || tTotalScore <= ctTotalScore) && tTeam.Count < maxPerTeam;
bool ctValidChoice = ctTeam.Count < maxPerTeam && ctTeam.Count < tTeam.Count + Config?.PluginSettings.MaxTeamSizeDifference;
bool tValidChoice = tTeam.Count < maxPerTeam && tTeam.Count < ctTeam.Count + Config?.PluginSettings.MaxTeamSizeDifference;

// Ensure team size difference is not exceeded before making a move
if (ctValidChoice && player.Team != (int)CsTeam.CounterTerrorist)
if (ctValidChoice && player.Team != (int)CsTeam.CounterTerrorist && CanMovePlayer(ctTeam, tTeam, player, currentRound))
{
if (Math.Abs((ctTeam.Count + 1) - tTeam.Count) <= Config?.PluginSettings.MaxTeamSizeDifference)
{
PrintDebugMessage($"Move {player.PlayerName} to CT (ctTotal={ctTotalScore}, ctCount={ctTeam.Count + 1})");
ChangePlayerTeam(player.PlayerSteamID, CsTeam.CounterTerrorist);
ctTeam.Add(player);
ctTotalScore += player.PerformanceScore;
balanceMade = true;
}
else
{
PrintDebugMessage("Skipping move to CT as it would exceed max team size difference.");
}
PrintDebugMessage($"Move {player.PlayerName} to CT (ctTotal={ctTotalScore}, ctCount={ctTeam.Count + 1})");
ChangePlayerTeam(player.PlayerSteamID, CsTeam.CounterTerrorist);
ctTeam.Add(player);
ctTotalScore += player.PerformanceScore;
movedPlayers.Add(player.PlayerSteamID);
player.LastMovedRound = currentRound;
balanceMade = true;
}
else if (tValidChoice && player.Team != (int)CsTeam.Terrorist)
else if (tValidChoice && player.Team != (int)CsTeam.Terrorist && CanMovePlayer(tTeam, ctTeam, player, currentRound))
{
if (Math.Abs(ctTeam.Count - (tTeam.Count + 1)) <= Config?.PluginSettings.MaxTeamSizeDifference)
{
PrintDebugMessage($"Move {player.PlayerName} to T (tTotal={tTotalScore}, tCount={tTeam.Count + 1})");
ChangePlayerTeam(player.PlayerSteamID, CsTeam.Terrorist);
tTeam.Add(player);
tTotalScore += player.PerformanceScore;
balanceMade = true;
}
else
{
PrintDebugMessage("Skipping move to T as it would exceed max team size difference.");
}
PrintDebugMessage($"Move {player.PlayerName} to T (tTotal={tTotalScore}, tCount={tTeam.Count + 1})");
ChangePlayerTeam(player.PlayerSteamID, CsTeam.Terrorist);
tTeam.Add(player);
tTotalScore += player.PerformanceScore;
movedPlayers.Add(player.PlayerSteamID);
player.LastMovedRound = currentRound;
balanceMade = true;
}
else
{
// Keep the player on their current team
// If no moves were made, add the player to their current team
if (player.Team == (int)CsTeam.CounterTerrorist)
{
ctTeam.Add(player);
Expand All @@ -111,12 +105,48 @@ private static bool RebalancePlayers(List<Player> players)
}
}

// Step 2: Further balance by performance score while keeping team sizes in check
while (Math.Abs(ctTotalScore - tTotalScore) > Config?.PluginSettings.MaxScoreBalanceRatio)
{
List<Player> candidatesToMove = ctTotalScore > tTotalScore
? ctTeam.OrderByDescending(p => p.PerformanceScore).ToList()
: tTeam.OrderByDescending(p => p.PerformanceScore).ToList();

// Select the first player in the list that can be moved and has not been moved recently
var playerToMove = candidatesToMove.FirstOrDefault(p => CanMovePlayer(
ctTotalScore > tTotalScore ? ctTeam : tTeam,
ctTotalScore > tTotalScore ? tTeam : ctTeam,
p,
currentRound
));

if (playerToMove == null)
break;

PrintDebugMessage($"Move {playerToMove.PlayerName} to {(ctTotalScore > tTotalScore ? "T" : "CT")} to balance score.");
ChangePlayerTeam(playerToMove.PlayerSteamID, ctTotalScore > tTotalScore ? CsTeam.Terrorist : CsTeam.CounterTerrorist);
ctTeam.Remove(playerToMove);
tTeam.Add(playerToMove);
if (ctTotalScore > tTotalScore)
{
ctTotalScore -= playerToMove.PerformanceScore;
tTotalScore += playerToMove.PerformanceScore;
}
else
{
tTotalScore -= playerToMove.PerformanceScore;
ctTotalScore += playerToMove.PerformanceScore;
}

playerToMove.LastMovedRound = currentRound;
movedPlayers.Add(playerToMove.PlayerSteamID);
}

PrintDebugMessage($"Final Team Distribution - CT: {ctTeam.Count} players, T: {tTeam.Count} players");

return balanceMade;
}


private static bool ShouldTeamsBeRebalanced()
{
PrintDebugMessage("Evaluating if teams need to be rebalanced...");
Expand Down
63 changes: 32 additions & 31 deletions Helpers/Misc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,37 +52,6 @@ private static bool ChangePlayerTeam(ulong steamId, CsTeam newTeam)
return true;
}

private HookResult Command_JoinTeam(CCSPlayerController? player, CommandInfo info)
{
if (player != null && player.IsValid)
{
int startIndex = 0;
if (info.ArgCount > 0 && info.ArgByIndex(0).ToLower() == "jointeam")
{
startIndex = 1;
}

if (info.ArgCount > startIndex)
{
string teamArg = info.ArgByIndex(startIndex);

if (int.TryParse(teamArg, out int teamId))
{
if (teamId >= (int)CsTeam.Spectator && teamId <= (int)CsTeam.CounterTerrorist)
{
if (playerCache.TryGetValue(player.SteamID, out var cachedPlayer))
{
cachedPlayer.Team = teamId;
PrintDebugMessage($"Player {cachedPlayer.PlayerName} updated to team {teamId} in cache.");
}
}
}
}
}

return HookResult.Continue;
}

private static void UpdatePlayerTeamsInCache()
{
PrintDebugMessage("Updating player teams in cache...");
Expand Down Expand Up @@ -112,4 +81,36 @@ private static void UpdatePlayerTeamsInCache()
}
}
}

private static bool CanMovePlayer(List<Player> fromTeam, List<Player> toTeam, Player player, int currentRound)
{
// Check if moving the player would exceed max team size difference
if (Math.Abs(fromTeam.Count - 1 - (toTeam.Count + 1)) > Config?.PluginSettings.MaxTeamSizeDifference)
{
return false;
}

// Check if the player has been moved recently
if (currentRound - player.LastMovedRound < Config?.PluginSettings.MinRoundsBetweenMoves)
{
return false;
}

return true;
}

public static int GetCurrentRound()
{
var gameRules = Utilities.FindAllEntitiesByDesignerName<CCSGameRulesProxy>("cs_gamerules").First().GameRules!;

int rounds = gameRules.TotalRoundsPlayed;

return rounds;
}

public static bool IsWarmup()
{
return Utilities.FindAllEntitiesByDesignerName<CCSGameRulesProxy>("cs_gamerules").First().GameRules!
.WarmupPeriod;
}
}
4 changes: 1 addition & 3 deletions Mesharsky_TeamBalance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,9 @@ namespace Mesharsky_TeamBalance;
public partial class Mesharsky_TeamBalance : BasePlugin
{
public override string ModuleName => "Mesharsky Team Balance";
public override string ModuleVersion => "0.1";
public override string ModuleVersion => "0.2";
public override string ModuleAuthor => "Mesharsky";

private bool BalanceHasBeenMade = false;

public override void Load(bool hotReload)
{
LoadConfiguration();
Expand Down
Loading

0 comments on commit 8bb2860

Please sign in to comment.