Skip to content
275 changes: 275 additions & 0 deletions DiscordBot/Modules/UserSlashModule.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Collections.Concurrent;
using Discord.Interactions;
using DiscordBot.Services;
using DiscordBot.Settings;
Expand All @@ -13,6 +14,7 @@ public class UserSlashModule : InteractionModuleBase
public CommandHandlingService CommandHandlingService { get; set; }
public UserService UserService { get; set; }
public BotSettings BotSettings { get; set; }
public ILoggingService LoggingService { get; set; }

#endregion

Expand Down Expand Up @@ -252,4 +254,277 @@ await Context.Interaction.ModifyOriginalResponseAsync(msg =>
}

#endregion

#region Duel System

private static readonly ConcurrentDictionary<string, (ulong challengerId, ulong opponentId)> _activeDuels = new ConcurrentDictionary<string, (ulong, ulong)>();
private static readonly Random _random = new Random();

private static readonly string[] _normalWinMessages =
{
"{winner} lands a solid hit on {loser} and wins the duel!",
"{winner} uses their sword to attack {loser}, but {loser} fails to dodge and {winner} wins!",
"{winner} outmaneuvers {loser} with a swift strike and claims victory!",
"{winner} blocks {loser}'s attack and counters with a decisive blow!",
"{winner} dodges {loser}'s clumsy swing and delivers the winning hit!",
"{winner} parries {loser}'s blade and strikes back to win the duel!",
"{winner} feints left, strikes right, and defeats {loser}!",
"{winner} overwhelms {loser} with superior technique and emerges victorious!"
};

[SlashCommand("duel", "Challenge another user to a duel!")]
public async Task Duel(
[Summary(description: "The user you want to duel")] IUser opponent,
[Summary(description: "Type of duel")]
[Choice("Normal", "normal")]
[Choice("Mute", "mute")]
string type = "normal")
{
// Prevent self-dueling
if (opponent.Id == Context.User.Id)
{
await Context.Interaction.RespondAsync("You cannot duel yourself!", ephemeral: true);
return;
}

// Prevent dueling bots
if (opponent.IsBot)
{
await Context.Interaction.RespondAsync("You cannot duel a bot!", ephemeral: true);
return;
}

// Check for active duel
string duelKey = $"{Context.User.Id}_{opponent.Id}";
string reverseDuelKey = $"{opponent.Id}_{Context.User.Id}";

if (_activeDuels.ContainsKey(duelKey) || _activeDuels.ContainsKey(reverseDuelKey))
{
await Context.Interaction.RespondAsync("There's already an active duel between you two!", ephemeral: true);
return;
}

// Store the duel with both user IDs for timeout tracking
_activeDuels[duelKey] = (Context.User.Id, opponent.Id);

var embed = new EmbedBuilder()
.WithColor(Color.Orange)
.WithTitle("⚔️ Duel Challenge!")
.WithDescription($"{Context.User.Mention} has challenged {opponent.Mention} to a {type} duel!")
.WithFooter($"This challenge will expire in 60 seconds")
.Build();

var components = new ComponentBuilder()
.WithButton("⚔️ Accept", $"duel_accept:{duelKey}:{type}", ButtonStyle.Success)
.WithButton("🛡️ Refuse", $"duel_refuse:{duelKey}", ButtonStyle.Danger)
.WithButton("❌ Cancel", $"duel_cancel:{duelKey}", ButtonStyle.Secondary)
.Build();

await Context.Interaction.RespondAsync(embed: embed, components: components);

// Store the message reference for timeout
var originalResponse = await Context.Interaction.GetOriginalResponseAsync();

// Auto-timeout after 60 seconds
_ = Task.Run(async () =>
{
await Task.Delay(60000); // 60 seconds
if (_activeDuels.ContainsKey(duelKey))
{
var (challengerId, opponentId) = _activeDuels[duelKey];
_activeDuels.TryRemove(duelKey, out _);

try
{
var challenger = await Context.Guild.GetUserAsync(challengerId);
var challengedUser = await Context.Guild.GetUserAsync(opponentId);

string timeoutMessage = challengedUser != null
? $"⏰ Duel challenge to {challengedUser.Mention} expired."
: "⏰ Duel challenge expired.";

await originalResponse.ModifyAsync(msg =>
{
msg.Content = string.Empty;
msg.Embed = new EmbedBuilder()
.WithColor(Color.LightGrey)
.WithDescription(timeoutMessage)
.Build();
msg.Components = new ComponentBuilder().Build();
});
}
catch (Exception ex)
{
await LoggingService.LogChannelAndFile($"Failed to modify duel timeout message: {ex.Message}", ExtendedLogSeverity.Warning);
}
}
});
}

