diff --git a/KfChatDotNetBot/Commands/Kasino/RainCommand.cs b/KfChatDotNetBot/Commands/Kasino/RainCommand.cs new file mode 100644 index 0000000..13449fc --- /dev/null +++ b/KfChatDotNetBot/Commands/Kasino/RainCommand.cs @@ -0,0 +1,248 @@ +using System.Text.Json; +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 Microsoft.EntityFrameworkCore; +namespace KfChatDotNetBot.Commands.Kasino; + +public class RainCommand : ICommand +{ + public static class RainManager + { + public static Dictionary rainLobbies { get; } = new(); //dictionary of lobbies with the creators id and the lobby + public static readonly SemaphoreSlim _lock = new(1, 1); + public static ChatBot botInstance; + + + public static async Task AddParticipant(GamblerDbModel gambler, UserDbModel user) + { + await _lock.WaitAsync(); + bool openLobby = false; + try + { + foreach (var lobby in rainLobbies.Values) + { + if (lobby.open) + { + lobby.AddParticipant(gambler); + openLobby = true; + } + + } + + } + finally + { + _lock.Release(); + } + return openLobby; + } + + public static async Task AddRain(int gamblerId, Rain rain) + { + await _lock.WaitAsync(); + try + { + if (!rainLobbies.TryGetValue(gamblerId, out var lobby)) + { + rainLobbies.Add(gamblerId, lobby); + } + else //can't start multiple rains at the same time + { + await botInstance.SendChatMessageAsync( + $"{lobby.creator.FormatUsername()}, you can't start multiple rains at the same time."); + return false; + } + } + finally + { + _lock.Release(); + } + + return true; + } + + public static async Task PayoutRain(Rain rain) + { + if (rain.participants.Count == 0) + { + await botInstance.SendChatMessageAsync($"{rain.creator.FormatUsername()} made it rain on nobody."); + await Money.ModifyBalanceAsync(rain.creatorGambler.Id, -rain.rainAmount, TransactionSourceEventType.Rain); + return; + } + else + { + decimal payout = rain.rainAmount / rain.participants.Count; + string rainParticipants = ""; + int counter = 0; + foreach (var participant in rain.participants) + { + rainParticipants += (rain.participants.Count > 1 && counter == rain.participants.Count - 1) ? $"and {participant.User.FormatUsername()}" : $"{participant.User.FormatUsername()}, "; + await Money.ModifyBalanceAsync(participant.Id, payout, TransactionSourceEventType.Rain); + counter++; + } + if (rain.participants.Count == 1) rainParticipants = rain.participants[0].User.FormatUsername(); + await botInstance.SendChatMessageAsync( + $"{rain.creator.FormatUsername()} made it rain {payout.FormatKasinoCurrencyAsync()} on {rainParticipants}", + true, autoDeleteAfter: TimeSpan.FromSeconds(30)); + await Money.ModifyBalanceAsync(rain.creatorGambler.Id, -rain.rainAmount, TransactionSourceEventType.Rain); + + } + } + + public static async Task RemoveRain(Rain rain) + { + await _lock.WaitAsync(); + try + { + rainLobbies.Remove(rain.creatorGambler.Id); + } + finally + { + _lock.Release(); + } + } + public class Rain + { + public List participants = new List(); + public UserDbModel creator; + public GamblerDbModel creatorGambler; + public DateTime startTime { get; } = DateTime.UtcNow; + public bool open = true; + public decimal rainAmount; + + public Rain(UserDbModel user, GamblerDbModel gambler, decimal wager) + { + creator = user; + creatorGambler = gambler; + rainAmount = wager; + _ = RunRain(); + } + + public void AddParticipant(GamblerDbModel gambler) + { + participants.Add(gambler); + } + + public async Task RunRain() + { + var chatTimer = TimeSpan.FromSeconds(5); + int rainTimer = 60; + var msgId = await botInstance.SendChatMessageAsync( + $"{creator.FormatUsername()} is making it rain with {rainAmount.FormatKasinoCurrencyAsync()}! You have {rainTimer} seconds left to join!", true, + autoDeleteAfter: TimeSpan.FromSeconds(rainTimer)); + int num = 0; + while (msgId.ChatMessageId == null) + { + num++; + if (msgId.Status is SentMessageTrackerStatus.NotSending or SentMessageTrackerStatus.Lost) return; + if (num > 100) return; + await Task.Delay(100); + } + for (int i = 0; i < rainTimer / Convert.ToInt32(chatTimer); i++) + { + await Task.Delay(chatTimer); + await botInstance.KfClient.EditMessageAsync(msgId.ChatMessageId!.Value, + $"{creator.FormatUsername()} is making it rain with {rainAmount.FormatKasinoCurrencyAsync()}! You have {rainTimer} seconds left to join!"); + } + open = false; + await PayoutRain(this); + await RemoveRain(this); + } + + + } + + } + + + + public List Patterns => [ + new Regex(@"^rain (?\d+)$", RegexOptions.IgnoreCase), + new Regex(@"^rain (?\d+\.\d+)$", RegexOptions.IgnoreCase), + new Regex(@"^rain", RegexOptions.IgnoreCase) + ]; + + public string? HelpText => "!rain to start a rain, !rain to join all active rains"; + public UserRight RequiredRight => UserRight.Loser; + public TimeSpan Timeout => TimeSpan.FromSeconds(30); + public RateLimitOptionsModel? RateLimitOptions => null; + + public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, + CancellationToken ctx) + { + if (!RainManager.botInstance.Equals(botInstance)) + { + RainManager.botInstance = botInstance; + } + var cleanupDelay = TimeSpan.FromSeconds(30); + 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 (!arguments.TryGetValue("amount", out var amount)) //if you're trying to join a rain + { + if (RainManager.rainLobbies.Count == 0) //if there are no lobbies + { + botInstance.SendChatMessageAsync( + $"{user.FormatUsername()}, there are no rain lobbies currently running. !rain to start a new rain lobby", + true, autoDeleteAfter: cleanupDelay); + return; + } + else + { + bool success = await RainManager.AddParticipant(gambler, user); + if (!success) + { + await botInstance.SendChatMessageAsync( + $"{user.FormatUsername()}, there are no rain lobbies currently running. !rain to start a new rain lobby", + true, autoDeleteAfter: cleanupDelay); + } + else + { + + string rainCreators = ""; + await RainManager._lock.WaitAsync(); + int lobbyCount = 0; + try + { + foreach (var lobby in RainManager.rainLobbies.Values) + { + rainCreators += (RainManager.rainLobbies.Count > 1 && lobbyCount == RainManager.rainLobbies.Count - 1) ? $"and {lobby.creator.FormatUsername()}": $"{lobby.creator.FormatUsername()}, "; + lobbyCount++; + } + + } + finally + { + RainManager._lock.Release(); + } + + string areIs = RainManager.rainLobbies.Count > 1 ? "are" : "is"; + await botInstance.SendChatMessageAsync( + $"{rainCreators} {areIs} making it rain on {user.FormatUsername()}!",true, autoDeleteAfter: cleanupDelay); + + } + } + } + //if you're trying to start the rain + decimal decAmount = Convert.ToDecimal(amount.Value); + if (decAmount <= 0) + { + await botInstance.SendChatMessageAsync($"{user.FormatUsername()}, you can't make it rain with nothing.", true, autoDeleteAfter: cleanupDelay); + return; + } + if (gambler.Balance < decAmount) + { + await botInstance.SendChatMessageAsync($"{user.FormatUsername()}, your balance ${gambler.Balance} KKK is not enough to make it rain for ${decAmount} KKK.", true, autoDeleteAfter: cleanupDelay); + return; + } + await RainManager.AddRain(gambler.Id,new RainManager.Rain(user, gambler, decAmount)); + + } +} diff --git a/KfChatDotNetBot/Models/DbModels/MoneyDbModels.cs b/KfChatDotNetBot/Models/DbModels/MoneyDbModels.cs index 065776c..c424918 100644 --- a/KfChatDotNetBot/Models/DbModels/MoneyDbModels.cs +++ b/KfChatDotNetBot/Models/DbModels/MoneyDbModels.cs @@ -274,7 +274,11 @@ public enum TransactionSourceEventType /// /// A specific form of 24 hour time-based reload that has no wager requirement /// - DailyDollar + DailyDollar, + /// + ///A form of juicer where the value is split among a number of participants + /// + Rain } public enum WagerGame