diff --git a/KfChatDotNetBot/Commands/KasinoUserCommands.cs b/KfChatDotNetBot/Commands/KasinoUserCommands.cs index 02bca01..4ddc71c 100644 --- a/KfChatDotNetBot/Commands/KasinoUserCommands.cs +++ b/KfChatDotNetBot/Commands/KasinoUserCommands.cs @@ -26,7 +26,7 @@ public class GetBalanceCommand : ICommand public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { - var gambler = await Money.GetGamblerEntityAsync(user, ct: ctx); + var gambler = await Money.GetGamblerEntityAsync(user.Id, ct: ctx); await botInstance.SendChatMessageAsync( $"{user.FormatUsername()}, your balance is {await gambler!.Balance.FormatKasinoCurrencyAsync()}", true); } @@ -45,12 +45,12 @@ public class GetExclusionCommand : ICommand public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { - var gambler = await Money.GetGamblerEntityAsync(user, ct: ctx); + var gambler = await Money.GetGamblerEntityAsync(user.Id, ct: ctx); if (gambler == null) { throw new InvalidOperationException($"Caught a null when retrieving {user.Id}'s gambler entity"); } - var exclusion = await Money.GetActiveExclusionAsync(gambler, ct: ctx); + var exclusion = await Money.GetActiveExclusionAsync(gambler.Id, ct: ctx); if (exclusion == null) { await botInstance.SendChatMessageAsync($"{user.FormatUsername()}, you are currently not excluded.", true); @@ -84,7 +84,7 @@ public class SendJuiceCommand : ICommand { var logger = LogManager.GetCurrentClassLogger(); await using var db = new ApplicationDbContext(); - var gambler = await Money.GetGamblerEntityAsync(user, ct: ctx); + var gambler = await Money.GetGamblerEntityAsync(user.Id, ct: ctx); var targetUser = await db.Users.FirstOrDefaultAsync(u => u.KfId == int.Parse(arguments["user_id"].Value), ctx); var amount = decimal.Parse(arguments["amount"].Value); if (gambler == null) @@ -104,17 +104,17 @@ public class SendJuiceCommand : ICommand return; } - var targetGambler = await Money.GetGamblerEntityAsync(targetUser, ct: ctx); + var targetGambler = await Money.GetGamblerEntityAsync(targetUser.Id, ct: ctx); if (targetGambler == null) { await botInstance.SendChatMessageAsync($"{user.FormatUsername()}, you can't juice a banned user", true); return; } - await Money.ModifyBalanceAsync(gambler, -amount, TransactionSourceEventType.Juicer, + await Money.ModifyBalanceAsync(gambler.Id, -amount, TransactionSourceEventType.Juicer, $"Juice sent to {targetUser.KfUsername}", ct: ctx); - await Money.ModifyBalanceAsync(targetGambler, amount, TransactionSourceEventType.Juicer, $"Juice from {user.KfUsername}", - gambler, ctx); + await Money.ModifyBalanceAsync(targetGambler.Id, amount, TransactionSourceEventType.Juicer, $"Juice from {user.KfUsername}", + gambler.Id, ctx); await botInstance.SendChatMessageAsync($"{user.FormatUsername()}, {await amount.FormatKasinoCurrencyAsync()} has been sent to {targetUser.KfUsername}", true); } } @@ -138,7 +138,7 @@ public class RakebackCommand : ICommand CancellationToken ctx) { await using var db = new ApplicationDbContext(); - var gambler = await Money.GetGamblerEntityAsync(user, ct: ctx); + var gambler = await Money.GetGamblerEntityAsync(user.Id, ct: ctx); if (gambler == null) { throw new InvalidOperationException($"Caught a null when retrieving {user.Id}'s gambler entity"); @@ -170,7 +170,7 @@ public class RakebackCommand : ICommand await botInstance.SendChatMessageAsync($"{user.FormatUsername()}, your rakeback payout of {await rakeback.FormatKasinoCurrencyAsync()} is below the minimum amount of {await minimumRakeback.FormatKasinoCurrencyAsync()}", true); return; } - await Money.ModifyBalanceAsync(gambler, rakeback, TransactionSourceEventType.Rakeback, "Rakeback claimed by gambler", + await Money.ModifyBalanceAsync(gambler.Id, rakeback, TransactionSourceEventType.Rakeback, "Rakeback claimed by gambler", ct: ctx); await botInstance.SendChatMessageAsync($"{user.FormatUsername()}, the hostess has given you {await rakeback.FormatKasinoCurrencyAsync()} rakeback", true); } @@ -194,7 +194,7 @@ public class LossbackCommand : ICommand CancellationToken ctx) { await using var db = new ApplicationDbContext(); - var gambler = await Money.GetGamblerEntityAsync(user, ct: ctx); + var gambler = await Money.GetGamblerEntityAsync(user.Id, ct: ctx); if (gambler == null) { throw new InvalidOperationException($"Caught a null when retrieving {user.Id}'s gambler entity"); @@ -226,7 +226,7 @@ public class LossbackCommand : ICommand await botInstance.SendChatMessageAsync($"{user.FormatUsername()}, your lossback payout of {await lossback.FormatKasinoCurrencyAsync()} is below the minimum amount of {await minimumLossback.FormatKasinoCurrencyAsync()}", true); return; } - await Money.ModifyBalanceAsync(gambler, lossback, TransactionSourceEventType.Lossback, "Lossback claimed by gambler", + await Money.ModifyBalanceAsync(gambler.Id, lossback, TransactionSourceEventType.Lossback, "Lossback claimed by gambler", ct: ctx); await botInstance.SendChatMessageAsync($"{user.FormatUsername()}, the hostess has given you {await lossback.FormatKasinoCurrencyAsync()} lossback", true); } @@ -255,7 +255,7 @@ public class AbandonKasinoCommand : ICommand return; } await using var db = new ApplicationDbContext(); - var gambler = await Money.GetGamblerEntityAsync(user, ct: ctx); + var gambler = await Money.GetGamblerEntityAsync(user.Id, ct: ctx); if (gambler == null) { throw new InvalidOperationException($"Caught a null when retrieving {user.Id}'s gambler entity"); diff --git a/KfChatDotNetBot/Services/BotCommands.cs b/KfChatDotNetBot/Services/BotCommands.cs index 59a41fb..48e5f9e 100644 --- a/KfChatDotNetBot/Services/BotCommands.cs +++ b/KfChatDotNetBot/Services/BotCommands.cs @@ -74,7 +74,7 @@ internal class BotCommands if (!kasinoEnabled) return; } - if (kasinoCommand && Money.IsPermanentlyBannedAsync(user, _cancellationToken).Result) + if (kasinoCommand && Money.IsPermanentlyBannedAsync(user.Id, _cancellationToken).Result) { _bot.SendChatMessage($"@{message.Author.Username}, you've been permanently banned from the kasino. Contact support for more information.", true); return; @@ -84,13 +84,16 @@ internal class BotCommands { // GetGamblerEntity will only return null if the user is permanbanned // and we have a check further up the chain for that hence ignoring the null - var gambler = Money.GetGamblerEntityAsync(user, ct: _cancellationToken).Result; - var exclusion = Money.GetActiveExclusionAsync(gambler!, ct: _cancellationToken).Result; - if (exclusion != null) + var gambler = Money.GetGamblerEntityAsync(user.Id, ct: _cancellationToken).Result; + if (gambler != null) { - _bot.SendChatMessage( - $"@{message.Author.Username}, you're self excluded from the kasino for another {(exclusion.Expires - DateTimeOffset.UtcNow).Humanize(precision: 3)}", true); - return; + var exclusion = Money.GetActiveExclusionAsync(gambler.Id, ct: _cancellationToken).Result; + if (exclusion != null) + { + _bot.SendChatMessage( + $"@{message.Author.Username}, you're self excluded from the kasino for another {(exclusion.Expires - DateTimeOffset.UtcNow).Humanize(precision: 3)}", true); + return; + } } } @@ -140,14 +143,14 @@ internal class BotCommands if (!(await SettingsProvider.GetValueAsync(BuiltIn.Keys.MoneyEnabled)).ToBoolean()) return; var wagerCommand = HasAttribute(command); if (!wagerCommand) return; - var gambler = await Money.GetGamblerEntityAsync(user, ct: _cancellationToken); + var gambler = await Money.GetGamblerEntityAsync(user.Id, ct: _cancellationToken); if (gambler == null) return; if (gambler.TotalWagered < gambler.NextVipLevelWagerRequirement) return; // The reason for doing this instead of passing in TotalWagered is that otherwise VIP levels might // get skipped if the user is a low VIP level but wagering very large amounts var newLevel = Money.GetNextVipLevel(gambler.NextVipLevelWagerRequirement); if (newLevel == null) return; - var payout = await Money.UpgradeVipLevelAsync(gambler, newLevel, _cancellationToken); + var payout = await Money.UpgradeVipLevelAsync(gambler.Id, newLevel, _cancellationToken); await _bot.SendChatMessageAsync( $"🤑🤑 {user.FormatUsername()} has leveled up to to {newLevel.VipLevel.Icon} {newLevel.VipLevel.Name} Tier {newLevel.Tier} " + $"and received a bonus of {await payout.FormatKasinoCurrencyAsync()}", true); diff --git a/KfChatDotNetBot/Services/Money.cs b/KfChatDotNetBot/Services/Money.cs index f1a565a..455ed11 100644 --- a/KfChatDotNetBot/Services/Money.cs +++ b/KfChatDotNetBot/Services/Money.cs @@ -210,13 +210,18 @@ public static class Money /// Also returns null if the user was permanently banned from gambling /// If there are multiple "active" gamblers, only the newest is returned /// - /// User whose gambler entity you wish to retrieve + /// User whose gambler entity you wish to retrieve /// Whether to create a gambler entity if none exists already /// Cancellation token /// - public static async Task GetGamblerEntityAsync(UserDbModel user, bool createIfNoneExists = true, CancellationToken ct = default) + public static async Task GetGamblerEntityAsync(int userId, bool createIfNoneExists = true, CancellationToken ct = default) { await using var db = new ApplicationDbContext(); + var user = await db.Users.FirstOrDefaultAsync(x => x.Id == userId, cancellationToken: ct); + if (user == null) + { + throw new Exception($"User ID {userId} not found"); + } var gambler = await db.Gamblers.OrderBy(x => x.Id).Include(x => x.User).LastOrDefaultAsync(g => g.User.Id == user.Id && g.State != GamblerState.PermanentlyBanned, cancellationToken: ct); @@ -226,7 +231,7 @@ public static class Money _logger.Info($"Gambler entity details: {gambler.Id}, Created: {gambler.Created:o}"); } if (!createIfNoneExists) return gambler; - var permaBanned = await IsPermanentlyBannedAsync(user, ct); + var permaBanned = await IsPermanentlyBannedAsync(userId, ct); _logger.Info($"permaBanned => {permaBanned}"); if (permaBanned) return null; var initialBalance = (await SettingsProvider.GetValueAsync(BuiltIn.Keys.MoneyInitialBalance)).ToType(); @@ -255,34 +260,39 @@ public static class Money /// /// Simple check to see whether a user has been permanently banned from the kasino /// - /// User to check for the permaban + /// User to check for the permaban /// Cancellation token /// - public static async Task IsPermanentlyBannedAsync(UserDbModel user, CancellationToken ct = default) + public static async Task IsPermanentlyBannedAsync(int userId, CancellationToken ct = default) { await using var db = new ApplicationDbContext(); - return await db.Gamblers.AnyAsync(u => u.User.Id == user.Id && u.State == GamblerState.PermanentlyBanned, + return await db.Gamblers.AnyAsync(u => u.User.Id == userId && u.State == GamblerState.PermanentlyBanned, cancellationToken: ct); } /// /// Modify a gambler's balance by a given +/- amount /// - /// Gambler entity whose balance you wish to modify + /// Gambler entity whose balance you wish to modify /// The 'effect' of this modification, as in how much to add or remove /// The event which initiated this balance modification /// Optional comment to provide for the transaction - /// If applicable, who sent the transaction (e.g. if a juicer) + /// If applicable, who sent the transaction (e.g. if a juicer) /// Cancellation token - public static async Task ModifyBalanceAsync(GamblerDbModel gambler, decimal effect, - TransactionSourceEventType eventSource, string? comment = null, GamblerDbModel? from = null, + public static async Task ModifyBalanceAsync(int gamblerId, decimal effect, + TransactionSourceEventType eventSource, string? comment = null, int? fromId = null, CancellationToken ct = default) { await using var db = new ApplicationDbContext(); - db.Attach(gambler); + var gambler = await db.Gamblers.FirstOrDefaultAsync(x => x.Id == gamblerId, cancellationToken: ct); + if (gambler == null) + { + throw new Exception($"Could not find gambler entity with given ID {gamblerId}"); + } _logger.Info($"Updating balance for {gambler.Id} with effect {effect:N}. Balance is currently {gambler.Balance:N}"); gambler.Balance += effect; _logger.Info($"Balance is now {gambler.Balance:N}"); + var from = await db.Gamblers.FirstOrDefaultAsync(x => x.Id == fromId, cancellationToken: ct); await db.Transactions.AddAsync(new TransactionDbModel { Gambler = gambler, @@ -301,7 +311,7 @@ public static class Money /// Add a wager to the database /// Will also issue a balance update unless you explicitly disable autoModifyBalance /// - /// Gambler who wagered + /// Gambler who wagered /// The amount they wagered /// The effect of the wager on the gambler's balance. /// Please note this includes the wager itself. So for a bet of 500 that paid out 50, you pass in an effect of -450 @@ -315,15 +325,20 @@ public static class Money /// Whether the game is 'complete'. Set to false for wagers with unknown outcomes. /// NOTE: wagerEffect will be ignored, instead value will be derived from the wagerAmount /// Cancellation token - public static async Task NewWagerAsync(GamblerDbModel gambler, decimal wagerAmount, decimal wagerEffect, + public static async Task NewWagerAsync(int gamblerId, decimal wagerAmount, decimal wagerEffect, WagerGame game, bool autoModifyBalance = true, dynamic? gameMeta = null, bool isComplete = true, CancellationToken ct = default) { + await using var db = new ApplicationDbContext(); + var gambler = await db.Gamblers.Include(gamblerDbModel => gamblerDbModel.User) + .FirstOrDefaultAsync(x => x.Id == gamblerId, ct); + if (gambler == null) + { + throw new Exception($"Tried to add wager for permanently excluded gambler {gamblerId}"); + } _logger.Info($"Adding a wager for {gambler.User.KfUsername}. wagerAmount => {wagerAmount:N}, " + $"wagerEffect => {wagerEffect:N}, game => {game.Humanize()}, autoModifyBalance => {autoModifyBalance}, " + $"isComplete => {isComplete}"); - await using var db = new ApplicationDbContext(); - db.Attach(gambler); string? metaJson = null; if (gameMeta != null) { @@ -382,13 +397,13 @@ public static class Money /// Get an active exclusion, returns null if there's no active exclusion /// If there's somehow multiple exclusions, will just grab the most recent one /// - /// Gambler entity to retrieve the exclusion for + /// Gambler ID to retrieve the exclusion for /// Cancellation token /// - public static async Task GetActiveExclusionAsync(GamblerDbModel gambler, CancellationToken ct = default) + public static async Task GetActiveExclusionAsync(int gamblerId, CancellationToken ct = default) { await using var db = new ApplicationDbContext(); - return (await db.Exclusions.Where(g => g.Gambler.Id == gambler.Id).ToListAsync(ct)) + return (await db.Exclusions.Where(g => g.Gambler.Id == gamblerId).ToListAsync(ct)) .LastOrDefault(e => e.Expires <= DateTimeOffset.UtcNow); } @@ -443,15 +458,19 @@ public static class Money /// /// Upgrade to the given VIP level. Grants a bonus as part of the level up. /// - /// The gambler you wish to level up + /// The gambler you wish to level up /// VIP level to grant them /// Cancellation token /// The bonus they received - public static async Task UpgradeVipLevelAsync(GamblerDbModel gambler, NextVipLevelModel nextVipLevel, + public static async Task UpgradeVipLevelAsync(int gamblerId, NextVipLevelModel nextVipLevel, CancellationToken ct = default) { await using var db = new ApplicationDbContext(); - db.Attach(gambler); + var gambler = await db.Gamblers.FirstOrDefaultAsync(x => x.Id == gamblerId, ct); + if (gambler == null) + { + throw new Exception($"Tried to upgrade VIP level for gambler with ID {gamblerId} who does not exist"); + } var payout = nextVipLevel.VipLevel.BonusPayout; if (nextVipLevel.Tier > 1) { @@ -470,7 +489,7 @@ public static class Money }, ct); gambler.NextVipLevelWagerRequirement = nextVipLevel.WagerRequirement; await db.SaveChangesAsync(ct); - await ModifyBalanceAsync(gambler, payout, TransactionSourceEventType.Bonus, + await ModifyBalanceAsync(gamblerId, payout, TransactionSourceEventType.Bonus, $"VIP Level '{nextVipLevel.VipLevel.Icon} {nextVipLevel.VipLevel.Name}' Tier {nextVipLevel.Tier} level up bonus", ct: ct); return payout; }