mirror of
				https://gitlab.com/Kwoth/nadekobot.git
				synced 2025-11-04 00:34:26 -05:00 
			
		
		
		
	Many changes. Will update merge request description with details
This commit is contained in:
		@@ -1,4 +1,6 @@
 | 
			
		||||
#nullable disable
 | 
			
		||||
using Nadeko.Econ;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.Gambling.Common.Blackjack;
 | 
			
		||||
 | 
			
		||||
public class Blackjack
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,6 @@
 | 
			
		||||
#nullable disable
 | 
			
		||||
using Nadeko.Econ;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.Gambling.Common.Blackjack;
 | 
			
		||||
 | 
			
		||||
public abstract class Player
 | 
			
		||||
 
 | 
			
		||||
@@ -30,14 +30,23 @@ public partial class Gambling
 | 
			
		||||
 | 
			
		||||
            var num1 = gen / 10;
 | 
			
		||||
            var num2 = gen % 10;
 | 
			
		||||
 | 
			
		||||
            
 | 
			
		||||
            using var img1 = await GetDiceAsync(num1);
 | 
			
		||||
            using var img2 = await GetDiceAsync(num2);
 | 
			
		||||
            using var img = new[] { img1, img2 }.Merge(out var format);
 | 
			
		||||
            await using var ms = await img.ToStreamAsync(format);
 | 
			
		||||
 | 
			
		||||
            var fileName = $"dice.{format.FileExtensions.First()}";
 | 
			
		||||
 | 
			
		||||
            var eb = _eb.Create(ctx)
 | 
			
		||||
                .WithOkColor()
 | 
			
		||||
                .WithAuthor(ctx.User)
 | 
			
		||||
                .AddField(GetText(strs.roll2), gen)
 | 
			
		||||
                .WithImageUrl($"attachment://{fileName}");
 | 
			
		||||
 | 
			
		||||
            await ctx.Channel.SendFileAsync(ms,
 | 
			
		||||
                $"dice.{format.FileExtensions.First()}",
 | 
			
		||||
                Format.Bold(ctx.User.ToString()) + " " + GetText(strs.dice_rolled(Format.Code(gen.ToString()))));
 | 
			
		||||
                fileName,
 | 
			
		||||
                embed: eb.Build());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
@@ -105,14 +114,18 @@ public partial class Gambling
 | 
			
		||||
            foreach (var d in dice)
 | 
			
		||||
                d.Dispose();
 | 
			
		||||
 | 
			
		||||
            var imageName = $"dice.{format.FileExtensions.First()}";
 | 
			
		||||
            var eb = _eb.Create(ctx)
 | 
			
		||||
                .WithOkColor()
 | 
			
		||||
                .WithAuthor(ctx.User)
 | 
			
		||||
                .AddField(GetText(strs.rolls), values.Select(x => Format.Code(x.ToString())).Join(' '), true)
 | 
			
		||||
                .AddField(GetText(strs.total), values.Sum(), true)
 | 
			
		||||
                .WithDescription(GetText(strs.dice_rolled_num(Format.Bold(values.Count.ToString()))))
 | 
			
		||||
                .WithImageUrl($"attachment://{imageName}");
 | 
			
		||||
 | 
			
		||||
            await ctx.Channel.SendFileAsync(ms,
 | 
			
		||||
                $"dice.{format.FileExtensions.First()}",
 | 
			
		||||
                Format.Bold(ctx.User.ToString())
 | 
			
		||||
                + " "
 | 
			
		||||
                + GetText(strs.dice_rolled_num(Format.Bold(values.Count.ToString())))
 | 
			
		||||
                + " "
 | 
			
		||||
                + GetText(strs.total_average(Format.Bold(values.Sum().ToString()),
 | 
			
		||||
                    Format.Bold((values.Sum() / (1.0f * values.Count)).ToString("N2")))));
 | 
			
		||||
                imageName,
 | 
			
		||||
                embed: eb.Build());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async Task InternallDndRoll(string arg, bool ordered)
 | 
			
		||||
@@ -130,9 +143,8 @@ public partial class Gambling
 | 
			
		||||
                    rolls.Add(_fateRolls[rng.Next(0, _fateRolls.Length)]);
 | 
			
		||||
                var embed = _eb.Create()
 | 
			
		||||
                               .WithOkColor()
 | 
			
		||||
                               .WithDescription(ctx.User.Mention
 | 
			
		||||
                                                + " "
 | 
			
		||||
                                                + GetText(strs.dice_rolled_num(Format.Bold(n1.ToString()))))
 | 
			
		||||
                               .WithAuthor(ctx.User)
 | 
			
		||||
                               .WithDescription(GetText(strs.dice_rolled_num(Format.Bold(n1.ToString()))))
 | 
			
		||||
                               .AddField(Format.Bold("Result"),
 | 
			
		||||
                                   string.Join(" ", rolls.Select(c => Format.Code($"[{c}]"))));
 | 
			
		||||
 | 
			
		||||