[ComponentInteraction("duel_accept:*:*")]
public async Task DuelAccept(string duelKey, string type)
{
// Extract user IDs from the duel key
var userIds = duelKey.Split('_');
if (userIds.Length != 2 || !ulong.TryParse(userIds[0], out var challengerId) || !ulong.TryParse(userIds[1], out var opponentId))
{
await Context.Interaction.RespondAsync("Invalid duel data!", ephemeral: true);
return;
}

// Only the challenged user can accept
if (Context.User.Id != opponentId)
{
await Context.Interaction.RespondAsync("Only the challenged user can accept this duel!", ephemeral: true);
return;
}

// Check if duel is still active
if (!_activeDuels.ContainsKey(duelKey))
{
await Context.Interaction.RespondAsync("This duel is no longer active!", ephemeral: true);
return;
}

// Remove from active duels
_activeDuels.TryRemove(duelKey, out _);

await Context.Interaction.DeferAsync();

// Get users
var challenger = await Context.Guild.GetUserAsync(challengerId);
var opponent = await Context.Guild.GetUserAsync(opponentId);

if (challenger == null || opponent == null)
{
await Context.Interaction.FollowupAsync("One of the duel participants is no longer available!");
return;
}

// Randomly select winner (50/50)
bool challengerWins = _random.Next(2) == 0;
var winner = challengerWins ? challenger : opponent;
var loser = challengerWins ? opponent : challenger;

// Generate flavor message
string flavorMessage = _normalWinMessages[_random.Next(_normalWinMessages.Length)];
flavorMessage = flavorMessage.Replace("{winner}", winner.Mention).Replace("{loser}", loser.Mention);

var resultEmbed = new EmbedBuilder()
.WithColor(Color.Gold)
.WithTitle("⚔️ Duel Results!")
.WithDescription(flavorMessage)
.AddField("Winner", winner.Mention, inline: true)
.Build();

await Context.Interaction.ModifyOriginalResponseAsync(msg =>
{
msg.Embed = resultEmbed;
msg.Components = new ComponentBuilder().Build();
});

// Handle mute duel using Discord timeout
if (type == "mute")
{
try
{
var guildLoser = loser as IGuildUser;
if (guildLoser != null)
{
// Use Discord's timeout feature for 10 minutes
await guildLoser.SetTimeOutAsync(TimeSpan.FromMinutes(10));
await Context.Interaction.FollowupAsync($"💀 {loser.Mention} has been timed out for 10 minutes as the duel loser!", ephemeral: false);
}
}
catch (Exception ex)
{
await LoggingService.LogChannelAndFile($"Failed to timeout the loser of the duel: {ex.Message}", ExtendedLogSeverity.Error);
await Context.Interaction.FollowupAsync("Failed to timeout the loser.", ephemeral: false);
}
}
}

[ComponentInteraction("duel_refuse:*")]
public async Task DuelRefuse(string duelKey)
{
// Extract user IDs from the duel key
var userIds = duelKey.Split('_');
if (userIds.Length != 2 || !ulong.TryParse(userIds[0], out var challengerId) || !ulong.TryParse(userIds[1], out var opponentId))
{
await Context.Interaction.RespondAsync("Invalid duel data!", ephemeral: true);
return;
}

// Only the challenged user can refuse
if (Context.User.Id != opponentId)
{
await Context.Interaction.RespondAsync("Only the challenged user can refuse this duel!", ephemeral: true);
return;
}

// Check if duel is still active
if (!_activeDuels.ContainsKey(duelKey))
{
await Context.Interaction.RespondAsync("This duel is no longer active!", ephemeral: true);
return;
}

// Remove from active duels
_activeDuels.TryRemove(duelKey, out _);

// Edit the embed to show refusal instead of deleting
await Context.Interaction.DeferAsync();
await Context.Interaction.ModifyOriginalResponseAsync(msg =>
{
msg.Content = string.Empty;
msg.Embed = new EmbedBuilder()
.WithColor(Color.LightGrey)
.WithDescription("🛡️ Duel challenge was refused.")
.Build();
msg.Components = new ComponentBuilder().Build();
});
}

[ComponentInteraction("duel_cancel:*")]
public async Task DuelCancel(string duelKey)
{
// Extract user IDs from the duel key
var userIds = duelKey.Split('_');
if (userIds.Length != 2 || !ulong.TryParse(userIds[0], out var challengerId) || !ulong.TryParse(userIds[1], out var opponentId))
{
await Context.Interaction.RespondAsync("Invalid duel data!", ephemeral: true);
return;
}

// Only the challenger can cancel
if (Context.User.Id != challengerId)
{
await Context.Interaction.RespondAsync("Only the challenger can cancel this duel!", ephemeral: true);
return;
}

// Check if duel is still active
if (!_activeDuels.ContainsKey(duelKey))
{
await Context.Interaction.RespondAsync("This duel is no longer active!", ephemeral: true);
return;
}

// Remove from active duels
_activeDuels.TryRemove(duelKey, out _);

// Edit the embed to show cancellation
await Context.Interaction.DeferAsync();
await Context.Interaction.ModifyOriginalResponseAsync(msg =>
{
msg.Content = string.Empty;
msg.Embed = new EmbedBuilder()
.WithColor(Color.LightGrey)
.WithDescription("❌ Duel challenge was cancelled by the challenger.")
.Build();
msg.Components = new ComponentBuilder().Build();
});
}

#endregion
}
Loading