187 lines
No EOL
6.1 KiB
C#
187 lines
No EOL
6.1 KiB
C#
#nullable disable
|
|
using LinqToDB;
|
|
using LinqToDB.EntityFrameworkCore;
|
|
using EllieBot.Common.ModuleBehaviors;
|
|
using EllieBot.Db.Models;
|
|
using EllieBot.Modules.Gambling.Common;
|
|
using EllieBot.Modules.Gambling.Common.Connect4;
|
|
|
|
namespace EllieBot.Modules.Gambling.Services;
|
|
|
|
public class GamblingService : IEService, IReadyExecutor
|
|
{
|
|
public ConcurrentDictionary<(ulong, ulong), RollDuelGame> Duels { get; } = new();
|
|
public ConcurrentDictionary<ulong, Connect4Game> Connect4Games { get; } = new();
|
|
private readonly DbService _db;
|
|
private readonly DiscordSocketClient _client;
|
|
private readonly IBotCache _cache;
|
|
private readonly GamblingConfigService _gss;
|
|
|
|
private static readonly TypedKey<long> _curDecayKey = new("currency:last_decay");
|
|
|
|
public GamblingService(
|
|
DbService db,
|
|
DiscordSocketClient client,
|
|
IBotCache cache,
|
|
GamblingConfigService gss)
|
|
{
|
|
_db = db;
|
|
_client = client;
|
|
_cache = cache;
|
|
_gss = gss;
|
|
}
|
|
|
|
public Task OnReadyAsync()
|
|
=> Task.WhenAll(CurrencyDecayLoopAsync(), TransactionClearLoopAsync());
|
|
|
|
private async Task TransactionClearLoopAsync()
|
|
{
|
|
if (_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.Set<CurrencyTransaction>()
|
|
.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 (_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;
|
|
|
|
var now = DateTime.UtcNow;
|
|
|
|
await using var uow = _db.GetDbContext();
|
|
var result = await _cache.GetAsync(_curDecayKey);
|
|
|
|
if (result.TryPickT0(out var bin, out _)
|
|
&& (now - DateTime.FromBinary(bin) < 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.Set<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
|
|
});
|
|
|
|
await uow.SaveChangesAsync();
|
|
|
|
await _cache.AddAsync(_curDecayKey, now.ToBinary());
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Warning(ex,
|
|
"An unexpected error occurred in currency decay loop: {ErrorMessage}",
|
|
ex.Message);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static readonly TypedKey<EconomyResult> _ecoKey = new("ellie:economy");
|
|
|
|
private static readonly SemaphoreSlim _timelyLock = new(1, 1);
|
|
|
|
private static TypedKey<Dictionary<ulong, long>> _timelyKey
|
|
= new("timely:claims");
|
|
|
|
public async Task<TimeSpan?> ClaimTimelyAsync(ulong userId, int period)
|
|
{
|
|
if (period == 0)
|
|
return null;
|
|
|
|
await _timelyLock.WaitAsync();
|
|
try
|
|
{
|
|
// get the dictionary from the cache or get a new one
|
|
var dict = (await _cache.GetOrAddAsync(_timelyKey,
|
|
() => Task.FromResult(new Dictionary<ulong, long>())))!;
|
|
|
|
var now = DateTime.UtcNow;
|
|
var nowB = now.ToBinary();
|
|
|
|
// try to get users last claim
|
|
if (!dict.TryGetValue(userId, out var lastB))
|
|
lastB = dict[userId] = now.ToBinary();
|
|
|
|
var diff = now - DateTime.FromBinary(lastB);
|
|
|
|
// if its now, or too long ago => success
|
|
if (lastB == nowB || diff > period.Hours())
|
|
{
|
|
// update the cache
|
|
dict[userId] = nowB;
|
|
await _cache.AddAsync(_timelyKey, dict);
|
|
|
|
return null;
|
|
}
|
|
else
|
|
{
|
|
// otherwise return the remaining time
|
|
return period.Hours() - diff;
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
_timelyLock.Release();
|
|
}
|
|
}
|
|
|
|
public bool UserHasTimelyReminder(ulong userId)
|
|
{
|
|
var db = _db.GetDbContext();
|
|
return db.GetTable<Reminder>().Any(x => x.UserId == userId
|
|
&& x.Type == ReminderType.Timely);
|
|
}
|
|
|
|
public async Task RemoveAllTimelyClaimsAsync()
|
|
=> await _cache.RemoveAsync(_timelyKey);
|
|
} |