mirror of
				https://gitlab.com/Kwoth/nadekobot.git
				synced 2025-11-03 16:24:27 -05:00 
			
		
		
		
	Many changes. Will update merge request description with details
This commit is contained in:
		@@ -1,7 +1,7 @@
 | 
				
			|||||||
#nullable disable
 | 
					#nullable disable
 | 
				
			||||||
using Nadeko.Common;
 | 
					using Nadeko.Common;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace NadekoBot.Modules.Gambling.Common;
 | 
					namespace Nadeko.Econ;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public class Deck
 | 
					public class Deck
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
							
								
								
									
										7
									
								
								src/Nadeko.Econ/Gambling/Betdraw/BetdrawColorGuess.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/Nadeko.Econ/Gambling/Betdraw/BetdrawColorGuess.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					namespace Nadeko.Econ.Gambling.Betdraw;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public enum BetdrawColorGuess
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    Red,
 | 
				
			||||||
 | 
					    Black
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										83
									
								
								src/Nadeko.Econ/Gambling/Betdraw/BetdrawGame.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								src/Nadeko.Econ/Gambling/Betdraw/BetdrawGame.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,83 @@
 | 
				
			|||||||
 | 
					namespace Nadeko.Econ.Gambling.Betdraw;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public sealed class BetdrawGame
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    private static readonly NadekoRandom _rng = new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private const decimal SINGLE_GUESS_MULTI = 2.075M;
 | 
				
			||||||
 | 
					    private const decimal DOUBLE_GUESS_MULTI = 4.15M;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    public BetdrawGame()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    public BetdrawResult Draw(BetdrawValueGuess? val, BetdrawColorGuess? col, decimal amount)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if (val is null && col is null)
 | 
				
			||||||
 | 
					            throw new ArgumentNullException(nameof(val));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var card = new Deck().CardPool[_rng.Next(0, 52)];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var realVal = card.Number < 7
 | 
				
			||||||
 | 
					            ? BetdrawValueGuess.Low
 | 
				
			||||||
 | 
					            : BetdrawValueGuess.High;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var realCol = card.Suit is Deck.CardSuit.Diamonds or Deck.CardSuit.Hearts
 | 
				
			||||||
 | 
					            ? BetdrawColorGuess.Red
 | 
				
			||||||
 | 
					            : BetdrawColorGuess.Black;
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // if card is 7, autoloss
 | 
				
			||||||
 | 
					        if (card.Number == 7)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return new()
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                Won = 0M,
 | 
				
			||||||
 | 
					                Multiplier = 0M,
 | 
				
			||||||
 | 
					                ResultType = BetdrawResultType.Lose,
 | 
				
			||||||
 | 
					                Card = card,
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        byte win = 0;
 | 
				
			||||||
 | 
					        if (val is BetdrawValueGuess valGuess)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (realVal != valGuess)
 | 
				
			||||||
 | 
					                return new()
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    Won = 0M,
 | 
				
			||||||
 | 
					                    Multiplier = 0M,
 | 
				
			||||||
 | 
					                    ResultType = BetdrawResultType.Lose,
 | 
				
			||||||
 | 
					                    Card = card
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            ++win;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        if (col is BetdrawColorGuess colGuess)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (realCol != colGuess)
 | 
				
			||||||
 | 
					                return new()
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    Won = 0M,
 | 
				
			||||||
 | 
					                    Multiplier = 0M,
 | 
				
			||||||
 | 
					                    ResultType = BetdrawResultType.Lose,
 | 
				
			||||||
 | 
					                    Card = card
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            ++win;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var multi = win == 1
 | 
				
			||||||
 | 
					            ? SINGLE_GUESS_MULTI
 | 
				
			||||||
 | 
					            : DOUBLE_GUESS_MULTI;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return new()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            Won = amount * multi,
 | 
				
			||||||
 | 
					            Multiplier = multi,
 | 
				
			||||||
 | 
					            ResultType = BetdrawResultType.Win,
 | 
				
			||||||
 | 
					            Card = card
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										9
									
								
								src/Nadeko.Econ/Gambling/Betdraw/BetdrawResult.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/Nadeko.Econ/Gambling/Betdraw/BetdrawResult.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					namespace Nadeko.Econ.Gambling.Betdraw;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public readonly struct BetdrawResult
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public decimal Won { get; init; }
 | 
				
			||||||
 | 
					    public decimal Multiplier { get; init; }
 | 
				
			||||||
 | 
					    public BetdrawResultType ResultType { get; init; }
 | 
				
			||||||
 | 
					    public Deck.Card Card { get; init; }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										7
									
								
								src/Nadeko.Econ/Gambling/Betdraw/BetdrawResultType.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/Nadeko.Econ/Gambling/Betdraw/BetdrawResultType.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					namespace Nadeko.Econ.Gambling.Betdraw;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public enum BetdrawResultType
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    Win,
 | 
				
			||||||
 | 
					    Lose
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										7
									
								
								src/Nadeko.Econ/Gambling/Betdraw/BetdrawValueGuess.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/Nadeko.Econ/Gambling/Betdraw/BetdrawValueGuess.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					namespace Nadeko.Econ.Gambling.Betdraw;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public enum BetdrawValueGuess
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    High,
 | 
				
			||||||
 | 
					    Low,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -3,26 +3,31 @@
 | 
				
			|||||||
