[Raid] Add start command

This commit is contained in:
2022-11-17 00:51:02 +01:00
parent 5b4a15bc8f
commit ac9c3a405f
12 changed files with 258 additions and 2 deletions

View File

@@ -11,4 +11,8 @@
<PackageReference Include="Discord.Net" Version="3.8.1" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.0" />
</ItemGroup>
<ItemGroup>
<Folder Include="Modules" />
</ItemGroup>
</Project>

View File

@@ -8,12 +8,13 @@ using Discord;
using Discord.Interactions;
using Discord.WebSocket;
namespace Cocotte.Modules;
namespace Cocotte.Modules.Ping;
/// <summary>
/// Module containing different test and debug commands
/// </summary>
[RequireOwner]
[Group("ping", "Debug related commands")]
public class PingModule : InteractionModuleBase<SocketInteractionContext>
{
private readonly ILogger<PingModule> _logger;
@@ -291,7 +292,7 @@ public class FoodModal : IModal
[ModalTextInput("food_name", placeholder: "Pizza", maxLength: 20)]
public string? Food { get; set; }
// Additional paremeters can be specified to further customize the input.
// Additional paremeters can be specified to further customize the input.
// Parameters can be optional
[RequiredInput(false)]
[InputLabel("Why??")]

View File

@@ -0,0 +1,8 @@
namespace Cocotte.Modules.Raids;
public interface IRaidsRepository
{
Raid this[ulong raidId] { get; }
bool AddNewRaid(ulong raidId, DateTime dateTime);
}

View File

@@ -0,0 +1,18 @@
namespace Cocotte.Modules.Raids;
public class MemoryRaidRepository : IRaidsRepository
{
private readonly Dictionary<ulong, Raid> _raids;
public Raid this[ulong raidId] => _raids[raidId];
public MemoryRaidRepository()
{
_raids = new Dictionary<ulong, Raid>();
}
public bool AddNewRaid(ulong raidId, DateTime dateTime)
{
return _raids.TryAdd(raidId, new Raid(raidId, dateTime));
}
}

View File

@@ -0,0 +1,38 @@
using Cocotte.Utils;
namespace Cocotte.Modules.Raids;
public class Raid
{
public ulong Id { get; }
public DateTime DateTime { get; }
private readonly RosterManager _rosterManager = new();
public IEnumerable<IGrouping<int, RosterPlayer>> Rosters => _rosterManager.Rosters;
public Raid(ulong id, DateTime dateTime)
{
Id = id;
DateTime = dateTime;
#if DEBUG
this.AddTestPlayers();
#endif
}
public bool AddPlayer(string name, PlayerRole role, int fc, bool substitute = false)
{
return _rosterManager.AddPlayer(new RosterPlayer(name, role, fc, substitute));
}
public override bool Equals(object? other)
{
return other is Raid roster && roster.Id == Id;
}
public override int GetHashCode()
{
return (int) (Id % int.MaxValue);
}
}

View File

@@ -0,0 +1,79 @@
using System.Diagnostics.CodeAnalysis;
using Cocotte.Utils;
using Discord;
using Discord.Interactions;
namespace Cocotte.Modules.Raids;
[Group("raid", "Raid related commands")]
public class RaidModule : InteractionModuleBase<SocketInteractionContext>
{
private readonly ILogger<RaidModule> _logger;
private readonly IRaidsRepository _raidsRepository;
public RaidModule(ILogger<RaidModule> logger, IRaidsRepository raidsRepository)
{
_logger = logger;
_raidsRepository = raidsRepository;
}
[SlashCommand("start", "Start a raid formation")]
public async Task Ping()
{
// Raids are identified using their original message id
await RespondAsync("`Creating a new raid...`");
var response = await GetOriginalResponseAsync();
var raidId = response.Id;
_logger.LogInformation("Created new raid with id {RaidId}", raidId);
// New raid instance
// TODO: Ask for date
if (!_raidsRepository.AddNewRaid(raidId, DateTime.Now))
{
// A raid with this message id already exists, how??
_logger.LogWarning("Tried to create a new raid with already existing id: {RaidId}", raidId);
await FollowupAsync(ephemeral: true, embed: EmbedUtils.ErrorEmbed("Can't create a new raid with same raid id").Build());
await DeleteOriginalResponseAsync();
return;
}
// Build the raid message
var embed = RaidEmbed(raidId);
await ModifyOriginalResponseAsync(m =>
{
m.Content = "";
m.Embed = embed.Build();
});
}
[SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
private EmbedBuilder RaidEmbed(ulong raidId)
{
EmbedFieldBuilder RosterEmbed(int rosterNumber, IEnumerable<RosterPlayer> players)
{
var nonSubstitute = players.Where(p => !p.Substitue);
var substitute = players.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
return new EmbedFieldBuilder()
.WithName($"Roster {rosterNumber}")
.WithValue($"{string.Join("\n", nonSubstitute)}\n{new string('━', separatorLength)}\n{string.Join("\n", substitute)}")
.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)));
}
}

