[Raid] Add Join and Leave buttons
This commit is contained in:
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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})";
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
@@ -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)",
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user