using System.ComponentModel.DataAnnotations; using System.Text; using EllieBot.Modules.Games.Fish; using Format = Discord.Format; namespace EllieBot.Modules.Games; public partial class Games { public class FishCommands( FishService fs, FishConfigService fcs, IBotCache cache, CaptchaService captchaService) : EllieModule { private static readonly EllieRandom _rng = new(); private TypedKey<bool> FishingWhitelistKey(ulong userId) => new($"fishingwhitelist:{userId}"); [Cmd] public async Task Fish() { var cRes = await cache.GetAsync(FishingWhitelistKey(ctx.User.Id)); if (cRes.TryPickT1(out _, out _)) { string? password = null; if (fcs.Data.RequireCaptcha) password = await captchaService.GetUserCaptcha(ctx.User.Id); if (password is not null) { var img = captchaService.GetPasswordImage(password); using var stream = await img.ToStreamAsync(); var toSend = Response() .File(stream, "timely.png"); #if GLOBAL_ELLIE if (_rng.Next(0, 8) == 0) toSend = toSend .Text("*[Sub on Patreon](https://patreon.com/elliebot) to remove captcha.*"); #endif var captcha = await toSend.SendAsync(); try { var userInput = await GetUserInputAsync(ctx.User.Id, ctx.Channel.Id); if (userInput?.ToLowerInvariant() != password?.ToLowerInvariant()) { return; } // whitelist the user for 30 minutes await cache.AddAsync(FishingWhitelistKey(ctx.User.Id), true, TimeSpan.FromMinutes(30)); // reset the password await captchaService.ClearUserCaptcha(ctx.User.Id); } finally { _ = captcha.DeleteAsync(); } } } var fishResult = await fs.FishAsync(ctx.User.Id, ctx.Channel.Id); if (fishResult.TryPickT1(out _, out var fishTask)) { return; } var currentWeather = fs.GetCurrentWeather(); var currentTod = fs.GetTime(); var spot = fs.GetSpot(ctx.Channel.Id); var msg = await Response() .Embed(CreateEmbed() .WithPendingColor() .WithAuthor(ctx.User) .WithDescription(GetText(strs.fish_waiting)) .AddField(GetText(strs.fish_spot), GetSpotEmoji(spot) + " " + spot.ToString(), true) .AddField(GetText(strs.fish_weather), GetWeatherEmoji(currentWeather) + " " + currentWeather, true) .AddField(GetText(strs.fish_tod), GetTodEmoji(currentTod) + " " + currentTod, true)) .SendAsync(); var res = await fishTask; if (res is null) { await Response().Error(strs.fish_nothing).SendAsync(); return; } var desc = GetText(strs.fish_caught(res.Fish.Emoji + " " + Format.Bold(res.Fish.Name))); if (res.IsSkillUp) { desc += "\n" + GetText(strs.fish_skill_up(res.Skill, res.MaxSkill)); } await Response() .Embed(CreateEmbed() .WithOkColor() .WithAuthor(ctx.User) .WithDescription(desc) .AddField(GetText(strs.fish_quality), GetStarText(res.Stars, res.Fish.Stars), true) .AddField(GetText(strs.desc), res.Fish.Fluff, true) .WithThumbnailUrl(res.Fish.Image)) .SendAsync(); await msg.DeleteAsync(); } [Cmd] public async Task FishSpot() { var ws = fs.GetWeatherForPeriods(7); var spot = fs.GetSpot(ctx.Channel.Id); var time = fs.GetTime(); await Response() .Embed(CreateEmbed() .WithOkColor() .WithDescription(GetText(strs.fish_weather_duration(fs.GetWeatherPeriodDuration()))) .AddField(GetText(strs.fish_spot), GetSpotEmoji(spot) + " " + spot, true) .AddField(GetText(strs.fish_tod), GetTodEmoji(time) + " " + time, true) .AddField(GetText(strs.fish_weather_forecast), ws.Select(x => GetWeatherEmoji(x)).Join(""), true)) .SendAsync(); } [Cmd] public async Task Fishlist(int page = 1) { if (--page < 0) return; var fishes = await fs.GetAllFish(); var catches = await fs.GetUserCatches(ctx.User.Id); var (skill, maxSkill) = await fs.GetSkill(ctx.User.Id); var catchDict = catches.ToDictionary(x => x.FishId, x => x); await Response() .Paginated() .Items(fishes) .PageSize(9) .CurrentPage(page) .Page((fs, i) => { var eb = CreateEmbed() .WithDescription($"🧠 **Skill:** {skill} / {maxSkill}") .WithAuthor(ctx.User) .WithTitle(GetText(strs.fish_list_title)) .WithOkColor(); foreach (var f in fs) { if (catchDict.TryGetValue(f.Id, out var c)) { eb.AddField(f.Name, GetFishEmoji(f, c.Count) + " " + GetSpotEmoji(f.Spot) + GetTodEmoji(f.Time) + GetWeatherEmoji(f.Weather) + "\n" + GetStarText(c.MaxStars, f.Stars) + "\n" + Format.Italics(f.Fluff), true); } else { eb.AddField("?", GetFishEmoji(null, 0) + "\n" + GetStarText(0, f.Stars), true); } } return eb; }) .SendAsync(); } private string GetFishEmoji(FishData? fish, int count) { if (fish is null) return ""; return fish.Emoji + " x" + count; } private string GetSpotEmoji(FishingSpot? spot) { if (spot is not FishingSpot fs) return string.Empty; var conf = fcs.Data; return conf.SpotEmojis[(int)fs]; } private string GetTodEmoji(FishingTime? fishTod) { return fishTod switch { FishingTime.Night => "🌑", FishingTime.Dawn => "🌅", FishingTime.Dusk => "🌆", FishingTime.Day => "☀️", _ => "" }; } private string GetWeatherEmoji(FishingWeather? w) => w switch { FishingWeather.Rain => "🌧️", FishingWeather.Snow => "❄️", FishingWeather.Storm => "⛈️", FishingWeather.Clear => "☀️", _ => "" }; private string GetStarText(int resStars, int fishStars) { if (resStars == fishStars) { return MultiplyStars(fcs.Data.StarEmojis[^1], fishStars); } var c = fcs.Data; var starsp1 = MultiplyStars(c.StarEmojis[resStars], resStars); var starsp2 = MultiplyStars(c.StarEmojis[0], fishStars - resStars); return starsp1 + starsp2; } private string MultiplyStars(string starEmoji, int count) { var sb = new StringBuilder(); for (var i = 0; i < count; i++) { sb.Append(starEmoji); } return sb.ToString(); } } } public enum FishingSpot { Ocean, River, Lake, Swamp, Reef } public enum FishingTime { Night, Dawn, Day, Dusk } public enum FishingWeather { Clear, Rain, Storm, Snow }