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
2024-06-27 16:16:52 +12:00

1045 lines
No EOL
32 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.

This file contains Unicode characters that might be confused with other characters. 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 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 EllieBot.Modules.Utility;
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 IBankService _bank;
private readonly IRemindService _remind;
private readonly GamblingTxTracker _gamblingTxTracker;
private readonly IPatronageService _ps;
private IUserMessage rdMsg;
public Gambling(
IGamblingService gs,
DbService db,
ICurrencyService currency,
DiscordSocketClient client,
DownloadTracker tracker,
GamblingConfigService configService,
IBankService bank,
IRemindService remind,
IPatronageService patronage,
GamblingTxTracker gamblingTxTracker)
: base(configService)
{
_gs = gs;
_db = db;
_cs = currency;
_client = client;
_bank = bank;
_remind = remind;
_gamblingTxTracker = gamblingTxTracker;
_ps = patronage;
_enUsCulture = new CultureInfo("en-US", false).NumberFormat;
_enUsCulture.NumberDecimalDigits = 0;
_enUsCulture.NumberGroupSeparator = "";
_tracker = tracker;
_configService = configService;
}
public async Task<string> GetBalanceStringAsync(ulong userId)
{
var bal = await _cs.GetBalanceAsync(userId);
return N(bal);
}
[Cmd]
public async Task BetStats()
{
var stats = await _gamblingTxTracker.GetAllAsync();
var eb = _sender.CreateEmbed()
.WithOkColor();
var str = "` Feature `` Bet ``Paid Out`` RoI `\n";
str += "――――――――――――――――――――\n";
foreach (var stat in stats)
{
var perc = (stat.PaidOut / stat.Bet).ToString("P2", Culture);
str += $"`{stat.Feature.PadBoth(9)}`"
+ $"`{stat.Bet.ToString("N0").PadLeft(8, '')}`"
+ $"`{stat.PaidOut.ToString("N0").PadLeft(8, '')}`"
+ $"`{perc.PadLeft(6, '')}`\n";
}
var bet = stats.Sum(x => x.Bet);
var paidOut = stats.Sum(x => x.PaidOut);
if (bet == 0)
bet = 1;
var tPerc = (paidOut / bet).ToString("P2", Culture);
str += "――――――――――――――――――――\n";
str += $"` {("TOTAL").PadBoth(7)}` "
+ $"**{N(bet).PadLeft(8, '')}**"
+ $"**{N(paidOut).PadLeft(8, '')}**"
+ $"`{tPerc.PadLeft(6, '')}`";
eb.WithDescription(str);
await Response().Embed(eb).SendAsync();
}
[Cmd]
public async Task Economy()
{
var ec = await _service.GetEconomyAsync();
decimal onePercent = 0;
// This stops the top 1% from owning more than 100% of the money
if (ec.Cash > 0)
{
onePercent = ec.OnePercent / (ec.Cash - ec.Bot);
}
// [21:03] Bob Page: Kinda remids me of US economy
var embed = _sender.CreateEmbed()
.WithTitle(GetText(strs.economy_state))
.AddField(GetText(strs.currency_owned), N(ec.Cash - ec.Bot))
.AddField(GetText(strs.currency_one_percent), (onePercent * 100).ToString("F2") + "%")
.AddField(GetText(strs.currency_planted), N(ec.Planted))
.AddField(GetText(strs.owned_waifus_total), N(ec.Waifus))
.AddField(GetText(strs.bot_currency), N(ec.Bot))
.AddField(GetText(strs.bank_accounts), N(ec.Bank))
.AddField(GetText(strs.total), N(ec.Cash + ec.Planted + ec.Waifus + ec.Bank))
.WithOkColor();
// ec.Cash already contains ec.Bot as it's the total of all values in the CurrencyAmount column of the DiscordUser table
await Response().Embed(embed).SendAsync();
}
private async Task RemindTimelyAction(SocketMessageComponent smc, DateTime when)
{
var tt = TimestampTag.FromDateTime(when, TimestampTagStyles.Relative);
await _remind.AddReminderAsync(ctx.User.Id,
ctx.User.Id,
ctx.Guild?.Id,
true,
when,
GetText(strs.timely_time),
ReminderType.Timely);
await smc.RespondConfirmAsync(_sender, GetText(strs.remind_timely(tt)), ephemeral: true);
}
// Creates timely reminder button, parameter in hours.
private EllieInteractionBase CreateRemindMeInteraction(int period)
=> _inter
.Create(ctx.User.Id,
new ButtonBuilder(
label: "Remind me",
emote: Emoji.Parse("⏰"),
customId: "timely:remind_me"),
(smc) => RemindTimelyAction(smc, DateTime.UtcNow.Add(TimeSpan.FromHours(period)))
);
// Creates timely reminder button, parameter in milliseconds.
private EllieInteractionBase CreateRemindMeInteraction(double ms)
=> _inter
.Create(ctx.User.Id,
new ButtonBuilder(
label: "Remind me",
emote: Emoji.Parse("⏰"),
customId: "timely:remind_me"),
(smc) => RemindTimelyAction(smc, DateTime.UtcNow.Add(TimeSpan.FromMilliseconds(ms)))
);
[Cmd]
public async Task Timely()
{
var val = Config.Timely.Amount;
var period = Config.Timely.Cooldown;
if (val <= 0 || period <= 0)
{
await Response().Error(strs.timely_none).SendAsync();
return;
}
if (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 patron = await _ps.GetPatronAsync(ctx.User.Id);
var percentBonus = (_ps.PercentBonus(patron) / 100f);
val += (int)(val * percentBonus);
var inter = CreateRemindMeInteraction(period);
await _cs.AddAsync(ctx.User.Id, val, new("timely", "claim"));
await Response().Confirm(strs.timely(N(val), period)).Interaction(inter).SendAsync();
}
[Cmd]
[OwnerOnly]
public async Task TimelyReset()
{
await _service.RemoveAllTimelyClaimsAsync();
await Response().Confirm(strs.timely_reset).SendAsync();
}
[Cmd]
[OwnerOnly]
public async Task TimelySet(int amount, int period = 24)
{
if (amount < 0 || period < 0)
{
return;
}
_configService.ModifyConfig(gs =>
{
gs.Timely.Amount = amount;
gs.Timely.Cooldown = period;
});
if (amount == 0)
{
await Response().Confirm(strs.timely_set_none).SendAsync();
}
else
{
await Response()
.Confirm(strs.timely_set(Format.Bold(N(amount)), Format.Bold(period.ToString())))
.SendAsync();
}
}
[Cmd]
[RequireContext(ContextType.Guild)]
public async Task Raffle([Leftover] IRole role = null)
{
role ??= ctx.Guild.EveryoneRole;
var members = (await role.GetMembersAsync()).Where(u => u.Status != UserStatus.Offline);
var membersArray = members as IUser[] ?? members.ToArray();
if (membersArray.Length == 0)
{
return;
}
var usr = membersArray[new EllieRandom().Next(0, membersArray.Length)];
await Response()
.Confirm("🎟 " + GetText(strs.raffled_user),
$"**{usr.Username}**",
footer: $"ID: {usr.Id}")
.SendAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
public async Task RaffleAny([Leftover] IRole role = null)
{
role ??= ctx.Guild.EveryoneRole;
var members = await role.GetMembersAsync();
var membersArray = members as IUser[] ?? members.ToArray();
if (membersArray.Length == 0)
{
return;
}
var usr = membersArray[new EllieRandom().Next(0, membersArray.Length)];
await Response()
.Confirm("🎟 " + GetText(strs.raffled_user),
$"**{usr.Username}**",
footer: $"ID: {usr.Id}")
.SendAsync();
}
[Cmd]
[Priority(2)]
public Task CurrencyTransactions(int page = 1)
=> InternalCurrencyTransactions(ctx.User.Id, page);
[Cmd]
[OwnerOnly]
[Priority(0)]
public Task CurrencyTransactions([Leftover] IUser usr)
=> InternalCurrencyTransactions(usr.Id, 1);
[Cmd]
[OwnerOnly]
[Priority(1)]
public Task CurrencyTransactions(IUser usr, int page)
=> InternalCurrencyTransactions(usr.Id, page);
private async Task InternalCurrencyTransactions(ulong userId, int page)
{
if (--page < 0)
{
return;
}
List<CurrencyTransaction> trs;
await using (var uow = _db.GetDbContext())
{
trs = await uow.Set<CurrencyTransaction>().GetPageFor(userId, page);
}
var embed = _sender.CreateEmbed()
.WithTitle(GetText(strs.transactions(((SocketGuild)ctx.Guild)?.GetUser(userId)?.ToString()
?? $"{userId}")))
.WithOkColor();
var sb = new StringBuilder();
foreach (var tr in trs)
{
var change = tr.Amount >= 0 ? "🔵" : "🔴";
var kwumId = new kwum(tr.Id).ToString();
var date = $"#{Format.Code(kwumId)} `〖{GetFormattedCurtrDate(tr)}〗`";
sb.AppendLine($"\\{change} {date} {Format.Bold(N(tr.Amount))}");
var transactionString = GetHumanReadableTransaction(tr.Type, tr.Extra, tr.OtherId);
if (transactionString is not null)
{
sb.AppendLine(transactionString);
}
if (!string.IsNullOrWhiteSpace(tr.Note))
{
sb.AppendLine($"\t`Note:` {tr.Note.TrimTo(50)}");
}
}
embed.WithDescription(sb.ToString());
embed.WithFooter(GetText(strs.page(page + 1)));
await Response().Embed(embed).SendAsync();
}
private static string GetFormattedCurtrDate(CurrencyTransaction ct)
=> $"{ct.DateAdded:HH:mm yyyy-MM-dd}";
[Cmd]
public async Task CurrencyTransaction(kwum id)
{
int intId = id;
await using var uow = _db.GetDbContext();
var tr = await uow.Set<CurrencyTransaction>()
.ToLinqToDBTable()
.Where(x => x.Id == intId && x.UserId == ctx.User.Id)
.FirstOrDefaultAsync();
if (tr is null)
{
await Response().Error(strs.not_found).SendAsync();
return;
}
var eb = _sender.CreateEmbed().WithOkColor();
eb.WithAuthor(ctx.User);
eb.WithTitle(GetText(strs.transaction));
eb.WithDescription(new kwum(tr.Id).ToString());
eb.AddField("Amount", N(tr.Amount));
eb.AddField("Type", tr.Type, true);
eb.AddField("Extra", tr.Extra, true);
if (tr.OtherId is ulong other)
{
eb.AddField("From Id", other);
}
if (!string.IsNullOrWhiteSpace(tr.Note))
{
eb.AddField("Note", tr.Note);
}
eb.WithFooter(GetFormattedCurtrDate(tr));
await Response().Embed(eb).SendAsync();
}
private string GetHumanReadableTransaction(string type, string subType, ulong? maybeUserId)
=> (type, subType, maybeUserId) switch
{
("gift", var name, ulong userId) => GetText(strs.curtr_gift(name, userId)),
("award", var name, ulong userId) => GetText(strs.curtr_award(name, userId)),
("take", var name, ulong userId) => GetText(strs.curtr_take(name, userId)),
("blackjack", _, _) => $"Blackjack - {subType}",
("wheel", _, _) => $"Lucky Ladder - {subType}",
("lula", _, _) => $"Lucky Ladder - {subType}",
("rps", _, _) => $"Rock Paper Scissors - {subType}",
(null, _, _) => null,
(_, null, _) => null,
(_, _, ulong userId) => $"{type} - {subType} | [{userId}]",
_ => $"{type} - {subType}"
};
[Cmd]
[Priority(0)]
public async Task Cash(ulong userId)
{
var cur = await GetBalanceStringAsync(userId);
await Response().Confirm(strs.has(Format.Code(userId.ToString()), cur)).SendAsync();
}
private async Task BankAction(SocketMessageComponent smc)
{
var balance = await _bank.GetBalanceAsync(ctx.User.Id);
await N(balance)
.Pipe(strs.bank_balance)
.Pipe(GetText)
.Pipe(text => smc.RespondConfirmAsync(_sender, text, ephemeral: true));
}
private EllieInteractionBase CreateCashInteraction()
=> _inter.Create(ctx.User.Id,
new ButtonBuilder(
customId: "cash:bank_show_balance",
emote: new Emoji("🏦")),
BankAction);
[Cmd]
[Priority(1)]
public async Task Cash([Leftover] IUser user = null)
{
user ??= ctx.User;
var cur = await GetBalanceStringAsync(user.Id);
var inter = user == ctx.User
? CreateCashInteraction()
: null;
await Response()
.Confirm(
user.ToString()
.Pipe(Format.Bold)
.With(cur)
.Pipe(strs.has))
.Interaction(inter)
.SendAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
[Priority(0)]
public async Task Give(
[OverrideTypeReader(typeof(BalanceTypeReader))]
long amount,
IGuildUser receiver,
[Leftover] string msg)
{
if (amount <= 0 || ctx.User.Id == receiver.Id || receiver.IsBot)
{
return;
}
if (!await _cs.TransferAsync(_sender, ctx.User, receiver, amount, msg, N(amount)))
{
await Response().Error(strs.not_enough(CurrencySign)).SendAsync();
return;
}
await Response().Confirm(strs.gifted(N(amount), Format.Bold(receiver.ToString()), ctx.User)).SendAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
[Priority(1)]
public Task Give([OverrideTypeReader(typeof(BalanceTypeReader))] long amount, [Leftover] IGuildUser receiver)
=> Give(amount, receiver, null);
[Cmd]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
[Priority(0)]
public Task Award(long amount, IGuildUser usr, [Leftover] string msg)
=> Award(amount, usr.Id, msg);
[Cmd]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
[Priority(1)]
public Task Award(long amount, [Leftover] IGuildUser usr)
=> Award(amount, usr.Id);
[Cmd]
[OwnerOnly]
[Priority(2)]
public async Task Award(long amount, ulong usrId, [Leftover] string msg = null)
{
if (amount <= 0)
{
return;
}
var usr = await ((DiscordSocketClient)Context.Client).Rest.GetUserAsync(usrId);
if (usr is null)
{
await Response().Error(strs.user_not_found).SendAsync();
return;
}
await _cs.AddAsync(usr.Id, amount, new("award", ctx.User.ToString()!, msg, ctx.User.Id));
await Response().Confirm(strs.awarded(N(amount), $"<@{usrId}>", ctx.User)).SendAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
[Priority(3)]
public async Task Award(long amount, [Leftover] IRole role)
{
var users = (await ctx.Guild.GetUsersAsync()).Where(u => u.GetRoles().Contains(role)).ToList();
await _cs.AddBulkAsync(users.Select(x => x.Id).ToList(),
amount,
new("award", ctx.User.ToString()!, role.Name, ctx.User.Id));
await Response()
.Confirm(strs.mass_award(N(amount),
Format.Bold(users.Count.ToString()),
Format.Bold(role.Name)))
.SendAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
[Priority(0)]
public async Task Take(long amount, [Leftover] IRole role)
{
var users = (await role.GetMembersAsync()).ToList();
await _cs.RemoveBulkAsync(users.Select(x => x.Id).ToList(),
amount,
new("take", ctx.User.ToString()!, null, ctx.User.Id));
await Response()
.Confirm(strs.mass_take(N(amount),
Format.Bold(users.Count.ToString()),
Format.Bold(role.Name)))
.SendAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
[Priority(1)]
public async Task Take(long amount, [Leftover] IGuildUser user)
{
if (amount <= 0)
{
return;
}
var extra = new TxData("take", ctx.User.ToString()!, null, ctx.User.Id);
if (await _cs.RemoveAsync(user.Id, amount, extra))
{
await Response().Confirm(strs.take(N(amount), Format.Bold(user.ToString()))).SendAsync();
}
else
{
await Response().Error(strs.take_fail(N(amount), Format.Bold(user.ToString()), CurrencySign)).SendAsync();
}
}
[Cmd]
[OwnerOnly]
public async Task Take(long amount, [Leftover] ulong usrId)
{
if (amount <= 0)
{
return;
}
var extra = new TxData("take", ctx.User.ToString()!, null, ctx.User.Id);
if (await _cs.RemoveAsync(usrId, amount, extra))
{
await Response().Confirm(strs.take(N(amount), $"<@{usrId}>")).SendAsync();
}
else
{
await Response().Error(strs.take_fail(N(amount), Format.Code(usrId.ToString()), CurrencySign)).SendAsync();
}
}
[Cmd]
[RequireContext(ContextType.Guild)]
public async Task RollDuel(IUser u)
{
if (ctx.User.Id == u.Id)
{
return;
}
//since the challenge is created by another user, we need to reverse the ids
//if it gets removed, means challenge is accepted
if (_service.Duels.TryRemove((ctx.User.Id, u.Id), out var game))
{
await game.StartGame();
}
}
[Cmd]
[RequireContext(ContextType.Guild)]
public async Task RollDuel([OverrideTypeReader(typeof(BalanceTypeReader))] long amount, IUser u)
{
if (ctx.User.Id == u.Id)
{
return;
}
if (amount <= 0)
{
return;
}
var embed = _sender.CreateEmbed().WithOkColor().WithTitle(GetText(strs.roll_duel));
var description = string.Empty;
var game = new RollDuelGame(_cs, _client.CurrentUser.Id, ctx.User.Id, u.Id, amount);
//means challenge is just created
if (_service.Duels.TryGetValue((ctx.User.Id, u.Id), out var other))
{
if (other.Amount != amount)
{
await Response().Error(strs.roll_duel_already_challenged).SendAsync();
}
else
{
await RollDuel(u);
}
return;
}
if (_service.Duels.TryAdd((u.Id, ctx.User.Id), game))
{
game.OnGameTick += GameOnGameTick;
game.OnEnded += GameOnEnded;
await Response()
.Confirm(strs.roll_duel_challenge(Format.Bold(ctx.User.ToString()),
Format.Bold(u.ToString()),
Format.Bold(N(amount))))
.SendAsync();
}
async Task GameOnGameTick(RollDuelGame arg)
{
var rolls = arg.Rolls.Last();
description += $@"{Format.Bold(ctx.User.ToString())} rolled **{rolls.Item1}**
{Format.Bold(u.ToString())} rolled **{rolls.Item2}**
--
";
embed = embed.WithDescription(description);
if (rdMsg is null)
{
rdMsg = await Response().Embed(embed).SendAsync();
}
else
{
await rdMsg.ModifyAsync(x => { x.Embed = embed.Build(); });
}
}
async Task GameOnEnded(RollDuelGame rdGame, RollDuelGame.Reason reason)
{
try
{
if (reason == RollDuelGame.Reason.Normal)
{
var winner = rdGame.Winner == rdGame.P1 ? ctx.User : u;
description += $"\n**{winner}** Won {N((long)(rdGame.Amount * 2 * 0.98))}";
embed = embed.WithDescription(description);
await rdMsg.ModifyAsync(x => x.Embed = embed.Build());
}
else if (reason == RollDuelGame.Reason.Timeout)
{
await Response().Error(strs.roll_duel_timeout).SendAsync();
}
else if (reason == RollDuelGame.Reason.NoFunds)
{
await Response().Error(strs.roll_duel_no_funds).SendAsync();
}
}
finally
{
_service.Duels.TryRemove((u.Id, ctx.User.Id), out _);
}
}
}
[Cmd]
public async Task BetRoll([OverrideTypeReader(typeof(BalanceTypeReader))] long amount)
{
if (!await CheckBetMandatory(amount))
{
return;
}
var maybeResult = await _gs.BetRollAsync(ctx.User.Id, amount);
if (!maybeResult.TryPickT0(out var result, out _))
{
await Response().Error(strs.not_enough(CurrencySign)).SendAsync();
return;
}
var win = (long)result.Won;
string str;
if (win > 0)
{
str = GetText(strs.br_win(N(win), result.Threshold + (result.Roll == 100 ? " 👑" : "")));
}
else
{
str = GetText(strs.better_luck);
}
var eb = _sender.CreateEmbed()
.WithAuthor(ctx.User)
.WithDescription(Format.Bold(str))
.AddField(GetText(strs.roll2), result.Roll.ToString(CultureInfo.InvariantCulture))
.WithOkColor();
await Response().Embed(eb).SendAsync();
}
[Cmd]
[EllieOptions<LbOpts>]
[Priority(0)]
public Task Leaderboard(params string[] args)
=> Leaderboard(1, args);
[Cmd]
[EllieOptions<LbOpts>]
[Priority(1)]
public async Task Leaderboard(int page = 1, params string[] args)
{
if (--page < 0)
{
return;
}
var (opts, _) = OptionsParser.ParseFrom(new LbOpts(), args);
// List<DiscordUser> cleanRichest;
// it's pointless to have clean on dm context
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);
await using var uow = _db.GetDbContext();
var cleanRichest = await uow.Set<DiscordUser>()
.GetTopRichest(_client.CurrentUser.Id, 0, 1000);
var sg = (SocketGuild)ctx.Guild!;
return cleanRichest.Where(x => sg.GetUser(x.UserId) is not null).ToList();
}
else
{
await using var uow = _db.GetDbContext();
return await uow.Set<DiscordUser>().GetTopRichest(_client.CurrentUser.Id, curPage);
}
}
var res = Response()
.Paginated();
await Response()
.Paginated()
.PageItems(GetTopRichest)
.TotalElements(900)
.PageSize(9)
.CurrentPage(page)
.Page((toSend, curPage) =>
{
var embed = _sender.CreateEmbed()
.WithOkColor()
.WithTitle(CurrencySign + " " + GetText(strs.leaderboard));
if (!toSend.Any())
{
embed.WithDescription(GetText(strs.no_user_on_this_page));
return Task.FromResult(embed);
}
for (var i = 0; i < toSend.Count; i++)
{
var x = toSend[i];
var usrStr = x.ToString().TrimTo(20, true);
var j = i;
embed.AddField("#" + ((9 * curPage) + j + 1) + " " + usrStr, N(x.CurrencyAmount), true);
}
return Task.FromResult(embed);
})
.SendAsync();
}
public enum InputRpsPick : byte
{
R = 0,
Rock = 0,
Rocket = 0,
P = 1,
Paper = 1,
Paperclip = 1,
S = 2,
Scissors = 2
}
[Cmd]
public async Task Rps(InputRpsPick pick, [OverrideTypeReader(typeof(BalanceTypeReader))] long amount = default)
{
static string GetRpsPick(InputRpsPick p)
{
switch (p)
{
case InputRpsPick.R:
return "🚀";
case InputRpsPick.P:
return "📎";
default:
return "✂️";
}
}
if (!await CheckBetOptional(amount) || amount == 1)
return;
var res = await _gs.RpsAsync(ctx.User.Id, amount, (byte)pick);
if (!res.TryPickT0(out var result, out _))
{
await Response().Error(strs.not_enough(CurrencySign)).SendAsync();
return;
}
var embed = _sender.CreateEmbed();
string msg;
if (result.Result == RpsResultType.Draw)
{
msg = GetText(strs.rps_draw(GetRpsPick(pick)));
}
else if (result.Result == RpsResultType.Win)
{
if ((long)result.Won > 0)
embed.AddField(GetText(strs.won), N((long)result.Won));
msg = GetText(strs.rps_win(ctx.User.Mention,
GetRpsPick(pick),
GetRpsPick((InputRpsPick)result.ComputerPick)));
}
else
{
msg = GetText(strs.rps_win(ctx.Client.CurrentUser.Mention,
GetRpsPick((InputRpsPick)result.ComputerPick),
GetRpsPick(pick)));
}
embed
.WithOkColor()
.WithDescription(msg);
await Response().Embed(embed).SendAsync();
}
private static readonly ImmutableArray<string> _emojis =
new[] { "⬆", "↖", "⬅", "↙", "⬇", "↘", "➡", "↗" }.ToImmutableArray();
[Cmd]
public async Task LuckyLadder([OverrideTypeReader(typeof(BalanceTypeReader))] long amount)
{
if (!await CheckBetMandatory(amount))
return;
var res = await _gs.LulaAsync(ctx.User.Id, amount);
if (!res.TryPickT0(out var result, out _))
{
await Response().Error(strs.not_enough(CurrencySign)).SendAsync();
return;
}
var multis = result.Multipliers;
var sb = new StringBuilder();
foreach (var multi in multis)
{
sb.Append($"╠══╣");
if (multi == result.Multiplier)
sb.Append($"{Format.Bold($"x{multi:0.##}")} ⬅️");
else
sb.Append($"||x{multi:0.##}||");
sb.AppendLine();
}
var eb = _sender.CreateEmbed()
.WithOkColor()
.WithDescription(sb.ToString())
.AddField(GetText(strs.multiplier), $"{result.Multiplier:0.##}x", true)
.AddField(GetText(strs.won), $"{(long)result.Won}", true)
.WithAuthor(ctx.User);
await Response().Embed(eb).SendAsync();
}
public enum GambleTestTarget
{
Slot,
Betroll,
Betflip,
BetflipT,
BetDraw,
BetDrawHL,
BetDrawRB,
Lula,
Rps,
}
[Cmd]
[OwnerOnly]
public async Task BetTest()
{
var values = Enum.GetValues<GambleTestTarget>()
.Select(x => $"`{x}`")
.Join(", ");
await Response().Confirm(GetText(strs.available_tests), values).SendAsync();
}
[Cmd]
[OwnerOnly]
public async Task BetTest(GambleTestTarget target, int tests = 1000)
{
if (tests <= 0)
return;
await ctx.Channel.TriggerTypingAsync();
var streak = 0;
var maxW = 0;
var maxL = 0;
var dict = new Dictionary<decimal, int>();
for (var i = 0; i < tests; i++)
{
var multi = target switch
{
GambleTestTarget.BetDraw => (await _gs.BetDrawAsync(ctx.User.Id, 0, 1, 0)).AsT0.Multiplier,
GambleTestTarget.BetDrawRB => (await _gs.BetDrawAsync(ctx.User.Id, 0, null, 1)).AsT0.Multiplier,
GambleTestTarget.BetDrawHL => (await _gs.BetDrawAsync(ctx.User.Id, 0, 0, null)).AsT0.Multiplier,
GambleTestTarget.Slot => (await _gs.SlotAsync(ctx.User.Id, 0)).AsT0.Multiplier,
GambleTestTarget.Betflip => (await _gs.BetFlipAsync(ctx.User.Id, 0, 0)).AsT0.Multiplier,
GambleTestTarget.BetflipT => (await _gs.BetFlipAsync(ctx.User.Id, 0, 1)).AsT0.Multiplier,
GambleTestTarget.Lula => (await _gs.LulaAsync(ctx.User.Id, 0)).AsT0.Multiplier,
GambleTestTarget.Rps => (await _gs.RpsAsync(ctx.User.Id, 0, (byte)(i % 3))).AsT0.Multiplier,
GambleTestTarget.Betroll => (await _gs.BetRollAsync(ctx.User.Id, 0)).AsT0.Multiplier,
_ => throw new ArgumentOutOfRangeException(nameof(target))
};
if (dict.ContainsKey(multi))
dict[multi] += 1;
else
dict.Add(multi, 1);
if (multi < 1)
{
if (streak <= 0)
--streak;
else
streak = -1;
maxL = Math.Max(maxL, -streak);
}
else if (multi > 1)
{
if (streak >= 0)
++streak;
else
streak = 1;
maxW = Math.Max(maxW, streak);
}
}
var sb = new StringBuilder();
decimal payout = 0;
foreach (var key in dict.Keys.OrderByDescending(x => x))
{
sb.AppendLine($"x**{key}** occured `{dict[key]}` times. {dict[key] * 1.0f / tests * 100}%");
payout += key * dict[key];
}
sb.AppendLine();
sb.AppendLine($"Longest win streak: `{maxW}`");
sb.AppendLine($"Longest lose streak: `{maxL}`");
await Response()
.Confirm(GetText(strs.test_results_for(target)),
sb.ToString(),
footer: $"Total Bet: {tests} | Payout: {payout:F0} | {payout * 1.0M / tests * 100}%")
.SendAsync();
}
}