[Activity] Add activity creation
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Discord.Net" Version="3.8.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.0" />
|
||||
<PackageReference Include="Discord.Net" Version="3.9.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
3
Cocotte/Modules/Activity/Activity.cs
Normal file
3
Cocotte/Modules/Activity/Activity.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
namespace Cocotte.Modules.Activity;
|
||||
|
||||
public abstract record Activity(ulong Owner, string Description, ActivityType ActivityType, ActivityName ActivityName, uint MaxPlayers);
|
||||
24
Cocotte/Modules/Activity/ActivityFormatter.cs
Normal file
24
Cocotte/Modules/Activity/ActivityFormatter.cs
Normal file
@@ -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)
|
||||
};
|
||||
}
|
||||
}
|
||||
82
Cocotte/Modules/Activity/ActivityHelper.cs
Normal file
82
Cocotte/Modules/Activity/ActivityHelper.cs
Normal file
@@ -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<ActivityOptions> options)
|
||||
{
|
||||
_options = options.Value;
|
||||
}
|
||||
|
||||
public ActivityRoles GetPlayerRoles(IReadOnlyCollection<SocketRole> 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
|
||||
};
|
||||
}
|
||||
130
Cocotte/Modules/Activity/ActivityModule.cs
Normal file
130
Cocotte/Modules/Activity/ActivityModule.cs
Normal file
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Module to ask and propose groups for different activities: Abyss, OOW, FC, ...
|
||||
/// </summary>
|
||||
[Group("activite", "Organise des activités")]
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Local")]
|
||||
public class ActivityModule : InteractionModuleBase<SocketInteractionContext>
|
||||
{
|
||||
private readonly ILogger<ActivityModule> _logger;
|
||||
private readonly ActivityOptions _options;
|
||||
private readonly ActivityHelper _activityHelper;
|
||||
|
||||
public ActivityModule(ILogger<ActivityModule> logger, IOptions<ActivityOptions> 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)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
17
Cocotte/Modules/Activity/ActivityName.cs
Normal file
17
Cocotte/Modules/Activity/ActivityName.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace Cocotte.Modules.Activity;
|
||||
|
||||
public enum ActivityName
|
||||
{
|
||||
Abyss,
|
||||
OriginsOfWar,
|
||||
Raids,
|
||||
FrontierClash,
|
||||
VoidRift,
|
||||
JointOperation,
|
||||
InterstellarExploration,
|
||||
BreakFromDestiny,
|
||||
CriticalAbyss,
|
||||
Fishing,
|
||||
Event,
|
||||
MirroriaRace
|
||||
}
|
||||
3
Cocotte/Modules/Activity/ActivityPlayer.cs
Normal file
3
Cocotte/Modules/Activity/ActivityPlayer.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
namespace Cocotte.Modules.Activity;
|
||||
|
||||
public record ActivityPlayer(ulong UserId, string PlayerName);
|
||||
3
Cocotte/Modules/Activity/ActivityRolePlayer.cs
Normal file
3
Cocotte/Modules/Activity/ActivityRolePlayer.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
namespace Cocotte.Modules.Activity;
|
||||
|
||||
public record ActivityRolePlayer(ulong UserId, string PlayerName, ActivityRoles Roles) : ActivityPlayer(UserId, PlayerName);
|
||||
11
Cocotte/Modules/Activity/ActivityRoles.cs
Normal file
11
Cocotte/Modules/Activity/ActivityRoles.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace Cocotte.Modules.Activity;
|
||||
|
||||
[Flags]
|
||||
public enum ActivityRoles : byte
|
||||
{
|
||||
None = 0b0000,
|
||||
Helper = 0b0001,
|
||||
Dps = 0b0010,
|
||||
Tank = 0b0100,
|
||||
Support = 0b1000
|
||||
}
|
||||
12
Cocotte/Modules/Activity/ActivityType.cs
Normal file
12
Cocotte/Modules/Activity/ActivityType.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace Cocotte.Modules.Activity;
|
||||
|
||||
public enum ActivityType
|
||||
{
|
||||
Pve4Players,
|
||||
Pve8Players,
|
||||
Pvp8Players,
|
||||
Pvp3Players,
|
||||
Event8Players,
|
||||
Event4Players,
|
||||
Other
|
||||
}
|
||||
4
Cocotte/Modules/Activity/StagedActivity.cs
Normal file
4
Cocotte/Modules/Activity/StagedActivity.cs
Normal file
@@ -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);
|
||||
@@ -1,42 +0,0 @@
|
||||
using Cocotte.Options;
|
||||
using Cocotte.Utils;
|
||||
using Discord;
|
||||
using Discord.Interactions;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Cocotte.Modules.Groups;
|
||||
|
||||
/// <summary>
|
||||
/// Module to ask and propose groups for different activities: Abyss, OOW, FC, ...
|
||||
/// </summary>
|
||||
[Group("group", "Group related commands")]
|
||||
public class GroupModule : InteractionModuleBase<SocketInteractionContext>
|
||||
{
|
||||
private readonly ILogger<GroupModule> _logger;
|
||||
private readonly GroupsOptions _options;
|
||||
|
||||
public GroupModule(ILogger<GroupModule> logger, IOptions<GroupsOptions> 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()}
|
||||
""");
|
||||
}
|
||||
}
|
||||
18
Cocotte/Options/ActivityOptions.cs
Normal file
18
Cocotte/Options/ActivityOptions.cs
Normal file
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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<DiscordOptions>(context.Configuration.GetSection(DiscordOptions.SectionName));
|
||||
services.Configure<GroupsOptions>(context.Configuration.GetSection(GroupsOptions.SectionName));
|
||||
services.Configure<ActivityOptions>(context.Configuration.GetSection(ActivityOptions.SectionName));
|
||||
|
||||
// Discord.Net
|
||||
services.AddHostedService<DiscordLoggingService>();
|
||||
@@ -41,6 +42,8 @@ IHost host = Host.CreateDefaultBuilder(args)
|
||||
services.AddSingleton<RolesOptions>();
|
||||
|
||||
// Groups
|
||||
services.AddTransient<ActivityFormatter>();
|
||||
services.AddSingleton<ActivityHelper>();
|
||||
|
||||
// Raids
|
||||
services.AddTransient<RaidFormatter>();
|
||||
|
||||
@@ -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<CocotteService> logger, IServiceProvider serviceProvider,
|
||||
IHostEnvironment hostEnvironment,
|
||||
IHostApplicationLifetime hostApplicationLifetime, DiscordSocketClient client,
|
||||
IOptions<DiscordOptions> options, IOptions<GroupsOptions> groupOptions, InteractionService interactionService)
|
||||
IOptions<DiscordOptions> options, IOptions<ActivityOptions> 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");
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"GroupsOptions": {
|
||||
"ActivityOptions": {
|
||||
"HelperRoleId": 0,
|
||||
"HelperEmote": "",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"TankRoleId": 0,
|
||||
"TankEmote": "",
|
||||
|
||||
"HealerRoleId": 0,
|
||||
"HealerEmote": ""
|
||||
"SupportRoleId": 0,
|
||||
"SupportEmote": ""
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user