wip reimplementation of xp gain loop
This commit is contained in:
parent
fd464731d5
commit
96c4ea0637
2 changed files with 154 additions and 257 deletions
src/EllieBot
|
@ -1,17 +1,17 @@
|
|||
#nullable disable
|
||||
namespace EllieBot.Db.Models;
|
||||
|
||||
|
||||
// FUTURE remove LastLevelUp from here and UserXpStats
|
||||
public class DiscordUser : DbEntity
|
||||
{
|
||||
public const string DEFAULT_USERNAME = "??Unknown";
|
||||
|
||||
public ulong UserId { get; set; }
|
||||
public string Username { get; set; }
|
||||
// public string Discriminator { get; set; }
|
||||
public string AvatarId { get; set; }
|
||||
public string? Username { get; set; }
|
||||
public string? AvatarId { get; set; }
|
||||
|
||||
public int? ClubId { get; set; }
|
||||
public ClubInfo Club { get; set; }
|
||||
public ClubInfo? Club { get; set; }
|
||||
public bool IsClubAdmin { get; set; }
|
||||
|
||||
public long TotalXp { get; set; }
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#nullable disable warnings
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using LinqToDB;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using EllieBot.Common.ModuleBehaviors;
|
||||
|
@ -11,10 +12,12 @@ using SixLabors.ImageSharp.Formats;
|
|||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
using System.Threading.Channels;
|
||||
using LinqToDB.Data;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
using LinqToDB.Tools;
|
||||
using EllieBot.Modules.Administration;
|
||||
using EllieBot.Modules.Patronage;
|
||||
using ArgumentOutOfRangeException = System.ArgumentOutOfRangeException;
|
||||
using Color = SixLabors.ImageSharp.Color;
|
||||
using Exception = System.Exception;
|
||||
using Image = SixLabors.ImageSharp.Image;
|
||||
|
@ -36,7 +39,7 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
|||
private readonly ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>> _excludedChannels = new();
|
||||
private readonly ConcurrentHashSet<ulong> _excludedServers;
|
||||
|
||||
private XpTemplate _template;
|
||||
private XpTemplate _template = new();
|
||||
private readonly DiscordSocketClient _client;
|
||||
|
||||
private readonly TypedKey<bool> _xpTemplateReloadKey;
|
||||
|
@ -44,9 +47,7 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
|||
private readonly IBotCache _c;
|
||||
|
||||
|
||||
private readonly QueueRunner _levelUpQueue = new(0, 50);
|
||||
private readonly Channel<UserXpGainData> _xpGainQueue = Channel.CreateUnbounded<UserXpGainData>();
|
||||
private readonly IMessageSenderService _sender;
|
||||
private readonly INotifySubscriber _notifySub;
|
||||
private readonly ShardData _shardData;
|
||||
|
||||
|
@ -62,7 +63,6 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
|||
XpConfigService xpConfig,
|
||||
IPubSub pubSub,
|
||||
IPatronageService ps,
|
||||
IMessageSenderService sender,
|
||||
INotifySubscriber notifySub,
|
||||
ShardData shardData)
|
||||
{
|
||||
|
@ -74,7 +74,6 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
|||
_httpFactory = http;
|
||||
_xpConfig = xpConfig;
|
||||
_pubSub = pubSub;
|
||||
_sender = sender;
|
||||
_notifySub = notifySub;
|
||||
_shardData = shardData;
|
||||
_excludedServers = new();
|
||||
|
@ -109,16 +108,16 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
|||
|
||||
public async Task OnReadyAsync()
|
||||
{
|
||||
_ = Task.Run(() => _levelUpQueue.RunAsync());
|
||||
|
||||
// initialize ignored
|
||||
ArgumentOutOfRangeException.ThrowIfLessThan(_xpConfig.Data.MessageXpCooldown, 1,
|
||||
nameof(_xpConfig.Data.MessageXpCooldown));
|
||||
|
||||
await using (var ctx = _db.GetDbContext())
|
||||
{
|
||||
var xps = await ctx.GetTable<XpSettings>()
|
||||
.Where(x => Queries.GuildOnShard(x.GuildId, _shardData.TotalShards, _shardData.ShardId))
|
||||
.LoadWith(x => x.ExclusionList)
|
||||
.ToListAsyncLinqToDB();
|
||||
.Where(x => Queries.GuildOnShard(x.GuildId, _shardData.TotalShards, _shardData.ShardId))
|
||||
.LoadWith(x => x.ExclusionList)
|
||||
.ToListAsyncLinqToDB();
|
||||
|
||||
foreach (var xp in xps)
|
||||
{
|
||||
|
@ -135,173 +134,69 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
|||
}
|
||||
}
|
||||
|
||||
using var timer = new PeriodicTimer(TimeSpan.FromSeconds(5));
|
||||
while (await timer.WaitForNextTickAsync())
|
||||
await Task.WhenAll(UpdateTimer(), PeriodClearTimer());
|
||||
|
||||
return;
|
||||
|
||||
async Task PeriodClearTimer()
|
||||
{
|
||||
await UpdateXp();
|
||||
using var timer = new PeriodicTimer(TimeSpan.FromSeconds(_xpConfig.Data.MessageXpCooldown));
|
||||
while (true)
|
||||
{
|
||||
await timer.WaitForNextTickAsync();
|
||||
_usersGainedInPeriod.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
async Task UpdateTimer()
|
||||
{
|
||||
// todo a bigger loop that runs once every XpTimer
|
||||
using var timer = new PeriodicTimer(TimeSpan.FromSeconds(3));
|
||||
while (await timer.WaitForNextTickAsync())
|
||||
{
|
||||
await UpdateXp();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The current batch of users that will gain xp
|
||||
/// </summary>
|
||||
private readonly ConcurrentHashSet<IGuildUser> _usersBatch = new();
|
||||
|
||||
private async Task UpdateXp()
|
||||
{
|
||||
try
|
||||
var xpAmount = _xpConfig.Data.XpPerMessage;
|
||||
var currentBatch = _usersBatch.ToArray();
|
||||
_usersBatch.Clear();
|
||||
|
||||
await using var ctx = _db.GetDbContext();
|
||||
await using var lctx = ctx.CreateLinqToDBConnection();
|
||||
|
||||
await using var batchTable = await lctx.CreateTempTableAsync<UserXpBatch>();
|
||||
|
||||
await batchTable.BulkCopyAsync(currentBatch.Select(x => new UserXpBatch()
|
||||
{
|
||||
var reader = _xpGainQueue.Reader;
|
||||
GuildId = x.GuildId,
|
||||
UserId = x.Id,
|
||||
UserName = x.Username,
|
||||
AvatarId = x.DisplayAvatarId,
|
||||
|
||||
// sum up all gains into a single UserCacheItem
|
||||
var globalToAdd = new Dictionary<ulong, UserXpGainData>();
|
||||
var guildToAdd = new Dictionary<ulong, Dictionary<ulong, UserXpGainData>>();
|
||||
while (reader.TryRead(out var item))
|
||||
{
|
||||
// add global xp to these users
|
||||
if (!globalToAdd.TryGetValue(item.User.Id, out var ci))
|
||||
globalToAdd[item.User.Id] = item.Clone();
|
||||
else
|
||||
ci.XpAmount += item.XpAmount;
|
||||
}));
|
||||
|
||||
await lctx.ExecuteAsync(
|
||||
$"""
|
||||
INSERT INTO ${nameof(DiscordUser)}
|
||||
SELECT ${nameof(UserXpBatch.GuildId)}, ${nameof(UserXpBatch.UserId)}, ${nameof(UserXpBatch.UserName)}, ${nameof(UserXpBatch.AvatarId)}, ${xpAmount}
|
||||
FROM ${nameof(UserXpBatch)}
|
||||
ON CONFLICT(${nameof(DiscordUser.UserId)}, ${nameof(DiscordUser.UserId)}) DO UPDATE SET
|
||||
${nameof(DiscordUser.Username)} = ${nameof(UserXpBatch)}.${nameof(UserXpBatch.UserName)}
|
||||
${nameof(DiscordUser.AvatarId)} = ${nameof(UserXpBatch)}.${nameof(UserXpBatch.AvatarId)}
|
||||
${nameof(DiscordUser.TotalXp)} = ${nameof(DiscordUser.TotalXp)} + ${xpAmount}
|
||||
RETURNING *;
|
||||
""");
|
||||
|
||||
// ad guild xp in these guilds to these users
|
||||
if (!guildToAdd.TryGetValue(item.Guild.Id, out var users))
|
||||
users = guildToAdd[item.Guild.Id] = new();
|
||||
|
||||
if (!users.TryGetValue(item.User.Id, out ci))
|
||||
users[item.User.Id] = item.Clone();
|
||||
else
|
||||
ci.XpAmount += item.XpAmount;
|
||||
}
|
||||
|
||||
var dus = new List<DiscordUser>(globalToAdd.Count);
|
||||
var gxps = new List<UserXpStats>(globalToAdd.Count);
|
||||
var conf = _xpConfig.Data;
|
||||
await using (var ctx = _db.GetDbContext())
|
||||
{
|
||||
if (conf.CurrencyPerXp > 0)
|
||||
{
|
||||
foreach (var user in globalToAdd)
|
||||
{
|
||||
var amount = (long)(user.Value.XpAmount * conf.CurrencyPerXp);
|
||||
if (amount > 0)
|
||||
await _cs.AddAsync(user.Key, amount, null);
|
||||
}
|
||||
}
|
||||
|
||||
// update global user xp in batches
|
||||
// group by xp amount and update the same amounts at the same time
|
||||
foreach (var group in globalToAdd.GroupBy(x => x.Value.XpAmount, x => x.Key))
|
||||
{
|
||||
var items = await ctx.Set<DiscordUser>()
|
||||
.Where(x => group.Contains(x.UserId))
|
||||
.UpdateWithOutputAsync(old => new()
|
||||
{
|
||||
TotalXp = old.TotalXp + group.Key
|
||||
},
|
||||
(_, n) => n);
|
||||
|
||||
await ctx.Set<ClubInfo>()
|
||||
.Where(x => x.Members.Any(m => group.Contains(m.UserId)))
|
||||
.UpdateAsync(old => new()
|
||||
{
|
||||
Xp = old.Xp + (group.Key * old.Members.Count(m => group.Contains(m.UserId)))
|
||||
});
|
||||
|
||||
dus.AddRange(items);
|
||||
}
|
||||
|
||||
// update guild user xp in batches
|
||||
foreach (var (guildId, toAdd) in guildToAdd)
|
||||
{
|
||||
foreach (var group in toAdd.GroupBy(x => x.Value.XpAmount, x => x.Key))
|
||||
{
|
||||
var items = await ctx
|
||||
.Set<UserXpStats>()
|
||||
.Where(x => x.GuildId == guildId)
|
||||
.Where(x => group.Contains(x.UserId))
|
||||
.UpdateWithOutputAsync(old => new()
|
||||
{
|
||||
Xp = old.Xp + group.Key
|
||||
},
|
||||
(_, n) => n);
|
||||
|
||||
gxps.AddRange(items);
|
||||
|
||||
var missingUserIds = group.Where(userId => !items.Any(x => x.UserId == userId)).ToArray();
|
||||
foreach (var userId in missingUserIds)
|
||||
{
|
||||
await ctx
|
||||
.Set<UserXpStats>()
|
||||
.ToLinqToDBTable()
|
||||
.InsertOrUpdateAsync(() => new UserXpStats()
|
||||
{
|
||||
UserId = userId,
|
||||
GuildId = guildId,
|
||||
Xp = group.Key,
|
||||
DateAdded = DateTime.UtcNow,
|
||||
},
|
||||
_ => new()
|
||||
{
|
||||
},
|
||||
() => new()
|
||||
{
|
||||
UserId = userId
|
||||
});
|
||||
}
|
||||
|
||||
if (missingUserIds.Length > 0)
|
||||
{
|
||||
var missingItems = await ctx.Set<UserXpStats>()
|
||||
.ToLinqToDBTable()
|
||||
.Where(x => missingUserIds.Contains(x.UserId))
|
||||
.ToArrayAsyncLinqToDB();
|
||||
|
||||
gxps.AddRange(missingItems);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var du in dus)
|
||||
{
|
||||
var oldLevel = new LevelStats(du.TotalXp - globalToAdd[du.UserId].XpAmount);
|
||||
var newLevel = new LevelStats(du.TotalXp);
|
||||
|
||||
if (oldLevel.Level != newLevel.Level)
|
||||
{
|
||||
var item = globalToAdd[du.UserId];
|
||||
await _levelUpQueue.EnqueueAsync(
|
||||
NotifyUser(item.Guild.Id,
|
||||
item.Channel.Id,
|
||||
du.UserId,
|
||||
false,
|
||||
oldLevel.Level,
|
||||
newLevel.Level));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var du in gxps)
|
||||
{
|
||||
if (guildToAdd.TryGetValue(du.GuildId, out var users)
|
||||
&& users.TryGetValue(du.UserId, out var xpGainData))
|
||||
{
|
||||
var oldLevel = new LevelStats(du.Xp - xpGainData.XpAmount);
|
||||
var newLevel = new LevelStats(du.Xp);
|
||||
|
||||
if (oldLevel.Level < newLevel.Level)
|
||||
{
|
||||
await _levelUpQueue.EnqueueAsync(
|
||||
NotifyUser(xpGainData.Guild.Id,
|
||||
xpGainData.Channel.Id,
|
||||
du.UserId,
|
||||
true,
|
||||
oldLevel.Level,
|
||||
newLevel.Level));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error In the XP update loop");
|
||||
}
|
||||
// todo send notifications
|
||||
}
|
||||
|
||||
private Func<Task> NotifyUser(
|
||||
|
@ -564,23 +459,23 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
|||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
return await uow
|
||||
.UserXpStats
|
||||
.Where(x => x.GuildId == guildId)
|
||||
.OrderByDescending(x => x.Xp)
|
||||
.Skip(page * 10)
|
||||
.Take(10)
|
||||
.ToArrayAsyncLinqToDB();
|
||||
.UserXpStats
|
||||
.Where(x => x.GuildId == guildId)
|
||||
.OrderByDescending(x => x.Xp)
|
||||
.Skip(page * 10)
|
||||
.Take(10)
|
||||
.ToArrayAsyncLinqToDB();
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyCollection<UserXpStats>> GetGuildUserXps(ulong guildId, List<ulong> users, int page)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
return await uow.Set<UserXpStats>()
|
||||
.Where(x => x.GuildId == guildId && x.UserId.In(users))
|
||||
.OrderByDescending(x => x.Xp)
|
||||
.Skip(page * 10)
|
||||
.Take(10)
|
||||
.ToArrayAsyncLinqToDB();
|
||||
.Where(x => x.GuildId == guildId && x.UserId.In(users))
|
||||
.OrderByDescending(x => x.Xp)
|
||||
.Skip(page * 10)
|
||||
.Take(10)
|
||||
.ToArrayAsyncLinqToDB();
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyCollection<DiscordUser>> GetGlobalUserXps(int page)
|
||||
|
@ -588,10 +483,10 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
|||
await using var uow = _db.GetDbContext();
|
||||
|
||||
return await uow.GetTable<DiscordUser>()
|
||||
.OrderByDescending(x => x.TotalXp)
|
||||
.Skip(page * 10)
|
||||
.Take(10)
|
||||
.ToArrayAsyncLinqToDB();
|
||||
.OrderByDescending(x => x.TotalXp)
|
||||
.Skip(page * 10)
|
||||
.Take(10)
|
||||
.ToArrayAsyncLinqToDB();
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyCollection<DiscordUser>> GetGlobalUserXps(int page, List<ulong> users)
|
||||
|
@ -599,11 +494,11 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
|||
await using var uow = _db.GetDbContext();
|
||||
|
||||
return await uow.GetTable<DiscordUser>()
|
||||
.Where(x => x.UserId.In(users))
|
||||
.OrderByDescending(x => x.TotalXp)
|
||||
.Skip(page * 10)
|
||||
.Take(10)
|
||||
.ToArrayAsyncLinqToDB();
|
||||
.Where(x => x.UserId.In(users))
|
||||
.OrderByDescending(x => x.TotalXp)
|
||||
.Skip(page * 10)
|
||||
.Take(10)
|
||||
.ToArrayAsyncLinqToDB();
|
||||
}
|
||||
|
||||
private Task Client_OnGuildAvailable(SocketGuild guild)
|
||||
|
@ -804,13 +699,7 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
|||
if (!await SetUserRewardedAsync(user.Id))
|
||||
return;
|
||||
|
||||
await _xpGainQueue.Writer.WriteAsync(new()
|
||||
{
|
||||
Guild = user.Guild,
|
||||
Channel = arg.Channel,
|
||||
User = user,
|
||||
XpAmount = xp
|
||||
});
|
||||
_usersBatch.Add(user);
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
@ -833,11 +722,11 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
|||
{
|
||||
await using var ctx = _db.GetDbContext();
|
||||
return await ctx.GetTable<UserXpStats>()
|
||||
.Where(x => x.GuildId == guildId && userIds.Contains(x.UserId))
|
||||
.UpdateAsync(old => new()
|
||||
{
|
||||
Xp = old.Xp + amount
|
||||
});
|
||||
.Where(x => x.GuildId == guildId && userIds.Contains(x.UserId))
|
||||
.UpdateAsync(old => new()
|
||||
{
|
||||
Xp = old.Xp + amount
|
||||
});
|
||||
}
|
||||
|
||||
public async Task AddXpAsync(ulong userId, ulong guildId, int amount)
|
||||
|
@ -1198,10 +1087,10 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
|||
using (var tempDraw = Image.Load<Rgba32>(avatarData))
|
||||
{
|
||||
tempDraw.Mutate(x => x
|
||||
.Resize(_template.User.Icon.Size.X, _template.User.Icon.Size.Y)
|
||||
.ApplyRoundedCorners(Math.Max(_template.User.Icon.Size.X,
|
||||
_template.User.Icon.Size.Y)
|
||||
/ 2.0f));
|
||||
.Resize(_template.User.Icon.Size.X, _template.User.Icon.Size.Y)
|
||||
.ApplyRoundedCorners(Math.Max(_template.User.Icon.Size.X,
|
||||
_template.User.Icon.Size.Y)
|
||||
/ 2.0f));
|
||||
await using (var stream = await tempDraw.ToStreamAsync())
|
||||
{
|
||||
data = stream.ToArray();
|
||||
|
@ -1361,10 +1250,10 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
|||
using (var tempDraw = Image.Load<Rgba32>(imgData))
|
||||
{
|
||||
tempDraw.Mutate(x => x
|
||||
.Resize(_template.Club.Icon.Size.X, _template.Club.Icon.Size.Y)
|
||||
.ApplyRoundedCorners(Math.Max(_template.Club.Icon.Size.X,
|
||||
_template.Club.Icon.Size.Y)
|
||||
/ 2.0f));
|
||||
.Resize(_template.Club.Icon.Size.X, _template.Club.Icon.Size.Y)
|
||||
.ApplyRoundedCorners(Math.Max(_template.Club.Icon.Size.X,
|
||||
_template.Club.Icon.Size.Y)
|
||||
/ 2.0f));
|
||||
await using (var tds = await tempDraw.ToStreamAsync())
|
||||
{
|
||||
data = tds.ToArray();
|
||||
|
@ -1395,7 +1284,7 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
|||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
await uow.GetTable<UserXpStats>()
|
||||
.DeleteAsync(x => x.UserId == userId && x.GuildId == guildId);
|
||||
.DeleteAsync(x => x.UserId == userId && x.GuildId == guildId);
|
||||
}
|
||||
|
||||
public void XpReset(ulong guildId)
|
||||
|
@ -1409,8 +1298,8 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
|||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
await uow.GetTable<XpSettings>()
|
||||
.Where(x => x.GuildId == guildId)
|
||||
.DeleteAsync();
|
||||
.Where(x => x.GuildId == guildId)
|
||||
.DeleteAsync();
|
||||
}
|
||||
|
||||
public ValueTask<Dictionary<string, XpConfig.ShopItemInfo>?> GetShopBgs()
|
||||
|
@ -1454,7 +1343,7 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
|||
try
|
||||
{
|
||||
if (await ctx.GetTable<XpShopOwnedItem>()
|
||||
.AnyAsyncLinqToDB(x => x.UserId == userId && x.ItemKey == key && x.ItemType == type))
|
||||
.AnyAsyncLinqToDB(x => x.UserId == userId && x.ItemKey == key && x.ItemType == type))
|
||||
return BuyResult.AlreadyOwned;
|
||||
|
||||
var item = GetShopItem(type, key);
|
||||
|
@ -1467,14 +1356,14 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
|||
|
||||
|
||||
await ctx.GetTable<XpShopOwnedItem>()
|
||||
.InsertAsync(() => new XpShopOwnedItem()
|
||||
{
|
||||
UserId = userId,
|
||||
IsUsing = false,
|
||||
ItemKey = key,
|
||||
ItemType = type,
|
||||
DateAdded = DateTime.UtcNow,
|
||||
});
|
||||
.InsertAsync(() => new XpShopOwnedItem()
|
||||
{
|
||||
UserId = userId,
|
||||
IsUsing = false,
|
||||
ItemKey = key,
|
||||
ItemType = type,
|
||||
DateAdded = DateTime.UtcNow,
|
||||
});
|
||||
|
||||
return BuyResult.Success;
|
||||
}
|
||||
|
@ -1514,9 +1403,9 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
|||
{
|
||||
await using var ctx = _db.GetDbContext();
|
||||
return await ctx.GetTable<XpShopOwnedItem>()
|
||||
.AnyAsyncLinqToDB(x => x.UserId == userId
|
||||
&& x.ItemType == itemType
|
||||
&& x.ItemKey == key);
|
||||
.AnyAsyncLinqToDB(x => x.UserId == userId
|
||||
&& x.ItemType == itemType
|
||||
&& x.ItemKey == key);
|
||||
}
|
||||
|
||||
|
||||
|
@ -1527,9 +1416,9 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
|||
{
|
||||
await using var ctx = _db.GetDbContext();
|
||||
return await ctx.GetTable<XpShopOwnedItem>()
|
||||
.FirstOrDefaultAsyncLinqToDB(x => x.UserId == userId
|
||||
&& x.ItemType == itemType
|
||||
&& x.ItemKey == key);
|
||||
.FirstOrDefaultAsyncLinqToDB(x => x.UserId == userId
|
||||
&& x.ItemType == itemType
|
||||
&& x.ItemKey == key);
|
||||
}
|
||||
|
||||
public async Task<XpShopOwnedItem?> GetItemInUse(
|
||||
|
@ -1538,9 +1427,9 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
|||
{
|
||||
await using var ctx = _db.GetDbContext();
|
||||
return await ctx.GetTable<XpShopOwnedItem>()
|
||||
.FirstOrDefaultAsyncLinqToDB(x => x.UserId == userId
|
||||
&& x.ItemType == itemType
|
||||
&& x.IsUsing);
|
||||
.FirstOrDefaultAsyncLinqToDB(x => x.UserId == userId
|
||||
&& x.ItemType == itemType
|
||||
&& x.IsUsing);
|
||||
}
|
||||
|
||||
public async Task<bool> UseShopItemAsync(ulong userId, XpShopItemType itemType, string key)
|
||||
|
@ -1564,11 +1453,11 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
|||
if (await OwnsItemAsync(userId, itemType, key))
|
||||
{
|
||||
await ctx.GetTable<XpShopOwnedItem>()
|
||||
.Where(x => x.UserId == userId && x.ItemType == itemType)
|
||||
.UpdateAsync(old => new()
|
||||
{
|
||||
IsUsing = key == old.ItemKey
|
||||
});
|
||||
.Where(x => x.UserId == userId && x.ItemType == itemType)
|
||||
.UpdateAsync(old => new()
|
||||
{
|
||||
IsUsing = key == old.ItemKey
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -1590,9 +1479,9 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
|||
{
|
||||
await using var ctx = _db.GetDbContext();
|
||||
return await ctx.GetTable<UserXpStats>()
|
||||
.Where(x => x.GuildId == requestGuildId
|
||||
&& (guildUsers == null || guildUsers.Contains(x.UserId)))
|
||||
.CountAsyncLinqToDB();
|
||||
.Where(x => x.GuildId == requestGuildId
|
||||
&& (guildUsers == null || guildUsers.Contains(x.UserId)))
|
||||
.CountAsyncLinqToDB();
|
||||
}
|
||||
|
||||
public async Task SetLevelAsync(ulong guildId, ulong userId, int level)
|
||||
|
@ -1600,21 +1489,29 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
|||
var lvlStats = LevelStats.CreateForLevel(level);
|
||||
await using var ctx = _db.GetDbContext();
|
||||
await ctx.GetTable<UserXpStats>()
|
||||
.InsertOrUpdateAsync(() => new()
|
||||
{
|
||||
GuildId = guildId,
|
||||
UserId = userId,
|
||||
Xp = lvlStats.TotalXp,
|
||||
DateAdded = DateTime.UtcNow
|
||||
},
|
||||
(old) => new()
|
||||
{
|
||||
Xp = lvlStats.TotalXp
|
||||
},
|
||||
() => new()
|
||||
{
|
||||
GuildId = guildId,
|
||||
UserId = userId
|
||||
});
|
||||
.InsertOrUpdateAsync(() => new()
|
||||
{
|
||||
GuildId = guildId,
|
||||
UserId = userId,
|
||||
Xp = lvlStats.TotalXp,
|
||||
DateAdded = DateTime.UtcNow
|
||||
},
|
||||
(old) => new()
|
||||
{
|
||||
Xp = lvlStats.TotalXp
|
||||
},
|
||||
() => new()
|
||||
{
|
||||
GuildId = guildId,
|
||||
UserId = userId
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class UserXpBatch
|
||||
{
|
||||
[Key] public ulong UserId { get; set; }
|
||||
public ulong GuildId { get; set; }
|
||||
public string UserName { get; set; } = string.Empty;
|
||||
public string AvatarId { get; set; } = string.Empty;
|
||||
}
|
Loading…
Add table
Reference in a new issue