@@ -160,10 +172,9 @@ public partial class Gambling
 | 
			
		||||
                    var sum = arr.Sum();
 | 
			
		||||
                    var embed = _eb.Create()
 | 
			
		||||
                                   .WithOkColor()
 | 
			
		||||
                                   .WithDescription(ctx.User.Mention
 | 
			
		||||
                                                    + " "
 | 
			
		||||
                                                    + GetText(strs.dice_rolled_num(n1 + $"`1 - {n2}`")))
 | 
			
		||||
                                   .AddField(Format.Bold("Rolls"),
 | 
			
		||||
                                   .WithAuthor(ctx.User)
 | 
			
		||||
                                   .WithDescription(GetText(strs.dice_rolled_num(n1 + $"`1 - {n2}`")))
 | 
			
		||||
                                   .AddField(Format.Bold(GetText(strs.rolls)),
 | 
			
		||||
                                       string.Join(" ",
 | 
			
		||||
                                           (ordered ? arr.OrderBy(x => x).AsEnumerable() : arr).Select(x
 | 
			
		||||
                                               => Format.Code(x.ToString()))))
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,7 @@
 | 
			
		||||
#nullable disable
 | 
			
		||||
using Nadeko.Econ;
 | 
			
		||||
using NadekoBot.Modules.Gambling.Common;
 | 
			
		||||
using NadekoBot.Modules.Gambling.Services;
 | 
			
		||||
using SixLabors.ImageSharp;
 | 
			
		||||
using SixLabors.ImageSharp.PixelFormats;
 | 
			
		||||
using Image = SixLabors.ImageSharp.Image;
 | 
			
		||||
@@ -9,23 +11,23 @@ namespace NadekoBot.Modules.Gambling;
 | 
			
		||||
public partial class Gambling
 | 
			
		||||
{
 | 
			
		||||
    [Group]
 | 
			
		||||
    public partial class DrawCommands : NadekoModule
 | 
			
		||||
    public partial class DrawCommands : GamblingSubmodule<IGamblingService>
 | 
			
		||||
    {
 | 
			
		||||
        private static readonly ConcurrentDictionary<IGuild, Deck> _allDecks = new();
 | 
			
		||||
        private readonly IImageCache _images;
 | 
			
		||||
 | 
			
		||||
        public DrawCommands(IImageCache images)
 | 
			
		||||
        public DrawCommands(IImageCache images, GamblingConfigService gcs) : base(gcs)
 | 
			
		||||
            => _images = images;
 | 
			
		||||
 | 
			
		||||
        private async Task<(Stream ImageStream, string ToSend)> InternalDraw(int num, ulong? guildId = null)
 | 
			
		||||
        private async Task InternalDraw(int count, ulong? guildId = null)
 | 
			
		||||
        {
 | 
			
		||||
            if (num is < 1 or > 10)
 | 
			
		||||
                throw new ArgumentOutOfRangeException(nameof(num));
 | 
			
		||||
            if (count is < 1 or > 10)
 | 
			
		||||
                throw new ArgumentOutOfRangeException(nameof(count));
 | 
			
		||||
 | 
			
		||||
            var cards = guildId is null ? new() : _allDecks.GetOrAdd(ctx.Guild, _ => new());
 | 
			
		||||
            var images = new List<Image<Rgba32>>();
 | 
			
		||||
            var cardObjects = new List<Deck.Card>();
 | 
			
		||||
            for (var i = 0; i < num; i++)
 | 
			
		||||
            for (var i = 0; i < count; i++)
 | 
			
		||||
            {
 | 
			
		||||
                if (cards.CardPool.Count == 0 && i != 0)
 | 
			
		||||
                {
 | 
			
		||||
@@ -43,22 +45,43 @@ public partial class Gambling
 | 
			
		||||
 | 
			
		||||
                var currentCard = cards.Draw();
 | 
			
		||||
                cardObjects.Add(currentCard);
 | 
			
		||||
                var cardName = currentCard.ToString().ToLowerInvariant().Replace(' ', '_');
 | 
			
		||||
                images.Add(Image.Load(await File.ReadAllBytesAsync($"data/images/cards/{cardName}.jpg")));
 | 
			
		||||
                var image = await GetCardImageAsync(currentCard);
 | 
			
		||||
                images.Add(image);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var imgName = "cards.jpg";
 | 
			
		||||
            using var img = images.Merge();
 | 
			
		||||
            foreach (var i in images)
 | 
			
		||||
                i.Dispose();
 | 
			
		||||
 | 
			
		||||
            var toSend = $"{Format.Bold(ctx.User.ToString())}";
 | 
			
		||||
            var eb = _eb.Create(ctx)
 | 
			
		||||
                .WithOkColor();
 | 
			
		||||
            
 | 
			
		||||
            var toSend = string.Empty;
 | 
			
		||||
            if (cardObjects.Count == 5)
 | 
			
		||||
                toSend += $" drew `{Deck.GetHandValue(cardObjects)}`";
 | 
			
		||||
                eb.AddField(GetText(strs.hand_value), Deck.GetHandValue(cardObjects), true);
 | 
			
		||||
 | 
			
		||||
            if (guildId is not null)
 | 
			
		||||
                toSend += "\n" + GetText(strs.cards_left(Format.Bold(cards.CardPool.Count.ToString())));
 | 
			
		||||
                toSend += GetText(strs.cards_left(Format.Bold(cards.CardPool.Count.ToString())));
 | 
			
		||||
 | 
			
		||||
            return (img.ToStream(), toSend);
 | 
			
		||||
            eb.WithDescription(toSend)
 | 
			
		||||
              .WithAuthor(ctx.User)
 | 
			
		||||
              .WithImageUrl($"attachment://{imgName}");
 | 
			
		||||
 | 
			
		||||
            if (count > 1)
 | 
			
		||||
                eb.AddField(GetText(strs.cards), count.ToString(), true);
 | 
			
		||||
                
 | 
			
		||||
            await using var imageStream = await img.ToStreamAsync();
 | 
			
		||||
            await ctx.Channel.SendFileAsync(imageStream,
 | 
			
		||||
                imgName,
 | 
			
		||||
                embed: eb.Build());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async Task<Image<Rgba32>> GetCardImageAsync(Deck.Card currentCard)
 | 
			
		||||
        {
 | 
			
		||||
            var cardName = currentCard.ToString().ToLowerInvariant().Replace(' ', '_');
 | 
			
		||||
            var cardBytes = await File.ReadAllBytesAsync($"data/images/cards/{cardName}.jpg");
 | 
			
		||||
            return Image.Load(cardBytes);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
@@ -66,30 +89,24 @@ public partial class Gambling
 | 
			
		||||
        public async partial Task Draw(int num = 1)
 | 
			
		||||
        {
 | 
			
		||||
            if (num < 1)
 | 
			
		||||
                num = 1;
 | 
			
		||||
                return;
 | 
			
		||||
            
 | 
			
		||||
            if (num > 10)
 | 
			
		||||
                num = 10;
 | 
			
		||||
 | 
			
		||||
            var (imageStream, toSend) = await InternalDraw(num, ctx.Guild.Id);
 | 
			
		||||
            await using (imageStream)
 | 
			
		||||
            {
 | 
			
		||||
                await ctx.Channel.SendFileAsync(imageStream, num + " cards.jpg", toSend);
 | 
			
		||||
            }
 | 
			
		||||
            await InternalDraw(num, ctx.Guild.Id);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
        public async partial Task DrawNew(int num = 1)
 | 
			
		||||
        {
 | 
			
		||||
            if (num < 1)
 | 
			
		||||
                num = 1;
 | 
			
		||||
                return;
 | 
			
		||||
            
 | 
			
		||||
            if (num > 10)
 | 
			
		||||
                num = 10;
 | 
			
		||||
 | 
			
		||||
            var (imageStream, toSend) = await InternalDraw(num);
 | 
			
		||||
            await using (imageStream)
 | 
			
		||||
            {
 | 
			
		||||
                await ctx.Channel.SendFileAsync(imageStream, num + " cards.jpg", toSend);
 | 
			
		||||
            }
 | 
			
		||||
            await InternalDraw(num);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
@@ -108,5 +125,98 @@ public partial class Gambling
 | 
			
		||||
 | 
			
		||||
            await ReplyConfirmLocalizedAsync(strs.deck_reshuffled);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        public partial Task BetDraw(ShmartNumber amount, InputValueGuess val, InputColorGuess? col = null)
 | 
			
		||||
            => BetDrawInternal(amount, val, col);
 | 
			
		||||
        
 | 
			
		||||
        [Cmd]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        public partial Task BetDraw(ShmartNumber amount, InputColorGuess col, InputValueGuess? val = null)
 | 
			
		||||
            => BetDrawInternal(amount, val, col);
 | 
			
		||||
        
 | 
			
		||||
        public async Task BetDrawInternal(long amount, InputValueGuess? val, InputColorGuess? col)
 | 
			
		||||
        {
 | 
			
		||||
            var res = await _service.BetDrawAsync(ctx.User.Id,
 | 
			
		||||
                amount,
 | 
			
		||||
                (byte?)val,
 | 
			
		||||
                (byte?)col);
 | 
			
		||||
 | 
			
		||||
            if (!res.TryPickT0(out var result, out _))
 | 
			
		||||
            {
 | 
			
		||||
                await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var eb = _eb.Create(ctx)
 | 
			
		||||
                .WithOkColor()
 | 
			
		||||
                .WithAuthor(ctx.User)
 | 
			
		||||
                .AddField(GetText(strs.guess), GetGuessInfo(val, col), true)
 | 
			
		||||
                .AddField(GetText(strs.card), GetCardInfo(result.Card), true)
 | 
			
		||||
                .AddField(GetText(strs.won), N((long)result.Won), false)
 | 
			
		||||
                .WithImageUrl("attachment://card.png");
 | 
			
		||||
 | 
			
		||||
            using var img = await GetCardImageAsync(result.Card);
 | 
			
		||||
            await using var imgStream = await img.ToStreamAsync();
 | 
			
		||||
            await ctx.Channel.SendFileAsync(imgStream, "card.png", embed: eb.Build());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private string GetGuessInfo(InputValueGuess? valG, InputColorGuess? colG)
 | 
			
		||||
        {
 | 
			
		||||
            var val = valG switch
 | 
			
		||||
            {
 | 
			
		||||
                InputValueGuess.H => "Hi ⬆️",
 | 
			
		||||
                InputValueGuess.L => "Lo ⬇️",
 | 
			
		||||
                _ => "❓"
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            var col = colG switch
 | 
			
		||||
            {
 | 
			
		||||
                InputColorGuess.Red => "R 🔴",
 | 
			
		||||
                InputColorGuess.Black => "B ⚫",
 | 
			
		||||
                _ => "❓"
 | 
			
		||||
            };
 | 
			
		||||
            
 | 
			
		||||
            return $"{val} / {col}";
 | 
			
		||||
        }
 | 
			
		||||
        private string GetCardInfo(Deck.Card card)
 | 
			
		||||
        {
 | 
			
		||||
            var val = card.Number switch
 | 
			
		||||
            {
 | 
			
		||||
                < 7 => "Lo ⬇️",
 | 
			
		||||
                > 7 => "Hi ⬆️",
 | 
			
		||||
                _ => "7 💀"
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            var col = card.Number == 7
 | 
			
		||||
                ? "7 💀"
 | 
			
		||||
                : card.Suit switch
 | 
			
		||||
                {
 | 
			
		||||
                    Deck.CardSuit.Diamonds or Deck.CardSuit.Hearts => "R 🔴",
 | 
			
		||||
                    _ => "B ⚫"
 | 
			
		||||
                };
 | 
			
		||||
            
 | 
			
		||||
            return $"{val} / {col}";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public enum InputValueGuess
 | 
			
		||||
        {
 | 
			
		||||
            High = 0,
 | 
			
		||||
            H = 0,
 | 
			
		||||
            Hi = 0,
 | 
			
		||||
            Low = 1,
 | 
			
		||||
            L = 1,
 | 
			
		||||
            Lo = 1,
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public enum InputColorGuess
 | 
			
		||||
        {
 | 
			
		||||
            R = 0,
 | 
			
		||||
            Red = 0,
 | 
			
		||||
            B = 1,
 | 
			
		||||
            Bl = 1,
 | 
			
		||||
            Black = 1,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -76,17 +76,23 @@ public partial class Gambling
 | 
			
		||||
            foreach (var i in imgs)
 | 
			
		||||
                i.Dispose();
 | 
			
		||||
 | 
			
		||||
            var imgName = $"coins.{format.FileExtensions.First()}";
 | 
			
		||||
            
 | 
			
		||||
            var msg = count != 1
 | 
			
		||||
                ? Format.Bold(ctx.User.ToString())
 | 
			
		||||
                  + " "
 | 
			
		||||
                  + GetText(strs.flip_results(count, headCount, tailCount))
 | 
			
		||||
                : Format.Bold(ctx.User.ToString())
 | 
			
		||||
                  + " "
 | 
			
		||||
                  + GetText(strs.flipped(headCount > 0
 | 
			
		||||
                      ? Format.Bold(GetText(strs.heads))
 | 
			
		||||
                      : Format.Bold(GetText(strs.tails))));
 | 
			
		||||
                ? Format.Bold(GetText(strs.flip_results(count, headCount, tailCount)))
 | 
			
		||||
                : GetText(strs.flipped(headCount > 0
 | 
			
		||||
                    ? Format.Bold(GetText(strs.heads))
 | 
			
		||||
                    : Format.Bold(GetText(strs.tails))));
 | 
			
		||||
            
 | 
			
		||||
            var eb = _eb.Create(ctx)
 | 
			
		||||
                .WithOkColor()
 | 
			
		||||
                .WithAuthor(ctx.User)
 | 
			
		||||
                .WithDescription(msg)
 | 
			
		||||
                .WithImageUrl($"attachment://{imgName}");
 | 
			
		||||
 | 
			
		||||
            await ctx.Channel.SendFileAsync(stream, $"{count} coins.{format.FileExtensions.First()}", msg);
 | 
			
		||||
            await ctx.Channel.SendFileAsync(stream,
 | 
			
		||||
                imgName,
 | 
			
		||||
                embed: eb.Build());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
 
 | 
			
		||||
@@ -345,7 +345,8 @@ public partial class Gambling : GamblingModule<GamblingService>
 | 
			
		||||
            ("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", _, _) => $"Wheel Of Fortune - {subType}",
 | 
			
		||||
            ("wheel", _, _) => $"Lucky Ladder - {subType}",
 | 
			
		||||
            ("lula", _, _) => $"Lucky Ladder - {subType}",
 | 
			
		||||
            ("rps", _, _) => $"Rock Paper Scissors - {subType}",
 | 
			
		||||
            (null, _, _) => null,
 | 
			
		||||
            (_, null, _) => null,
 | 
			
		||||
@@ -671,7 +672,7 @@ public partial class Gambling : GamblingModule<GamblingService>
 | 
			
		||||
 | 
			
		||||
        var eb = _eb.Create(ctx)
 | 
			
		||||
            .WithAuthor(ctx.User)
 | 
			
		||||
            .WithFooter(str)
 | 
			
		||||
            .WithDescription(Format.Bold(str))
 | 
			
		||||
            .AddField(GetText(strs.roll2), result.Roll.ToString(CultureInfo.InvariantCulture))
 | 
			
		||||
            .WithOkColor();
 | 
			
		||||
 | 
			
		||||
@@ -772,7 +773,6 @@ public partial class Gambling : GamblingModule<GamblingService>
 | 
			
		||||
        Scissors = 2
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // todo check if trivia is being disposed
 | 
			
		||||
    [Cmd]
 | 
			
		||||
    public async partial Task Rps(InputRpsPick pick, ShmartNumber amount = default)
 | 
			
		||||
    {
 | 
			
		||||
@@ -839,7 +839,7 @@ public partial class Gambling : GamblingModule<GamblingService>
 | 
			
		||||
        if (!await CheckBetMandatory(amount))
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        var res = await _gs.WofAsync(ctx.User.Id, amount);
 | 
			
		||||
        var res = await _gs.LulaAsync(ctx.User.Id, amount);
 | 
			
		||||
        if (!res.TryPickT0(out var result, out _))
 | 
			
		||||
        {
 | 
			
		||||
            await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
 | 
			
		||||
@@ -871,4 +871,99 @@ public partial class Gambling : GamblingModule<GamblingService>
 | 
			
		||||
 | 
			
		||||
        await ctx.Channel.EmbedAsync(eb);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
        public enum GambleTestTarget
 | 
			
		||||
        {
 | 
			
		||||
            Slot,
 | 
			
		||||
            BetDraw,
 | 
			
		||||
            BetDrawHL,
 | 
			
		||||
            BetDrawRB,
 | 
			
		||||
            Betflip,
 | 
			
		||||
            BetflipT,
 | 
			
		||||
            Lula,
 | 
			
		||||
            Rps,
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
        [OwnerOnly]
 | 
			
		||||
        public async partial Task BetTest()
 | 
			
		||||
        {
 | 
			
		||||
            await SendConfirmAsync(GetText(strs.available_tests),
 | 
			
		||||
                Enum.GetValues<GambleTestTarget>()
 | 
			
		||||
                    .Select(x => $"`{x}`")
 | 
			
		||||
                    .Join(", "));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
        [OwnerOnly]
 | 
			
		||||
        public async partial 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,
 | 
			
		||||
                    _ => 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 SendConfirmAsync(GetText(strs.test_results_for(target)),
 | 
			
		||||
                sb.ToString(),
 | 
			
		||||
                footer: $"Total Bet: {tests} | Payout: {payout:F0} | {payout * 1.0M / tests * 100}%");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
#nullable disable
 | 
			
		||||
#nullable disable warnings
 | 
			
		||||
using NadekoBot.Db.Models;
 | 
			
		||||
using NadekoBot.Modules.Gambling.Common;
 | 
			
		||||
using NadekoBot.Modules.Gambling.Services;
 | 
			
		||||
@@ -73,178 +73,58 @@ public partial class Gambling
 | 
			
		||||
 | 
			
		||||
            await ctx.Channel.EmbedAsync(embed);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
        [OwnerOnly]
 | 
			
		||||
        public async partial Task SlotTest(int tests = 1000)
 | 
			
		||||
        public sealed class SlotInteraction : NInteraction
 | 
			
		||||
        {
 | 
			
		||||
            if (tests <= 0)
 | 
			
		||||
                return;
 | 
			
		||||
            //multi vs how many times it occured
 | 
			
		||||
            
 | 
			
		||||
            int streak = 0;
 | 
			
		||||
            int maxW = 0;
 | 
			
		||||
            int maxL = 0;
 | 
			
		||||
            
 | 
			
		||||
            var dict = new Dictionary<decimal, int>();
 | 
			
		||||
            for (var i = 0; i < tests; i++)
 | 
			
		||||
            public SlotInteraction(DiscordSocketClient client, ulong userId, Func<SocketMessageComponent, Task> action) : base(client, userId, action)
 | 
			
		||||
            {
 | 
			
		||||
                var res = await _service.SlotAsync(ctx.User.Id, 0);
 | 
			
		||||
                var multi = res.AsT0.Multiplier;
 | 
			
		||||
                if (dict.ContainsKey(multi))
 | 
			
		||||
                    dict[multi] += 1;
 | 
			
		||||
                else
 | 
			
		||||
                    dict.Add(multi, 1);
 | 
			
		||||
 | 
			
		||||
                if (multi == 0)
 | 
			
		||||
                {
 | 
			
		||||
                    if (streak <= 0)
 | 
			
		||||
                        --streak;
 | 
			
		||||
                    else
 | 
			
		||||
                        streak = -1;
 | 
			
		||||
 | 
			
		||||
                    maxL = Math.Max(maxL, -streak);
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    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 SendConfirmAsync("Slot Test Results",
 | 
			
		||||
                sb.ToString(),
 | 
			
		||||
                footer: $"Total Bet: {tests} | Payout: {payout:F0} | {payout * 1.0M / tests * 100}%");
 | 
			
		||||
            protected override NadekoInteractionData Data { get; } = new(Emoji.Parse("🔁"),
 | 
			
		||||
                "slot:again",
 | 
			
		||||
                "Pull Again");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
        public async partial Task Slot(ShmartNumber amount)
 | 
			
		||||
        {
 | 
			
		||||
            if (!await CheckBetMandatory(amount))
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            // var slotInteraction = CreateSlotInteractionIntenal(amount);
 | 
			
		||||
 | 
			
		||||
            await ctx.Channel.TriggerTypingAsync();
 | 
			
		||||
 | 
			
		||||
            if (!_runningUsers.Add(ctx.User.Id))
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                if (!await CheckBetMandatory(amount))
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                await ctx.Channel.TriggerTypingAsync();
 | 
			
		||||
 | 
			
		||||
                var maybeResult = await _service.SlotAsync(ctx.User.Id, amount);
 | 
			
		||||
 | 
			
		||||
                if (!maybeResult.TryPickT0(out var result, out var error))
 | 
			
		||||
                if (await InternalSlotAsync(amount) is not SlotResult result)
 | 
			
		||||
                {
 | 
			
		||||
                    if (error == GamblingError.InsufficientFunds)
 | 
			
		||||
                        await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
 | 
			
		||||
 | 
			
		||||
                    await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                lock (_slotStatsLock)
 | 
			
		||||
                {
 | 
			
		||||
                    totalBet += amount;
 | 
			
		||||
                    totalPaidOut += result.Won;
 | 
			
		||||
                }
 | 
			
		||||
                var msg = GetSlotMessageInternal(result);
 | 
			
		||||
 | 
			
		||||
                long ownedAmount;
 | 
			
		||||
                await using (var uow = _db.GetDbContext())
 | 
			
		||||
                {
 | 
			
		||||
                    ownedAmount = uow.Set<DiscordUser>().FirstOrDefault(x => x.UserId == ctx.User.Id)?.CurrencyAmount
 | 
			
		||||
                                  ?? 0;
 | 
			
		||||
                }
 | 
			
		||||
                using var image = await GenerateSlotImageAsync(amount, result);
 | 
			
		||||
                await using var imgStream = await image.ToStreamAsync();
 | 
			
		||||
 | 
			
		||||
                var slotBg = await _images.GetSlotBgAsync();
 | 
			
		||||
                using (var bgImage = Image.Load<Rgba32>(slotBg, out _))
 | 
			
		||||
                {
 | 
			
		||||
                    var numbers = new int[3];
 | 
			
		||||
                    result.Rolls.CopyTo(numbers, 0);
 | 
			
		||||
 | 
			
		||||
                    Color fontColor = Config.Slots.CurrencyFontColor;
 | 
			
		||||
                var eb = _eb.Create(ctx)
 | 
			
		||||
                    .WithAuthor(ctx.User)
 | 
			
		||||
                    .WithDescription(Format.Bold(msg))
 | 
			
		||||
                    .WithImageUrl($"attachment://result.png")
 | 
			
		||||
                    .WithOkColor();
 | 
			
		||||
                
 | 
			
		||||
                // var inter = slotInteraction.GetInteraction();
 | 
			
		||||
                await ctx.Channel.SendFileAsync(imgStream,
 | 
			
		||||
                    "result.png",
 | 
			
		||||
                    embed: eb.Build()
 | 
			
		||||
                    // components: inter.CreateComponent()
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                    bgImage.Mutate(x => x.DrawText(new()
 | 
			
		||||
                        {
 | 
			
		||||
                            TextOptions = new()
 | 
			
		||||
                            {
 | 
			
		||||
                                HorizontalAlignment = HorizontalAlignment.Center,
 | 
			
		||||
                                VerticalAlignment = VerticalAlignment.Center,
 | 
			
		||||
                                WrapTextWidth = 140
 | 
			
		||||
                            }
 | 
			
		||||
                        },
 | 
			
		||||
                        ((long)result.Won).ToString(),
 | 
			
		||||
                        _fonts.DottyFont.CreateFont(65),
 | 
			
		||||
                        fontColor,
 | 
			
		||||
                        new(227, 92)));
 | 
			
		||||
 | 
			
		||||
                    var bottomFont = _fonts.DottyFont.CreateFont(50);
 | 
			
		||||
 | 
			
		||||
                    bgImage.Mutate(x => x.DrawText(new()
 | 
			
		||||
                        {
 | 
			
		||||
                            TextOptions = new()
 | 
			
		||||
                            {
 | 
			
		||||
                                HorizontalAlignment = HorizontalAlignment.Center,
 | 
			
		||||
                                VerticalAlignment = VerticalAlignment.Center,
 | 
			
		||||
                                WrapTextWidth = 135
 | 
			
		||||
                            }
 | 
			
		||||
                        },
 | 
			
		||||
                        amount.ToString(),
 | 
			
		||||
                        bottomFont,
 | 
			
		||||
                        fontColor,
 | 
			
		||||
                        new(129, 472)));
 | 
			
		||||
 | 
			
		||||
                    bgImage.Mutate(x => x.DrawText(new()
 | 
			
		||||
                        {
 | 
			
		||||
                            TextOptions = new()
 | 
			
		||||
                            {
 | 
			
		||||
                                HorizontalAlignment = HorizontalAlignment.Center,
 | 
			
		||||
                                VerticalAlignment = VerticalAlignment.Center,
 | 
			
		||||
                                WrapTextWidth = 135
 | 
			
		||||
                            }
 | 
			
		||||
                        },
 | 
			
		||||
                        ownedAmount.ToString(),
 | 
			
		||||
                        bottomFont,
 | 
			
		||||
                        fontColor,
 | 
			
		||||
                        new(325, 472)));
 | 
			
		||||
                    //sw.PrintLap("drew red text");
 | 
			
		||||
 | 
			
		||||
                    for (var i = 0; i < 3; i++)
 | 
			
		||||
                    {
 | 
			
		||||
                        using var img = Image.Load(await _images.GetSlotEmojiAsync(numbers[i]));
 | 
			
		||||
                        bgImage.Mutate(x => x.DrawImage(img, new Point(148 + (105 * i), 217), 1f));
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    var multi = result.Multiplier.ToString("0.##");
 | 
			
		||||
                    var msg = result.WinType switch
 | 
			
		||||
                    {
 | 
			
		||||
                        SlotWinType.SingleJoker => GetText(strs.slot_single(CurrencySign, multi)),
 | 
			
		||||
                        SlotWinType.DoubleJoker => GetText(strs.slot_two(CurrencySign, multi)),
 | 
			
		||||
                        SlotWinType.TrippleNormal => GetText(strs.slot_three(multi)),
 | 
			
		||||
                        SlotWinType.TrippleJoker => GetText(strs.slot_jackpot(multi)),
 | 
			
		||||
                        _ => GetText(strs.better_luck),
 | 
			
		||||
                    };
 | 
			
		||||
 | 
			
		||||
                    await using (var imgStream = await bgImage.ToStreamAsync())
 | 
			
		||||
                    {
 | 
			
		||||
                        await ctx.Channel.SendFileAsync(imgStream,
 | 
			
		||||
                            "result.png",
 | 
			
		||||
                            Format.Bold(ctx.User.ToString()) + " " + msg);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                // await inter.RunAsync(resMsg);
 | 
			
		||||
            }
 | 
			
		||||
            finally
 | 
			
		||||
            {
 | 
			
		||||
@@ -252,5 +132,156 @@ public partial class Gambling
 | 
			
		||||
                _runningUsers.TryRemove(ctx.User.Id);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // private SlotInteraction CreateSlotInteractionIntenal(long amount)
 | 
			
		||||
        // {
 | 
			
		||||
        //     return new SlotInteraction((DiscordSocketClient)ctx.Client,
 | 
			
		||||
        //         ctx.User.Id,
 | 
			
		||||
        //         async (smc) =>
 | 
			
		||||
        //         {
 | 
			
		||||
        //             try
 | 
			
		||||
        //             {
 | 
			
		||||
        //                 if (await InternalSlotAsync(amount) is not SlotResult result)
 | 
			
		||||
        //                 {
 | 
			
		||||
        //                     await smc.RespondErrorAsync(_eb, GetText(strs.not_enough(CurrencySign)), true);
 | 
			
		||||
        //                     return;
 | 
			
		||||
        //                 }
 | 
			
		||||
        //
 | 
			
		||||
        //                 var msg = GetSlotMessageInternal(result);
 | 
			
		||||
        //
 | 
			
		||||
        //                 using var image = await GenerateSlotImageAsync(amount, result);
 | 
			
		||||
        //                 await using var imgStream = await image.ToStreamAsync();
 | 
			
		||||
        //
 | 
			
		||||
        //                 var guid = Guid.NewGuid();
 | 
			
		||||
        //                 var imgName = $"result_{guid}.png";
 | 
			
		||||
        //                 
 | 
			
		||||
        //                 var slotInteraction = CreateSlotInteractionIntenal(amount).GetInteraction();
 | 
			
		||||
        //                 
 | 
			
		||||
        //                 await smc.Message.ModifyAsync(m =>
 | 
			
		||||
        //                 {
 | 
			
		||||
        //                     m.Content = msg;
 | 
			
		||||
        //                     m.Attachments = new[]
 | 
			
		||||
        //                     {
 | 
			
		||||
        //                         new FileAttachment(imgStream, imgName)
 | 
			
		||||
        //                     };
 | 
			
		||||
        //                     m.Components = slotInteraction.CreateComponent();
 | 
			
		||||
        //                 });
 | 
			
		||||
        //                 
 | 
			
		||||
        //                 _ = slotInteraction.RunAsync(smc.Message);
 | 
			
		||||
        //             }
 | 
			
		||||
        //             catch (Exception ex)
 | 
			
		||||
        //             {
 | 
			
		||||
        //                 Log.Error(ex, "Error pulling slot again");
 | 
			
		||||
        //             }
 | 
			
		||||
        //             // finally
 | 
			
		||||
        //             // {
 | 
			
		||||
        //             //     await Task.Delay(1000);
 | 
			
		||||
        //             //     _runningUsers.TryRemove(ctx.User.Id);
 | 
			
		||||
        //             // }
 | 
			
		||||
        //         });
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        private string GetSlotMessageInternal(SlotResult result)
 | 
			
		||||
        {
 | 
			
		||||
            var multi = result.Multiplier.ToString("0.##");
 | 
			
		||||
            var msg = result.WinType switch
 | 
			
		||||
            {
 | 
			
		||||
                SlotWinType.SingleJoker => GetText(strs.slot_single(CurrencySign, multi)),
 | 
			
		||||
                SlotWinType.DoubleJoker => GetText(strs.slot_two(CurrencySign, multi)),
 | 
			
		||||
                SlotWinType.TrippleNormal => GetText(strs.slot_three(multi)),
 | 
			
		||||
                SlotWinType.TrippleJoker => GetText(strs.slot_jackpot(multi)),
 | 
			
		||||
                _ => GetText(strs.better_luck),
 | 
			
		||||
            };
 | 
			
		||||
            return msg;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async Task<SlotResult?> InternalSlotAsync(long amount)
 | 
			
		||||
        {
 | 
			
		||||
            var maybeResult = await _service.SlotAsync(ctx.User.Id, amount);
 | 
			
		||||
 | 
			
		||||
            if (!maybeResult.TryPickT0(out var result, out var error))
 | 
			
		||||
            {
 | 
			
		||||
                await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            lock (_slotStatsLock)
 | 
			
		||||
            {
 | 
			
		||||
                totalBet += amount;
 | 
			
		||||
                totalPaidOut += result.Won;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return result;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async Task<Image<Rgba32>> GenerateSlotImageAsync(long amount, SlotResult result)
 | 
			
		||||
        {
 | 
			
		||||
            long ownedAmount;
 | 
			
		||||
            await using (var uow = _db.GetDbContext())
 | 
			
		||||
            {
 | 
			
		||||
                ownedAmount = uow.Set<DiscordUser>().FirstOrDefault(x => x.UserId == ctx.User.Id)?.CurrencyAmount
 | 
			
		||||
                              ?? 0;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var slotBg = await _images.GetSlotBgAsync();
 | 
			
		||||
            var bgImage = Image.Load<Rgba32>(slotBg, out _);
 | 
			
		||||
            var numbers = new int[3];
 | 
			
		||||
            result.Rolls.CopyTo(numbers, 0);
 | 
			
		||||
 | 
			
		||||
            Color fontColor = Config.Slots.CurrencyFontColor;
 | 
			
		||||
 | 
			
		||||
            bgImage.Mutate(x => x.DrawText(new()
 | 
			
		||||
                {
 | 
			
		||||
                    TextOptions = new()
 | 
			
		||||
                    {
 | 
			
		||||
                        HorizontalAlignment = HorizontalAlignment.Center,
 | 
			
		||||
                        VerticalAlignment = VerticalAlignment.Center,
 | 
			
		||||
                        WrapTextWidth = 140
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                ((long)result.Won).ToString(),
 | 
			
		||||
                _fonts.DottyFont.CreateFont(65),
 | 
			
		||||
                fontColor,
 | 
			
		||||
                new(227, 92)));
 | 
			
		||||
 | 
			
		||||
            var bottomFont = _fonts.DottyFont.CreateFont(50);
 | 
			
		||||
 | 
			
		||||
            bgImage.Mutate(x => x.DrawText(new()
 | 
			
		||||
                {
 | 
			
		||||
                    TextOptions = new()
 | 
			
		||||
                    {
 | 
			
		||||
                        HorizontalAlignment = HorizontalAlignment.Center,
 | 
			
		||||
                        VerticalAlignment = VerticalAlignment.Center,
 | 
			
		||||
                        WrapTextWidth = 135
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                amount.ToString(),
 | 
			
		||||
                bottomFont,
 | 
			
		||||
                fontColor,
 | 
			
		||||
                new(129, 472)));
 | 
			
		||||
 | 
			
		||||
            bgImage.Mutate(x => x.DrawText(new()
 | 
			
		||||
                {
 | 
			
		||||
                    TextOptions = new()
 | 
			
		||||
                    {
 | 
			
		||||
                        HorizontalAlignment = HorizontalAlignment.Center,
 | 
			
		||||
                        VerticalAlignment = VerticalAlignment.Center,
 | 
			
		||||
                        WrapTextWidth = 135
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                ownedAmount.ToString(),
 | 
			
		||||
                bottomFont,
 | 
			
		||||
                fontColor,
 | 
			
		||||
                new(325, 472)));
 | 
			
		||||
            //sw.PrintLap("drew red text");
 | 
			
		||||
 | 
			
		||||
            for (var i = 0; i < 3; i++)
 | 
			
		||||
            {
 | 
			
		||||
                using var img = Image.Load(await _images.GetSlotEmojiAsync(numbers[i]));
 | 
			
		||||
                bgImage.Mutate(x => x.DrawImage(img, new Point(148 + (105 * i), 217), 1f));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return bgImage;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,311 +0,0 @@
 | 
			
		||||
#nullable disable
 | 
			
		||||
using Nadeko.Common;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.Gambling.Common;
 | 
			
		||||
 | 
			
		||||
public class Deck
 | 
			
		||||
{
 | 
			
		||||
    public enum CardSuit
 | 
			
		||||
    {
 | 
			
		||||
        Spades = 1,
 | 
			
		||||
        Hearts = 2,
 | 
			
		||||
        Diamonds = 3,
 | 
			
		||||
        Clubs = 4
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static readonly Dictionary<int, string> _cardNames = new()
 | 
			
		||||
    {
 | 
			
		||||
        { 1, "Ace" },
 | 
			
		||||
        { 2, "Two" },
 | 
			
		||||
        { 3, "Three" },
 | 
			
		||||
        { 4, "Four" },
 | 
			
		||||
        { 5, "Five" },
 | 
			
		||||
        { 6, "Six" },
 | 
			
		||||
        { 7, "Seven" },
 | 
			
		||||
        { 8, "Eight" },
 | 
			
		||||
        { 9, "Nine" },
 | 
			
		||||
        { 10, "Ten" },
 | 
			
		||||
        { 11, "Jack" },
 | 
			
		||||
        { 12, "Queen" },
 | 
			
		||||
        { 13, "King" }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    private static Dictionary<string, Func<List<Card>, bool>> handValues;
 | 
			
		||||
 | 
			
		||||
    public List<Card> CardPool { get; set; }
 | 
			
		||||
    private readonly Random _r = new NadekoRandom();
 | 
			
		||||
 | 
			
		||||
    static Deck()
 | 
			
		||||
        => InitHandValues();
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    ///     Creates a new instance of the BlackJackGame, this allows you to create multiple games running at one time.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public Deck()
 | 
			
		||||
        => RefillPool();
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    ///     Restart the game of blackjack. It will only refill the pool for now. Probably wont be used, unless you want to have
 | 
			
		||||
    ///     only 1 bjg running at one time,
 | 
			
		||||
    ///     then you will restart the same game every time.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public void Restart()
 | 
			
		||||
        => RefillPool();
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    ///     Removes all cards from the pool and refills the pool with all of the possible cards. NOTE: I think this is too
 | 
			
		||||
    ///     expensive.
 | 
			
		||||
    ///     We should probably make it so it copies another premade list with all the cards, or something.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    protected virtual void RefillPool()
 | 
			
		||||
    {
 | 
			
		||||
        CardPool = new(52);
 | 
			
		||||
        //foreach suit
 | 
			
		||||
        for (var j = 1; j < 14; j++)
 | 
			
		||||
            // and number
 | 
			
		||||
        for (var i = 1; i < 5; i++)
 | 
			
		||||
            //generate a card of that suit and number and add it to the pool
 | 
			
		||||
 | 
			
		||||
            // the pool will go from ace of spades,hears,diamonds,clubs all the way to the king of spades. hearts, ...
 | 
			
		||||
            CardPool.Add(new((CardSuit)i, j));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    ///     Take a card from the pool, you either take it from the top if the deck is shuffled, or from a random place if the
 | 
			
		||||
    ///     deck is in the default order.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <returns>A card from the pool</returns>
 | 
			
		||||
    public Card Draw()
 | 
			
		||||
    {
 | 
			
		||||
        if (CardPool.Count == 0)
 | 
			
		||||
            Restart();
 | 
			
		||||
        //you can either do this if your deck is not shuffled
 | 
			
		||||
 | 
			
		||||
        var num = _r.Next(0, CardPool.Count);
 | 
			
		||||
        var c = CardPool[num];
 | 
			
		||||
        CardPool.RemoveAt(num);
 | 
			
		||||
        return c;
 | 
			
		||||
 | 
			
		||||
        // if you want to shuffle when you fill, then take the first one
 | 
			
		||||
        /*
 | 
			
		||||
        Card c = cardPool[0];
 | 
			
		||||
        cardPool.RemoveAt(0);
 | 
			
		||||
        return c;
 | 
			
		||||
        */
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    ///     Shuffles the deck. Use this if you want to take cards from the top of the deck, instead of randomly. See DrawACard
 | 
			
		||||
    ///     method.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    private void Shuffle()
 | 
			
		||||
    {
 | 
			
		||||
        if (CardPool.Count <= 1)
 | 
			
		||||
            return;
 | 
			
		||||
        var orderedPool = CardPool.Shuffle();
 | 
			
		||||
        CardPool ??= orderedPool.ToList();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public override string ToString()
 | 
			
		||||
        => string.Concat(CardPool.Select(c => c.ToString())) + Environment.NewLine;
 | 
			
		||||
 | 
			
		||||
    private static void InitHandValues()
 | 
			
		||||
    {
 | 
			
		||||
        bool HasPair(List<Card> cards)
 | 
			
		||||
        {
 | 
			
		||||
            return cards.GroupBy(card => card.Number).Count(group => group.Count() == 2) == 1;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        bool IsPair(List<Card> cards)
 | 
			
		||||
        {
 | 
			
		||||
            return cards.GroupBy(card => card.Number).Count(group => group.Count() == 3) == 0 && HasPair(cards);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        bool IsTwoPair(List<Card> cards)
 | 
			
		||||
        {
 | 
			
		||||
            return cards.GroupBy(card => card.Number).Count(group => group.Count() == 2) == 2;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        bool IsStraight(List<Card> cards)
 | 
			
		||||
        {
 | 
			
		||||
            if (cards.GroupBy(card => card.Number).Count() != cards.Count())
 | 
			
		||||
                return false;
 | 
			
		||||
            var toReturn = cards.Max(card => card.Number) - cards.Min(card => card.Number) == 4;
 | 
			
		||||
            if (toReturn || cards.All(c => c.Number != 1))
 | 
			
		||||
                return toReturn;
 | 
			
		||||
 | 
			
		||||
            var newCards = cards.Select(c => c.Number == 1 ? new(c.Suit, 14) : c).ToArray();
 | 
			
		||||
            return newCards.Max(card => card.Number) - newCards.Min(card => card.Number) == 4;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        bool HasThreeOfKind(List<Card> cards)
 | 
			
		||||
        {
 | 
			
		||||
            return cards.GroupBy(card => card.Number).Any(group => group.Count() == 3);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        bool IsThreeOfKind(List<Card> cards)
 | 
			
		||||
        {
 | 
			
		||||
            return HasThreeOfKind(cards) && !HasPair(cards);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        bool IsFlush(List<Card> cards)
 | 
			
		||||
        {
 | 
			
		||||
            return cards.GroupBy(card => card.Suit).Count() == 1;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        bool IsFourOfKind(List<Card> cards)
 | 
			
		||||
        {
 | 
			
		||||
            return cards.GroupBy(card => card.Number).Any(group => group.Count() == 4);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        bool IsFullHouse(List<Card> cards)
 | 
			
		||||
        {
 | 
			
		||||
            return HasPair(cards) && HasThreeOfKind(cards);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        bool HasStraightFlush(List<Card> cards)
 | 
			
		||||
        {
 | 
			
		||||
            return IsFlush(cards) && IsStraight(cards);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        bool IsRoyalFlush(List<Card> cards)
 | 
			
		||||
        {
 | 
			
		||||
            return cards.Min(card => card.Number) == 1
 | 
			
		||||
                   && cards.Max(card => card.Number) == 13
 | 
			
		||||
                   && HasStraightFlush(cards);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        bool IsStraightFlush(List<Card> cards)
 | 
			
		||||
        {
 | 
			
		||||
            return HasStraightFlush(cards) && !IsRoyalFlush(cards);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        handValues = new()
 | 
			
		||||
        {
 | 
			
		||||
            { "Royal Flush", IsRoyalFlush },
 | 
			
		||||
            { "Straight Flush", IsStraightFlush },
 | 
			
		||||
            { "Four Of A Kind", IsFourOfKind },
 | 
			
		||||
            { "Full House", IsFullHouse },
 | 
			
		||||
            { "Flush", IsFlush },
 | 
			
		||||
            { "Straight", IsStraight },
 | 
			
		||||
            { "Three Of A Kind", IsThreeOfKind },
 | 
			
		||||
            { "Two Pairs", IsTwoPair },
 | 
			
		||||
            { "A Pair", IsPair }
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static string GetHandValue(List<Card> cards)
 | 
			
		||||
    {
 | 
			
		||||
        if (handValues is null)
 | 
			
		||||
            InitHandValues();
 | 
			
		||||
 | 
			
		||||
        foreach (var kvp in handValues.Where(x => x.Value(cards)))
 | 
			
		||||
            return kvp.Key;
 | 
			
		||||
        return "High card " + (cards.FirstOrDefault(c => c.Number == 1)?.GetValueText() ?? cards.Max().GetValueText());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public class Card : IComparable
 | 
			
		||||
    {
 | 
			
		||||
        private static readonly IReadOnlyDictionary<CardSuit, string> _suitToSuitChar = new Dictionary<CardSuit, string>
 | 
			
		||||
        {
 | 
			
		||||
            { CardSuit.Diamonds, "♦" },
 | 
			
		||||
            { CardSuit.Clubs, "♣" },
 | 
			
		||||
            { CardSuit.Spades, "♠" },
 | 
			
		||||
            { CardSuit.Hearts, "♥" }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        private static readonly IReadOnlyDictionary<string, CardSuit> _suitCharToSuit = new Dictionary<string, CardSuit>
 | 
			
		||||
        {
 | 
			
		||||
            { "♦", CardSuit.Diamonds },
 | 
			
		||||
            { "d", CardSuit.Diamonds },
 | 
			
		||||
            { "♣", CardSuit.Clubs },
 | 
			
		||||
            { "c", CardSuit.Clubs },
 | 
			
		||||
            { "♠", CardSuit.Spades },
 | 
			
		||||
            { "s", CardSuit.Spades },
 | 
			
		||||
            { "♥", CardSuit.Hearts },
 | 
			
		||||
            { "h", CardSuit.Hearts }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        private static readonly IReadOnlyDictionary<char, int> _numberCharToNumber = new Dictionary<char, int>
 | 
			
		||||
        {
 | 
			
		||||
            { 'a', 1 },
 | 
			
		||||
            { '2', 2 },
 | 
			
		||||
            { '3', 3 },
 | 
			
		||||
            { '4', 4 },
 | 
			
		||||
            { '5', 5 },
 | 
			
		||||
            { '6', 6 },
 | 
			
		||||
            { '7', 7 },
 | 
			
		||||
            { '8', 8 },
 | 
			
		||||
            { '9', 9 },
 | 
			
		||||
            { 't', 10 },
 | 
			
		||||
            { 'j', 11 },
 | 
			
		||||
            { 'q', 12 },
 | 
			
		||||
            { 'k', 13 }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        public CardSuit Suit { get; }
 | 
			
		||||
        public int Number { get; }
 | 
			
		||||
 | 
			
		||||
        public string FullName
 | 
			
		||||
        {
 | 
			
		||||
            get
 | 
			
		||||
            {
 | 
			
		||||
                var str = string.Empty;
 | 
			
		||||
 | 
			
		||||
                if (Number is <= 10 and > 1)
 | 
			
		||||
                    str += "_" + Number;
 | 
			
		||||
                else
 | 
			
		||||
                    str += GetValueText().ToLowerInvariant();
 | 
			
		||||
                return str + "_of_" + Suit.ToString().ToLowerInvariant();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private readonly string[] _regIndicators =
 | 
			
		||||
        {
 | 
			
		||||
            "🇦", ":two:", ":three:", ":four:", ":five:", ":six:", ":seven:", ":eight:", ":nine:", ":keycap_ten:",
 | 
			
		||||
            "🇯", "🇶", "🇰"
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        public Card(CardSuit s, int cardNum)
 | 
			
		||||
        {
 | 
			
		||||
            Suit = s;
 | 
			
		||||
            Number = cardNum;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public string GetValueText()
 | 
			
		||||
            => _cardNames[Number];
 | 
			
		||||
 | 
			
		||||
        public override string ToString()
 | 
			
		||||
            => _cardNames[Number] + " Of " + Suit;
 | 
			
		||||
 | 
			
		||||
        public int CompareTo(object obj)
 | 
			
		||||
        {
 | 
			
		||||
            if (obj is not Card card)
 | 
			
		||||
                return 0;
 | 
			
		||||
            return Number - card.Number;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static Card Parse(string input)
 | 
			
		||||
        {
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(input))
 | 
			
		||||
                throw new ArgumentNullException(nameof(input));
 | 
			
		||||
 | 
			
		||||
            if (input.Length != 2
 | 
			
		||||
                || !_numberCharToNumber.TryGetValue(input[0], out var n)
 | 
			
		||||
                || !_suitCharToSuit.TryGetValue(input[1].ToString(), out var s))
 | 
			
		||||
                throw new ArgumentException("Invalid input", nameof(input));
 | 
			
		||||
 | 
			
		||||
            return new(s, n);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public string GetEmojiString()
 | 
			
		||||
        {
 | 
			
		||||
            var str = string.Empty;
 | 
			
		||||
 | 
			
		||||
            str += _regIndicators[Number - 1];
 | 
			
		||||
            str += _suitToSuitChar[Suit];
 | 
			
		||||
 | 
			
		||||
            return str;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,4 +1,6 @@
 | 
			
		||||
namespace NadekoBot.Modules.Gambling.Common;
 | 
			
		||||
using Nadeko.Econ;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.Gambling.Common;
 | 
			
		||||
 | 
			
		||||
public class QuadDeck : Deck
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
#nullable disable
 | 
			
		||||
using Nadeko.Econ.Gambling;
 | 
			
		||||
using Nadeko.Econ.Gambling.Betdraw;
 | 
			
		||||
using Nadeko.Econ.Gambling.Rps;
 | 
			
		||||
using OneOf;
 | 
			
		||||
 | 
			
		||||
@@ -7,10 +8,11 @@ namespace NadekoBot.Modules.Gambling;
 | 
			
		||||
 | 
			
		||||
public interface IGamblingService
 | 
			
		||||
{
 | 
			
		||||
    Task<OneOf<LuLaResult, GamblingError>> WofAsync(ulong userId, long amount);
 | 
			
		||||
    Task<OneOf<LuLaResult, GamblingError>> LulaAsync(ulong userId, long amount);
 | 
			
		||||
    Task<OneOf<BetrollResult, GamblingError>> BetRollAsync(ulong userId, long amount);
 | 
			
		||||
    Task<OneOf<BetflipResult, GamblingError>> BetFlipAsync(ulong userId, long amount, byte guess);
 | 
			
		||||
    Task<OneOf<SlotResult, GamblingError>> SlotAsync(ulong userId, long amount);
 | 
			
		||||
    Task<FlipResult[]> FlipAsync(int count);
 | 
			
		||||
    Task<OneOf<RpsResult, GamblingError>> RpsAsync(ulong userId, long amount, byte pick);
 | 
			
		||||
    Task<OneOf<BetdrawResult, GamblingError>> BetDrawAsync(ulong userId, long amount, byte? guessValue, byte? guessColor);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
#nullable disable
 | 
			
		||||
using Nadeko.Econ.Gambling;
 | 
			
		||||
using Nadeko.Econ.Gambling.Betdraw;
 | 
			
		||||
using Nadeko.Econ.Gambling.Rps;
 | 
			
		||||
using NadekoBot.Modules.Gambling.Services;
 | 
			
		||||
using OneOf;
 | 
			
		||||
@@ -17,9 +18,7 @@ public sealed class NewGamblingService : IGamblingService, INService
 | 
			
		||||
        _cs = cs;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // todo input checks
 | 
			
		||||
    // todo ladder of fortune
 | 
			
		||||
    public async Task<OneOf<LuLaResult, GamblingError>> WofAsync(ulong userId, long amount)
 | 
			
		||||
    public async Task<OneOf<LuLaResult, GamblingError>> LulaAsync(ulong userId, long amount)
 | 
			
		||||
    {
 | 
			
		||||
        if (amount < 0)
 | 
			
		||||
            throw new ArgumentOutOfRangeException(nameof(amount));
 | 
			
		||||
@@ -64,9 +63,9 @@ public sealed class NewGamblingService : IGamblingService, INService
 | 
			
		||||
        var game = new BetrollGame(_bcs.Data.BetRoll.Pairs
 | 
			
		||||
            .Select(x => (x.WhenAbove, (decimal)x.MultiplyBy))
 | 
			
		||||
            .ToList());
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        var result = game.Roll(amount);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        var won = (long)result.Won;
 | 
			
		||||
        if (won > 0)
 | 
			
		||||
        {
 | 
			
		||||
@@ -81,6 +80,9 @@ public sealed class NewGamblingService : IGamblingService, INService
 | 
			
		||||
        if (amount < 0)
 | 
			
		||||
            throw new ArgumentOutOfRangeException(nameof(amount));
 | 
			
		||||
 | 
			
		||||
        if (guess > 1)
 | 
			
		||||
            throw new ArgumentOutOfRangeException(nameof(guess));
 | 
			
		||||
 | 
			
		||||
        if (amount > 0)
 | 
			
		||||
        {
 | 
			
		||||
            var isTakeSuccess = await _cs.RemoveAsync(userId, amount, new("betflip", "bet"));
 | 
			
		||||
@@ -102,6 +104,42 @@ public sealed class NewGamblingService : IGamblingService, INService
 | 
			
		||||
        
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public async Task<OneOf<BetdrawResult, GamblingError>> BetDrawAsync(ulong userId, long amount, byte? guessValue, byte? guessColor)
 | 
			
		||||
    {
 | 
			
		||||
        if (amount < 0)
 | 
			
		||||
            throw new ArgumentOutOfRangeException(nameof(amount));
 | 
			
		||||
 | 
			
		||||
        if (guessColor is null && guessValue is null)
 | 
			
		||||
            throw new ArgumentNullException();
 | 
			
		||||
 | 
			
		||||
        if (guessColor > 1)
 | 
			
		||||
            throw new ArgumentOutOfRangeException(nameof(guessColor));
 | 
			
		||||
        
 | 
			
		||||
        if (guessValue > 1)
 | 
			
		||||
            throw new ArgumentOutOfRangeException(nameof(guessValue));
 | 
			
		||||
 | 
			
		||||
        if (amount > 0)
 | 
			
		||||
        {
 | 
			
		||||
            var isTakeSuccess = await _cs.RemoveAsync(userId, amount, new("betflip", "bet"));
 | 
			
		||||
 | 
			
		||||
            if (!isTakeSuccess)
 | 
			
		||||
            {
 | 
			
		||||
                return GamblingError.InsufficientFunds;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var game = new BetdrawGame();
 | 
			
		||||
        var result = game.Draw((BetdrawValueGuess?)guessValue, (BetdrawColorGuess?)guessColor, amount);
 | 
			
		||||
        
 | 
			
		||||
        var won = (long)result.Won;
 | 
			
		||||
        if (won > 0)
 | 
			
		||||
        {
 | 
			
		||||
            await _cs.AddAsync(userId, won, new("betflip", "win"));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<OneOf<SlotResult, GamblingError>> SlotAsync(ulong userId, long amount)
 | 
			
		||||
    {
 | 
			
		||||
@@ -132,6 +170,9 @@ public sealed class NewGamblingService : IGamblingService, INService
 | 
			
		||||
 | 
			
		||||
    public Task<FlipResult[]> FlipAsync(int count)
 | 
			
		||||
    {
 | 
			
		||||
        if (count < 1)
 | 
			
		||||
            throw new ArgumentOutOfRangeException(nameof(count));
 | 
			
		||||
        
 | 
			
		||||
        var game = new BetflipGame(0);
 | 
			
		||||
 | 
			
		||||
        var results = new FlipResult[count];
 | 
			
		||||
 
 | 
			
		||||
@@ -108,7 +108,7 @@ public partial class NSFW : NadekoModule<ISearchImagesService>
 | 
			
		||||
                return t;
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        await ReplyConfirmLocalizedAsync(strs.autohentai_started(interval, string.Join(", ", tags)));
 | 
			
		||||
        await SendConfirmAsync($"Autohentai started. Interval: {interval}, Tags: {string.Join(", ", tags)}");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [Cmd]
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -653,7 +653,7 @@ rolluo:
 | 
			
		||||
    - "7"
 | 
			
		||||
    - "3d5"
 | 
			
		||||
nroll:
 | 
			
		||||
  desc: "Rolls in a given range. If you specify just one number instead of the range, it will role from 0 to that number."
 | 
			
		||||
  desc: "Rolls in a given range. If you specify just one number instead of the range, it will roll from 0 to that number."
 | 
			
		||||
  args:
 | 
			
		||||
    - "5"
 | 
			
		||||
    - "5-15"
 | 
			
		||||
@@ -2231,4 +2231,23 @@ eval:
 | 
			
		||||
  args:
 | 
			
		||||
    - "123 / 4.5f"
 | 
			
		||||
    - "await ctx.OkAsync();"
 | 
			
		||||
    - 'await ctx.SendConfirmAsync("uwu");'
 | 
			
		||||
    - 'await ctx.SendConfirmAsync("uwu");'
 | 
			
		||||
betdraw:
 | 
			
		||||
  desc: |-
 | 
			
		||||
    Bet on the card value and/or color. Specify the amount followed by your guess. 
 | 
			
		||||
    You can specify `r` or `b` for red or black, and `h` or `l` for high or low.
 | 
			
		||||
    You can specify only h/l or only r/b or both.
 | 
			
		||||
    Returns are high but **7 always loses**.
 | 
			
		||||
  args:
 | 
			
		||||
    - "50 r"
 | 
			
		||||
    - "200 b l"
 | 
			
		||||
    - "1000 h"
 | 
			
		||||
    - "38 hi black"
 | 
			
		||||
bettest:
 | 
			
		||||
  desc: |-
 | 
			
		||||
    Tests a betting command by specifying the name followed by the number of tests. Some have multiple variations.
 | 
			
		||||
    See the list of all tests by specifying no parameters.
 | 
			
		||||
  args:
 | 
			
		||||
    - ""
 | 
			
		||||
    - "betflip 1000"
 | 
			
		||||
    - "slot 2000"
 | 
			
		||||
 
 | 
			
		||||
@@ -81,7 +81,6 @@
 | 
			
		||||
  "boostdel_on": "Boost messages will be deleted after {0} seconds.",
 | 
			
		||||
  "hierarchy": "You can't use this command on users with a role higher or equal than yours (or mine) in the role hierarchy.",
 | 
			
		||||
  "role_too_high": "You can't use this command with roles which are above your highest role, unless you're server administrator.",
 | 
			
		||||
  "images_loading": "Images will be reloaded within a few seconds.",
 | 
			
		||||
  "insuf_perms_i": "I have insufficient permissions.",
 | 
			
		||||
  "insuf_perms_u": "You have insufficient permissions.",
 | 
			
		||||
  "invalid_format": "Invalid input format.",
 | 
			
		||||
@@ -216,11 +215,13 @@
 | 
			
		||||
  "better_luck": "Better luck next time ^_^",
 | 
			
		||||
  "br_win": "Congratulations! You won {0} for rolling above {1}",
 | 
			
		||||
  "deck_reshuffled": "Deck reshuffled.",
 | 
			
		||||
  "flipped": "flipped {0}.",
 | 
			
		||||
  "flipped": "Flipped {0}",
 | 
			
		||||
  "flip_guess": "You guessed it! You won {0}",
 | 
			
		||||
  "flip_invalid": "Invalid number specified. You can flip 1 to {0} coins.",
 | 
			
		||||
  "flip_results": "Flipped {0} coins. {1} heads, {2} tails.",
 | 
			
		||||
  "cards_left": "{0} cards left in the deck.",
 | 
			
		||||
  "cards": "Cards",
 | 
			
		||||
  "hand_value": "Hand value",
 | 
			
		||||
  "gifted": "has gifted {0} to {1}",
 | 
			
		||||
  "has": "{0} has {1}",
 | 
			
		||||
  "heads": "Head",
 | 
			
		||||
@@ -232,11 +233,13 @@
 | 
			
		||||
  "no_more_cards": "No more cards in the deck.",
 | 
			
		||||
  "raffled_user": "Raffled user",
 | 
			
		||||
  "roll2": "Roll",
 | 
			
		||||
  "slot_bet": "Bet",
 | 
			
		||||
  "rolls": "Rolls",
 | 
			
		||||
  "slot_jackpot": "WOAAHHHHHH!!! Congratulations!!! x{0}",
 | 
			
		||||
  "slot_single": "A single {0}, x{1}",
 | 
			
		||||
  "slot_three": "Wow! Lucky! Three of a kind! x{0}",
 | 
			
		||||
  "slot_two": "Good job! Two {0} - bet x{1}",
 | 
			
		||||
  "available_tests": "Available Tests",
 | 
			
		||||
  "test_results_for": "Test results for {0}",
 | 
			
		||||
  "won": "Won",
 | 
			
		||||
  "multiplier": "Multiplier",
 | 
			
		||||
  "tails": "Tail",
 | 
			
		||||
@@ -256,7 +259,6 @@
 | 
			
		||||
  "usage": "Usage",
 | 
			
		||||
  "options": "Options",
 | 
			
		||||
  "requires": "Requires",
 | 
			
		||||
  "autohentai_started": "Autohentai started. Reposting every {0}s with one of the following tags:\n{1}",
 | 
			
		||||
  "tag": "Tag",
 | 
			
		||||
  "animal_race": "Animal race",
 | 
			
		||||
  "animal_race_failed": "Failed to start since there was not enough participants.",
 | 
			
		||||
@@ -319,7 +321,6 @@
 | 
			
		||||
  "rps_win": "{0} won! {1} beats {2}",
 | 
			
		||||
  "submissions_closed": "Submissions closed",
 | 
			
		||||
  "animal_race_already_started": "Animal Race is already running.",
 | 
			
		||||
  "total_average": "Total: {0} Average: {1}",
 | 
			
		||||
  "category": "Category",
 | 
			
		||||
  "cleverbot_disabled": "Disabled cleverbot on this server.",
 | 
			
		||||
  "cleverbot_enabled": "Enabled cleverbot on this server.",
 | 
			
		||||
@@ -327,7 +328,6 @@
 | 
			
		||||
  "curgen_enabled": "Currency generation has been enabled on this channel.",
 | 
			
		||||
  "curgen_pl": "{0} random {1} appeared!",
 | 
			
		||||
  "curgen_sn": "A random {0} appeared!",
 | 
			
		||||
  "failed_loading_question": "Failed loading a question.",
 | 
			
		||||
  "game_started": "Game started",
 | 
			
		||||
  "hangman_game_started": "Hangman game started",
 | 
			
		||||
  "hangman_running": "Hangman game already running on this channel.",
 | 
			
		||||
@@ -357,10 +357,7 @@
 | 
			
		||||
  "vs": "{0} vs {1}",
 | 
			
		||||
  "attempting_to_queue": "Attempting to queue {0} tracks...",
 | 
			
		||||
  "dir_queue_complete": "Directory queue complete.",
 | 
			
		||||
  "fairplay": "Fairplay",
 | 
			
		||||
  "finished_track": "Track Finished",
 | 
			
		||||
  "fp_disabled": "Fair play disabled.",
 | 
			
		||||
  "fp_enabled": "Fair play enabled.",
 | 
			
		||||
  "from_position": "From position",
 | 
			
		||||
  "id": "Id",
 | 
			
		||||
  "now_playing": "Now playing",
 | 
			
		||||
@@ -447,10 +444,8 @@
 | 
			
		||||
  "word_filter_channel_on": "Word filtering enabled on this channel.",
 | 
			
		||||
  "word_filter_server_off": "Word filtering disabled on this server.",
 | 
			
		||||
  "word_filter_server_on": "Word filtering enabled on this server.",
 | 
			
		||||
  "avatar_none": "User {0} has no avatar set.",
 | 
			
		||||
  "abilities": "Abilities",
 | 
			
		||||
  "anime_no_fav": "No favorite anime yet",
 | 
			
		||||
  "atl_ad_started": "Started automatic translation of messages on this channel. User messages will be auto-deleted.",
 | 
			
		||||
  "atl_removed": "your auto-translate language has been removed.",
 | 
			
		||||
  "atl_set": "Your auto-translate language has been set to {0}>{1}",
 | 
			
		||||
  "atl_started": "Started automatic translation of messages on this channel.",
 | 
			
		||||
@@ -458,6 +453,8 @@
 | 
			
		||||
  "atl_not_enabled": "Automatic translation is not enabled on this channel or you've provided an invalid language.",
 | 
			
		||||
  "bad_input_format": "Bad input format, or something went wrong.",
 | 
			
		||||
  "card_not_found": "Couldn't find that card.",
 | 
			
		||||
  "card": "Card",
 | 
			
		||||
  "guess": "Guess",
 | 
			
		||||
  "catfact": "fact",
 | 
			
		||||
  "chapters": "Chapters",
 | 
			
		||||
  "comic_number": "Comic #",
 | 
			
		||||
@@ -483,7 +480,6 @@
 | 
			
		||||
  "height_weight": "Height/Weight",
 | 
			
		||||
  "height_weight_val": "{0}m/{1}kg",
 | 
			
		||||
  "humidity": "Humidity",
 | 
			
		||||
  "image_search_for": "Image search for:",
 | 
			
		||||
  "imdb_fail": "Failed to find that movie.",
 | 
			
		||||
  "invalid_lang": "Invalid source or target language.",
 | 
			
		||||
  "jokes_not_loaded": "Jokes not loaded.",
 | 
			
		||||
@@ -506,7 +502,6 @@
 | 
			
		||||
  "pokemon_none": "No pokemon found.",
 | 
			
		||||
  "rating": "Rating",
 | 
			
		||||
  "score": "Score:",
 | 
			
		||||
  "search_for": "Search for:",
 | 
			
		||||
  "short_url": "Short url",
 | 
			
		||||
  "something_went_wrong": "Something went wrong.",
 | 
			
		||||
  "specify_search_params": "Please specify search parameters.",
 | 
			
		||||
@@ -793,7 +788,6 @@
 | 
			
		||||
  "config_prop_not_found": "Property {0} not found on {1} configuration",
 | 
			
		||||
  "config_list": "Config list",
 | 
			
		||||
  "bot_strings_reloaded": "Bot strings have been reloaded.",
 | 
			
		||||
  "level_req": "Level Req.",
 | 
			
		||||
  "xpn_setting_global": "Global Level-Up notifications",
 | 
			
		||||
  "xpn_setting_server": "Server Level-Up notifications",
 | 
			
		||||
  "xpn_notif_channel": "In the channel where you sent the last message.",
 | 
			
		||||
@@ -902,7 +896,6 @@
 | 
			
		||||
  "mass_ban_in_progress": "Banning {0} users...",
 | 
			
		||||
  "mass_ban_completed": "Banned {0} users.",
 | 
			
		||||
  "mass_kill_completed": "Mass Banning and Blacklisting of {0} users is complete.",
 | 
			
		||||
  "failed_finding_novel": "Can't find that novel. Make sure you've typed the exact full name, and that it exists on novelupdates.com",
 | 
			
		||||
  "club_transfered": "Ownership of the club {0} has been transferred to {1}",
 | 
			
		||||
  "club_transfer_failed": "Transfer failed. You must be the club owner. Target must be a member of your club.",
 | 
			
		||||
  "roll_duel_challenge": "challenged {1} for a roll duel for {2}",
 | 
			
		||||
@@ -913,11 +906,6 @@
 | 
			
		||||
  "account_not_found": "That account does not exist or is set to private.",
 | 
			
		||||
  "ninja_not_found": "Currency with that name was not found or an invalid league name was provided.",
 | 
			
		||||
  "leagues_not_found": "Unable to retrieve data from Path of Exile API.",
 | 
			
		||||
  "reaction_roles_message": "**Roles:** {0}\n**Content:** {1}",
 | 
			
		||||
  "no_reaction_roles": "There are no ReactionRole features enabled on this server.",
 | 
			
		||||
  "reaction_cant_access": "I can't access {0} reaction. You can only use emotes from servers I'm in.",
 | 
			
		||||
  "reaction_role_removed": "Removed ReactionRole message #{0}",
 | 
			
		||||
  "reaction_roles_full": "You've reached the limit on ReactionRole messages. You have to delete some.",
 | 
			
		||||
  "reminder_list": "List of reminders",
 | 
			
		||||
  "reminder_server_list": "List of server reminders",
 | 
			
		||||
  "reminder_deleted": "Reminder #{0} was deleted.",
 | 
			
		||||
@@ -966,7 +954,6 @@
 | 
			
		||||
  "module_description_xp": "Gain xp based on chat activity, check users' xp cards",
 | 
			
		||||
  "module_description_medusa": "**Bot Owner only.** Load, unload and handle dynamic modules. Read more [here](https://nadekobot.readthedocs.io/en/latest/medusa/creating-a-medusa/)",
 | 
			
		||||
  "module_description_missing": "Description is missing for this module.",
 | 
			
		||||
  "obsolete_use": "⚠ Obsolete, use {0} instead.",
 | 
			
		||||
  "purge_user_confirm": "Are you sure that you want to purge {0} from the database?",
 | 
			
		||||
  "expr_import_no_input": "Invalid input. No valid file upload or input text found.",
 | 
			
		||||
  "expr_import_invalid_data": "Unable to parse the file. Make sure it's a valid .yml file",
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user