public sealed class BetflipGame
 | 
					public sealed class BetflipGame
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    private readonly decimal _winMulti;
 | 
					    private readonly decimal _winMulti;
 | 
				
			||||||
    private readonly NadekoRandom _rng;
 | 
					    private static readonly NadekoRandom _rng = new NadekoRandom();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public BetflipGame(decimal winMulti)
 | 
					    public BetflipGame(decimal winMulti)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        _winMulti = winMulti;
 | 
					        _winMulti = winMulti;
 | 
				
			||||||
        _rng = new NadekoRandom();
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public BetflipResult Flip(byte guess, decimal amount)
 | 
					    public BetflipResult Flip(byte guess, decimal amount)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var side = _rng.Next(0, 2);
 | 
					        var side = _rng.Next(0, 2);
 | 
				
			||||||
        decimal won = 0;
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        if (side == guess)
 | 
					        if (side == guess)
 | 
				
			||||||
            won = amount * _winMulti;
 | 
					        {
 | 
				
			||||||
 | 
					            return new BetflipResult()
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                Side = side,
 | 
				
			||||||
 | 
					                Won = amount * _winMulti,
 | 
				
			||||||
 | 
					                Multiplier = _winMulti
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return new BetflipResult()
 | 
					        return new BetflipResult()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            Side = side,
 | 
					            Side = side,
 | 
				
			||||||
            Won = won,
 | 
					            Won = 0,
 | 
				
			||||||
 | 
					            Multiplier = 0,
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -4,4 +4,5 @@ public readonly struct BetflipResult
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    public decimal Won { get; init; }
 | 
					    public decimal Won { get; init; }
 | 
				
			||||||
    public byte Side { get; init; }
 | 
					    public byte Side { get; init; }
 | 
				
			||||||
 | 
					    public decimal Multiplier { get; init; }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -2,15 +2,14 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
public sealed class LulaGame
 | 
					public sealed class LulaGame
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public static IReadOnlyList<decimal> DEFAULT_MULTIPLIERS = new[] { 1.7M, 1.5M, 0.2M, 0.1M, 0.3M, 0.5M, 1.2M, 2.4M };
 | 
					    private static readonly IReadOnlyList<decimal> DEFAULT_MULTIPLIERS = new[] { 1.7M, 1.5M, 0.2M, 0.1M, 0.3M, 0.5M, 1.2M, 2.4M };
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    private readonly IReadOnlyList<decimal> _multipliers;
 | 
					    private readonly IReadOnlyList<decimal> _multipliers;
 | 
				
			||||||
    private readonly NadekoRandom _rng;
 | 
					    private static readonly NadekoRandom _rng = new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public LulaGame(IReadOnlyList<decimal> multipliers)
 | 
					    public LulaGame(IReadOnlyList<decimal> multipliers)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        _multipliers = multipliers;
 | 
					        _multipliers = multipliers;
 | 
				
			||||||
        _rng = new();
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public LulaGame() : this(DEFAULT_MULTIPLIERS)
 | 
					    public LulaGame() : this(DEFAULT_MULTIPLIERS)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,6 @@
 | 
				
			|||||||
#nullable disable
 | 
					#nullable disable
 | 
				
			||||||
 | 
					using Nadeko.Econ;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace NadekoBot.Modules.Gambling.Common.Blackjack;
 | 
					namespace NadekoBot.Modules.Gambling.Common.Blackjack;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public class Blackjack
 | 
					public class Blackjack
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,6 @@
 | 
				
			|||||||
#nullable disable
 | 
					#nullable disable
 | 
				
			||||||
 | 
					using Nadeko.Econ;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace NadekoBot.Modules.Gambling.Common.Blackjack;
 | 
					namespace NadekoBot.Modules.Gambling.Common.Blackjack;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public abstract class Player
 | 
					public abstract class Player
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -35,9 +35,18 @@ public partial class Gambling
 | 
				
			|||||||
            using var img2 = await GetDiceAsync(num2);
 | 
					            using var img2 = await GetDiceAsync(num2);
 | 
				
			||||||
            using var img = new[] { img1, img2 }.Merge(out var format);
 | 
					            using var img = new[] { img1, img2 }.Merge(out var format);
 | 
				
			||||||
            await using var ms = await img.ToStreamAsync(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,
 | 
					            await ctx.Channel.SendFileAsync(ms,
 | 
				
			||||||
                $"dice.{format.FileExtensions.First()}",
 | 
					                fileName,
 | 
				
			||||||
                Format.Bold(ctx.User.ToString()) + " " + GetText(strs.dice_rolled(Format.Code(gen.ToString()))));
 | 
					                embed: eb.Build());
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [Cmd]
 | 
					        [Cmd]
 | 
				
			||||||
@@ -105,14 +114,18 @@ public partial class Gambling
 | 
				
			|||||||
            foreach (var d in dice)
 | 
					            foreach (var d in dice)
 | 
				
			||||||
                d.Dispose();
 | 
					                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,
 | 
					            await ctx.Channel.SendFileAsync(ms,
 | 
				
			||||||
                $"dice.{format.FileExtensions.First()}",
 | 
					                imageName,
 | 
				
			||||||
                Format.Bold(ctx.User.ToString())
 | 
					                embed: eb.Build());
 | 
				
			||||||
                + " "
 | 
					 | 
				
			||||||
                + 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")))));
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private async Task InternallDndRoll(string arg, bool ordered)
 | 
					        private async Task InternallDndRoll(string arg, bool ordered)
 | 
				
			||||||
@@ -130,9 +143,8 @@ public partial class Gambling
 | 
				
			|||||||
                    rolls.Add(_fateRolls[rng.Next(0, _fateRolls.Length)]);
 | 
					                    rolls.Add(_fateRolls[rng.Next(0, _fateRolls.Length)]);
 | 
				
			||||||
                var embed = _eb.Create()
 | 
					                var embed = _eb.Create()
 | 
				
			||||||
                               .WithOkColor()
 | 
					                               .WithOkColor()
 | 
				
			||||||
                               .WithDescription(ctx.User.Mention
 | 
					                               .WithAuthor(ctx.User)
 | 
				
			||||||
                                                + " "
 | 
					                               .WithDescription(GetText(strs.dice_rolled_num(Format.Bold(n1.ToString()))))
 | 
				
			||||||
                                                + GetText(strs.dice_rolled_num(Format.Bold(n1.ToString()))))
 | 
					 | 
				
			||||||
                               .AddField(Format.Bold("Result"),
 | 
					                               .AddField(Format.Bold("Result"),
 | 
				
			||||||
                                   string.Join(" ", rolls.Select(c => Format.Code($"[{c}]"))));
 | 
					                                   string.Join(" ", rolls.Select(c => Format.Code($"[{c}]"))));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -160,10 +172,9 @@ public partial class Gambling
 | 
				
			|||||||
                    var sum = arr.Sum();
 | 
					                    var sum = arr.Sum();
 | 
				
			||||||
                    var embed = _eb.Create()
 | 
					                    var embed = _eb.Create()
 | 
				
			||||||
                                   .WithOkColor()
 | 
					                                   .WithOkColor()
 | 
				
			||||||
                                   .WithDescription(ctx.User.Mention
 | 
					                                   .WithAuthor(ctx.User)
 | 
				
			||||||
                                                    + " "
 | 
					                                   .WithDescription(GetText(strs.dice_rolled_num(n1 + $"`1 - {n2}`")))
 | 
				
			||||||
                                                    + GetText(strs.dice_rolled_num(n1 + $"`1 - {n2}`")))
 | 
					                                   .AddField(Format.Bold(GetText(strs.rolls)),
 | 
				
			||||||
                                   .AddField(Format.Bold("Rolls"),
 | 
					 | 
				
			||||||
                                       string.Join(" ",
 | 
					                                       string.Join(" ",
 | 
				
			||||||
                                           (ordered ? arr.OrderBy(x => x).AsEnumerable() : arr).Select(x
 | 
					                                           (ordered ? arr.OrderBy(x => x).AsEnumerable() : arr).Select(x
 | 
				
			||||||
                                               => Format.Code(x.ToString()))))
 | 
					                                               => Format.Code(x.ToString()))))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,7 @@
 | 
				
			|||||||
#nullable disable
 | 
					#nullable disable
 | 
				
			||||||
 | 
					using Nadeko.Econ;
 | 
				
			||||||
using NadekoBot.Modules.Gambling.Common;
 | 
					using NadekoBot.Modules.Gambling.Common;
 | 
				
			||||||
 | 
					using NadekoBot.Modules.Gambling.Services;
 | 
				
			||||||
using SixLabors.ImageSharp;
 | 
					using SixLabors.ImageSharp;
 | 
				
			||||||
using SixLabors.ImageSharp.PixelFormats;
 | 
					using SixLabors.ImageSharp.PixelFormats;
 | 
				
			||||||
using Image = SixLabors.ImageSharp.Image;
 | 
					using Image = SixLabors.ImageSharp.Image;
 | 
				
			||||||
@@ -9,23 +11,23 @@ namespace NadekoBot.Modules.Gambling;
 | 
				
			|||||||
public partial class Gambling
 | 
					public partial class Gambling
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    [Group]
 | 
					    [Group]
 | 
				
			||||||
    public partial class DrawCommands : NadekoModule
 | 
					    public partial class DrawCommands : GamblingSubmodule<IGamblingService>
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        private static readonly ConcurrentDictionary<IGuild, Deck> _allDecks = new();
 | 
					        private static readonly ConcurrentDictionary<IGuild, Deck> _allDecks = new();
 | 
				
			||||||
        private readonly IImageCache _images;
 | 
					        private readonly IImageCache _images;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public DrawCommands(IImageCache images)
 | 
					        public DrawCommands(IImageCache images, GamblingConfigService gcs) : base(gcs)
 | 
				
			||||||
            => _images = images;
 | 
					            => _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)
 | 
					            if (count is < 1 or > 10)
 | 
				
			||||||
                throw new ArgumentOutOfRangeException(nameof(num));
 | 
					                throw new ArgumentOutOfRangeException(nameof(count));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var cards = guildId is null ? new() : _allDecks.GetOrAdd(ctx.Guild, _ => new());
 | 
					            var cards = guildId is null ? new() : _allDecks.GetOrAdd(ctx.Guild, _ => new());
 | 
				
			||||||
            var images = new List<Image<Rgba32>>();
 | 
					            var images = new List<Image<Rgba32>>();
 | 
				
			||||||
            var cardObjects = new List<Deck.Card>();
 | 
					            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)
 | 
					                if (cards.CardPool.Count == 0 && i != 0)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
