358 lines
12 KiB
C#
358 lines
12 KiB
C#
using System.Diagnostics.CodeAnalysis;
|
|
using Cocotte.Utils;
|
|
using Discord;
|
|
using Discord.Interactions;
|
|
using Discord.WebSocket;
|
|
|
|
// ReSharper disable UnusedMember.Global
|
|
|
|
namespace Cocotte.Modules.Raids;
|
|
|
|
[Group("raid", "Raid related commands")]
|
|
[SuppressMessage("Performance", "CA1822:Mark members as static")]
|
|
public partial class RaidModule : InteractionModuleBase<SocketInteractionContext>
|
|
{
|
|
private readonly ILogger<RaidModule> _logger;
|
|
private readonly IRaidsRepository _raids;
|
|
private readonly IPlayerInfosRepository _playerInfos;
|
|
private readonly RaidFormatter _raidFormatter;
|
|
private readonly RaidRegisterManager _registerManager;
|
|
private readonly RosterAssigner _rosterAssigner;
|
|
|
|
public RaidModule(ILogger<RaidModule> logger, IRaidsRepository raids, IPlayerInfosRepository playerInfos,
|
|
RaidFormatter raidFormatter, RaidRegisterManager registerManager, RosterAssigner rosterAssigner)
|
|
{
|
|
_logger = logger;
|
|
_raids = raids;
|
|
_playerInfos = playerInfos;
|
|
_raidFormatter = raidFormatter;
|
|
_registerManager = registerManager;
|
|
_rosterAssigner = rosterAssigner;
|
|
}
|
|
|
|
[EnabledInDm(false)]
|
|
[SlashCommand("start", "Start a raid formation")]
|
|
public async Task Start(DayOfWeek day, string time)
|
|
{
|
|
// Check if time is valid
|
|
if (!TimeOnly.TryParse(time, out var timeOnly))
|
|
{
|
|
await RespondAsync(
|
|
ephemeral: true,
|
|
embed: EmbedUtils.ErrorEmbed("Invalid time, try using format 'hh:mm'").Build()
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
// 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);
|
|
|
|
// Calculate date
|
|
var date = DateTime.Today;
|
|
date = date.AddDays(DateTimeUtils.CalculateDayOfWeekOffset(date.DayOfWeek, day))
|
|
.Add(timeOnly.ToTimeSpan());
|
|
|
|
// New raid instance
|
|
if (!_raids.AddNewRaid(raidId, date))
|
|
{
|
|
// 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 raid = _raids[raidId];
|
|
var components = RaidComponents(raidId);
|
|
var embed = _raidFormatter.RaidEmbed(raid);
|
|
|
|
await ModifyOriginalResponseAsync(m =>
|
|
{
|
|
m.Content = "";
|
|
m.Components = components.Build();
|
|
m.Embed = embed.Build();
|
|
});
|
|
}
|
|
|
|
[ComponentInteraction("raid raid_join:*", true)]
|
|
public async Task RaidJoin(ulong raidId)
|
|
{
|
|
if (!_raids.TryGetRaid(raidId, out var raid))
|
|
{
|
|
await RespondAsync(
|
|
ephemeral: true,
|
|
embed: EmbedUtils.ErrorEmbed("This raid does not exist").Build()
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
var user = (IGuildUser) Context.User;
|
|
|
|
// Check if player is already registered early
|
|
if (raid.ContainsPlayer(user.Id))
|
|
{
|
|
await RespondAsync(
|
|
ephemeral: true,
|
|
embed: EmbedUtils.InfoEmbed("You're already registered for this raid").Build());
|
|
|
|
return;
|
|
}
|
|
|
|
// Ask player info
|
|
_registerManager.RegisteringPlayers[(raidId, user.Id)] = new RosterPlayer(user.Id, user.DisplayName);
|
|
|
|
_logger.LogDebug("User {User} is registering for raid {Raid}", user.Username, raid);
|
|
|
|
// Add name
|
|
await RespondAsync($"Please select a role for raid", components: PlayerRoleComponent(raid, user).Build(), ephemeral: true);
|
|
}
|
|
|
|
[ComponentInteraction("raid player_select_role:*:*", ignoreGroupNames: true)]
|
|
public async Task PlayerSelectRole(ulong raidId, ulong playerId, string selectedRoleInput)
|
|
{
|
|
var selectedRole = Enum.Parse<PlayerRole>(selectedRoleInput);
|
|
|
|
if (_registerManager.RegisteringPlayers.TryGetValue((raidId, playerId), out var rosterPlayer))
|
|
{
|
|
_registerManager.RegisteringPlayers[(raidId, playerId)] = rosterPlayer with { Role = selectedRole };
|
|
|
|
// Also update preferred role
|
|
_playerInfos.UpdatePlayerInfo(playerId, p => p.PreferredRole = selectedRole);
|
|
|
|
await RespondAsync();
|
|
}
|
|
// The user is not currently registering, wonder how he got here then
|
|
else
|
|
{
|
|
await RespondAsync(ephemeral: true,
|
|
embed: EmbedUtils.ErrorEmbed("You are not registering for this raid :thinking:").Build());
|
|
}
|
|
}
|
|
|
|
[ComponentInteraction("raid player_join:*:*", ignoreGroupNames: true)]
|
|
public async Task PlayerJoinNonSubstitute(ulong raidId, ulong playerId)
|
|
{
|
|
await PlayerJoin(raidId, playerId, false);
|
|
}
|
|
|
|
[ComponentInteraction("raid player_join_substitute:*:*", ignoreGroupNames: true)]
|
|
public async Task PlayerJoinSubstitute(ulong raidId, ulong playerId)
|
|
{
|
|
await PlayerJoin(raidId, playerId, true);
|
|
}
|
|
|
|
private async Task PlayerJoin(ulong raidId, ulong playerId, bool substitute)
|
|
{
|
|
// Check if player is registering
|
|
if (!_registerManager.RegisteringPlayers.TryGetValue((raidId, playerId), out var rosterPlayer))
|
|
{
|
|
await RespondAsync(ephemeral: true,
|
|
embed: EmbedUtils.ErrorEmbed("You are not registering for this raid :thinking:").Build());
|
|
|
|
return;
|
|
}
|
|
|
|
// Check if we need to ask FC
|
|
if (!_playerInfos.TryGetPlayerInfo(playerId, out var playerInfo))
|
|
{
|
|
await Context.Interaction.RespondWithModalAsync<FcModal>($"raid modal_fc:{raidId}");
|
|
|
|
return;
|
|
}
|
|
|
|
// Player already has FC registered but it's outdated
|
|
if (playerInfo.IsFcUpdateRequired)
|
|
{
|
|
await Context.Interaction.RespondWithModalAsync<FcModal>($"raid modal_fc:{raidId}",
|
|
modifyModal: m =>
|
|
m.UpdateTextInput("fc", component => component.Value = playerInfo.Fc.FormatSpaced())
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
// Register user for raid
|
|
await RegisterPlayer(raidId, rosterPlayer with { Fc = rosterPlayer.Fc, Substitute = substitute});
|
|
}
|
|
|
|
[ModalInteraction("raid modal_fc:*", ignoreGroupNames: true)]
|
|
public async Task ModalFcSubmit(ulong raidId, FcModal modal)
|
|
{
|
|
var playerId = Context.User.Id;
|
|
_logger.LogTrace("Received modal FC modal from {User} with value: {Fc}", Context.User.Username, modal.Fc);
|
|
|
|
// Check if player is registering
|
|
if (!_registerManager.RegisteringPlayers.TryGetValue((raidId, playerId), out var rosterPlayer))
|
|
{
|
|
await RespondAsync(ephemeral: true,
|
|
embed: EmbedUtils.ErrorEmbed("You are not registering for this raid :thinking:").Build());
|
|
|
|
return;
|
|
}
|
|
|
|
var fcInput = modal.Fc.Replace(" ", "");
|
|
|
|
if (!uint.TryParse(fcInput, out var fc))
|
|
{
|
|
await RespondAsync(ephemeral: true,
|
|
embed: EmbedUtils.ErrorEmbed("Invalid fc, try registering again").Build());
|
|
|
|
_registerManager.RegisteringPlayers.Remove((raidId, playerId));
|
|
|
|
return;
|
|
}
|
|
|
|
_playerInfos.UpdatePlayerInfo(playerId, p => p.Fc = fc);
|
|
|
|
await RegisterPlayer(raidId, rosterPlayer with { Fc = fc });
|
|
}
|
|
|
|
private async Task RegisterPlayer(ulong raidId, RosterPlayer rosterPlayer)
|
|
{
|
|
// Check if raid exists
|
|
if (!_raids.TryGetRaid(raidId, out var raid))
|
|
{
|
|
await RespondAsync(
|
|
ephemeral: true,
|
|
embed: EmbedUtils.InfoEmbed("This raid does not exist").Build());
|
|
|
|
return;
|
|
}
|
|
|
|
_registerManager.RegisteringPlayers[(raidId, Context.User.Id)] = rosterPlayer;
|
|
|
|
// Player is already registered, update info
|
|
if (!raid.AddPlayer(rosterPlayer))
|
|
{
|
|
raid.UpdatePlayer(rosterPlayer);
|
|
|
|
await UpdateRaidRosterEmbed(raid);
|
|
// await RespondAsync(
|
|
// ephemeral: true,
|
|
// embed: EmbedUtils.SuccessEmbed($"Successfully update you're role to: {rosterPlayer.Role}").Build());
|
|
await RespondAsync();
|
|
}
|
|
// It's a new player
|
|
else
|
|
{
|
|
_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(rosterPlayer.Id).Role}").Build()
|
|
);
|
|
}
|
|
}
|
|
|
|
[ComponentInteraction("raid raid_leave:*", ignoreGroupNames: true)]
|
|
public async Task Leave(ulong raidId)
|
|
{
|
|
if (!_raids.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)
|
|
{
|
|
raid.AssignRosters(_rosterAssigner);
|
|
|
|
await userMessage.ModifyAsync(
|
|
m => m.Embed = _raidFormatter.RaidEmbed(raid).Build()
|
|
);
|
|
}
|
|
}
|
|
|
|
private static 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)
|
|
)
|
|
);
|
|
}
|
|
|
|
private ComponentBuilder PlayerRoleComponent(Raid raid, IGuildUser user)
|
|
{
|
|
var select = new SelectMenuBuilder()
|
|
.WithCustomId($"raid player_select_role:{raid.Id}:{user.Id}")
|
|
.WithMinValues(1)
|
|
.WithMaxValues(1);
|
|
|
|
// Preselect preferred role
|
|
var preferredRole = _playerInfos.TryGetPlayerInfo(user.Id, out var playerInfo) ? playerInfo.PreferredRole : PlayerRole.Dps;
|
|
|
|
foreach (var role in Enum.GetValues<PlayerRole>())
|
|
{
|
|
var roleText = role.ToString();
|
|
select.AddOption(roleText, roleText, emote: _raidFormatter.RoleToEmote(role), isDefault: role == preferredRole);
|
|
}
|
|
|
|
return new ComponentBuilder()
|
|
.AddRow(new ActionRowBuilder()
|
|
.WithSelectMenu(select)
|
|
)
|
|
.AddRow(new ActionRowBuilder()
|
|
.WithButton("Join", $"raid player_join:{raid.Id}:{user.Id}")
|
|
.WithButton("Join substitute", $"raid player_join_substitute:{raid.Id}:{user.Id}")
|
|
);
|
|
}
|
|
}
|
|
|
|
public class FcModal : IModal
|
|
{
|
|
public string Title => "Please enter your FC";
|
|
|
|
[NotNull]
|
|
[InputLabel("FC")]
|
|
[ModalTextInput("fc", placeholder: "30 000", minLength: 1, maxLength: 7)]
|
|
public string? Fc { get; set; }
|
|
} |