[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
|
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);
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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})";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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)",
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user