View File

@@ -0,0 +1,16 @@
namespace Cocotte.Modules.Raids;
public class RosterManager
{
private readonly ISet<RosterPlayer> _rosters = new HashSet<RosterPlayer>();
public IEnumerable<IGrouping<int, RosterPlayer>> Rosters => _rosters.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);
}
}

View File

@@ -0,0 +1,33 @@
namespace Cocotte.Modules.Raids;
public record RosterPlayer(string Name, PlayerRole Role, int Fc, bool Substitue = false)
{
public int RosterNumber { get; set; }
private static string RoleToEmote(PlayerRole role) => role switch
{
PlayerRole.Dps => ":red_circle:",
PlayerRole.Tank => ":yellow_circle:",
PlayerRole.Healer => ":green_circle:",
_ => ":question:"
};
public static string FcFormat(int fc) => fc switch
{
< 1_000 => $"{fc}",
_ => $"{fc/1000}k"
};
public override string ToString() => Substitue switch
{
false => $"{RoleToEmote(Role)} {Name} ({FcFormat(Fc)} FC)",
true => $"*{RoleToEmote(Role)} {Name} ({FcFormat(Fc)} FC)*"
};
}
public enum PlayerRole
{
Dps,
Healer,
Tank
}

View File

@@ -1,3 +1,4 @@
using Cocotte.Modules.Raids;
using Cocotte.Options;
using Cocotte.Services;
using Discord;
@@ -31,6 +32,9 @@ IHost host = Host.CreateDefaultBuilder(args)
services.AddHostedService<CocotteService>();
// Data
services.AddSingleton<IRaidsRepository, MemoryRaidRepository>();
// Custom
services.AddSingleton<SharedCounter>();
services.AddTransient<TransientCounter>();

13
Cocotte/Utils/Colors.cs Normal file
View File

@@ -0,0 +1,13 @@
using Discord;
namespace Cocotte.Utils;
public static class Colors
{
// Main Cocotte colors
public static Color CocotteBlue => new(0x3196c8);
// Colors used in embeds
public static Color ErrorColor => new(0xFB6060);
public static Color InfoColor => new(0x66D9EF);
}

View File

@@ -0,0 +1,28 @@
using Discord;
namespace Cocotte.Utils;
public static class EmbedUtils
{
public static EmbedBuilder ErrorEmbed(string message)
{
return new EmbedBuilder()
.WithColor(Colors.ErrorColor)
.WithAuthor(a => a
.WithName("Error")
.WithIconUrl("https://sage.cdn.ilysix.fr/assets/Cocotte/icons/error.webp")
)
.WithDescription(message);
}
public static EmbedBuilder InfoEmbed(string message)
{
return new EmbedBuilder()
.WithColor(Colors.InfoColor)
.WithAuthor(a => a
.WithName("Info")
.WithIconUrl("https://sage.cdn.ilysix.fr/assets/Cocotte/icons/info.webp")
)
.WithDescription(message);
}
}

View File

@@ -0,0 +1,14 @@
using Cocotte.Modules.Raids;
namespace Cocotte.Utils;
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);
}
}