[Raid] Add Join and Leave buttons

This commit is contained in:
2022-11-23 22:00:00 +01:00
parent ac9c3a405f
commit a6df178dff
9 changed files with 174 additions and 24 deletions

View File

@@ -1,8 +1,12 @@
namespace Cocotte.Modules.Raids; using System.Diagnostics.CodeAnalysis;
namespace Cocotte.Modules.Raids;
public interface IRaidsRepository public interface IRaidsRepository
{ {
Raid this[ulong raidId] { get; } Raid this[ulong raidId] { get; }
bool AddNewRaid(ulong raidId, DateTime dateTime); bool AddNewRaid(ulong raidId, DateTime dateTime);
bool TryGetRaid(ulong raidId, [MaybeNullWhen(false)] out Raid raid);
} }

View File

@@ -1,4 +1,6 @@
namespace Cocotte.Modules.Raids; using System.Diagnostics.CodeAnalysis;
namespace Cocotte.Modules.Raids;
public class MemoryRaidRepository : IRaidsRepository public class MemoryRaidRepository : IRaidsRepository
{ {
@@ -15,4 +17,9 @@ public class MemoryRaidRepository : IRaidsRepository
{ {
return _raids.TryAdd(raidId, new Raid(raidId, dateTime)); return _raids.TryAdd(raidId, new Raid(raidId, dateTime));
} }
public bool TryGetRaid(ulong raidId, [MaybeNullWhen(false)] out Raid raid)
{
return _raids.TryGetValue(raidId, out raid);
}
} }

View File

@@ -21,9 +21,19 @@ public class Raid
#endif #endif
} }
public bool AddPlayer(string name, PlayerRole role, int fc, bool substitute = false) public bool AddPlayer(ulong id, string name, PlayerRole role, int fc, bool substitute = false)
{ {
return _rosterManager.AddPlayer(new RosterPlayer(name, role, fc, substitute)); return _rosterManager.AddPlayer(new RosterPlayer(id, name, role, fc, substitute));
}
public RosterPlayer GetPlayer(ulong id)
{
return _rosterManager.GetPlayer(id);
}
public bool RemovePlayer(ulong id)
{
return _rosterManager.RemovePlayer(id);
} }
public override bool Equals(object? other) public override bool Equals(object? other)
@@ -35,4 +45,9 @@ public class Raid
{ {
return (int) (Id % int.MaxValue); return (int) (Id % int.MaxValue);
} }
public override string ToString()
{
return $"Raid({DateTime})";
}
} }

View File

