[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
{
Raid this[ulong raidId] { get; }
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
{
@@ -15,4 +17,9 @@ public class MemoryRaidRepository : IRaidsRepository
{
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
}
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)
@@ -35,4 +45,9 @@ public class Raid
{
return (int) (Id % int.MaxValue);
}
public override string ToString()
{
return $"Raid({DateTime})";
}
}

View File

@@ -2,10 +2,14 @@
using Cocotte.Utils;
using Discord;
using Discord.Interactions;
using Discord.WebSocket;
// ReSharper disable UnusedMember.Global
namespace Cocotte.Modules.Raids;
[Group("raid", "Raid related commands")]
[SuppressMessage("Performance", "CA1822:Mark members as static")]
public class RaidModule : InteractionModuleBase<SocketInteractionContext>
{
private readonly ILogger<RaidModule> _logger;
@@ -17,8 +21,9 @@ public class RaidModule : InteractionModuleBase<SocketInteractionContext>
_raidsRepository = raidsRepository;
}
[EnabledInDm(false)]
[SlashCommand("start", "Start a raid formation")]
public async Task Ping()
public async Task Start()
{
// Raids are identified using their original message id
await RespondAsync("`Creating a new raid...`");
@@ -42,22 +47,82 @@ public class RaidModule : InteractionModuleBase<SocketInteractionContext>
}
// Build the raid message
var embed = RaidEmbed(raidId);
var raid = _raidsRepository[raidId];
var embed = RaidEmbed(raid);
var components = RaidComponents(raidId);
await ModifyOriginalResponseAsync(m =>
{
m.Content = "";
m.Embed = embed.Build();
m.Components = components.Build();
});
}
[SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
private EmbedBuilder RaidEmbed(ulong raidId)
[ComponentInteraction("raid raid_join:*", true)]
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)
{
var nonSubstitute = players.Where(p => !p.Substitue);
var substitute = players.Where(p => p.Substitue);
var rosterPlayers = players.ToList();
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());
separatorLength = (int) ((separatorLength + 13) * 0.49); // Don't ask why, it just works
@@ -68,12 +133,27 @@ public class RaidModule : InteractionModuleBase<SocketInteractionContext>
.WithIsInline(true);
}
var raid = _raidsRepository[raidId];
return new EmbedBuilder()
.WithColor(Colors.CocotteBlue)
.WithTitle(":crossed_swords: Raid")
.WithDescription($"**Date:** {TimestampTag.FromDateTime(raid.DateTime, TimestampTagStyles.LongDateTime)}")
.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
{
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)
{
// TODO add logic to split player in multiple rosters
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;
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; }
@@ -18,6 +18,16 @@ public record RosterPlayer(string Name, PlayerRole Role, int Fc, bool Substitue
_ => $"{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
{
false => $"{RoleToEmote(Role)} {Name} ({FcFormat(Fc)} FC)",

View File

@@ -10,4 +10,6 @@ public static class Colors
// Colors used in embeds
public static Color ErrorColor => new(0xFB6060);
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 EmbedBuilder ErrorEmbed(string message)
public static EmbedBuilder ErrorEmbed(string message, string title = "Error")
{
return new EmbedBuilder()
.WithColor(Colors.ErrorColor)
.WithAuthor(a => a
.WithName("Error")
.WithName(title)
.WithIconUrl("https://sage.cdn.ilysix.fr/assets/Cocotte/icons/error.webp")
)
.WithDescription(message);
}
public static EmbedBuilder InfoEmbed(string message)
public static EmbedBuilder InfoEmbed(string message, string title = "Info")
{
return new EmbedBuilder()
.WithColor(Colors.InfoColor)
.WithAuthor(a => a
.WithName("Info")
.WithName(title)
.WithIconUrl("https://sage.cdn.ilysix.fr/assets/Cocotte/icons/info.webp")
)
.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)
{
raid.AddPlayer("YamaRaja", PlayerRole.Healer, 30000, false);
raid.AddPlayer("Zaku", PlayerRole.Dps, 40000, false);
raid.AddPlayer("Juchi", PlayerRole.Tank, 40000, false);
raid.AddPlayer("Akeno", PlayerRole.Dps, 40000, true);
raid.AddPlayer(0, "YamaRaja", PlayerRole.Healer, 30000, false);
raid.AddPlayer(1, "Zaku", PlayerRole.Dps, 40000, false);
raid.AddPlayer(2, "Juchi", PlayerRole.Tank, 40000, false);
raid.AddPlayer(3, "Akeno", PlayerRole.Dps, 40000, true);
}
}