#nullable disable using LinqToDB; using LinqToDB.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using NadekoBot.Common.ModuleBehaviors; using NadekoBot.Db; using NadekoBot.Db.Models; using NadekoBot.Migrations; using NadekoBot.Modules.Gambling.Common; using NadekoBot.Modules.Gambling.Common.Connect4; using NadekoBot.Modules.Gambling.Common.Slot; using NadekoBot.Modules.Gambling.Common.WheelOfFortune; using Newtonsoft.Json; namespace NadekoBot.Modules.Gambling.Services; public class GamblingService : INService, IReadyExecutor { public ConcurrentDictionary<(ulong, ulong), RollDuelGame> Duels { get; } = new(); public ConcurrentDictionary Connect4Games { get; } = new(); private readonly DbService _db; private readonly ICurrencyService _cs; private readonly Bot _bot; private readonly DiscordSocketClient _client; private readonly IDataCache _cache; private readonly GamblingConfigService _gss; public GamblingService( DbService db, Bot bot, ICurrencyService cs, DiscordSocketClient client, IDataCache cache, GamblingConfigService gss) { _db = db; _cs = cs; _bot = bot; _client = client; _cache = cache; _gss = gss; } public Task OnReadyAsync() => Task.WhenAll(CurrencyDecayLoopAsync(), TransactionClearLoopAsync()); private async Task TransactionClearLoopAsync() { if (_bot.Client.ShardId != 0) return; using var timer = new PeriodicTimer(TimeSpan.FromHours(1)); while (await timer.WaitForNextTickAsync()) { try { var lifetime = _gss.Data.Currency.TransactionsLifetime; if (lifetime <= 0) continue; var now = DateTime.UtcNow; var days = TimeSpan.FromDays(lifetime); await using var uow = _db.GetDbContext(); await uow.CurrencyTransactions .DeleteAsync(ct => ct.DateAdded == null || now - ct.DateAdded < days); } catch (Exception ex) { Log.Warning(ex, "An unexpected error occurred in transactions cleanup loop: {ErrorMessage}", ex.Message); } } } private async Task CurrencyDecayLoopAsync() { if (_bot.Client.ShardId != 0) return; using var timer = new PeriodicTimer(TimeSpan.FromMinutes(5)); while (await timer.WaitForNextTickAsync()) { try { var config = _gss.Data; var maxDecay = config.Decay.MaxDecay; if (config.Decay.Percent is <= 0 or > 1 || maxDecay < 0) continue; await using var uow = _db.GetDbContext(); var lastCurrencyDecay = _cache.GetLastCurrencyDecay(); if (DateTime.UtcNow - lastCurrencyDecay < TimeSpan.FromHours(config.Decay.HourInterval)) continue; Log.Information(@"Decaying users' currency - decay: {ConfigDecayPercent}% | max: {MaxDecay} | threshold: {DecayMinTreshold}", config.Decay.Percent * 100, maxDecay, config.Decay.MinThreshold); if (maxDecay == 0) maxDecay = int.MaxValue; var decay = (double)config.Decay.Percent; await uow.DiscordUser .Where(x => x.CurrencyAmount > config.Decay.MinThreshold && x.UserId != _client.CurrentUser.Id) .UpdateAsync(old => new() { CurrencyAmount = maxDecay > Sql.Round((old.CurrencyAmount * decay) - 0.5) ? (long)(old.CurrencyAmount - Sql.Round((old.CurrencyAmount * decay) - 0.5)) : old.CurrencyAmount - maxDecay }); _cache.SetLastCurrencyDecay(); await uow.SaveChangesAsync(); } catch (Exception ex) { Log.Warning(ex, "An unexpected error occurred in currency decay loop: {ErrorMessage}", ex.Message); } } } public async Task SlotAsync(ulong userId, long amount) { var takeRes = await _cs.RemoveAsync(userId, amount, new("slot", "bet")); if (!takeRes) { return new() { Error = GamblingError.NotEnough }; } var game = new SlotGame(); var result = game.Spin(); long won = 0; if (result.Multiplier > 0) { won = (long)(result.Multiplier * amount); await _cs.AddAsync(userId, won, new("slot", "win", $"Slot Machine x{result.Multiplier}")); } var toReturn = new SlotResponse { Multiplier = result.Multiplier, Won = won }; toReturn.Rolls.AddRange(result.Rolls); return toReturn; } public async Task GetEconomyAsync() { if (_cache.TryGetEconomy(out var data)) { try { return JsonConvert.DeserializeObject(data); } catch { } } decimal cash; decimal onePercent; decimal planted; decimal waifus; decimal bank; long bot; using (var uow = _db.GetDbContext()) { cash = uow.DiscordUser.GetTotalCurrency(); onePercent = uow.DiscordUser.GetTopOnePercentCurrency(_client.CurrentUser.Id); planted = uow.PlantedCurrency.AsQueryable().Sum(x => x.Amount); waifus = uow.WaifuInfo.GetTotalValue(); bot = uow.DiscordUser.GetUserCurrency(_client.CurrentUser.Id); bank = await uow.GetTable() .SumAsyncLinqToDB(x => x.Balance); } var result = new EconomyResult { Cash = cash, Planted = planted, Bot = bot, Waifus = waifus, OnePercent = onePercent, Bank = bank }; _cache.SetEconomy(JsonConvert.SerializeObject(result)); return result; } public Task WheelOfFortuneSpinAsync(ulong userId, long bet) => new WheelOfFortuneGame(userId, bet, _gss.Data, _cs).SpinAsync(); public struct EconomyResult { public decimal Cash { get; set; } public decimal Planted { get; set; } public decimal Waifus { get; set; } public decimal OnePercent { get; set; } public decimal Bank { get; set; } public long Bot { get; set; } } }