This repository has been archived on 2024-12-22. You can view files and clone it, but cannot push or open issues or pull requests.
elliebot/src/EllieBot/Modules/Gambling/Gambling.cs
Toastie cd1c461690
fixed .iam
fixed .sclr not being respected on many different commands
.rps now also has the amount bet
2024-11-29 19:22:10 +13:00

1084 lines
No EOL
33 KiB
C#
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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