diff --git a/Cocotte/Cocotte.csproj b/Cocotte/Cocotte.csproj
index 347f228..7f7576f 100644
--- a/Cocotte/Cocotte.csproj
+++ b/Cocotte/Cocotte.csproj
@@ -8,7 +8,7 @@
-
-
+
+
diff --git a/Cocotte/Modules/Activity/Activity.cs b/Cocotte/Modules/Activity/Activity.cs
new file mode 100644
index 0000000..2bf01ad
--- /dev/null
+++ b/Cocotte/Modules/Activity/Activity.cs
@@ -0,0 +1,3 @@
+namespace Cocotte.Modules.Activity;
+
+public abstract record Activity(ulong Owner, string Description, ActivityType ActivityType, ActivityName ActivityName, uint MaxPlayers);
diff --git a/Cocotte/Modules/Activity/ActivityFormatter.cs b/Cocotte/Modules/Activity/ActivityFormatter.cs
new file mode 100644
index 0000000..77e913c
--- /dev/null
+++ b/Cocotte/Modules/Activity/ActivityFormatter.cs
@@ -0,0 +1,24 @@
+namespace Cocotte.Modules.Activity;
+
+public class ActivityFormatter
+{
+ public static string ActivityName(ActivityName activityName)
+ {
+ return activityName switch
+ {
+ Modules.Activity.ActivityName.Abyss => "Abîme du Néant",
+ Modules.Activity.ActivityName.Raids => "Raids",
+ Modules.Activity.ActivityName.FrontierClash => "Clash Frontalier",
+ Modules.Activity.ActivityName.VoidRift => "Failles du Néant",
+ Modules.Activity.ActivityName.OriginsOfWar => "Origines de la Guerre",
+ Modules.Activity.ActivityName.JointOperation => "Opération Conjointe",
+ Modules.Activity.ActivityName.InterstellarExploration => "Exploration Interstellaire",
+ Modules.Activity.ActivityName.BreakFromDestiny => "Échapper au Destin (3v3)",
+ Modules.Activity.ActivityName.CriticalAbyss => "Abîme Critique (8v8)",
+ Modules.Activity.ActivityName.Event => "Event",
+ Modules.Activity.ActivityName.Fishing => "Pêche",
+ Modules.Activity.ActivityName.MirroriaRace => "Course Mirroria",
+ _ => throw new ArgumentOutOfRangeException(nameof(activityName), activityName, null)
+ };
+ }
+}
\ No newline at end of file
diff --git a/Cocotte/Modules/Activity/ActivityHelper.cs b/Cocotte/Modules/Activity/ActivityHelper.cs
new file mode 100644
index 0000000..fb3bf5f
--- /dev/null
+++ b/Cocotte/Modules/Activity/ActivityHelper.cs
@@ -0,0 +1,82 @@
+using Cocotte.Options;
+using Discord.WebSocket;
+using Microsoft.Extensions.Options;
+
+namespace Cocotte.Modules.Activity;
+
+public class ActivityHelper
+{
+ private const uint UnlimitedPlayers = uint.MaxValue;
+
+ private readonly ActivityOptions _options;
+
+ public ActivityHelper(IOptions options)
+ {
+ _options = options.Value;
+ }
+
+ public ActivityRoles GetPlayerRoles(IReadOnlyCollection userRoles)
+ {
+ var roles = ActivityRoles.None;
+
+ foreach (var socketRole in userRoles)
+ {
+ if (socketRole.Id == _options.HelperRoleId)
+ {
+ roles |= ActivityRoles.Helper;
+ }
+ else if (socketRole.Id == _options.DpsRoleId)
+ {
+ roles |= ActivityRoles.Dps;
+ }
+ else if (socketRole.Id == _options.TankRoleId)
+ {
+ roles |= ActivityRoles.Tank;
+ }
+ else if (socketRole.Id == _options.SupportRoleId)
+ {
+ roles |= ActivityRoles.Support;
+ }
+ }
+
+ return roles;
+ }
+
+ public static ActivityType ActivityNameToType(ActivityName activityName) => activityName switch
+ {
+ ActivityName.Abyss or
+ ActivityName.FrontierClash or
+ ActivityName.InterstellarExploration or
+ ActivityName.JointOperation or
+ ActivityName.VoidRift or
+ ActivityName.OriginsOfWar => ActivityType.Pve4Players,
+
+ ActivityName.Raids => ActivityType.Pve8Players,
+
+ ActivityName.CriticalAbyss => ActivityType.Pvp8Players,
+
+ ActivityName.BreakFromDestiny => ActivityType.Pvp3Players,
+
+ ActivityName.Event or
+ ActivityName.Fishing => ActivityType.Event8Players,
+
+ ActivityName.MirroriaRace => ActivityType.Event4Players,
+
+ _ => ActivityType.Other
+ };
+
+ public static uint ActivityTypeToMaxPlayers(ActivityType activityType) => activityType switch
+ {
+ ActivityType.Pve4Players or
+ ActivityType.Event4Players => 4,
+
+ ActivityType.Pve8Players or
+ ActivityType.Event8Players => 8,
+
+ ActivityType.Pvp3Players => 3,
+
+ ActivityType.Other => UnlimitedPlayers,
+
+ _ => 0
+ };
+}
\ No newline at end of file
diff --git a/Cocotte/Modules/Activity/ActivityModule.cs b/Cocotte/Modules/Activity/ActivityModule.cs
new file mode 100644
index 0000000..b295c2f
--- /dev/null
+++ b/Cocotte/Modules/Activity/ActivityModule.cs
@@ -0,0 +1,130 @@
+using System.Diagnostics.CodeAnalysis;
+using Cocotte.Options;
+using Cocotte.Utils;
+using Discord;
+using Discord.Interactions;
+using Discord.WebSocket;
+using Microsoft.Extensions.Options;
+
+namespace Cocotte.Modules.Activity;
+
+///
+/// Module to ask and propose groups for different activities: Abyss, OOW, FC, ...
+///
+[Group("activite", "Organise des activités")]
+[SuppressMessage("ReSharper", "UnusedMember.Local")]
+public class ActivityModule : InteractionModuleBase
+{
+ private readonly ILogger _logger;
+ private readonly ActivityOptions _options;
+ private readonly ActivityHelper _activityHelper;
+
+ public ActivityModule(ILogger logger, IOptions options, ActivityHelper activityHelper)
+ {
+ _logger = logger;
+ _activityHelper = activityHelper;
+ _options = options.Value;
+ }
+
+ [RequireOwner]
+ [SlashCommand("setup-info", "Display activity setup info")]
+ public async Task SetupInfo()
+ {
+ await RespondAsync($"""
+ - Helper: {MentionUtils.MentionRole(_options.HelperRoleId)} {_options.HelperEmote.ToEmote()}
+ - Dps: {MentionUtils.MentionRole(_options.DpsRoleId)} {_options.DpsEmote.ToEmote()}
+ - Tank: {MentionUtils.MentionRole(_options.TankRoleId)} {_options.TankEmote.ToEmote()}
+ - Healer: {MentionUtils.MentionRole(_options.SupportRoleId)} {_options.SupportEmote.ToEmote()}
+ """);
+ }
+
+ [SlashCommand("abyss", "Créer un groupe pour l'Abîme du Néant")]
+ public async Task GroupAbyss([Summary("étage", "A quel étage êtes vous")] uint stage, [Summary("description", "Message accompagnant la demande de groupe")] string description = "")
+ {
+ const ActivityName activityName = ActivityName.Abyss;
+ var activityType = ActivityHelper.ActivityNameToType(activityName);
+ var maxPlayers = ActivityHelper.ActivityTypeToMaxPlayers(activityType);
+ var activityId = Context.Interaction.Id;
+
+ var activity = new StagedActivity(Context.User.Id, description, activityType, activityName, maxPlayers, stage);
+
+ await CreateActivity(activity);
+ }
+
+ private async Task CreateActivity(Activity activity)
+ {
+ _logger.LogTrace("Creating activity {Activity}", activity);
+
+ // Activities are identified using their original message id
+ await RespondAsync("`Création de l'activité en cours...`");
+
+ var response = await GetOriginalResponseAsync();
+ var activityId = response.Id;
+
+ // Add components
+ var components = ActivityComponent(activityId);
+
+ await ModifyOriginalResponseAsync(m =>
+ {
+ m.Content = "";
+ m.Components = components.Build();
+ // m.Embed = embed.Build();
+ });
+ }
+
+ [ComponentInteraction("activity join:*", ignoreGroupNames: true)]
+ private async Task JoinActivity(ulong activityId)
+ {
+ var user = (SocketGuildUser)Context.User;
+
+ _logger.LogTrace("Player {Player} joined activity {Id}", user.DisplayName, activityId);
+
+ var roles = _activityHelper.GetPlayerRoles(user.Roles);
+ var activityPlayer = new ActivityRolePlayer(user.Id, user.DisplayName, roles);
+
+ await RespondAsync(
+ ephemeral: true,
+ embed: EmbedUtils.SuccessEmbed("Vous avez bien été inscrit pour cette activité").Build()
+ );
+ }
+
+ [ComponentInteraction("activity leave:*", ignoreGroupNames: true)]
+ private async Task LeaveActivity(ulong activityId)
+ {
+ var user = (SocketGuildUser)Context.User;
+
+ _logger.LogTrace("Player {Player} left activity {Id}", user.DisplayName, activityId);
+
+ // TODO: remove the user from the activity
+
+ await RespondAsync(
+ ephemeral: true,
+ embed: EmbedUtils.SuccessEmbed("Vous avez bien été désinscrit pour cette activité").Build()
+ );
+ }
+
+ [ComponentInteraction("activity event_join:*", ignoreGroupNames: true)]
+ private async Task JoinEventActivity(ulong activityId)
+ {
+ _logger.LogTrace("Player {Player} joined activity {Id}", ((SocketGuildUser)Context.User).DisplayName, activityId);
+
+ await RespondAsync(activityId.ToString());
+ }
+
+ private static ComponentBuilder ActivityComponent(ulong activityId)
+ {
+ return new ComponentBuilder()
+ .AddRow(new ActionRowBuilder()
+ .WithButton(new ButtonBuilder()
+ .WithLabel("Rejoindre l'activité")
+ .WithCustomId($"activity join:{activityId}")
+ .WithStyle(ButtonStyle.Primary)
+ )
+ .WithButton(new ButtonBuilder()
+ .WithLabel("Se désinscrire de l'activité")
+ .WithCustomId($"activity leave:{activityId}")
+ .WithStyle(ButtonStyle.Danger)
+ )
+ );
+ }
+}
diff --git a/Cocotte/Modules/Activity/ActivityName.cs b/Cocotte/Modules/Activity/ActivityName.cs
new file mode 100644
index 0000000..671e094
--- /dev/null
+++ b/Cocotte/Modules/Activity/ActivityName.cs
@@ -0,0 +1,17 @@
+namespace Cocotte.Modules.Activity;
+
+public enum ActivityName
+{
+ Abyss,
+ OriginsOfWar,
+ Raids,
+ FrontierClash,
+ VoidRift,
+ JointOperation,
+ InterstellarExploration,
+ BreakFromDestiny,
+ CriticalAbyss,
+ Fishing,
+ Event,
+ MirroriaRace
+}
\ No newline at end of file
diff --git a/Cocotte/Modules/Activity/ActivityPlayer.cs b/Cocotte/Modules/Activity/ActivityPlayer.cs
new file mode 100644
index 0000000..8234862
--- /dev/null
+++ b/Cocotte/Modules/Activity/ActivityPlayer.cs
@@ -0,0 +1,3 @@
+namespace Cocotte.Modules.Activity;
+
+public record ActivityPlayer(ulong UserId, string PlayerName);
\ No newline at end of file
diff --git a/Cocotte/Modules/Activity/ActivityRolePlayer.cs b/Cocotte/Modules/Activity/ActivityRolePlayer.cs
new file mode 100644
index 0000000..407b678
--- /dev/null
+++ b/Cocotte/Modules/Activity/ActivityRolePlayer.cs
@@ -0,0 +1,3 @@
+namespace Cocotte.Modules.Activity;
+
+public record ActivityRolePlayer(ulong UserId, string PlayerName, ActivityRoles Roles) : ActivityPlayer(UserId, PlayerName);
\ No newline at end of file
diff --git a/Cocotte/Modules/Activity/ActivityRoles.cs b/Cocotte/Modules/Activity/ActivityRoles.cs
new file mode 100644
index 0000000..ea6e32c
--- /dev/null
+++ b/Cocotte/Modules/Activity/ActivityRoles.cs
@@ -0,0 +1,11 @@
+namespace Cocotte.Modules.Activity;
+
+[Flags]
+public enum ActivityRoles : byte
+{
+ None = 0b0000,
+ Helper = 0b0001,
+ Dps = 0b0010,
+ Tank = 0b0100,
+ Support = 0b1000
+}
\ No newline at end of file
diff --git a/Cocotte/Modules/Activity/ActivityType.cs b/Cocotte/Modules/Activity/ActivityType.cs
new file mode 100644
index 0000000..60890e9
--- /dev/null
+++ b/Cocotte/Modules/Activity/ActivityType.cs
@@ -0,0 +1,12 @@
+namespace Cocotte.Modules.Activity;
+
+public enum ActivityType
+{
+ Pve4Players,
+ Pve8Players,
+ Pvp8Players,
+ Pvp3Players,
+ Event8Players,
+ Event4Players,
+ Other
+}
\ No newline at end of file
diff --git a/Cocotte/Modules/Activity/StagedActivity.cs b/Cocotte/Modules/Activity/StagedActivity.cs
new file mode 100644
index 0000000..5537349
--- /dev/null
+++ b/Cocotte/Modules/Activity/StagedActivity.cs
@@ -0,0 +1,4 @@
+namespace Cocotte.Modules.Activity;
+
+public record StagedActivity(ulong Owner, string Description, ActivityType ActivityType, ActivityName ActivityName, uint MaxPlayers, uint Stage)
+ : Activity(Owner, Description, ActivityType, ActivityName, MaxPlayers);
\ No newline at end of file
diff --git a/Cocotte/Modules/Groups/GroupModule.cs b/Cocotte/Modules/Groups/GroupModule.cs
deleted file mode 100644
index c82129d..0000000
--- a/Cocotte/Modules/Groups/GroupModule.cs
+++ /dev/null
@@ -1,42 +0,0 @@
-using Cocotte.Options;
-using Cocotte.Utils;
-using Discord;
-using Discord.Interactions;
-using Microsoft.Extensions.Options;
-
-namespace Cocotte.Modules.Groups;
-
-///
-/// Module to ask and propose groups for different activities: Abyss, OOW, FC, ...
-///
-[Group("group", "Group related commands")]
-public class GroupModule : InteractionModuleBase
-{
- private readonly ILogger _logger;
- private readonly GroupsOptions _options;
-
- public GroupModule(ILogger logger, IOptions options)
- {
- _logger = logger;
- _options = options.Value;
- }
-
- [RequireOwner]
- [SlashCommand("test", "Test group module")]
- public async Task Test()
- {
- await RespondAsync("Module is active!!");
- }
-
- [RequireOwner]
- [SlashCommand("setup-info", "Display group setup info")]
- public async Task SetupInfo()
- {
- await RespondAsync($"""
- - Helper: {MentionUtils.MentionRole(_options.HelperRoleId)} {_options.HelperEmote.ToEmote()}
- - Dps: {MentionUtils.MentionRole(_options.DpsRoleId)} {_options.DpsEmote.ToEmote()}
- - Tank: {MentionUtils.MentionRole(_options.TankRoleId)} {_options.TankEmote.ToEmote()}
- - Healer: {MentionUtils.MentionRole(_options.HealerRoleId)} {_options.HealerEmote.ToEmote()}
- """);
- }
-}
diff --git a/Cocotte/Options/ActivityOptions.cs b/Cocotte/Options/ActivityOptions.cs
new file mode 100644
index 0000000..558922c
--- /dev/null
+++ b/Cocotte/Options/ActivityOptions.cs
@@ -0,0 +1,18 @@
+namespace Cocotte.Options;
+
+public class ActivityOptions
+{
+ public const string SectionName = "ActivityOptions";
+
+ public ulong HelperRoleId { get; init; }
+ public required string HelperEmote { get; init; }
+
+ public ulong DpsRoleId { get; init; }
+ public required string DpsEmote { get; init; }
+
+ public ulong TankRoleId { get; init; }
+ public required string TankEmote { get; init; }
+
+ public ulong SupportRoleId { get; init; }
+ public required string SupportEmote { get; init; }
+}
\ No newline at end of file
diff --git a/Cocotte/Options/GroupsOptions.cs b/Cocotte/Options/GroupsOptions.cs
deleted file mode 100644
index 23c7ea0..0000000
--- a/Cocotte/Options/GroupsOptions.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-namespace Cocotte.Options;
-
-public class GroupsOptions
-{
- public const string SectionName = "GroupsOptions";
-
- public ulong HelperRoleId { get; init; }
- public string HelperEmote { get; init; }
-
- public ulong DpsRoleId { get; init; }
- public string DpsEmote { get; init; }
-
- public ulong TankRoleId { get; init; }
- public string TankEmote { get; init; }
-
- public ulong HealerRoleId { get; init; }
- public string HealerEmote { get; init; }
-}
\ No newline at end of file
diff --git a/Cocotte/Program.cs b/Cocotte/Program.cs
index d862e86..31045d2 100644
--- a/Cocotte/Program.cs
+++ b/Cocotte/Program.cs
@@ -1,3 +1,4 @@
+using Cocotte.Modules.Activity;
using Cocotte.Modules.Raids;
using Cocotte.Options;
using Cocotte.Services;
@@ -10,20 +11,20 @@ DiscordSocketConfig discordSocketConfig = new()
{
LogLevel = LogSeverity.Debug,
MessageCacheSize = 200,
- GatewayIntents = GatewayIntents.None
+ GatewayIntents = GatewayIntents.AllUnprivileged
};
IHost host = Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((_, configuration) =>
{
configuration.AddJsonFile("discord.json", false, false);
- configuration.AddJsonFile("groups.json", false, false);
+ configuration.AddJsonFile("activity.json", false, false);
})
.ConfigureServices((context, services) =>
{
// Options
services.Configure(context.Configuration.GetSection(DiscordOptions.SectionName));
- services.Configure(context.Configuration.GetSection(GroupsOptions.SectionName));
+ services.Configure(context.Configuration.GetSection(ActivityOptions.SectionName));
// Discord.Net
services.AddHostedService();
@@ -41,6 +42,8 @@ IHost host = Host.CreateDefaultBuilder(args)
services.AddSingleton();
// Groups
+ services.AddTransient();
+ services.AddSingleton();
// Raids
services.AddTransient();
diff --git a/Cocotte/Services/CocotteService.cs b/Cocotte/Services/CocotteService.cs
index 6debb4d..8504b12 100644
--- a/Cocotte/Services/CocotteService.cs
+++ b/Cocotte/Services/CocotteService.cs
@@ -15,20 +15,20 @@ public class CocotteService : BackgroundService
private readonly IHostApplicationLifetime _hostApplicationLifetime;
private readonly DiscordSocketClient _client;
private readonly DiscordOptions _options;
- private readonly GroupsOptions _groupOptions;
+ private readonly ActivityOptions _activityOptions;
private readonly InteractionService _interactionService;
public CocotteService(ILogger logger, IServiceProvider serviceProvider,
IHostEnvironment hostEnvironment,
IHostApplicationLifetime hostApplicationLifetime, DiscordSocketClient client,
- IOptions options, IOptions groupOptions, InteractionService interactionService)
+ IOptions options, IOptions groupOptions, InteractionService interactionService)
{
_logger = logger;
_serviceProvider = serviceProvider;
_hostApplicationLifetime = hostApplicationLifetime;
_client = client;
_options = options.Value;
- _groupOptions = groupOptions.Value;
+ _activityOptions = groupOptions.Value;
_interactionService = interactionService;
_hostEnvironment = hostEnvironment;
}
@@ -66,10 +66,10 @@ public class CocotteService : BackgroundService
private bool ValidateOptions()
{
// Validate group options
- if ((_groupOptions.HelperRoleId
- | _groupOptions.DpsRoleId
- | _groupOptions.TankRoleId
- | _groupOptions.HealerRoleId) == 0)
+ if ((_activityOptions.HelperRoleId
+ | _activityOptions.DpsRoleId
+ | _activityOptions.TankRoleId
+ | _activityOptions.SupportRoleId) == 0)
{
_logger.LogError("One of the group options id is invalid, it cannot be 0");
diff --git a/Cocotte/groups.json b/Cocotte/activity.json
similarity index 66%
rename from Cocotte/groups.json
rename to Cocotte/activity.json
index 5c7787f..5c2fb79 100644
--- a/Cocotte/groups.json
+++ b/Cocotte/activity.json
@@ -1,5 +1,5 @@
{
- "GroupsOptions": {
+ "ActivityOptions": {
"HelperRoleId": 0,
"HelperEmote": "",
@@ -9,7 +9,7 @@
"TankRoleId": 0,
"TankEmote": "",
- "HealerRoleId": 0,
- "HealerEmote": ""
+ "SupportRoleId": 0,
+ "SupportEmote": ""
}
}
\ No newline at end of file