using System.Net.Http.Headers; using System.Text.RegularExpressions; using KfChatDotNetBot.Extensions; using KfChatDotNetBot.Models; using KfChatDotNetBot.Models.DbModels; using KfChatDotNetBot.Services; using KfChatDotNetBot.Settings; using KfChatDotNetWsClient.Models.Events; using RandN; using RandN.Compat; namespace KfChatDotNetBot.Commands.Kasino; [KasinoCommand] [WagerCommand] public class CoinflipCommand : ICommand { public List Patterns => [ new Regex("^coinflip$", RegexOptions.IgnoreCase), new Regex(@"^coinflip (?\d+) (?heads|tails)$", RegexOptions.IgnoreCase), new Regex(@"^coinflip (?\d+\.\d+) (?heads|tails)$", RegexOptions.IgnoreCase), ]; public string? HelpText => "!coinflip , flip a coin"; public UserRight RequiredRight => UserRight.Loser; public TimeSpan Timeout => TimeSpan.FromSeconds(5); public RateLimitOptionsModel? RateLimitOptions => new() { MaxInvocations = 3, Window = TimeSpan.FromSeconds(15) }; public bool WhisperCanInvoke => false; private static double _houseEdge = 0.015; // house edge hack? public async Task RunCommand(ChatBot botInstance, BotCommandMessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { var settings = await SettingsProvider.GetMultipleValuesAsync([ BuiltIn.Keys.KasinoGameDisabledMessageCleanupDelay, BuiltIn.Keys.KasinoCoinflipCleanupDelay, BuiltIn.Keys.KasinoCoinflipEnabled ]); var coinflipEnabled = settings[BuiltIn.Keys.KasinoCoinflipEnabled].ToBoolean(); if (!coinflipEnabled) { var gameDisabledCleanupDelay = TimeSpan.FromMilliseconds(settings[BuiltIn.Keys.KasinoGameDisabledMessageCleanupDelay].ToType()); await botInstance.SendChatMessageAsync( $"{user.FormatUsername()}, coinflip is currently disabled.", true, autoDeleteAfter: gameDisabledCleanupDelay); return; } if (message is { IsWhisper: false, MessageUuid: not null }) { await botInstance.KfClient.DeleteMessageAsync(message.MessageUuid); } var cleanupDelay = TimeSpan.FromMilliseconds(settings[BuiltIn.Keys.KasinoCoinflipCleanupDelay].ToType()); if (!arguments.TryGetValue("amount", out var amount)) { await botInstance.SendChatMessageAsync( $"{user.FormatUsername()}, not enough arguments. !coinflip ", true, autoDeleteAfter: cleanupDelay); RateLimitService.RemoveMostRecentEntry(user, this); return; } if (!arguments.TryGetValue("choice", out var choice)) { await botInstance.SendChatMessageAsync( $"{user.FormatUsername()}, not enough arguments. !coinflip ", true, autoDeleteAfter: cleanupDelay); RateLimitService.RemoveMostRecentEntry(user, this); return; } var choiceStr = choice.Value.ToLowerInvariant(); var wager = Convert.ToDecimal(amount.Value); if (wager <= 0) { await botInstance.SendChatMessageAsync( $"{user.FormatUsername()}, your wager must be greater than zero.", true, autoDeleteAfter: cleanupDelay); RateLimitService.RemoveMostRecentEntry(user, this); return; } var gambler = await Money.GetGamblerEntityAsync(user.Id, ct: ctx); if (gambler == null) throw new InvalidOperationException($"Caught a null when retrieving gambler for {user.KfUsername}"); if (gambler.Balance < wager) { await botInstance.SendChatMessageAsync( $"{user.FormatUsername()}, your balance of {await gambler.Balance.FormatKasinoCurrencyAsync()} isn't enough for this wager.", true, autoDeleteAfter: cleanupDelay); RateLimitService.RemoveMostRecentEntry(user, this); return; } var rolled = Money.GetRandomDouble(gambler); var colors = await SettingsProvider.GetMultipleValuesAsync([ BuiltIn.Keys.KiwiFarmsGreenColor, BuiltIn.Keys.KiwiFarmsRedColor ]); decimal newBalance; if (rolled > 0.5 + _houseEdge) { // won var coinflipAnimation = GetCoinFlipAnimationUrl(choiceStr); await botInstance.SendChatMessageAsync($"[IMG]{coinflipAnimation}[/IMG]", true, autoDeleteAfter: cleanupDelay); await Task.Delay(1500, ctx); var effect = wager; newBalance = await Money.NewWagerAsync(gambler.Id, wager, effect, WagerGame.CoinFlip, ct: ctx); await botInstance.SendChatMessageAsync( $"{user.FormatUsername()}, you [B][COLOR={colors[BuiltIn.Keys.KiwiFarmsGreenColor].Value}]WON![/COLOR][/B] " + $"You won {await effect.FormatKasinoCurrencyAsync()} and your balance is now {await newBalance.FormatKasinoCurrencyAsync()}", true, autoDeleteAfter: cleanupDelay); return; } // lost bool isJacky = rolled > 0.5; // would've won without house edge var coinflipAnimationURL = GetCoinFlipAnimationUrl("heads" == choiceStr ? "tails" : "heads", isJacky); await botInstance.SendChatMessageAsync($"[IMG]{coinflipAnimationURL}[/IMG]", true, autoDeleteAfter: cleanupDelay); await Task.Delay(1500, ctx); newBalance = await Money.NewWagerAsync(gambler.Id, wager, -wager, WagerGame.CoinFlip, ct: ctx); await botInstance.SendChatMessageAsync( $"{user.FormatUsername()}, you [B][COLOR={colors[BuiltIn.Keys.KiwiFarmsRedColor].Value}]LOST![/COLOR][/B] " + $"Your balance is now {await newBalance.FormatKasinoCurrencyAsync()}", true, autoDeleteAfter: cleanupDelay); } private static string GetCoinFlipAnimationUrl(string choiceStr, bool isJacky = false) { var baseUrl = "https://i.ddos.lgbt/u"; var unixTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); if (isJacky) { return $"{baseUrl}/bossmancoin-{choiceStr}-jacky.webp?{unixTime}"; } return $"{baseUrl}/bossmancoin-{choiceStr}.webp?{unixTime}"; } }