@@ -43,22 +45,43 @@ public partial class Gambling
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                var currentCard = cards.Draw();
 | 
					                var currentCard = cards.Draw();
 | 
				
			||||||
                cardObjects.Add(currentCard);
 | 
					                cardObjects.Add(currentCard);
 | 
				
			||||||
                var cardName = currentCard.ToString().ToLowerInvariant().Replace(' ', '_');
 | 
					                var image = await GetCardImageAsync(currentCard);
 | 
				
			||||||
                images.Add(Image.Load(await File.ReadAllBytesAsync($"data/images/cards/{cardName}.jpg")));
 | 
					                images.Add(image);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var imgName = "cards.jpg";
 | 
				
			||||||
            using var img = images.Merge();
 | 
					            using var img = images.Merge();
 | 
				
			||||||
            foreach (var i in images)
 | 
					            foreach (var i in images)
 | 
				
			||||||
                i.Dispose();
 | 
					                i.Dispose();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var toSend = $"{Format.Bold(ctx.User.ToString())}";
 | 
					            var eb = _eb.Create(ctx)
 | 
				
			||||||
 | 
					                .WithOkColor();
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            var toSend = string.Empty;
 | 
				
			||||||
            if (cardObjects.Count == 5)
 | 
					            if (cardObjects.Count == 5)
 | 
				
			||||||
                toSend += $" drew `{Deck.GetHandValue(cardObjects)}`";
 | 
					                eb.AddField(GetText(strs.hand_value), Deck.GetHandValue(cardObjects), true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (guildId is not null)
 | 
					            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]
 | 
					        [Cmd]
 | 
				
			||||||