@@ -2,10 +2,14 @@
using Cocotte.Utils; using Cocotte.Utils;
using Discord; using Discord;
using Discord.Interactions; using Discord.Interactions;
using Discord.WebSocket;
// ReSharper disable UnusedMember.Global
namespace Cocotte.Modules.Raids; namespace Cocotte.Modules.Raids;
[Group("raid", "Raid related commands")] [Group("raid", "Raid related commands")]
[SuppressMessage("Performance", "CA1822:Mark members as static")]
public class RaidModule : InteractionModuleBase<SocketInteractionContext> public class RaidModule : InteractionModuleBase<SocketInteractionContext>
{ {
private readonly ILogger<RaidModule> _logger; private readonly ILogger<RaidModule> _logger;
@@ -17,8 +21,9 @@ public class RaidModule : InteractionModuleBase<SocketInteractionContext>
_raidsRepository = raidsRepository; _raidsRepository = raidsRepository;
} }
[EnabledInDm(false)]
[SlashCommand("start", "Start a raid formation")] [SlashCommand("start", "Start a raid formation")]
public async Task Ping() public async Task Start()
{ {
// Raids are identified using their original message id // Raids are identified using their original message id
await RespondAsync("`Creating a new raid...`"); await RespondAsync("`Creating a new raid...`");
@@ -42,22 +47,82 @@ public class RaidModule : InteractionModuleBase<SocketInteractionContext>
} }
// Build the raid message // Build the raid message
var embed = RaidEmbed(raidId); var raid = _raidsRepository[raidId];
var embed = RaidEmbed(raid);
var components = RaidComponents(raidId);
await ModifyOriginalResponseAsync(m => await ModifyOriginalResponseAsync(m =>
{ {
m.Content = ""; m.Content = "";
m.Embed = embed.Build(); m.Embed = embed.Build();
m.Components = components.Build();
}); });
} }
[SuppressMessage("ReSharper", "PossibleMultipleEnumeration")] [ComponentInteraction("raid raid_join:*", true)]
private EmbedBuilder RaidEmbed(ulong raidId) public async Task Join(ulong raidId)
{
if (!_raidsRepository.TryGetRaid(raidId, out var raid))
{
await RespondAsync(ephemeral: true, embed: EmbedUtils.ErrorEmbed("This raid does not exist").Build());
return;
}
// Todo: Ask role, FC, substitute
var user = (IGuildUser)Context.User;
if (!raid.AddPlayer(user.Id, user.DisplayName, PlayerRole.Dps, 10000))
{
await RespondAsync(ephemeral: true, embed: EmbedUtils.InfoEmbed("You're already registered for this raid").Build());
return;
}
_logger.LogInformation("User {User} joined raid {Raid}", Context.User.Username, raid);
await UpdateRaidRosterEmbed(raid);
await RespondAsync(ephemeral: true, embed: EmbedUtils.SuccessEmbed($"Successfully joined the raid as {raid.GetPlayer(user.Id).Role}").Build());
}
[ComponentInteraction("raid raid_leave:*", ignoreGroupNames: true)]
public async Task Leave(ulong raidId)
{
if (!_raidsRepository.TryGetRaid(raidId, out var raid))
{
await RespondAsync(ephemeral: true, embed: EmbedUtils.ErrorEmbed("This raid does not exist").Build());
return;
}
var user = (IGuildUser)Context.User;
if (!raid.RemovePlayer(user.Id))
{
await RespondAsync(ephemeral: true, embed: EmbedUtils.WarningEmbed("You're not registered for this raid").Build());
return;
}
await UpdateRaidRosterEmbed(raid);
await RespondAsync(ephemeral: true, embed: EmbedUtils.SuccessEmbed("Successfully left the raid").Build());
}
public async Task UpdateRaidRosterEmbed(Raid raid)
{
var message = await Context.Channel.GetMessageAsync(raid.Id);
if (message is SocketUserMessage userMessage)
{
await userMessage.ModifyAsync(m => m.Embed = RaidEmbed(raid).Build());
}
}
private EmbedBuilder RaidEmbed(Raid raid)
{ {
EmbedFieldBuilder RosterEmbed(int rosterNumber, IEnumerable<RosterPlayer> players) EmbedFieldBuilder RosterEmbed(int rosterNumber, IEnumerable<RosterPlayer> players)
{ {
var nonSubstitute = players.Where(p => !p.Substitue); var rosterPlayers = players.ToList();
var substitute = players.Where(p => p.Substitue); var nonSubstitute = rosterPlayers.Where(p => !p.Substitue);
var substitute = rosterPlayers.Where(p => p.Substitue);
var separatorLength = Math.Max(nonSubstitute.Select(p => p.Name.Length).Max(), substitute.Select(p => p.Name.Length).Max()); 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 separatorLength = (int) ((separatorLength + 13) * 0.49); // Don't ask why, it just works
@@ -68,12 +133,27 @@ public class RaidModule : InteractionModuleBase<SocketInteractionContext>
.WithIsInline(true); .WithIsInline(true);
} }
var raid = _raidsRepository[raidId];
return new EmbedBuilder() return new EmbedBuilder()
.WithColor(Colors.CocotteBlue) .WithColor(Colors.CocotteBlue)
.WithTitle(":crossed_swords: Raid") .WithTitle(":crossed_swords: Raid")
.WithDescription($"**Date:** {TimestampTag.FromDateTime(raid.DateTime, TimestampTagStyles.LongDateTime)}") .WithDescription($"**Date:** {TimestampTag.FromDateTime(raid.DateTime, TimestampTagStyles.LongDateTime)}")
.WithFields(raid.Rosters.Select(r => RosterEmbed(r.Key, r))); .WithFields(raid.Rosters.Select(r => RosterEmbed(r.Key, r)));
} }
private ComponentBuilder RaidComponents(ulong raidId)
{
return new ComponentBuilder()
.AddRow(new ActionRowBuilder()
.WithButton(new ButtonBuilder()
.WithLabel("Join")
.WithCustomId($"raid raid_join:{raidId}")
.WithStyle(ButtonStyle.Primary)
)
.WithButton(new ButtonBuilder()
.WithLabel("Leave")
.WithCustomId($"raid raid_leave:{raidId}")
.WithStyle(ButtonStyle.Danger)
)
);
}
} }

View File

@@ -2,15 +2,25 @@
public class RosterManager public class RosterManager
{ {
private readonly ISet<RosterPlayer> _rosters = new HashSet<RosterPlayer>(); private readonly IDictionary<ulong, RosterPlayer> _players = new Dictionary<ulong, RosterPlayer>();
public IEnumerable<IGrouping<int, RosterPlayer>> Rosters => _rosters.GroupBy(p => p.RosterNumber); public IEnumerable<IGrouping<int, RosterPlayer>> Rosters => _players.Select(p => p.Value).GroupBy(p => p.RosterNumber);
public bool AddPlayer(RosterPlayer rosterPlayer) public bool AddPlayer(RosterPlayer rosterPlayer)
{ {
// TODO add logic to split player in multiple rosters // TODO add logic to split player in multiple rosters
rosterPlayer.RosterNumber = 1; rosterPlayer.RosterNumber = 1;
return _rosters.Add(rosterPlayer); return _players.TryAdd(rosterPlayer.Id, rosterPlayer);
}
public bool RemovePlayer(ulong id)
{
return _players.Remove(id);
}
public RosterPlayer GetPlayer(ulong id)
{
return _players[id];
} }
} }

