From 1463d991c1ca253a9687de834ac2711e5fd49809 Mon Sep 17 00:00:00 2001 From: barelyprofessional <150058423+barelyprofessional@users.noreply.github.com> Date: Wed, 10 Dec 2025 19:41:23 -0600 Subject: [PATCH] Added Limbo --- .../Commands/Kasino/LimboCommand.cs | 143 ++++++++++++++++++ KfChatDotNetBot/Settings/BuiltIn.cs | 2 + 2 files changed, 145 insertions(+) create mode 100644 KfChatDotNetBot/Commands/Kasino/LimboCommand.cs diff --git a/KfChatDotNetBot/Commands/Kasino/LimboCommand.cs b/KfChatDotNetBot/Commands/Kasino/LimboCommand.cs new file mode 100644 index 0000000..f502bb1 --- /dev/null +++ b/KfChatDotNetBot/Commands/Kasino/LimboCommand.cs @@ -0,0 +1,143 @@ +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 LimboCommand : ICommand +{ + public List Patterns => [ + new Regex(@"^limbo (?\d+)$ (?\d+\.\d+)$", RegexOptions.IgnoreCase), + new Regex(@"^limbo (?\d+\.\d+)$ (?\d+\.\d+)$", RegexOptions.IgnoreCase), + new Regex(@"^limbo (?\d+\.\d+)$ (?\d+)$", RegexOptions.IgnoreCase), + new Regex(@"^limbo (?\d+)$ (?\d+)$", RegexOptions.IgnoreCase), + new Regex(@"^limbo (?\d+)$", RegexOptions.IgnoreCase), + new Regex(@"^limbo (?\d+\.\d+)$", RegexOptions.IgnoreCase), + new Regex("^limbo") + ]; + public string? HelpText => "!limbo "; + public UserRight RequiredRight => UserRight.Loser; + public TimeSpan Timeout => TimeSpan.FromSeconds(10); + public RateLimitOptionsModel? RateLimitOptions => new() + { + MaxInvocations = 10, + Window = TimeSpan.FromSeconds(30) + }; + + private const double Min = 1; + private const double Max = 10000; + + public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, + CancellationToken ctx) + { + decimal limboNumber; //user number + var settings = await SettingsProvider.GetMultipleValuesAsync([ + BuiltIn.Keys.KasinoLimboCleanupDelay, BuiltIn.Keys.KiwiFarmsGreenColor, BuiltIn.Keys.KiwiFarmsRedColor + ]); + var cleanupDelay = TimeSpan.FromMilliseconds(settings[BuiltIn.Keys.KasinoLimboCleanupDelay].ToType()); + if (!arguments.TryGetValue("amount", out var amount)) + { + await botInstance.SendChatMessageAsync($"{user.FormatUsername()}, not enough arguments. !limbo ", + true, autoDeleteAfter: cleanupDelay); + return; + } + var wager = Convert.ToDecimal(amount.Value); + 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); + return; + } + + if (!arguments.TryGetValue("number", out var number)) + { + limboNumber = 2; + //set user number to 2 if they didn't enter anything + } + else limboNumber = Convert.ToDecimal(number.Value); + + if (limboNumber <= 1) + { + //cancel the game if user does not choose a correct number + await botInstance.SendChatMessageAsync($"{user.FormatUsername()}, you must choose a number greater than 1", true, autoDeleteAfter: cleanupDelay); + return; + } + decimal newBalance; + var casinoNumbers = Get1XWeightedRandomNumber(Min, (double)(limboNumber * limboNumber), limboNumber); + string colorToUse; + if (casinoNumbers[0] >= limboNumber) + { + //you win + colorToUse = settings[BuiltIn.Keys.KiwiFarmsGreenColor].Value!; + newBalance = await Money.NewWagerAsync(gambler.Id, wager, wager * limboNumber, WagerGame.Limbo, ct: ctx); + await botInstance.SendChatMessageAsync( + $"[b][color={colorToUse}] {casinoNumbers[1]}", true, autoDeleteAfter: cleanupDelay); + await botInstance.SendChatMessageAsync($"{user.FormatUsername()}, you [color={settings[BuiltIn.Keys.KiwiFarmsGreenColor].Value}] won {wager * limboNumber}![/color] Your balance is now: {newBalance}.", true, autoDeleteAfter: cleanupDelay); + + } + else + { + if (limboNumber < casinoNumbers[1] / 2) colorToUse = settings[BuiltIn.Keys.KiwiFarmsRedColor].Value!; //use red for the number if you're not close + else if (limboNumber > casinoNumbers[1] * 3 / 4) + colorToUse = "yellow"; //use yellow for the number if you're pretty close + else colorToUse = "orange"; //use orange for mid range guess + //you lose + newBalance = await Money.NewWagerAsync(gambler.Id, wager, -wager, WagerGame.Limbo, ct: ctx); + await botInstance.SendChatMessageAsync($"[b][color={colorToUse}] {casinoNumbers}", true, autoDeleteAfter: cleanupDelay); + await botInstance.SendChatMessageAsync( + $"{user.FormatUsername()}, you [color={settings[BuiltIn.Keys.KiwiFarmsRedColor].Value}]lost {await wager.FormatKasinoCurrencyAsync()}[/color]. Your balance is now: {newBalance}.", + true, autoDeleteAfter: cleanupDelay); + } + + } + + //returns a distribution with a 1/multi chance of getting a number below or above sqr(min * max) (so max should basically be multi^2). basically gives you a 1/x fair chance to win + //then scales the number using the number scaling function + private static decimal[] Get1XWeightedRandomNumber(double minValue, double maxValue, decimal multi) + { + var random = RandomShim.Create(StandardRng.Create()); + var skew = 1.0 / (double)(multi * (decimal)1.01); + var gamma = Math.Log(0.5) / Math.Log(skew); + var r = random.NextDouble(); + var rP = 1 - Math.Pow(1 - r, gamma); + var lnMin = Math.Log(minValue); + var lnMax = Math.Log(maxValue); + var exponent = lnMin + rP * (lnMax - lnMin); + var result = new decimal[2]; + result[0] = (decimal)Math.Exp(exponent); + result[1] = GetScaledNumber(lnMin, lnMax, exponent, result[0], multi); + return result; + } + + private static decimal GetScaledNumber(double lnMin, double lnMax, double exponent, decimal result, decimal multi) + { + var anchor = Math.Log((double)multi); + var deltaMax = lnMax - anchor; + var k = Math.Log(Max / (double)(multi * multi)) / deltaMax; + var delta = exponent - anchor; + var logFactor = k * delta; + var factor = Math.Exp(logFactor); + var preResult = (result * (decimal)factor); + + if (!((double)preResult < anchor)) return preResult; + + var minTheo = (double)(multi * multi) / Max; + var logMinTheo = Math.Log(minTheo); + var logPreResult = Math.Log((double)preResult); + var fraction = (logPreResult - logMinTheo) / (anchor - logMinTheo); + return (decimal)Math.Exp((anchor * fraction)); + } +} + diff --git a/KfChatDotNetBot/Settings/BuiltIn.cs b/KfChatDotNetBot/Settings/BuiltIn.cs index 5775199..755a496 100644 --- a/KfChatDotNetBot/Settings/BuiltIn.cs +++ b/KfChatDotNetBot/Settings/BuiltIn.cs @@ -445,6 +445,8 @@ public static class BuiltIn public static string YouTubePubSubRedisConnectionString = "YouTube.PubSub.RedisConnectionString"; [BuiltInSetting("Channel for YouTube PubSub with Redis", SettingValueType.Text, "yt-pubsub")] public static string YouTubePubSubRedisChannel = "YouTube.PubSub.RedisChannel"; + [BuiltInSetting("Delay in milliseconds before cleaning up limbo", SettingValueType.Text, "15000", WholeNumberRegex)] + public static string KasinoLimboCleanupDelay = "Kasino.Limbo.CleanupDelay"; } }