@@ -66,30 +89,24 @@ public partial class Gambling
 | 
				
			|||||||
        public async partial Task Draw(int num = 1)
 | 
					        public async partial Task Draw(int num = 1)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (num < 1)
 | 
					            if (num < 1)
 | 
				
			||||||
                num = 1;
 | 
					                return;
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
            if (num > 10)
 | 
					            if (num > 10)
 | 
				
			||||||
                num = 10;
 | 
					                num = 10;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var (imageStream, toSend) = await InternalDraw(num, ctx.Guild.Id);
 | 
					            await InternalDraw(num, ctx.Guild.Id);
 | 
				
			||||||
            await using (imageStream)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                await ctx.Channel.SendFileAsync(imageStream, num + " cards.jpg", toSend);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [Cmd]
 | 
					        [Cmd]
 | 
				
			||||||
        public async partial Task DrawNew(int num = 1)
 | 
					        public async partial Task DrawNew(int num = 1)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (num < 1)
 | 
					            if (num < 1)
 | 
				
			||||||
                num = 1;
 | 
					                return;
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
            if (num > 10)
 | 
					            if (num > 10)
 | 
				
			||||||
                num = 10;
 | 
					                num = 10;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var (imageStream, toSend) = await InternalDraw(num);
 | 
					            await InternalDraw(num);
 | 
				
			||||||
            await using (imageStream)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                await ctx.Channel.SendFileAsync(imageStream, num + " cards.jpg", toSend);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [Cmd]
 | 
					        [Cmd]
 | 
				
			||||||
@@ -108,5 +125,98 @@ public partial class Gambling
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            await ReplyConfirmLocalizedAsync(strs.deck_reshuffled);
 | 
					            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)
 | 
					            foreach (var i in imgs)
 | 
				
			||||||
                i.Dispose();
 | 
					                i.Dispose();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var msg = count != 1
 | 
					            var imgName = $"coins.{format.FileExtensions.First()}";
 | 
				
			||||||
                ? 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))));
 | 
					 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            await ctx.Channel.SendFileAsync(stream, $"{count} coins.{format.FileExtensions.First()}", msg);
 | 
					            var msg = count != 1
 | 
				
			||||||
 | 
					                ? 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,
 | 
				
			||||||
 | 
					                imgName,
 | 
				
			||||||
 | 
					                embed: eb.Build());
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [Cmd]
 | 
					        [Cmd]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -345,7 +345,8 @@ public partial class Gambling : GamblingModule<GamblingService>
 | 
				
			|||||||
            ("award", var name, ulong userId) => GetText(strs.curtr_award(name, userId)),
 | 
					            ("award", var name, ulong userId) => GetText(strs.curtr_award(name, userId)),
 | 
				
			||||||
            ("take", var name, ulong userId) => GetText(strs.curtr_take(name, userId)),
 | 
					            ("take", var name, ulong userId) => GetText(strs.curtr_take(name, userId)),
 | 
				
			||||||
            ("blackjack", _, _) => $"Blackjack - {subType}",
 | 
					            ("blackjack", _, _) => $"Blackjack - {subType}",
 | 
				
			||||||
            ("wheel", _, _) => $"Wheel Of Fortune - {subType}",
 | 
					            ("wheel", _, _) => $"Lucky Ladder - {subType}",
 | 
				
			||||||
 | 
					            ("lula", _, _) => $"Lucky Ladder - {subType}",
 | 
				
			||||||
            ("rps", _, _) => $"Rock Paper Scissors - {subType}",
 | 
					            ("rps", _, _) => $"Rock Paper Scissors - {subType}",
 | 
				
			||||||
            (null, _, _) => null,
 | 
					            (null, _, _) => null,
 | 
				
			||||||
            (_, null, _) => null,
 | 
					            (_, null, _) => null,
 | 
				
			||||||
@@ -671,7 +672,7 @@ public partial class Gambling : GamblingModule<GamblingService>
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        var eb = _eb.Create(ctx)
 | 
					        var eb = _eb.Create(ctx)
 | 
				
			||||||
            .WithAuthor(ctx.User)
 | 
					            .WithAuthor(ctx.User)
 | 
				
			||||||
            .WithFooter(str)
 | 
					            .WithDescription(Format.Bold(str))
 | 
				
			||||||
            .AddField(GetText(strs.roll2), result.Roll.ToString(CultureInfo.InvariantCulture))
 | 
					            .AddField(GetText(strs.roll2), result.Roll.ToString(CultureInfo.InvariantCulture))
 | 
				
			||||||
            .WithOkColor();
 | 
					            .WithOkColor();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -772,7 +773,6 @@ public partial class Gambling : GamblingModule<GamblingService>
 | 
				
			|||||||
        Scissors = 2
 | 
					        Scissors = 2
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // todo check if trivia is being disposed
 | 
					 | 
				
			||||||
    [Cmd]
 | 
					    [Cmd]
 | 
				
			||||||
    public async partial Task Rps(InputRpsPick pick, ShmartNumber amount = default)
 | 
					    public async partial Task Rps(InputRpsPick pick, ShmartNumber amount = default)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
@@ -839,7 +839,7 @@ public partial class Gambling : GamblingModule<GamblingService>
 | 
				
			|||||||
        if (!await CheckBetMandatory(amount))
 | 
					        if (!await CheckBetMandatory(amount))
 | 
				
			||||||
            return;
 | 
					            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 _))
 | 
					        if (!res.TryPickT0(out var result, out _))
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
 | 
					            await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
 | 
				
			||||||
@@ -871,4 +871,99 @@ public partial class Gambling : GamblingModule<GamblingService>
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        await ctx.Channel.EmbedAsync(eb);
 | 
					        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.Db.Models;
 | 
				
			||||||
using NadekoBot.Modules.Gambling.Common;
 | 
					using NadekoBot.Modules.Gambling.Common;
 | 
				
			||||||
using NadekoBot.Modules.Gambling.Services;
 | 
					using NadekoBot.Modules.Gambling.Services;
 | 
				
			||||||