View File

@@ -1,6 +1,6 @@
namespace Cocotte.Modules.Raids; namespace Cocotte.Modules.Raids;
public record RosterPlayer(string Name, PlayerRole Role, int Fc, bool Substitue = false) public record RosterPlayer(ulong Id, string Name, PlayerRole Role, int Fc, bool Substitue = false)
{ {
public int RosterNumber { get; set; } public int RosterNumber { get; set; }
@@ -18,6 +18,16 @@ public record RosterPlayer(string Name, PlayerRole Role, int Fc, bool Substitue
_ => $"{fc/1000}k" _ => $"{fc/1000}k"
}; };
public override int GetHashCode()
{
return (int) (Id % int.MaxValue);
}
public virtual bool Equals(RosterPlayer? other)
{
return other is not null && other.Id == Id;
}
public override string ToString() => Substitue switch public override string ToString() => Substitue switch
{ {
false => $"{RoleToEmote(Role)} {Name} ({FcFormat(Fc)} FC)", false => $"{RoleToEmote(Role)} {Name} ({FcFormat(Fc)} FC)",

View File

@@ -10,4 +10,6 @@ public static class Colors
// Colors used in embeds // Colors used in embeds
public static Color ErrorColor => new(0xFB6060); public static Color ErrorColor => new(0xFB6060);
public static Color InfoColor => new(0x66D9EF); public static Color InfoColor => new(0x66D9EF);
public static Color SuccessColor => new(0x2Ecc71);
public static Color WarningColor => new(0xf1c40F);
} }

View File

@@ -4,25 +4,47 @@ namespace Cocotte.Utils;
public static class EmbedUtils public static class EmbedUtils
{ {
public static EmbedBuilder ErrorEmbed(string message) public static EmbedBuilder ErrorEmbed(string message, string title = "Error")
{ {
return new EmbedBuilder() return new EmbedBuilder()
.WithColor(Colors.ErrorColor) .WithColor(Colors.ErrorColor)
.WithAuthor(a => a .WithAuthor(a => a
.WithName("Error") .WithName(title)
.WithIconUrl("https://sage.cdn.ilysix.fr/assets/Cocotte/icons/error.webp") .WithIconUrl("https://sage.cdn.ilysix.fr/assets/Cocotte/icons/error.webp")
) )
.WithDescription(message); .WithDescription(message);
} }
public static EmbedBuilder InfoEmbed(string message) public static EmbedBuilder InfoEmbed(string message, string title = "Info")
{ {
return new EmbedBuilder() return new EmbedBuilder()
.WithColor(Colors.InfoColor) .WithColor(Colors.InfoColor)
.WithAuthor(a => a .WithAuthor(a => a
.WithName("Info") .WithName(title)
.WithIconUrl("https://sage.cdn.ilysix.fr/assets/Cocotte/icons/info.webp") .WithIconUrl("https://sage.cdn.ilysix.fr/assets/Cocotte/icons/info.webp")
) )
.WithDescription(message); .WithDescription(message);
} }
public static EmbedBuilder SuccessEmbed(string message, string title = "Success")
{
return new EmbedBuilder()
.WithColor(Colors.SuccessColor)
.WithAuthor(a => a
.WithName(title)
.WithIconUrl("https://sage.cdn.ilysix.fr/assets/Cocotte/icons/success.webp")
)
.WithDescription(message);
}
public static EmbedBuilder WarningEmbed(string message, string title = "Warning")
{
return new EmbedBuilder()
.WithColor(Colors.WarningColor)
.WithAuthor(a => a
.WithName(title)
.WithIconUrl("https://sage.cdn.ilysix.fr/assets/Cocotte/icons/warning.webp")
)
.WithDescription(message);
}
} }

View File

@@ -6,9 +6,9 @@ public static class RaidExtensions
{ {
public static void AddTestPlayers(this Raid raid) public static void AddTestPlayers(this Raid raid)
{ {
raid.AddPlayer("YamaRaja", PlayerRole.Healer, 30000, false); raid.AddPlayer(0, "YamaRaja", PlayerRole.Healer, 30000, false);
raid.AddPlayer("Zaku", PlayerRole.Dps, 40000, false); raid.AddPlayer(1, "Zaku", PlayerRole.Dps, 40000, false);
raid.AddPlayer("Juchi", PlayerRole.Tank, 40000, false); raid.AddPlayer(2, "Juchi", PlayerRole.Tank, 40000, false);
raid.AddPlayer("Akeno", PlayerRole.Dps, 40000, true); raid.AddPlayer(3, "Akeno", PlayerRole.Dps, 40000, true);
} }
} }