2024-09-21 00:44:21 +12:00
#nullable disable
using LinqToDB ;
using LinqToDB.EntityFrameworkCore ;
2024-10-03 18:46:10 +13:00
using LinqToDB.Tools ;
2024-09-21 00:44:21 +12:00
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 ;
2024-11-05 20:38:37 +13:00
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 ;
2024-09-21 00:44:21 +12:00
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 ;
2024-11-02 01:31:06 +13:00
private readonly FontProvider _fonts ;
2024-09-21 00:44:21 +12:00
private readonly IBankService _bank ;
private readonly IRemindService _remind ;
private readonly GamblingTxTracker _gamblingTxTracker ;
private readonly IPatronageService _ps ;
2024-11-07 18:28:18 +13:00
private readonly RakebackService _rb ;
2024-09-21 00:44:21 +12:00
public Gambling (
IGamblingService gs ,
DbService db ,
ICurrencyService currency ,
DiscordSocketClient client ,
DownloadTracker tracker ,
GamblingConfigService configService ,
2024-11-02 01:31:06 +13:00
FontProvider fonts ,
2024-09-21 00:44:21 +12:00
IBankService bank ,
IRemindService remind ,
IPatronageService patronage ,
2024-11-07 18:28:18 +13:00
GamblingTxTracker gamblingTxTracker ,
RakebackService rb )
2024-09-21 00:44:21 +12:00
: base ( configService )
{
_gs = gs ;
_db = db ;
_cs = currency ;
_client = client ;
_bank = bank ;
_remind = remind ;
_gamblingTxTracker = gamblingTxTracker ;
2024-11-07 18:28:18 +13:00
_rb = rb ;
2024-09-21 00:44:21 +12:00
_ps = patronage ;
2024-11-03 23:59:58 +13:00
_rng = new EllieRandom ( ) ;
2024-09-21 00:44:21 +12:00
_enUsCulture = new CultureInfo ( "en-US" , false ) . NumberFormat ;
_enUsCulture . NumberDecimalDigits = 0 ;
_enUsCulture . NumberGroupSeparator = " " ;
_tracker = tracker ;
_configService = configService ;
2024-11-02 01:31:06 +13:00
_fonts = fonts ;
2024-09-21 00:44:21 +12:00
}
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 ) ) )
) ;
2024-10-03 19:14:24 +13:00
2024-09-21 00:44:21 +12:00
// 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 ) ) )
) ;
2024-11-04 00:03:25 +13:00
private EllieInteractionBase CreateTimelyInteraction ( )
= > _inter
. Create ( ctx . User . Id ,
new ButtonBuilder (
label : "Timely" ,
emote : Emoji . Parse ( "💰" ) ,
customId : "timely:" + _rng . Next ( 123456 , 999999 ) ) ,
async ( smc ) = >
{
2024-11-05 20:38:37 +13:00
await smc . DeferAsync ( ) ;
2024-11-04 00:03:25 +13:00
await ClaimTimely ( ) ;
} ) ;
2024-09-21 00:44:21 +12:00
[Cmd]
2024-11-05 20:20:44 +13:00
[RequireContext(ContextType.Guild)]
2024-09-21 00:44:21 +12:00
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 ;
}
2024-11-05 20:38:37 +13:00
if ( Config . Timely . ProtType = = TimelyProt . Button )
2024-11-02 01:31:06 +13:00
{
2024-11-04 00:03:25 +13:00
var interaction = CreateTimelyInteraction ( ) ;
var msg = await Response ( ) . Pending ( strs . timely_button ) . Interaction ( interaction ) . SendAsync ( ) ;
await msg . DeleteAsync ( ) ;
return ;
2024-11-02 01:31:06 +13:00
}
2024-11-05 20:38:37 +13:00
else if ( Config . Timely . ProtType = = TimelyProt . Captcha )
{
var password = _service . GeneratePassword ( ) ;
2024-11-27 22:38:06 +13:00
var img = new Image < Rgba32 > ( 60 , 30 ) ;
2024-11-05 20:38:37 +13:00
2024-11-27 22:38:06 +13:00
var font = _fonts . NotoSans . CreateFont ( 25 ) ;
2024-11-05 20:38:37 +13:00
var outlinePen = new SolidPen ( Color . Black , 1f ) ;
var strikeoutRun = new RichTextRun
{
Start = 0 ,
End = password . GetGraphemeCount ( ) ,
Font = font ,
2024-11-27 22:38:06 +13:00
StrikeoutPen = new SolidPen ( Color . White , 4 ) ,
2024-11-05 20:38:37 +13:00
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 ( 35 , 17 ) ,
TextRuns = [ strikeoutRun ]
} ,
password ,
Brushes . Solid ( Color . White ) ,
outlinePen ) ;
} ) ;
using var stream = await img . ToStreamAsync ( ) ;
var captcha = await Response ( )
// .Embed(_sender.CreateEmbed()
// .WithOkColor()
// .WithImageUrl("attachment://timely.png"))
. File ( stream , "timely.png" )
. SendAsync ( ) ;
try
{
var userInput = await GetUserInputAsync ( ctx . User . Id , ctx . Channel . Id ) ;
if ( userInput ? . ToLowerInvariant ( ) ! = password ? . ToLowerInvariant ( ) )
{
return ;
}
}
finally
{
_ = captcha . DeleteAsync ( ) ;
}
}
2024-11-02 01:31:06 +13:00
2024-11-04 00:03:25 +13:00
await ClaimTimely ( ) ;
}
private async Task ClaimTimely ( )
{
var period = Config . Timely . Cooldown ;
2024-09-21 00:44:21 +12:00
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 ;
}
2024-10-03 19:14:24 +13:00
2024-09-21 00:44:21 +12:00
2024-11-04 00:03:25 +13:00
var val = Config . Timely . Amount ;
2024-11-05 20:20:44 +13:00
var boostGuilds = Config . BoostBonus . GuildIds ? ? new ( ) ;
var guildUsers = await boostGuilds
. Select ( async gid = >
{
try
{
2024-11-05 21:11:30 +13:00
var guild = await _client . Rest . GetGuildAsync ( gid , false ) ;
var user = await _client . Rest . GetGuildUserAsync ( gid , ctx . User . Id ) ;
return ( guild , user ) ;
2024-11-05 20:20:44 +13:00
}
catch
{
2024-11-05 21:11:30 +13:00
return default ;
2024-11-05 20:20:44 +13:00
}
} )
2024-11-05 16:11:05 +13:00
. WhenAll ( ) ;
2024-11-05 21:11:30 +13:00
var userInfo = guildUsers . FirstOrDefault ( x = > x . user ? . PremiumSince is not null ) ;
var booster = userInfo ! = default ;
2024-11-05 16:11:05 +13:00
if ( booster )
val + = Config . BoostBonus . BaseTimelyBonus ;
2024-09-21 00:44:21 +12:00
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" ) ) ;
2024-11-07 19:04:47 +13:00
var msg = GetText ( strs . timely ( N ( val ) , period ) ) ;
if ( booster | | percentBonus > float . Epsilon )
2024-11-05 16:11:05 +13:00
{
2024-11-07 19:04:47 +13:00
msg + = "\n\n" ;
if ( booster )
2024-11-13 18:20:54 +13:00
msg + = $"*+{N(Config.BoostBonus.BaseTimelyBonus)} bonus for boosting {userInfo.guild}!*\n" ;
2024-11-07 19:04:47 +13:00
if ( percentBonus > float . Epsilon )
msg + =
$"*+{percentBonus:P0} bonus for the [Patreon](https://patreon.com/elliebot) pledge! <:hart:746995901758832712>*" ;
2024-11-05 16:11:05 +13:00
await Response ( ) . Confirm ( msg ) . Interaction ( inter ) . SendAsync ( ) ;
}
else
await Response ( ) . Confirm ( strs . timely ( N ( val ) , period ) ) . Interaction ( inter ) . SendAsync ( ) ;
2024-09-21 00:44:21 +12:00
}
[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 ( )
2024-11-05 16:11:05 +13:00
. WithTitle ( GetText ( strs . transactions (
( ( SocketGuild ) ctx . Guild ) ? . GetUser ( userId ) ? . ToString ( )
? ? $"{userId}" ) ) )
2024-09-21 00:44:21 +12:00
. 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
{
2024-11-05 16:11:05 +13:00
await Response ( )
. Error ( strs . take_fail ( N ( amount ) , Format . Bold ( user . ToString ( ) ) , CurrencySign ) )
. SendAsync ( ) ;
2024-09-21 00:44:21 +12:00
}
}
[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
{
2024-11-05 16:11:05 +13:00
await Response ( )
. Error ( strs . take_fail ( N ( amount ) , Format . Code ( usrId . ToString ( ) ) , CurrencySign ) )
. SendAsync ( ) ;
2024-09-21 00:44:21 +12:00
}
}
[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 ) ;
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 ) ;
2024-10-03 18:46:10 +13:00
var users = ( ( SocketGuild ) ctx . Guild ) . Users . Map ( x = > x . Id ) ;
var perPage = 9 ;
2024-09-21 00:44:21 +12:00
2024-10-03 18:46:10 +13:00
await using var uow = _db . GetDbContext ( ) ;
var cleanRichest = await uow . GetTable < DiscordUser > ( )
. Where ( x = > x . UserId . In ( users ) )
. OrderByDescending ( x = > x . CurrencyAmount )
2024-10-03 19:14:24 +13:00
. Skip ( curPage * perPage )
2024-10-03 18:46:10 +13:00
. Take ( perPage )
. ToListAsync ( ) ;
2024-09-21 00:44:21 +12:00
2024-10-03 19:14:24 +13:00
return cleanRichest ;
2024-09-21 00:44:21 +12:00
}
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 = _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 ( ) ;
2024-11-03 23:59:58 +13:00
private readonly EllieRandom _rng ;
2024-09-21 00:44:21 +12:00
[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 ( ) ;
}
2024-11-07 18:28:18 +13:00
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 ( ) ;
}
2024-09-21 00:44:21 +12:00
}