@@ -73,178 +73,58 @@ public partial class Gambling
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            await ctx.Channel.EmbedAsync(embed);
 | 
					            await ctx.Channel.EmbedAsync(embed);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        public sealed class SlotInteraction : NInteraction
 | 
				
			||||||
        [Cmd]
 | 
					 | 
				
			||||||
        [OwnerOnly]
 | 
					 | 
				
			||||||
        public async partial Task SlotTest(int tests = 1000)
 | 
					 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (tests <= 0)
 | 
					            public SlotInteraction(DiscordSocketClient client, ulong userId, Func<SocketMessageComponent, Task> action) : base(client, userId, action)
 | 
				
			||||||
                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++)
 | 
					 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                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();
 | 
					            protected override NadekoInteractionData Data { get; } = new(Emoji.Parse("🔁"),
 | 
				
			||||||
            decimal payout = 0;
 | 
					                "slot:again",
 | 
				
			||||||
            foreach (var key in dict.Keys.OrderByDescending(x => x))
 | 
					                "Pull Again");
 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                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}%");
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [Cmd]
 | 
					        [Cmd]
 | 
				
			||||||
        public async partial Task Slot(ShmartNumber amount)
 | 
					        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))
 | 
					            if (!_runningUsers.Add(ctx.User.Id))
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            try
 | 
					            try
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                if (!await CheckBetMandatory(amount))
 | 
					                if (await InternalSlotAsync(amount) is not SlotResult result)
 | 
				
			||||||
                    return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                await ctx.Channel.TriggerTypingAsync();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                var maybeResult = await _service.SlotAsync(ctx.User.Id, amount);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (!maybeResult.TryPickT0(out var result, out var error))
 | 
					 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    if (error == GamblingError.InsufficientFunds)
 | 
					                    await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
 | 
				
			||||||
                        await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    return;
 | 
					                    return;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                lock (_slotStatsLock)
 | 
					                var msg = GetSlotMessageInternal(result);
 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    totalBet += amount;
 | 
					 | 
				
			||||||
                    totalPaidOut += result.Won;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                long ownedAmount;
 | 
					                using var image = await GenerateSlotImageAsync(amount, result);
 | 
				
			||||||
                await using (var uow = _db.GetDbContext())
 | 
					                await using var imgStream = await image.ToStreamAsync();
 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    ownedAmount = uow.Set<DiscordUser>().FirstOrDefault(x => x.UserId == ctx.User.Id)?.CurrencyAmount
 | 
					 | 
				
			||||||
                                  ?? 0;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                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();
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
                    bgImage.Mutate(x => x.DrawText(new()
 | 
					                // var inter = slotInteraction.GetInteraction();
 | 
				
			||||||
                        {
 | 
					                await ctx.Channel.SendFileAsync(imgStream,
 | 
				
			||||||
                            TextOptions = new()
 | 
					                    "result.png",
 | 
				
			||||||
                            {
 | 
					                    embed: eb.Build()
 | 
				
			||||||
                                HorizontalAlignment = HorizontalAlignment.Center,
 | 
					                    // components: inter.CreateComponent()
 | 
				
			||||||
                                VerticalAlignment = VerticalAlignment.Center,
 | 
					                );
 | 
				
			||||||
                                WrapTextWidth = 140
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
                        },
 | 
					 | 
				
			||||||
                        ((long)result.Won).ToString(),
 | 
					 | 
				
			||||||
                        _fonts.DottyFont.CreateFont(65),
 | 
					 | 
				
			||||||
                        fontColor,
 | 
					 | 
				
			||||||
                        new(227, 92)));
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    var bottomFont = _fonts.DottyFont.CreateFont(50);
 | 
					                // await inter.RunAsync(resMsg);
 | 
				
			||||||
 | 
					 | 
				
			||||||
                    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);
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            finally
 | 
					            finally
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@@ -252,5 +132,156 @@ public partial class Gambling
 | 
				
			|||||||
                _runningUsers.TryRemove(ctx.User.Id);
 | 
					                _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,4 +1,6 @@
 | 
				
			|||||||
namespace NadekoBot.Modules.Gambling.Common;
 | 
					using Nadeko.Econ;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace NadekoBot.Modules.Gambling.Common;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public class QuadDeck : Deck
 | 
					public class QuadDeck : Deck
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
#nullable disable
 | 
					#nullable disable
 | 
				
			||||||
using Nadeko.Econ.Gambling;
 | 
					using Nadeko.Econ.Gambling;
 | 
				
			||||||
 | 
					using Nadeko.Econ.Gambling.Betdraw;
 | 
				
			||||||
using Nadeko.Econ.Gambling.Rps;
 | 
					using Nadeko.Econ.Gambling.Rps;
 | 
				
			||||||
using OneOf;
 | 
					using OneOf;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -7,10 +8,11 @@ namespace NadekoBot.Modules.Gambling;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
public interface IGamblingService
 | 
					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<BetrollResult, GamblingError>> BetRollAsync(ulong userId, long amount);
 | 
				
			||||||
    Task<OneOf<BetflipResult, GamblingError>> BetFlipAsync(ulong userId, long amount, byte guess);
 | 
					    Task<OneOf<BetflipResult, GamblingError>> BetFlipAsync(ulong userId, long amount, byte guess);
 | 
				
			||||||
    Task<OneOf<SlotResult, GamblingError>> SlotAsync(ulong userId, long amount);
 | 
					    Task<OneOf<SlotResult, GamblingError>> SlotAsync(ulong userId, long amount);
 | 
				
			||||||
    Task<FlipResult[]> FlipAsync(int count);
 | 
					    Task<FlipResult[]> FlipAsync(int count);
 | 
				
			||||||
    Task<OneOf<RpsResult, GamblingError>> RpsAsync(ulong userId, long amount, byte pick);
 | 
					    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
 | 
					#nullable disable
 | 
				
			||||||
