#nullable disable using LinqToDB; using LinqToDB.EntityFrameworkCore; using LinqToDB.Tools; using EllieBot.Db.Models; using EllieBot.Modules.Gambling.Bank; using EllieBot.Modules.Gambling.Common; using EllieBot.Modules.Gambling.Services; using EllieBot.Modules.Utility.Services; using EllieBot.Services.Currency; using System.Collections.Immutable; using System.Globalization; using System.Text; using EllieBot.Modules.Gambling.Rps; using EllieBot.Common.TypeReaders; using EllieBot.Modules.Patronage; namespace EllieBot.Modules.Gambling; public partial class Gambling : GamblingModule<GamblingService> { private readonly IGamblingService _gs; private readonly DbService _db; private readonly ICurrencyService _cs; private readonly DiscordSocketClient _client; private readonly NumberFormatInfo _enUsCulture; private readonly DownloadTracker _tracker; private readonly GamblingConfigService _configService; private readonly FontProvider _fonts; private readonly IBankService _bank; private readonly IRemindService _remind; private readonly GamblingTxTracker _gamblingTxTracker; private readonly IPatronageService _ps; public Gambling( IGamblingService gs, DbService db, ICurrencyService currency, DiscordSocketClient client, DownloadTracker tracker, GamblingConfigService configService, FontProvider fonts, IBankService bank, IRemindService remind, IPatronageService patronage, GamblingTxTracker gamblingTxTracker) : base(configService) { _gs = gs; _db = db; _cs = currency; _client = client; _bank = bank; _remind = remind; _gamblingTxTracker = gamblingTxTracker; _ps = patronage; _rng = new EllieRandom(); _enUsCulture = new CultureInfo("en-US", false).NumberFormat; _enUsCulture.NumberDecimalDigits = 0; _enUsCulture.NumberGroupSeparator = " "; _tracker = tracker; _configService = configService; _fonts = fonts; } public async Task<string> GetBalanceStringAsync(ulong userId) { var bal = await _cs.GetBalanceAsync(userId); return N(bal); } [Cmd] public async Task BetStats() { var stats = await _gamblingTxTracker.GetAllAsync(); var eb = _sender.CreateEmbed() .WithOkColor(); var str = "` Feature `|` Bet `|`Paid Out`|` RoI `\n"; str += "――――――――――――――――――――\n"; foreach (var stat in stats) { var perc = (stat.PaidOut / stat.Bet).ToString("P2", Culture); str += $"`{stat.Feature.PadBoth(9)}`" + $"|`{stat.Bet.ToString("N0").PadLeft(8, ' ')}`" + $"|`{stat.PaidOut.ToString("N0").PadLeft(8, ' ')}`" + $"|`{perc.PadLeft(6, ' ')}`\n"; } var bet = stats.Sum(x => x.Bet); var paidOut = stats.Sum(x => x.PaidOut); if (bet == 0) bet = 1; var tPerc = (paidOut / bet).ToString("P2", Culture); str += "――――――――――――――――――――\n"; str += $"` {("TOTAL").PadBoth(7)}` " + $"|**{N(bet).PadLeft(8, ' ')}**" + $"|**{N(paidOut).PadLeft(8, ' ')}**" + $"|`{tPerc.PadLeft(6, ' ')}`"; eb.WithDescription(str); await Response().Embed(eb).SendAsync(); } private async Task RemindTimelyAction(SocketMessageComponent smc, DateTime when) { var tt = TimestampTag.FromDateTime(when, TimestampTagStyles.Relative); await _remind.AddReminderAsync(ctx.User.Id, ctx.User.Id, ctx.Guild?.Id, true, when, GetText(strs.timely_time), ReminderType.Timely); await smc.RespondConfirmAsync(_sender, GetText(strs.remind_timely(tt)), ephemeral: true); } // Creates timely reminder button, parameter in hours. private EllieInteractionBase CreateRemindMeInteraction(int period) => _inter .Create(ctx.User.Id, new ButtonBuilder( label: "Remind me", emote: Emoji.Parse("⏰"), customId: "timely:remind_me"), (smc) => RemindTimelyAction(smc, DateTime.UtcNow.Add(TimeSpan.FromHours(period))) ); // Creates timely reminder button, parameter in milliseconds. private EllieInteractionBase CreateRemindMeInteraction(double ms) => _inter .Create(ctx.User.Id, new ButtonBuilder( label: "Remind me", emote: Emoji.Parse("⏰"), customId: "timely:remind_me"), (smc) => RemindTimelyAction(smc, DateTime.UtcNow.Add(TimeSpan.FromMilliseconds(ms))) ); private EllieInteractionBase CreateTimelyInteraction() => _inter .Create(ctx.User.Id, new ButtonBuilder( label: "Timely", emote: Emoji.Parse("💰"), customId: "timely:" + _rng.Next(123456, 999999)), async (smc) => { await ClaimTimely(); }); [Cmd] public async Task Timely() { var val = Config.Timely.Amount; var period = Config.Timely.Cooldown; if (val <= 0 || period <= 0) { await Response().Error(strs.timely_none).SendAsync(); return; } if (Config.Timely.RequirePassword) { // var password = _service.GeneratePassword(); // // var img = new Image<Rgba32>(100, 40); // // var font = _fonts.NotoSans.CreateFont(30); // var outlinePen = new SolidPen(Color.Black, 1f); // var strikeoutRun = new RichTextRun // { // Start = 0, // End = password.GetGraphemeCount(), // Font = font, // StrikeoutPen = new SolidPen(Color.White, 3), // TextDecorations = TextDecorations.Strikeout // }; // // draw password on the image // img.Mutate(x => // { // x.DrawText(new RichTextOptions(font) // { // HorizontalAlignment = HorizontalAlignment.Center, // VerticalAlignment = VerticalAlignment.Center, // FallbackFontFamilies = _fonts.FallBackFonts, // Origin = new(50, 20), // TextRuns = [strikeoutRun] // }, // password, // Brushes.Solid(Color.White), // outlinePen); // }); // using var stream = await img.ToStreamAsync(); // var captcha = await Response() // .Embed(_sender.CreateEmbed() // .WithOkColor() // .WithImageUrl("attachment://timely.png")) // .File(stream, "timely.png") // .SendAsync(); // try // { // var userInput = await GetUserInputAsync(ctx.User.Id, ctx.Channel.Id); // if (userInput?.ToLowerInvariant() != password?.ToLowerInvariant()) // { // return; // } // } // finally // { // _ = captcha.DeleteAsync(); // } var interaction = CreateTimelyInteraction(); var msg = await Response().Pending(strs.timely_button).Interaction(interaction).SendAsync(); await msg.DeleteAsync(); return; } await ClaimTimely(); } private async Task ClaimTimely() { var period = Config.Timely.Cooldown; if (await _service.ClaimTimelyAsync(ctx.User.Id, period) is { } remainder) { // Get correct time form remainder var interaction = CreateRemindMeInteraction(remainder.TotalMilliseconds); // Removes timely button if there is a timely reminder in DB if (_service.UserHasTimelyReminder(ctx.User.Id)) { interaction = null; } var now = DateTime.UtcNow; var relativeTag = TimestampTag.FromDateTime(now.Add(remainder), TimestampTagStyles.Relative); await Response().Pending(strs.timely_already_claimed(relativeTag)).Interaction(interaction).SendAsync(); return; } var val = Config.Timely.Amount; var patron = await _ps.GetPatronAsync(ctx.User.Id); var percentBonus = (_ps.PercentBonus(patron) / 100f); val += (int)(val * percentBonus); var inter = CreateRemindMeInteraction(period); await _cs.AddAsync(ctx.User.Id, val, new("timely", "claim")); await Response().Confirm(strs.timely(N(val), period)).Interaction(inter).SendAsync(); } [Cmd] [OwnerOnly] public async Task TimelyReset() { await _service.RemoveAllTimelyClaimsAsync(); await Response().Confirm(strs.timely_reset).SendAsync(); } [Cmd] [OwnerOnly] public async Task TimelySet(int amount, int period = 24) { if (amount < 0 || period < 0) { return; } _configService.ModifyConfig(gs => { gs.Timely.Amount = amount; gs.Timely.Cooldown = period; }); if (amount == 0) { await Response().Confirm(strs.timely_set_none).SendAsync(); } else { await Response() .Confirm(strs.timely_set(Format.Bold(N(amount)), Format.Bold(period.ToString()))) .SendAsync(); } } [Cmd] [RequireContext(ContextType.Guild)] public async Task Raffle([Leftover] IRole role = null) { role ??= ctx.Guild.EveryoneRole; var members = (await role.GetMembersAsync()).Where(u => u.Status != UserStatus.Offline); var membersArray = members as IUser[] ?? members.ToArray(); if (membersArray.Length == 0) { return; } var usr = membersArray[new EllieRandom().Next(0, membersArray.Length)]; await Response() .Confirm("🎟 " + GetText(strs.raffled_user), $"**{usr.Username}**", footer: $"ID: {usr.Id}") .SendAsync(); } [Cmd] [RequireContext(ContextType.Guild)] public async Task RaffleAny([Leftover] IRole role = null) { role ??= ctx.Guild.EveryoneRole; var members = await role.GetMembersAsync(); var membersArray = members as IUser[] ?? members.ToArray(); if (membersArray.Length == 0) { return; } var usr = membersArray[new EllieRandom().Next(0, membersArray.Length)]; await Response() .Confirm("🎟 " + GetText(strs.raffled_user), $"**{usr.Username}**", footer: $"ID: {usr.Id}") .SendAsync(); } [Cmd] [Priority(2)] public Task CurrencyTransactions(int page = 1) => InternalCurrencyTransactions(ctx.User.Id, page); [Cmd] [OwnerOnly] [Priority(0)] public Task CurrencyTransactions([Leftover] IUser usr) => InternalCurrencyTransactions(usr.Id, 1); [Cmd] [OwnerOnly] [Priority(1)] public Task CurrencyTransactions(IUser usr, int page) => InternalCurrencyTransactions(usr.Id, page); private async Task InternalCurrencyTransactions(ulong userId, int page) { if (--page < 0) { return; } List<CurrencyTransaction> trs; await using (var uow = _db.GetDbContext()) { trs = await uow.Set<CurrencyTransaction>().GetPageFor(userId, page); } var embed = _sender.CreateEmbed() .WithTitle(GetText(strs.transactions(((SocketGuild)ctx.Guild)?.GetUser(userId)?.ToString() ?? $"{userId}"))) .WithOkColor(); var sb = new StringBuilder(); foreach (var tr in trs) { var change = tr.Amount >= 0 ? "🔵" : "🔴"; var kwumId = new kwum(tr.Id).ToString(); var date = $"#{Format.Code(kwumId)} `〖{GetFormattedCurtrDate(tr)}〗`"; sb.AppendLine($"\\{change} {date} {Format.Bold(N(tr.Amount))}"); var transactionString = GetHumanReadableTransaction(tr.Type, tr.Extra, tr.OtherId); if (transactionString is not null) { sb.AppendLine(transactionString); } if (!string.IsNullOrWhiteSpace(tr.Note)) { sb.AppendLine($"\t`Note:` {tr.Note.TrimTo(50)}"); } } embed.WithDescription(sb.ToString()); embed.WithFooter(GetText(strs.page(page + 1))); await Response().Embed(embed).SendAsync(); } private static string GetFormattedCurtrDate(CurrencyTransaction ct) => $"{ct.DateAdded:HH:mm yyyy-MM-dd}"; [Cmd] public async Task CurrencyTransaction(kwum id) { int intId = id; await using var uow = _db.GetDbContext(); var tr = await uow.Set<CurrencyTransaction>() .ToLinqToDBTable() .Where(x => x.Id == intId && x.UserId == ctx.User.Id) .FirstOrDefaultAsync(); if (tr is null) { await Response().Error(strs.not_found).SendAsync(); return; } var eb = _sender.CreateEmbed().WithOkColor(); eb.WithAuthor(ctx.User); eb.WithTitle(GetText(strs.transaction)); eb.WithDescription(new kwum(tr.Id).ToString()); eb.AddField("Amount", N(tr.Amount)); eb.AddField("Type", tr.Type, true); eb.AddField("Extra", tr.Extra, true); if (tr.OtherId is ulong other) { eb.AddField("From Id", other); } if (!string.IsNullOrWhiteSpace(tr.Note)) { eb.AddField("Note", tr.Note); } eb.WithFooter(GetFormattedCurtrDate(tr)); await Response().Embed(eb).SendAsync(); } private string GetHumanReadableTransaction(string type, string subType, ulong? maybeUserId) => (type, subType, maybeUserId) switch { ("gift", var name, ulong userId) => GetText(strs.curtr_gift(name, userId)), ("award", var name, ulong userId) => GetText(strs.curtr_award(name, userId)), ("take", var name, ulong userId) => GetText(strs.curtr_take(name, userId)), ("blackjack", _, _) => $"Blackjack - {subType}", ("wheel", _, _) => $"Lucky Ladder - {subType}", ("lula", _, _) => $"Lucky Ladder - {subType}", ("rps", _, _) => $"Rock Paper Scissors - {subType}", (null, _, _) => null, (_, null, _) => null, (_, _, ulong userId) => $"{type} - {subType} | [{userId}]", _ => $"{type} - {subType}" }; [Cmd] [Priority(0)] public async Task Cash(ulong userId) { var cur = await GetBalanceStringAsync(userId); await Response().Confirm(strs.has(Format.Code(userId.ToString()), cur)).SendAsync(); } private async Task BankAction(SocketMessageComponent smc) { var balance = await _bank.GetBalanceAsync(ctx.User.Id); await N(balance) .Pipe(strs.bank_balance) .Pipe(GetText) .Pipe(text => smc.RespondConfirmAsync(_sender, text, ephemeral: true)); } private EllieInteractionBase CreateCashInteraction() => _inter.Create(ctx.User.Id, new ButtonBuilder( customId: "cash:bank_show_balance", emote: new Emoji("🏦")), BankAction); [Cmd] [Priority(1)] public async Task Cash([Leftover] IUser user = null) { user ??= ctx.User; var cur = await GetBalanceStringAsync(user.Id); var inter = user == ctx.User ? CreateCashInteraction() : null; await Response() .Confirm( user.ToString() .Pipe(Format.Bold) .With(cur) .Pipe(strs.has)) .Interaction(inter) .SendAsync(); } [Cmd] [RequireContext(ContextType.Guild)] [Priority(0)] public async Task Give( [OverrideTypeReader(typeof(BalanceTypeReader))] long amount, IGuildUser receiver, [Leftover] string msg) { if (amount <= 0 || ctx.User.Id == receiver.Id || receiver.IsBot) { return; } if (!await _cs.TransferAsync(_sender, ctx.User, receiver, amount, msg, N(amount))) { await Response().Error(strs.not_enough(CurrencySign)).SendAsync(); return; } await Response().Confirm(strs.gifted(N(amount), Format.Bold(receiver.ToString()), ctx.User)).SendAsync(); } [Cmd] [RequireContext(ContextType.Guild)] [Priority(1)] public Task Give([OverrideTypeReader(typeof(BalanceTypeReader))] long amount, [Leftover] IGuildUser receiver) => Give(amount, receiver, null); [Cmd] [RequireContext(ContextType.Guild)] [OwnerOnly] [Priority(0)] public Task Award(long amount, IGuildUser usr, [Leftover] string msg) => Award(amount, usr.Id, msg); [Cmd] [RequireContext(ContextType.Guild)] [OwnerOnly] [Priority(1)] public Task Award(long amount, [Leftover] IGuildUser usr) => Award(amount, usr.Id); [Cmd] [OwnerOnly] [Priority(2)] public async Task Award(long amount, ulong usrId, [Leftover] string msg = null) { if (amount <= 0) { return; } var usr = await ((DiscordSocketClient)Context.Client).Rest.GetUserAsync(usrId); if (usr is null) { await Response().Error(strs.user_not_found).SendAsync(); return; } await _cs.AddAsync(usr.Id, amount, new("award", ctx.User.ToString()!, msg, ctx.User.Id)); await Response().Confirm(strs.awarded(N(amount), $"<@{usrId}>", ctx.User)).SendAsync(); } [Cmd] [RequireContext(ContextType.Guild)] [OwnerOnly] [Priority(3)] public async Task Award(long amount, [Leftover] IRole role) { var users = (await ctx.Guild.GetUsersAsync()).Where(u => u.GetRoles().Contains(role)).ToList(); await _cs.AddBulkAsync(users.Select(x => x.Id).ToList(), amount, new("award", ctx.User.ToString()!, role.Name, ctx.User.Id)); await Response() .Confirm(strs.mass_award(N(amount), Format.Bold(users.Count.ToString()), Format.Bold(role.Name))) .SendAsync(); } [Cmd] [RequireContext(ContextType.Guild)] [OwnerOnly] [Priority(0)] public async Task Take(long amount, [Leftover] IRole role) { var users = (await role.GetMembersAsync()).ToList(); await _cs.RemoveBulkAsync(users.Select(x => x.Id).ToList(), amount, new("take", ctx.User.ToString()!, null, ctx.User.Id)); await Response() .Confirm(strs.mass_take(N(amount), Format.Bold(users.Count.ToString()), Format.Bold(role.Name))) .SendAsync(); } [Cmd] [RequireContext(ContextType.Guild)] [OwnerOnly] [Priority(1)] public async Task Take(long amount, [Leftover] IGuildUser user) { if (amount <= 0) { return; } var extra = new TxData("take", ctx.User.ToString()!, null, ctx.User.Id); if (await _cs.RemoveAsync(user.Id, amount, extra)) { await Response().Confirm(strs.take(N(amount), Format.Bold(user.ToString()))).SendAsync(); } else { await Response().Error(strs.take_fail(N(amount), Format.Bold(user.ToString()), CurrencySign)).SendAsync(); } } [Cmd] [OwnerOnly] public async Task Take(long amount, [Leftover] ulong usrId) { if (amount <= 0) { return; } var extra = new TxData("take", ctx.User.ToString()!, null, ctx.User.Id); if (await _cs.RemoveAsync(usrId, amount, extra)) { await Response().Confirm(strs.take(N(amount), $"<@{usrId}>")).SendAsync(); } else { await Response().Error(strs.take_fail(N(amount), Format.Code(usrId.ToString()), CurrencySign)).SendAsync(); } } [Cmd] public async Task BetRoll([OverrideTypeReader(typeof(BalanceTypeReader))] long amount) { if (!await CheckBetMandatory(amount)) { return; } var maybeResult = await _gs.BetRollAsync(ctx.User.Id, amount); if (!maybeResult.TryPickT0(out var result, out _)) { await Response().Error(strs.not_enough(CurrencySign)).SendAsync(); return; } var win = (long)result.Won; string str; if (win > 0) { str = GetText(strs.br_win(N(win), result.Threshold + (result.Roll == 100 ? " 👑" : ""))); } else { str = GetText(strs.better_luck); } var eb = _sender.CreateEmbed() .WithAuthor(ctx.User) .WithDescription(Format.Bold(str)) .AddField(GetText(strs.roll2), result.Roll.ToString(CultureInfo.InvariantCulture)) .WithOkColor(); await Response().Embed(eb).SendAsync(); } [Cmd] [EllieOptions<LbOpts>] [Priority(0)] public Task Leaderboard(params string[] args) => Leaderboard(1, args); [Cmd] [EllieOptions<LbOpts>] [Priority(1)] public async Task Leaderboard(int page = 1, params string[] args) { if (--page < 0) { return; } var (opts, _) = OptionsParser.ParseFrom(new LbOpts(), args); if (ctx.Guild is null) { opts.Clean = false; } async Task<IReadOnlyCollection<DiscordUser>> GetTopRichest(int curPage) { if (opts.Clean) { await ctx.Channel.TriggerTypingAsync(); await _tracker.EnsureUsersDownloadedAsync(ctx.Guild); var users = ((SocketGuild)ctx.Guild).Users.Map(x => x.Id); var perPage = 9; await using var uow = _db.GetDbContext(); var cleanRichest = await uow.GetTable<DiscordUser>() .Where(x => x.UserId.In(users)) .OrderByDescending(x => x.CurrencyAmount) .Skip(curPage * perPage) .Take(perPage) .ToListAsync(); return cleanRichest; } else { await using var uow = _db.GetDbContext(); return await uow.Set<DiscordUser>().GetTopRichest(_client.CurrentUser.Id, curPage); } } await Response() .Paginated() .PageItems(GetTopRichest) .PageSize(9) .CurrentPage(page) .Page((toSend, curPage) => { var embed = _sender.CreateEmbed() .WithOkColor() .WithTitle(CurrencySign + " " + GetText(strs.leaderboard)); if (!toSend.Any()) { embed.WithDescription(GetText(strs.no_user_on_this_page)); return Task.FromResult(embed); } for (var i = 0; i < toSend.Count; i++) { var x = toSend[i]; var usrStr = x.ToString().TrimTo(20, true); var j = i; embed.AddField("#" + ((9 * curPage) + j + 1) + " " + usrStr, N(x.CurrencyAmount), true); } return Task.FromResult(embed); }) .SendAsync(); } public enum InputRpsPick : byte { R = 0, Rock = 0, Rocket = 0, P = 1, Paper = 1, Paperclip = 1, S = 2, Scissors = 2 } [Cmd] public async Task Rps(InputRpsPick pick, [OverrideTypeReader(typeof(BalanceTypeReader))] long amount = default) { static string GetRpsPick(InputRpsPick p) { switch (p) { case InputRpsPick.R: return "🚀"; case InputRpsPick.P: return "📎"; default: return "✂️"; } } if (!await CheckBetOptional(amount) || amount == 1) return; var res = await _gs.RpsAsync(ctx.User.Id, amount, (byte)pick); if (!res.TryPickT0(out var result, out _)) { await Response().Error(strs.not_enough(CurrencySign)).SendAsync(); return; } var embed = _sender.CreateEmbed(); string msg; if (result.Result == RpsResultType.Draw) { msg = GetText(strs.rps_draw(GetRpsPick(pick))); } else if (result.Result == RpsResultType.Win) { if ((long)result.Won > 0) embed.AddField(GetText(strs.won), N((long)result.Won)); msg = GetText(strs.rps_win(ctx.User.Mention, GetRpsPick(pick), GetRpsPick((InputRpsPick)result.ComputerPick))); } else { msg = GetText(strs.rps_win(ctx.Client.CurrentUser.Mention, GetRpsPick((InputRpsPick)result.ComputerPick), GetRpsPick(pick))); } embed .WithOkColor() .WithDescription(msg); await Response().Embed(embed).SendAsync(); } private static readonly ImmutableArray<string> _emojis = new[] { "⬆", "↖", "⬅", "↙", "⬇", "↘", "➡", "↗" }.ToImmutableArray(); private readonly EllieRandom _rng; [Cmd] public async Task LuckyLadder([OverrideTypeReader(typeof(BalanceTypeReader))] long amount) { if (!await CheckBetMandatory(amount)) return; var res = await _gs.LulaAsync(ctx.User.Id, amount); if (!res.TryPickT0(out var result, out _)) { await Response().Error(strs.not_enough(CurrencySign)).SendAsync(); return; } var multis = result.Multipliers; var sb = new StringBuilder(); foreach (var multi in multis) { sb.Append($"╠══╣"); if (multi == result.Multiplier) sb.Append($"{Format.Bold($"x{multi:0.##}")} ⬅️"); else sb.Append($"||x{multi:0.##}||"); sb.AppendLine(); } var eb = _sender.CreateEmbed() .WithOkColor() .WithDescription(sb.ToString()) .AddField(GetText(strs.multiplier), $"{result.Multiplier:0.##}x", true) .AddField(GetText(strs.won), $"{(long)result.Won}", true) .WithAuthor(ctx.User); await Response().Embed(eb).SendAsync(); } public enum GambleTestTarget { Slot, Betroll, Betflip, BetflipT, BetDraw, BetDrawHL, BetDrawRB, Lula, Rps, } [Cmd] [OwnerOnly] public async Task BetTest() { var values = Enum.GetValues<GambleTestTarget>() .Select(x => $"`{x}`") .Join(", "); await Response().Confirm(GetText(strs.available_tests), values).SendAsync(); } [Cmd] [OwnerOnly] public async Task BetTest(GambleTestTarget target, int tests = 1000) { if (tests <= 0) return; await ctx.Channel.TriggerTypingAsync(); var streak = 0; var maxW = 0; var maxL = 0; var dict = new Dictionary<decimal, int>(); for (var i = 0; i < tests; i++) { var multi = target switch { GambleTestTarget.BetDraw => (await _gs.BetDrawAsync(ctx.User.Id, 0, 1, 0)).AsT0.Multiplier, GambleTestTarget.BetDrawRB => (await _gs.BetDrawAsync(ctx.User.Id, 0, null, 1)).AsT0.Multiplier, GambleTestTarget.BetDrawHL => (await _gs.BetDrawAsync(ctx.User.Id, 0, 0, null)).AsT0.Multiplier, GambleTestTarget.Slot => (await _gs.SlotAsync(ctx.User.Id, 0)).AsT0.Multiplier, GambleTestTarget.Betflip => (await _gs.BetFlipAsync(ctx.User.Id, 0, 0)).AsT0.Multiplier, GambleTestTarget.BetflipT => (await _gs.BetFlipAsync(ctx.User.Id, 0, 1)).AsT0.Multiplier, GambleTestTarget.Lula => (await _gs.LulaAsync(ctx.User.Id, 0)).AsT0.Multiplier, GambleTestTarget.Rps => (await _gs.RpsAsync(ctx.User.Id, 0, (byte)(i % 3))).AsT0.Multiplier, GambleTestTarget.Betroll => (await _gs.BetRollAsync(ctx.User.Id, 0)).AsT0.Multiplier, _ => throw new ArgumentOutOfRangeException(nameof(target)) }; if (dict.ContainsKey(multi)) dict[multi] += 1; else dict.Add(multi, 1); if (multi < 1) { if (streak <= 0) --streak; else streak = -1; maxL = Math.Max(maxL, -streak); } else if (multi > 1) { if (streak >= 0) ++streak; else streak = 1; maxW = Math.Max(maxW, streak); } } var sb = new StringBuilder(); decimal payout = 0; foreach (var key in dict.Keys.OrderByDescending(x => x)) { sb.AppendLine($"x**{key}** occured `{dict[key]}` times. {dict[key] * 1.0f / tests * 100}%"); payout += key * dict[key]; } sb.AppendLine(); sb.AppendLine($"Longest win streak: `{maxW}`"); sb.AppendLine($"Longest lose streak: `{maxL}`"); await Response() .Confirm(GetText(strs.test_results_for(target)), sb.ToString(), footer: $"Total Bet: {tests} | Payout: {payout:F0} | {payout * 1.0M / tests * 100}%") .SendAsync(); } }