From 12507b0fed525041a750080c1b7fe40777d8df65 Mon Sep 17 00:00:00 2001 From: Eveldee Date: Mon, 5 Dec 2022 09:55:31 +0100 Subject: [PATCH] [Raid] Add preliminary roster assigner --- Cocotte/Modules/Raids/RaidFormatter.cs | 1 + Cocotte/Modules/Raids/RaidRegisterManager.cs | 2 +- Cocotte/Modules/Raids/RosterAssigner.cs | 136 +++++++++++++++++++ Cocotte/Modules/Raids/RosterExtensions.cs | 39 ++++++ Cocotte/Program.cs | 3 +- 5 files changed, 179 insertions(+), 2 deletions(-) create mode 100644 Cocotte/Modules/Raids/RosterAssigner.cs create mode 100644 Cocotte/Modules/Raids/RosterExtensions.cs diff --git a/Cocotte/Modules/Raids/RaidFormatter.cs b/Cocotte/Modules/Raids/RaidFormatter.cs index fe57185..502b292 100644 --- a/Cocotte/Modules/Raids/RaidFormatter.cs +++ b/Cocotte/Modules/Raids/RaidFormatter.cs @@ -44,6 +44,7 @@ public class RaidFormatter var separatorLength = Math.Max(nonSubstitute.Select(p => p.Name.Length).Max(), substitute.Select(p => p.Name.Length).Max()); separatorLength = (int) ((separatorLength + 13) * 0.49); // Don't ask why, it just works + // Todo add Total FC number return new EmbedFieldBuilder() .WithName($"Roster {rosterNumber}") .WithValue($"{string.Join("\n", nonSubstitute.Select(FormatRosterPlayer))}\n{new string('━', separatorLength)}\n{string.Join("\n", substitute.Select(FormatRosterPlayer))}") diff --git a/Cocotte/Modules/Raids/RaidRegisterManager.cs b/Cocotte/Modules/Raids/RaidRegisterManager.cs index 26db65f..ae49e2f 100644 --- a/Cocotte/Modules/Raids/RaidRegisterManager.cs +++ b/Cocotte/Modules/Raids/RaidRegisterManager.cs @@ -2,6 +2,6 @@ public class RaidRegisterManager { - public IDictionary<(ulong raidId, ulong playerId), RosterPlayer> RegisteringPlayers = + public readonly IDictionary<(ulong raidId, ulong playerId), RosterPlayer> RegisteringPlayers = new Dictionary<(ulong raidId, ulong playerId), RosterPlayer>(); } \ No newline at end of file diff --git a/Cocotte/Modules/Raids/RosterAssigner.cs b/Cocotte/Modules/Raids/RosterAssigner.cs new file mode 100644 index 0000000..192cc64 --- /dev/null +++ b/Cocotte/Modules/Raids/RosterAssigner.cs @@ -0,0 +1,136 @@ +namespace Cocotte.Modules.Raids; + +public class RosterAssigner +{ + public void AssignRosters(IEnumerable players, uint playersPerRoster) + { + // Start by grouping players + var groups = GroupPlayers(players.OrderByDescending(p => p.Fc)); + + // Create rosters + var neededRosters = (int)Math.Ceiling(players.Count(p => !p.Substitute) / (double)playersPerRoster); + var rosters = new List(Enumerable.Repeat(new RosterInfo(), neededRosters)); + + // Todo Check when there's more than max players per roster + + // First pass: assign healers and tanks + // Always assign to the group which have the least amount of healer/tank, biased towards healers + // Skip groups without players + var dpsGroup = new List(); + foreach (var group in groups.Where(g => !g.AllSubstitutes)) + { + if (group.Players.AnyHealer()) + { + var nextHealerRoster = rosters.MinBy(r => r.RealHealerCount()); + + nextHealerRoster!.AddGroup(group); + } + else if (group.Players.AnyTank()) + { + var nextTankRoster = rosters.MinBy(r => r.RealTankCount()); + + nextTankRoster!.AddGroup(group); + } + // Those groups will be used to assign dps, they should still be in descending order of FC + else + { + dpsGroup.Add(group); + } + } + + // Third pass: assign dps + foreach (var group in dpsGroup) + { + var nextDpsRoster = rosters.MinBy(r => r.TotalRealFc); + + nextDpsRoster!.AddGroup(group); + } + + // Last pass: fill with substitute + + // Assign rosters + for (int i = 0; i < rosters.Count; i++) + { + var roster = rosters[i]; + + roster.AssignRosterNumer(i); + } + } + + private IList GroupPlayers(IEnumerable players) + { + var groups = new List(); + + // Todo create groups from player preferences + foreach (var rosterPlayer in players) + { + groups.Add(new PlayerGroup(rosterPlayer)); + } + + return groups; + } +} + +public class RosterInfo +{ + public long TotalRealFc => _groups.Sum(g => g.RealFc); + public long TotalFc => _groups.Sum(g => g.TotalFc); + + public IEnumerable PlayerGroups => _groups.Where(g => !g.AllSubstitutes); + public IEnumerable SubstituteGroups => _groups.Where(g => g.AllSubstitutes); + + private readonly IList _groups; + + public RosterInfo() + { + _groups = new List(); + } + + public void AddGroup(PlayerGroup group) + { + _groups.Add(group); + } + + public void AssignRosterNumer(int rosterNumber) + { + foreach (var group in _groups) + { + group.AssignRosterNumer(rosterNumber); + } + } +} + +public class PlayerGroup +{ + public long RealFc => _players.Where(p => !p.Substitute).TotalFc(); + public long TotalFc => _players.TotalFc(); + public bool AllSubstitutes => _players.All(p => p.Substitute); + + public IEnumerable Players => _players.Where(p => !p.Substitute); + public IEnumerable Substitutes => _players.Where(p => p.Substitute); + + private readonly IList _players; + + public PlayerGroup() + { + _players = new List(); + } + + public PlayerGroup(params RosterPlayer[] players) + { + _players = players; + } + + public void AddPlayer(RosterPlayer player) + { + _players.Add(player); + } + + public void AssignRosterNumer(int rosterNumber) + { + foreach (var rosterPlayer in _players) + { + rosterPlayer.RosterNumber = rosterNumber; + } + } +} \ No newline at end of file diff --git a/Cocotte/Modules/Raids/RosterExtensions.cs b/Cocotte/Modules/Raids/RosterExtensions.cs new file mode 100644 index 0000000..b4c4fc9 --- /dev/null +++ b/Cocotte/Modules/Raids/RosterExtensions.cs @@ -0,0 +1,39 @@ +namespace Cocotte.Modules.Raids; + +public static class RosterExtensions +{ + public static bool AnyHealer(this IEnumerable players) + { + return players.Any(p => p.Role == PlayerRole.Healer); + } + + public static bool AnyTank(this IEnumerable players) + { + return players.Any(p => p.Role == PlayerRole.Tank); + } + + public static int HealerCount(this IEnumerable players) + { + return players.Count(p => p.Role == PlayerRole.Healer); + } + + public static int TankCount(this IEnumerable players) + { + return players.Count(p => p.Role == PlayerRole.Tank); + } + + public static long TotalFc(this IEnumerable players) + { + return players.Sum(p => p.Fc); + } + + public static int RealHealerCount(this RosterInfo rosterInfo) + { + return rosterInfo.PlayerGroups.Sum(g => g.Players.HealerCount()); + } + + public static int RealTankCount(this RosterInfo rosterInfo) + { + return rosterInfo.PlayerGroups.Sum(g => g.Players.TankCount()); + } +} \ No newline at end of file diff --git a/Cocotte/Program.cs b/Cocotte/Program.cs index 7886dad..b6cfe6f 100644 --- a/Cocotte/Program.cs +++ b/Cocotte/Program.cs @@ -38,8 +38,9 @@ IHost host = Host.CreateDefaultBuilder(args) services.AddSingleton(); // Raids - services.AddSingleton(); + services.AddTransient(); services.AddSingleton(); + services.AddTransient(); // Custom services.AddSingleton();