using Nadeko.Econ.Gambling;
 | 
					using Nadeko.Econ.Gambling;
 | 
				
			||||||
 | 
					using Nadeko.Econ.Gambling.Betdraw;
 | 
				
			||||||
using Nadeko.Econ.Gambling.Rps;
 | 
					using Nadeko.Econ.Gambling.Rps;
 | 
				
			||||||
using NadekoBot.Modules.Gambling.Services;
 | 
					using NadekoBot.Modules.Gambling.Services;
 | 
				
			||||||
using OneOf;
 | 
					using OneOf;
 | 
				
			||||||
@@ -17,9 +18,7 @@ public sealed class NewGamblingService : IGamblingService, INService
 | 
				
			|||||||
        _cs = cs;
 | 
					        _cs = cs;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // todo input checks
 | 
					    public async Task<OneOf<LuLaResult, GamblingError>> LulaAsync(ulong userId, long amount)
 | 
				
			||||||
    // todo ladder of fortune
 | 
					 | 
				
			||||||
    public async Task<OneOf<LuLaResult, GamblingError>> WofAsync(ulong userId, long amount)
 | 
					 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        if (amount < 0)
 | 
					        if (amount < 0)
 | 
				
			||||||
            throw new ArgumentOutOfRangeException(nameof(amount));
 | 
					            throw new ArgumentOutOfRangeException(nameof(amount));
 | 
				
			||||||
@@ -81,6 +80,9 @@ public sealed class NewGamblingService : IGamblingService, INService
 | 
				
			|||||||
        if (amount < 0)
 | 
					        if (amount < 0)
 | 
				
			||||||
            throw new ArgumentOutOfRangeException(nameof(amount));
 | 
					            throw new ArgumentOutOfRangeException(nameof(amount));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (guess > 1)
 | 
				
			||||||
 | 
					            throw new ArgumentOutOfRangeException(nameof(guess));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (amount > 0)
 | 
					        if (amount > 0)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var isTakeSuccess = await _cs.RemoveAsync(userId, amount, new("betflip", "bet"));
 | 
					            var isTakeSuccess = await _cs.RemoveAsync(userId, amount, new("betflip", "bet"));
 | 
				
			||||||
@@ -103,6 +105,42 @@ public sealed class NewGamblingService : IGamblingService, INService
 | 
				
			|||||||
        return result;
 | 
					        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)
 | 
					    public async Task<OneOf<SlotResult, GamblingError>> SlotAsync(ulong userId, long amount)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        if (amount < 0)
 | 
					        if (amount < 0)
 | 
				
			||||||
