From d47442b98420c0f78ac4cf576eec8e2d9dec4a69 Mon Sep 17 00:00:00 2001 From: Eveldee Date: Mon, 20 Mar 2023 14:34:50 +0100 Subject: [PATCH] [Activity] Add activity creation --- Cocotte/Cocotte.csproj | 4 +- Cocotte/Modules/Activity/Activity.cs | 3 + Cocotte/Modules/Activity/ActivityFormatter.cs | 24 ++++ Cocotte/Modules/Activity/ActivityHelper.cs | 82 +++++++++++ Cocotte/Modules/Activity/ActivityModule.cs | 130 ++++++++++++++++++ Cocotte/Modules/Activity/ActivityName.cs | 17 +++ Cocotte/Modules/Activity/ActivityPlayer.cs | 3 + .../Modules/Activity/ActivityRolePlayer.cs | 3 + Cocotte/Modules/Activity/ActivityRoles.cs | 11 ++ Cocotte/Modules/Activity/ActivityType.cs | 12 ++ Cocotte/Modules/Activity/StagedActivity.cs | 4 + Cocotte/Modules/Groups/GroupModule.cs | 42 ------ Cocotte/Options/ActivityOptions.cs | 18 +++ Cocotte/Options/GroupsOptions.cs | 18 --- Cocotte/Program.cs | 9 +- Cocotte/Services/CocotteService.cs | 14 +- Cocotte/{groups.json => activity.json} | 6 +- 17 files changed, 325 insertions(+), 75 deletions(-) create mode 100644 Cocotte/Modules/Activity/Activity.cs create mode 100644 Cocotte/Modules/Activity/ActivityFormatter.cs create mode 100644 Cocotte/Modules/Activity/ActivityHelper.cs create mode 100644 Cocotte/Modules/Activity/ActivityModule.cs create mode 100644 Cocotte/Modules/Activity/ActivityName.cs create mode 100644 Cocotte/Modules/Activity/ActivityPlayer.cs create mode 100644 Cocotte/Modules/Activity/ActivityRolePlayer.cs create mode 100644 Cocotte/Modules/Activity/ActivityRoles.cs create mode 100644 Cocotte/Modules/Activity/ActivityType.cs create mode 100644 Cocotte/Modules/Activity/StagedActivity.cs delete mode 100644 Cocotte/Modules/Groups/GroupModule.cs create mode 100644 Cocotte/Options/ActivityOptions.cs delete mode 100644 Cocotte/Options/GroupsOptions.cs rename Cocotte/{groups.json => activity.json} (66%) 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