[Raid] Add preliminary roster assigner
This commit is contained in:
@@ -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))}")
|
||||
|
||||
@@ -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>();
|
||||
}
|
||||
136
Cocotte/Modules/Raids/RosterAssigner.cs
Normal file
136
Cocotte/Modules/Raids/RosterAssigner.cs
Normal file
@@ -0,0 +1,136 @@
|
||||
namespace Cocotte.Modules.Raids;
|
||||
|
||||
public class RosterAssigner
|
||||
{
|
||||
public void AssignRosters(IEnumerable<RosterPlayer> 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<RosterInfo>(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<PlayerGroup>();
|
||||
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<PlayerGroup> GroupPlayers(IEnumerable<RosterPlayer> players)
|
||||
{
|
||||
var groups = new List<PlayerGroup>();
|
||||
|
||||
// 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<PlayerGroup> PlayerGroups => _groups.Where(g => !g.AllSubstitutes);
|
||||
public IEnumerable<PlayerGroup> SubstituteGroups => _groups.Where(g => g.AllSubstitutes);
|
||||
|
||||
private readonly IList<PlayerGroup> _groups;
|
||||
|
||||
public RosterInfo()
|
||||
{
|
||||
_groups = new List<PlayerGroup>();
|
||||
}
|
||||
|
||||
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<RosterPlayer> Players => _players.Where(p => !p.Substitute);
|
||||
public IEnumerable<RosterPlayer> Substitutes => _players.Where(p => p.Substitute);
|
||||
|
||||
private readonly IList<RosterPlayer> _players;
|
||||
|
||||
public PlayerGroup()
|
||||
{
|
||||
_players = new List<RosterPlayer>();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
39
Cocotte/Modules/Raids/RosterExtensions.cs
Normal file
39
Cocotte/Modules/Raids/RosterExtensions.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
namespace Cocotte.Modules.Raids;
|
||||
|
||||
public static class RosterExtensions
|
||||
{
|
||||
public static bool AnyHealer(this IEnumerable<RosterPlayer> players)
|
||||
{
|
||||
return players.Any(p => p.Role == PlayerRole.Healer);
|
||||
}
|
||||
|
||||
public static bool AnyTank(this IEnumerable<RosterPlayer> players)
|
||||
{
|
||||
return players.Any(p => p.Role == PlayerRole.Tank);
|
||||
}
|
||||
|
||||
public static int HealerCount(this IEnumerable<RosterPlayer> players)
|
||||
{
|
||||
return players.Count(p => p.Role == PlayerRole.Healer);
|
||||
}
|
||||
|
||||
public static int TankCount(this IEnumerable<RosterPlayer> players)
|
||||
{
|
||||
return players.Count(p => p.Role == PlayerRole.Tank);
|
||||
}
|
||||
|
||||
public static long TotalFc(this IEnumerable<RosterPlayer> 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());
|
||||
}
|
||||
}
|
||||
@@ -38,8 +38,9 @@ IHost host = Host.CreateDefaultBuilder(args)
|
||||
services.AddSingleton<RolesOptions>();
|
||||
|
||||
// Raids
|
||||
services.AddSingleton<RaidFormatter>();
|
||||
services.AddTransient<RaidFormatter>();
|
||||
services.AddSingleton<RaidRegisterManager>();
|
||||
services.AddTransient<RosterAssigner>();
|
||||
|
||||
// Custom
|
||||
services.AddSingleton<SharedCounter>();
|
||||
|
||||
Reference in New Issue
Block a user