@@ -132,6 +170,9 @@ public sealed class NewGamblingService : IGamblingService, INService
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    public Task<FlipResult[]> FlipAsync(int count)
 | 
					    public Task<FlipResult[]> FlipAsync(int count)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 | 
					        if (count < 1)
 | 
				
			||||||
 | 
					            throw new ArgumentOutOfRangeException(nameof(count));
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
        var game = new BetflipGame(0);
 | 
					        var game = new BetflipGame(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var results = new FlipResult[count];
 | 
					        var results = new FlipResult[count];
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -108,7 +108,7 @@ public partial class NSFW : NadekoModule<ISearchImagesService>
 | 
				
			|||||||
                return t;
 | 
					                return t;
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        await ReplyConfirmLocalizedAsync(strs.autohentai_started(interval, string.Join(", ", tags)));
 | 
					        await SendConfirmAsync($"Autohentai started. Interval: {interval}, Tags: {string.Join(", ", tags)}");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [Cmd]
 | 
					    [Cmd]
 | 
				
			||||||
 
 | 
				
			|||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -653,7 +653,7 @@ rolluo:
 | 
				
			|||||||
    - "7"
 | 
					    - "7"
 | 
				
			||||||
    - "3d5"
 | 
					    - "3d5"
 | 
				
			||||||
nroll:
 | 
					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:
 | 
					  args:
 | 
				
			||||||
    - "5"
 | 
					    - "5"
 | 
				
			||||||
    - "5-15"
 | 
					    - "5-15"
 | 
				
			||||||
@@ -2232,3 +2232,22 @@ eval:
 | 
				
			|||||||
    - "123 / 4.5f"
 | 
					    - "123 / 4.5f"
 | 
				
			||||||
    - "await ctx.OkAsync();"
 | 
					    - "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.",
 | 
					  "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.",
 | 
					  "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.",
 | 
					  "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_i": "I have insufficient permissions.",
 | 
				
			||||||
  "insuf_perms_u": "You have insufficient permissions.",
 | 
					  "insuf_perms_u": "You have insufficient permissions.",
 | 
				
			||||||
  "invalid_format": "Invalid input format.",
 | 
					  "invalid_format": "Invalid input format.",
 | 
				
			||||||
@@ -216,11 +215,13 @@
 | 
				
			|||||||
  "better_luck": "Better luck next time ^_^",
 | 
					  "better_luck": "Better luck next time ^_^",
 | 
				
			||||||
  "br_win": "Congratulations! You won {0} for rolling above {1}",
 | 
					  "br_win": "Congratulations! You won {0} for rolling above {1}",
 | 
				
			||||||
  "deck_reshuffled": "Deck reshuffled.",
 | 
					  "deck_reshuffled": "Deck reshuffled.",
 | 
				
			||||||
  "flipped": "flipped {0}.",
 | 
					  "flipped": "Flipped {0}",
 | 
				
			||||||
  "flip_guess": "You guessed it! You won {0}",
 | 
					  "flip_guess": "You guessed it! You won {0}",
 | 
				
			||||||
  "flip_invalid": "Invalid number specified. You can flip 1 to {0} coins.",
 | 
					  "flip_invalid": "Invalid number specified. You can flip 1 to {0} coins.",
 | 
				
			||||||
  "flip_results": "Flipped {0} coins. {1} heads, {2} tails.",
 | 
					  "flip_results": "Flipped {0} coins. {1} heads, {2} tails.",
 | 
				
			||||||
  "cards_left": "{0} cards left in the deck.",
 | 
					  "cards_left": "{0} cards left in the deck.",
 | 
				
			||||||
 | 
					  "cards": "Cards",
 | 
				
			||||||
 | 
					  "hand_value": "Hand value",
 | 
				
			||||||
  "gifted": "has gifted {0} to {1}",
 | 
					  "gifted": "has gifted {0} to {1}",
 | 
				
			||||||
  "has": "{0} has {1}",
 | 
					  "has": "{0} has {1}",
 | 
				
			||||||
  "heads": "Head",
 | 
					  "heads": "Head",
 | 
				
			||||||
@@ -232,11 +233,13 @@
 | 
				
			|||||||
  "no_more_cards": "No more cards in the deck.",
 | 
					  "no_more_cards": "No more cards in the deck.",
 | 
				
			||||||
  "raffled_user": "Raffled user",
 | 
					  "raffled_user": "Raffled user",
 | 
				
			||||||
  "roll2": "Roll",
 | 
					  "roll2": "Roll",
 | 
				
			||||||
  "slot_bet": "Bet",
 | 
					  "rolls": "Rolls",
 | 
				
			||||||
  "slot_jackpot": "WOAAHHHHHH!!! Congratulations!!! x{0}",
 | 
					  "slot_jackpot": "WOAAHHHHHH!!! Congratulations!!! x{0}",
 | 
				
			||||||
  "slot_single": "A single {0}, x{1}",
 | 
					  "slot_single": "A single {0}, x{1}",
 | 
				
			||||||
  "slot_three": "Wow! Lucky! Three of a kind! x{0}",
 | 
					  "slot_three": "Wow! Lucky! Three of a kind! x{0}",
 | 
				
			||||||
  "slot_two": "Good job! Two {0} - bet x{1}",
 | 
					  "slot_two": "Good job! Two {0} - bet x{1}",
 | 
				
			||||||
 | 
					  "available_tests": "Available Tests",
 | 
				
			||||||
 | 
					  "test_results_for": "Test results for {0}",
 | 
				
			||||||
  "won": "Won",
 | 
					  "won": "Won",
 | 
				
			||||||
  "multiplier": "Multiplier",
 | 
					  "multiplier": "Multiplier",
 | 
				
			||||||
  "tails": "Tail",
 | 
					  "tails": "Tail",
 | 
				
			||||||
@@ -256,7 +259,6 @@
 | 
				
			|||||||
  "usage": "Usage",
 | 
					  "usage": "Usage",
 | 
				
			||||||
  "options": "Options",
 | 
					  "options": "Options",
 | 
				
			||||||
  "requires": "Requires",
 | 
					  "requires": "Requires",
 | 
				
			||||||
  "autohentai_started": "Autohentai started. Reposting every {0}s with one of the following tags:\n{1}",
 | 
					 | 
				
			||||||
  "tag": "Tag",
 | 
					  "tag": "Tag",
 | 
				
			||||||
  "animal_race": "Animal race",
 | 
					  "animal_race": "Animal race",
 | 
				
			||||||
  "animal_race_failed": "Failed to start since there was not enough participants.",
 | 
					  "animal_race_failed": "Failed to start since there was not enough participants.",
 | 
				
			||||||
@@ -319,7 +321,6 @@
 | 
				
			|||||||
  "rps_win": "{0} won! {1} beats {2}",
 | 
					  "rps_win": "{0} won! {1} beats {2}",
 | 
				
			||||||
  "submissions_closed": "Submissions closed",
 | 
					  "submissions_closed": "Submissions closed",
 | 
				
			||||||
  "animal_race_already_started": "Animal Race is already running.",
 | 
					  "animal_race_already_started": "Animal Race is already running.",
 | 
				
			||||||
  "total_average": "Total: {0} Average: {1}",
 | 
					 | 
				
			||||||
  "category": "Category",
 | 
					  "category": "Category",
 | 
				
			||||||
  "cleverbot_disabled": "Disabled cleverbot on this server.",
 | 
					  "cleverbot_disabled": "Disabled cleverbot on this server.",
 | 
				
			||||||
  "cleverbot_enabled": "Enabled 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_enabled": "Currency generation has been enabled on this channel.",
 | 
				
			||||||
  "curgen_pl": "{0} random {1} appeared!",
 | 
					  "curgen_pl": "{0} random {1} appeared!",
 | 
				
			||||||
  "curgen_sn": "A random {0} appeared!",
 | 
					  "curgen_sn": "A random {0} appeared!",
 | 
				
			||||||
  "failed_loading_question": "Failed loading a question.",
 | 
					 | 
				
			||||||
  "game_started": "Game started",
 | 
					  "game_started": "Game started",
 | 
				
			||||||
  "hangman_game_started": "Hangman game started",
 | 
					  "hangman_game_started": "Hangman game started",
 | 
				
			||||||
  "hangman_running": "Hangman game already running on this channel.",
 | 
					  "hangman_running": "Hangman game already running on this channel.",
 | 
				
			||||||
@@ -357,10 +357,7 @@
 | 
				
			|||||||
  "vs": "{0} vs {1}",
 | 
					  "vs": "{0} vs {1}",
 | 
				
			||||||
  "attempting_to_queue": "Attempting to queue {0} tracks...",
 | 
					  "attempting_to_queue": "Attempting to queue {0} tracks...",
 | 
				
			||||||
  "dir_queue_complete": "Directory queue complete.",
 | 
					  "dir_queue_complete": "Directory queue complete.",
 | 
				
			||||||
  "fairplay": "Fairplay",
 | 
					 | 
				
			||||||
  "finished_track": "Track Finished",
 | 
					  "finished_track": "Track Finished",
 | 
				
			||||||
  "fp_disabled": "Fair play disabled.",
 | 
					 | 
				
			||||||
  "fp_enabled": "Fair play enabled.",
 | 
					 | 
				
			||||||
  "from_position": "From position",
 | 
					  "from_position": "From position",
 | 
				
			||||||
  "id": "Id",
 | 
					  "id": "Id",
 | 
				
			||||||
  "now_playing": "Now playing",
 | 
					  "now_playing": "Now playing",
 | 
				
			||||||
@@ -447,10 +444,8 @@
 | 
				
			|||||||
  "word_filter_channel_on": "Word filtering enabled on this channel.",
 | 
					  "word_filter_channel_on": "Word filtering enabled on this channel.",
 | 
				
			||||||
  "word_filter_server_off": "Word filtering disabled on this server.",
 | 
					  "word_filter_server_off": "Word filtering disabled on this server.",
 | 
				
			||||||
  "word_filter_server_on": "Word filtering enabled on this server.",
 | 
					  "word_filter_server_on": "Word filtering enabled on this server.",
 | 
				
			||||||
  "avatar_none": "User {0} has no avatar set.",
 | 
					 | 
				
			||||||
  "abilities": "Abilities",
 | 
					  "abilities": "Abilities",
 | 
				
			||||||
  "anime_no_fav": "No favorite anime yet",
 | 
					  "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_removed": "your auto-translate language has been removed.",
 | 
				
			||||||
  "atl_set": "Your auto-translate language has been set to {0}>{1}",
 | 
					  "atl_set": "Your auto-translate language has been set to {0}>{1}",
 | 
				
			||||||
  "atl_started": "Started automatic translation of messages on this channel.",
 | 
					  "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.",
 | 
					  "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.",
 | 
					  "bad_input_format": "Bad input format, or something went wrong.",
 | 
				
			||||||
  "card_not_found": "Couldn't find that card.",
 | 
					  "card_not_found": "Couldn't find that card.",
 | 
				
			||||||
 | 
					  "card": "Card",
 | 
				
			||||||
 | 
					  "guess": "Guess",
 | 
				
			||||||
  "catfact": "fact",
 | 
					  "catfact": "fact",
 | 
				
			||||||
  "chapters": "Chapters",
 | 
					  "chapters": "Chapters",
 | 
				
			||||||
  "comic_number": "Comic #",
 | 
					  "comic_number": "Comic #",
 | 
				
			||||||
@@ -483,7 +480,6 @@
 | 
				
			|||||||
  "height_weight": "Height/Weight",
 | 
					  "height_weight": "Height/Weight",
 | 
				
			||||||
  "height_weight_val": "{0}m/{1}kg",
 | 
					  "height_weight_val": "{0}m/{1}kg",
 | 
				
			||||||
  "humidity": "Humidity",
 | 
					  "humidity": "Humidity",
 | 
				
			||||||
  "image_search_for": "Image search for:",
 | 
					 | 
				
			||||||
  "imdb_fail": "Failed to find that movie.",
 | 
					  "imdb_fail": "Failed to find that movie.",
 | 
				
			||||||
  "invalid_lang": "Invalid source or target language.",
 | 
					  "invalid_lang": "Invalid source or target language.",
 | 
				
			||||||
  "jokes_not_loaded": "Jokes not loaded.",
 | 
					  "jokes_not_loaded": "Jokes not loaded.",
 | 
				
			||||||
@@ -506,7 +502,6 @@
 | 
				
			|||||||
  "pokemon_none": "No pokemon found.",
 | 
					  "pokemon_none": "No pokemon found.",
 | 
				
			||||||
  "rating": "Rating",
 | 
					  "rating": "Rating",
 | 
				
			||||||
  "score": "Score:",
 | 
					  "score": "Score:",
 | 
				
			||||||
  "search_for": "Search for:",
 | 
					 | 
				
			||||||
  "short_url": "Short url",
 | 
					  "short_url": "Short url",
 | 
				
			||||||
  "something_went_wrong": "Something went wrong.",
 | 
					  "something_went_wrong": "Something went wrong.",
 | 
				
			||||||
  "specify_search_params": "Please specify search parameters.",
 | 
					  "specify_search_params": "Please specify search parameters.",
 | 
				
			||||||
@@ -793,7 +788,6 @@
 | 
				
			|||||||
  "config_prop_not_found": "Property {0} not found on {1} configuration",
 | 
					  "config_prop_not_found": "Property {0} not found on {1} configuration",
 | 
				
			||||||
  "config_list": "Config list",
 | 
					  "config_list": "Config list",
 | 
				
			||||||
  "bot_strings_reloaded": "Bot strings have been reloaded.",
 | 
					  "bot_strings_reloaded": "Bot strings have been reloaded.",
 | 
				
			||||||
  "level_req": "Level Req.",
 | 
					 | 
				
			||||||
  "xpn_setting_global": "Global Level-Up notifications",
 | 
					  "xpn_setting_global": "Global Level-Up notifications",
 | 
				
			||||||
  "xpn_setting_server": "Server Level-Up notifications",
 | 
					  "xpn_setting_server": "Server Level-Up notifications",
 | 
				
			||||||
  "xpn_notif_channel": "In the channel where you sent the last message.",
 | 
					  "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_in_progress": "Banning {0} users...",
 | 
				
			||||||
  "mass_ban_completed": "Banned {0} users.",
 | 
					  "mass_ban_completed": "Banned {0} users.",
 | 
				
			||||||
  "mass_kill_completed": "Mass Banning and Blacklisting of {0} users is complete.",
 | 
					  "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_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.",
 | 
					  "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}",
 | 
					  "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.",
 | 
					  "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.",
 | 
					  "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.",
 | 
					  "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_list": "List of reminders",
 | 
				
			||||||
  "reminder_server_list": "List of server reminders",
 | 
					  "reminder_server_list": "List of server reminders",
 | 
				
			||||||
  "reminder_deleted": "Reminder #{0} was deleted.",
 | 
					  "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_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_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.",
 | 
					  "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?",
 | 
					  "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_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",
 | 
					  "expr_import_invalid_data": "Unable to parse the file. Make sure it's a valid .yml file",
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user