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