Skip to content

Commit

Permalink
Private Raids
Browse files Browse the repository at this point in the history
- Users can mention up to 3 users to do Private Raids with.
- Works for the ra command only.
- Example:  ra <seed> <difficulty> <storyprogress> <event name (optional)> @user1 @user2 @user3
- DMs all invited users information about the raid and the code when it starts
- Raid will be posted publicly after the seconds defined in RequestEmbedTime
- New Setting added "PrivateRaidsEnabled" to allow/disallow private raids.
  • Loading branch information
bdawg1989 committed Apr 7, 2024
1 parent 814b8e7 commit 5aa6917
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 62 deletions.
66 changes: 59 additions & 7 deletions SysBot.Pokemon.Discord/Commands/Bots/RaidModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using static SysBot.Pokemon.RotatingRaidSettingsSV;
Expand All @@ -20,7 +21,7 @@
namespace SysBot.Pokemon.Discord.Commands.Bots
{
[Summary("Generates and queues various silly trade additions")]
public class RaidModule<T> : ModuleBase<SocketCommandContext> where T : PKM, new()
public partial class RaidModule<T> : ModuleBase<SocketCommandContext> where T : PKM, new()
{
private readonly PokeRaidHub<T> Hub = SysCord<T>.Runner.Hub;
private static DiscordSocketClient _client => SysCord<T>.Instance.GetClient();
Expand Down Expand Up @@ -428,7 +429,9 @@ public async Task AddNewRaidParamNext(
[Summary("Seed")] string seed,
[Summary("Difficulty Level (1-7)")] int level,
[Summary("Story Progress Level")] int storyProgressLevel = 6,
[Summary("Species Name (Optional)")] string? speciesName = null)
[Summary("Species Name or User Mention (Optional)")] string? speciesNameOrUserMention = null,
[Summary("User Mention 2 (Optional)")] SocketGuildUser? user2 = null,
[Summary("User Mention 3 (Optional)")] SocketGuildUser? user3 = null)
{
var botPrefix = SysCord<T>.Runner.Config.Discord.CommandPrefix;
if (Hub.Config.RotatingRaidSV.RaidSettings.DisableRequests)
Expand All @@ -443,6 +446,35 @@ public async Task AddNewRaidParamNext(
return;
}

// Check if the first parameter after story progress level is a user mention
bool isUserMention = speciesNameOrUserMention != null && MyRegex1().IsMatch(speciesNameOrUserMention);
SocketGuildUser? user1 = null;
string? speciesName = null;

if (isUserMention)
{
// Extract the user ID from the mention and retrieve the user
var userId2 = ulong.Parse(Regex.Match(speciesNameOrUserMention, @"\d+").Value);
user1 = Context.Guild.GetUser(userId2);
}
else
{
speciesName = speciesNameOrUserMention;
}

// Check if private raids are enabled
if (!Hub.Config.RotatingRaidSV.RaidSettings.PrivateRaidsEnabled && (user1 != null || user2 != null || user3 != null))
{
await ReplyAsync("Private raids are currently disabled by the host.").ConfigureAwait(false);
return;
}
// Check if the number of user mentions exceeds the limit
int mentionCount = (user1 != null ? 1 : 0) + (user2 != null ? 1 : 0) + (user3 != null ? 1 : 0);
if (mentionCount > 3)
{
await ReplyAsync("You can only mention up to 3 users for a private raid.").ConfigureAwait(false);
return;
}
var userId = Context.User.Id;
if (Hub.Config.RotatingRaidSV.ActiveRaids.Any(r => r.RequestedByUserID == userId))
{
Expand All @@ -453,7 +485,7 @@ public async Task AddNewRaidParamNext(
var userRoles = (Context.User as SocketGuildUser)?.Roles.Select(r => r.Id) ?? new List<ulong>();

if (!Hub.Config.RotatingRaidSV.RaidSettings.BypassLimitRequests.ContainsKey(userId) &&
!userRoles.Any(roleId => Hub.Config.RotatingRaidSV.RaidSettings.BypassLimitRequests.ContainsKey(roleId)))
!userRoles.Any(Hub.Config.RotatingRaidSV.RaidSettings.BypassLimitRequests.ContainsKey))
{
if (!userRequestManager.CanRequest(userId, Hub.Config.RotatingRaidSV.RaidSettings.LimitRequests, Hub.Config.RotatingRaidSV.RaidSettings.LimitRequestsTime, out var remainingCooldown))
{
Expand Down Expand Up @@ -505,12 +537,12 @@ public async Task AddNewRaidParamNext(

int raidDeliveryGroupID = -1;

if (!string.IsNullOrEmpty(speciesName) && SpeciesToGroupIDMap.TryGetValue(speciesName, out var groupIDAndIndices))
if (isEvent && SpeciesToGroupIDMap.TryGetValue(speciesName, out var groupIDAndIndices))
{
var firstRaidGroupID = groupIDAndIndices.First().GroupID;
raidDeliveryGroupID = firstRaidGroupID;
}
else if (!string.IsNullOrEmpty(speciesName))
else if (isEvent)
{
await ReplyAsync("Species name not recognized or not associated with an active event. Please check the name and try again.");
return;
Expand Down Expand Up @@ -566,6 +598,7 @@ public async Task AddNewRaidParamNext(
Title = $"{Context.User.Username}'s Requested Raid{(isEvent ? $" ({speciesName} Event Raid)" : "")}",
RaidUpNext = false,
User = Context.User,
MentionedUsers = new List<SocketUser> { user1, user2, user3 }.Where(u => u != null).ToList(),
};

// Check if Species is Ditto and set PartyPK to Showdown template
Expand Down Expand Up @@ -608,10 +641,26 @@ public async Task AddNewRaidParamNext(
var replyMsg = $"{Context.User.Mention}, added your raid to the queue! I'll DM you when it's about to start.";
await ReplyAsync(replyMsg, embed: raidEmbed).ConfigureAwait(false);

// Notify the mentioned users
var mentionedUsers = new List<SocketGuildUser>();
if (user1 != null) mentionedUsers.Add(user1);
if (user2 != null) mentionedUsers.Add(user2);
if (user3 != null) mentionedUsers.Add(user3);

foreach (var user in mentionedUsers)
{
try
{
await user.SendMessageAsync($"{Context.User.Username} invited you to a private raid! I'll DM you the code when it's about to start.", false, raidEmbed).ConfigureAwait(false);
}
catch
{
await ReplyAsync($"Failed to send DM to {user.Mention}. Please make sure their DMs are open.").ConfigureAwait(false);
}
}
try
{
var user = Context.User as SocketGuildUser;
if (user != null)
if (Context.User is SocketGuildUser user)
{
await user.SendMessageAsync($"Here's your raid information:\n{queuePositionMessage}\nYour request command: `{newparam.RequestCommand}`", false, raidEmbed).ConfigureAwait(false);
}
Expand Down Expand Up @@ -1080,5 +1129,8 @@ public async Task GetRaidHelpListAsync()
_ => EntityConverter.ConvertToType(dl.Data, typeof(T), out _) as T,
};
}

[GeneratedRegex(@"^<@!?\d+>$")]
private static partial Regex MyRegex1();
}
}
87 changes: 32 additions & 55 deletions SysBot.Pokemon/SV/BotRaid/RotatingRaidBotSV.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public class RotatingRaidBotSV : PokeRoutineExecutor9SV
private readonly PokeRaidHub<PK9> Hub;
private readonly RotatingRaidSettingsSV Settings;
private RemoteControlAccessList RaiderBanList => Settings.RaiderBanList;
public static Dictionary<string, List<(int GroupID, int Index, string DenIdentifier)>> SpeciesToGroupIDMap = new();
public static Dictionary<string, List<(int GroupID, int Index, string DenIdentifier)>> SpeciesToGroupIDMap = [];

public RotatingRaidBotSV(PokeBotState cfg, PokeRaidHub<PK9> hub) : base(cfg)
{
Expand Down Expand Up @@ -62,7 +62,7 @@ public class PlayerInfo
private readonly ulong[] TeraNIDOffsets = new ulong[3];
private string TeraRaidCode { get; set; } = string.Empty;
private string BaseDescription = string.Empty;
private readonly Dictionary<ulong, int> RaidTracker = new();
private readonly Dictionary<ulong, int> RaidTracker = [];
private SAV9SV HostSAV = new();
private DateTime StartTime = DateTime.Now;
public static RaidContainer? container;
Expand Down Expand Up @@ -371,22 +371,30 @@ private async Task InnerLoop(CancellationToken token)
if (Settings.ActiveRaids[RotationCount].AddedByRACommand)
{
var user = Settings.ActiveRaids[RotationCount].User;
var mentionedUsers = Settings.ActiveRaids[RotationCount].MentionedUsers;

// Determine if the raid is a "Free For All"
bool isFreeForAll = !Settings.ActiveRaids[RotationCount].IsCoded || EmptyRaid >= Settings.LobbyOptions.EmptyRaidLimit;

if (user != null && !isFreeForAll)
if (!isFreeForAll)
{
try
{
// Only get and send the raid code if it's not a "Free For All"
var code = await GetRaidCode(token).ConfigureAwait(false);
await user.SendMessageAsync($"Your Raid Code is **{code}**").ConfigureAwait(false);
if (user != null)
{
await user.SendMessageAsync($"Your Raid Code is **{code}**").ConfigureAwait(false);
}
foreach (var mentionedUser in mentionedUsers)
{
await mentionedUser.SendMessageAsync($"The Raid Code for the private raid you were invited to by {user?.Username ?? "the host"} is **{code}**").ConfigureAwait(false);
}
}
catch (Discord.Net.HttpException ex)
{
// Handle exception (e.g., log the error or send a message to a logging channel)
Log($"Failed to send DM to {user.Username}. They might have DMs turned off. Exception: {ex.Message}");
Log($"Failed to send DM to the user or mentioned users. They might have DMs turned off. Exception: {ex.Message}");
}
}
}
Expand Down Expand Up @@ -1509,21 +1517,30 @@ private async Task<bool> PrepareForRaid(CancellationToken token)
if (Settings.ActiveRaids[RotationCount].AddedByRACommand)
{
var user = Settings.ActiveRaids[RotationCount].User;
var mentionedUsers = Settings.ActiveRaids[RotationCount].MentionedUsers;

// Determine if the raid is a "Free For All"
bool isFreeForAll = !Settings.ActiveRaids[RotationCount].IsCoded || EmptyRaid >= Settings.LobbyOptions.EmptyRaidLimit;

if (user != null && !isFreeForAll)
if (!isFreeForAll)
{
try
{
// Only send the message if it's not a "Free For All"
await user.SendMessageAsync("Get Ready! Your raid is about to start!").ConfigureAwait(false);
if (user != null)
{
await user.SendMessageAsync("Get Ready! Your raid is being prepared now!").ConfigureAwait(false);
}

foreach (var mentionedUser in mentionedUsers)
{
await mentionedUser.SendMessageAsync($"Get Ready! The raid you were invited to by {user?.Username ?? "the host"} is about to start!").ConfigureAwait(false);
}
}
catch (Discord.Net.HttpException ex)
{
// Handle exception (e.g., log the error or send a message to a logging channel)
Log($"Failed to send DM to {user.Username}. They might have DMs turned off. Exception: {ex.Message}");
Log($"Failed to send DM to the user or mentioned users. They might have DMs turned off. Exception: {ex.Message}");
}
}
}
Expand Down Expand Up @@ -1748,7 +1765,7 @@ private async Task<bool> CheckIfTrainerBanned(RaidMyStatus trainer, ulong nid, i

await EnqueueEmbed(null, "", false, false, false, false, token).ConfigureAwait(false);

List<(ulong, RaidMyStatus)> lobbyTrainers = new();
List<(ulong, RaidMyStatus)> lobbyTrainers = [];
var wait = TimeSpan.FromSeconds(Settings.RaidSettings.TimeToWait);
var endTime = DateTime.Now + wait;
bool full = false;
Expand Down Expand Up @@ -2074,7 +2091,7 @@ private async Task EnqueueEmbed(List<string>? names, string message, bool hatTri
Settings.ActiveRaids[RotationCount].Title != "Mystery Shiny Raid" &&
code != "Free For All")
{
await Task.Delay(Settings.EmbedToggles.RequestEmbedTime * 1000).ConfigureAwait(false);
await Task.Delay(Settings.EmbedToggles.RequestEmbedTime * 1000, token).ConfigureAwait(false);
}

// Description can only be up to 4096 characters.
Expand All @@ -2088,11 +2105,11 @@ private async Task EnqueueEmbed(List<string>? names, string message, bool hatTri
if (disband) // Wait for trainer to load before disband
await Task.Delay(5_000, token).ConfigureAwait(false);

byte[]? bytes = Array.Empty<byte>();
byte[]? bytes = [];
if (Settings.EmbedToggles.TakeScreenshot && !upnext)
try
{
bytes = await SwitchConnection.PixelPeek(token).ConfigureAwait(false) ?? Array.Empty<byte>();
bytes = await SwitchConnection.PixelPeek(token).ConfigureAwait(false) ?? [];
}
catch (Exception ex)
{
Expand Down Expand Up @@ -2395,42 +2412,6 @@ private async Task<bool> ConnectToOnline(PokeRaidHubConfig config, CancellationT
return true;
}

private async Task<bool> RecoverToOverworld(CancellationToken token)
{
if (await IsOnOverworld(OverworldOffset, token).ConfigureAwait(false))
return true;

Log("Attempting to recover to overworld.");
var attempts = 0;
while (!await IsOnOverworld(OverworldOffset, token).ConfigureAwait(false))
{
attempts++;
if (attempts >= 30)
break;

await Click(B, 1_300, token).ConfigureAwait(false);
if (await IsOnOverworld(OverworldOffset, token).ConfigureAwait(false))
break;

await Click(B, 2_000, token).ConfigureAwait(false);
if (await IsOnOverworld(OverworldOffset, token).ConfigureAwait(false))
break;

await Click(A, 1_300, token).ConfigureAwait(false);
if (await IsOnOverworld(OverworldOffset, token).ConfigureAwait(false))
break;
}

// We didn't make it for some reason.
if (!await IsOnOverworld(OverworldOffset, token).ConfigureAwait(false))
{
Log("Failed to recover to overworld, rebooting the game.");
await ReOpenGame(Hub.Config, token).ConfigureAwait(false);
}
await Task.Delay(1_000, token).ConfigureAwait(false);
return true;
}

public async Task StartGameRaid(PokeRaidHubConfig config, CancellationToken token)
{
// First, check if the time rollback feature is enabled
Expand Down Expand Up @@ -3005,8 +2986,8 @@ private async Task<byte[]> ReadBlueberryRaids(CancellationToken token)

private static (List<int> distGroupIDs, List<int> mightGroupIDs) GetPossibleGroups(RaidContainer container)
{
List<int> distGroupIDs = new();
List<int> mightGroupIDs = new();
List<int> distGroupIDs = [];
List<int> mightGroupIDs = [];

if (container.DistTeraRaids != null)
{
Expand Down Expand Up @@ -3127,7 +3108,7 @@ private async Task ProcessAllRaids(CancellationToken token)
{
if (!SpeciesToGroupIDMap.ContainsKey(speciesKey))
{
SpeciesToGroupIDMap[speciesKey] = new List<(int GroupID, int Index, string DenIdentifier)> { (groupID, i, denIdentifier) };
SpeciesToGroupIDMap[speciesKey] = [(groupID, i, denIdentifier)];
}
else
{
Expand Down Expand Up @@ -3493,9 +3474,5 @@ private async Task<bool> SaveGame(PokeRaidHubConfig config, CancellationToken to
await Click(B, 1_000, token).ConfigureAwait(false);
return true;
}

public class RaidEmbedInfo
{
}
}
}
7 changes: 7 additions & 0 deletions SysBot.Pokemon/SV/BotRaid/RotatingRaidSettingsSV.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ public class RotatingRaidParameters
[Browsable(false)]
[System.Text.Json.Serialization.JsonIgnore]
public SocketUser? User { get; set; }

[Browsable(false)]
[System.Text.Json.Serialization.JsonIgnore]
public List<SocketUser> MentionedUsers { get; set; } = [];
}

[Category(Hosting), TypeConverter(typeof(CategoryConverter<EventSettingsCategory>))]
Expand Down Expand Up @@ -119,6 +123,9 @@ public class RotatingRaidSettingsCategory
[Category(Hosting), Description("When true, the bot will not allow user requested raids and will inform them that this setting is on.")]
public bool DisableRequests { get; set; } = false;

[Category(Hosting), Description("When true, the bot will allow private raids.")]
public bool PrivateRaidsEnabled { get; set; } = true;

[Category(Hosting), Description("Limit the number of requests a user can issue. Set to 0 to disable.\nCommands: $lr <number>")]
public int LimitRequests { get; set; } = 0;

Expand Down

0 comments on commit 5aa6917

Please sign in to comment.