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;
|
namespace EllieBot.Db.Models;
|
||||||
|
|
||||||
|
|
||||||
// FUTURE remove LastLevelUp from here and UserXpStats
|
// FUTURE remove LastLevelUp from here and UserXpStats
|
||||||
public class DiscordUser : DbEntity
|
public class DiscordUser : DbEntity
|
||||||
{
|
{
|
||||||
|
public const string DEFAULT_USERNAME = "??Unknown";
|
||||||
|
|
||||||
public ulong UserId { get; set; }
|
public ulong UserId { get; set; }
|
||||||
public string Username { get; set; }
|
public string? Username { get; set; }
|
||||||
// public string Discriminator { get; set; }
|
public string? AvatarId { get; set; }
|
||||||
public string AvatarId { get; set; }
|
|
||||||
|
|
||||||
public int? ClubId { get; set; }
|
public int? ClubId { get; set; }
|
||||||
public ClubInfo Club { get; set; }
|
public ClubInfo? Club { get; set; }
|
||||||
public bool IsClubAdmin { get; set; }
|
public bool IsClubAdmin { get; set; }
|
||||||
|
|
||||||
public long TotalXp { get; set; }
|
public long TotalXp { get; set; }
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#nullable disable warnings
|
#nullable disable warnings
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
using LinqToDB;
|
using LinqToDB;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using EllieBot.Common.ModuleBehaviors;
|
using EllieBot.Common.ModuleBehaviors;
|
||||||
|
@ -11,10 +12,12 @@ using SixLabors.ImageSharp.Formats;
|
||||||
using SixLabors.ImageSharp.PixelFormats;
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
using SixLabors.ImageSharp.Processing;
|
using SixLabors.ImageSharp.Processing;
|
||||||
using System.Threading.Channels;
|
using System.Threading.Channels;
|
||||||
|
using LinqToDB.Data;
|
||||||
using LinqToDB.EntityFrameworkCore;
|
using LinqToDB.EntityFrameworkCore;
|
||||||
using LinqToDB.Tools;
|
using LinqToDB.Tools;
|
||||||
using EllieBot.Modules.Administration;
|
using EllieBot.Modules.Administration;
|
||||||
using EllieBot.Modules.Patronage;
|
using EllieBot.Modules.Patronage;
|
||||||
|
using ArgumentOutOfRangeException = System.ArgumentOutOfRangeException;
|
||||||
using Color = SixLabors.ImageSharp.Color;
|
using Color = SixLabors.ImageSharp.Color;
|
||||||
using Exception = System.Exception;
|
using Exception = System.Exception;
|
||||||
using Image = SixLabors.ImageSharp.Image;
|
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 ConcurrentDictionary<ulong, ConcurrentHashSet<ulong>> _excludedChannels = new();
|
||||||
private readonly ConcurrentHashSet<ulong> _excludedServers;
|
private readonly ConcurrentHashSet<ulong> _excludedServers;
|
||||||
|
|
||||||
private XpTemplate _template;
|
private XpTemplate _template = new();
|
||||||
private readonly DiscordSocketClient _client;
|
private readonly DiscordSocketClient _client;
|
||||||
|
|
||||||
private readonly TypedKey<bool> _xpTemplateReloadKey;
|
private readonly TypedKey<bool> _xpTemplateReloadKey;
|
||||||
|
@ -44,9 +47,7 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
||||||
private readonly IBotCache _c;
|
private readonly IBotCache _c;
|
||||||
|
|
||||||
|
|
||||||
private readonly QueueRunner _levelUpQueue = new(0, 50);
|
|
||||||
private readonly Channel<UserXpGainData> _xpGainQueue = Channel.CreateUnbounded<UserXpGainData>();
|
private readonly Channel<UserXpGainData> _xpGainQueue = Channel.CreateUnbounded<UserXpGainData>();
|
||||||
private readonly IMessageSenderService _sender;
|
|
||||||
private readonly INotifySubscriber _notifySub;
|
private readonly INotifySubscriber _notifySub;
|
||||||
private readonly ShardData _shardData;
|
private readonly ShardData _shardData;
|
||||||
|
|
||||||
|
@ -62,7 +63,6 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
||||||
XpConfigService xpConfig,
|
XpConfigService xpConfig,
|
||||||
IPubSub pubSub,
|
IPubSub pubSub,
|
||||||
IPatronageService ps,
|
IPatronageService ps,
|
||||||
IMessageSenderService sender,
|
|
||||||
INotifySubscriber notifySub,
|
INotifySubscriber notifySub,
|
||||||
ShardData shardData)
|
ShardData shardData)
|
||||||
{
|
{
|
||||||
|
@ -74,7 +74,6 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
||||||
_httpFactory = http;
|
_httpFactory = http;
|
||||||
_xpConfig = xpConfig;
|
_xpConfig = xpConfig;
|
||||||
_pubSub = pubSub;
|
_pubSub = pubSub;
|
||||||
_sender = sender;
|
|
||||||
_notifySub = notifySub;
|
_notifySub = notifySub;
|
||||||
_shardData = shardData;
|
_shardData = shardData;
|
||||||
_excludedServers = new();
|
_excludedServers = new();
|
||||||
|
@ -109,16 +108,16 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
||||||
|
|
||||||
public async Task OnReadyAsync()
|
public async Task OnReadyAsync()
|
||||||
{
|
{
|
||||||
_ = Task.Run(() => _levelUpQueue.RunAsync());
|
|
||||||
|
|
||||||
// initialize ignored
|
// initialize ignored
|
||||||
|
ArgumentOutOfRangeException.ThrowIfLessThan(_xpConfig.Data.MessageXpCooldown, 1,
|
||||||
|
nameof(_xpConfig.Data.MessageXpCooldown));
|
||||||
|
|
||||||
await using (var ctx = _db.GetDbContext())
|
await using (var ctx = _db.GetDbContext())
|
||||||
{
|
{
|
||||||
var xps = await ctx.GetTable<XpSettings>()
|
var xps = await ctx.GetTable<XpSettings>()
|
||||||
.Where(x => Queries.GuildOnShard(x.GuildId, _shardData.TotalShards, _shardData.ShardId))
|
.Where(x => Queries.GuildOnShard(x.GuildId, _shardData.TotalShards, _shardData.ShardId))
|
||||||
.LoadWith(x => x.ExclusionList)
|
.LoadWith(x => x.ExclusionList)
|
||||||
.ToListAsyncLinqToDB();
|
.ToListAsyncLinqToDB();
|
||||||
|
|
||||||
foreach (var xp in xps)
|
foreach (var xp in xps)
|
||||||
{
|
{
|
||||||
|
@ -135,173 +134,69 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
using var timer = new PeriodicTimer(TimeSpan.FromSeconds(5));
|
await Task.WhenAll(UpdateTimer(), PeriodClearTimer());
|
||||||
while (await timer.WaitForNextTickAsync())
|
|
||||||
|
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()
|
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
|
// todo send notifications
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Func<Task> NotifyUser(
|
private Func<Task> NotifyUser(
|
||||||
|
@ -564,23 +459,23 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
||||||
{
|
{
|
||||||
await using var uow = _db.GetDbContext();
|
await using var uow = _db.GetDbContext();
|
||||||
return await uow
|
return await uow
|
||||||
.UserXpStats
|
.UserXpStats
|
||||||
.Where(x => x.GuildId == guildId)
|
.Where(x => x.GuildId == guildId)
|
||||||
.OrderByDescending(x => x.Xp)
|
.OrderByDescending(x => x.Xp)
|
||||||
.Skip(page * 10)
|
.Skip(page * 10)
|
||||||
.Take(10)
|
.Take(10)
|
||||||
.ToArrayAsyncLinqToDB();
|
.ToArrayAsyncLinqToDB();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IReadOnlyCollection<UserXpStats>> GetGuildUserXps(ulong guildId, List<ulong> users, int page)
|
public async Task<IReadOnlyCollection<UserXpStats>> GetGuildUserXps(ulong guildId, List<ulong> users, int page)
|
||||||
{
|
{
|
||||||
await using var uow = _db.GetDbContext();
|
await using var uow = _db.GetDbContext();
|
||||||
return await uow.Set<UserXpStats>()
|
return await uow.Set<UserXpStats>()
|
||||||
.Where(x => x.GuildId == guildId && x.UserId.In(users))
|
.Where(x => x.GuildId == guildId && x.UserId.In(users))
|
||||||
.OrderByDescending(x => x.Xp)
|
.OrderByDescending(x => x.Xp)
|
||||||
.Skip(page * 10)
|
.Skip(page * 10)
|
||||||
.Take(10)
|
.Take(10)
|
||||||
.ToArrayAsyncLinqToDB();
|
.ToArrayAsyncLinqToDB();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IReadOnlyCollection<DiscordUser>> GetGlobalUserXps(int page)
|
public async Task<IReadOnlyCollection<DiscordUser>> GetGlobalUserXps(int page)
|
||||||
|
@ -588,10 +483,10 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
||||||
await using var uow = _db.GetDbContext();
|
await using var uow = _db.GetDbContext();
|
||||||
|
|
||||||
return await uow.GetTable<DiscordUser>()
|
return await uow.GetTable<DiscordUser>()
|
||||||
.OrderByDescending(x => x.TotalXp)
|
.OrderByDescending(x => x.TotalXp)
|
||||||
.Skip(page * 10)
|
.Skip(page * 10)
|
||||||
.Take(10)
|
.Take(10)
|
||||||
.ToArrayAsyncLinqToDB();
|
.ToArrayAsyncLinqToDB();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IReadOnlyCollection<DiscordUser>> GetGlobalUserXps(int page, List<ulong> users)
|
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();
|
await using var uow = _db.GetDbContext();
|
||||||
|
|
||||||
return await uow.GetTable<DiscordUser>()
|
return await uow.GetTable<DiscordUser>()
|
||||||
.Where(x => x.UserId.In(users))
|
.Where(x => x.UserId.In(users))
|
||||||
.OrderByDescending(x => x.TotalXp)
|
.OrderByDescending(x => x.TotalXp)
|
||||||
.Skip(page * 10)
|
.Skip(page * 10)
|
||||||
.Take(10)
|
.Take(10)
|
||||||
.ToArrayAsyncLinqToDB();
|
.ToArrayAsyncLinqToDB();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task Client_OnGuildAvailable(SocketGuild guild)
|
private Task Client_OnGuildAvailable(SocketGuild guild)
|
||||||
|
@ -804,13 +699,7 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
||||||
if (!await SetUserRewardedAsync(user.Id))
|
if (!await SetUserRewardedAsync(user.Id))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
await _xpGainQueue.Writer.WriteAsync(new()
|
_usersBatch.Add(user);
|
||||||
{
|
|
||||||
Guild = user.Guild,
|
|
||||||
Channel = arg.Channel,
|
|
||||||
User = user,
|
|
||||||
XpAmount = xp
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
@ -833,11 +722,11 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
||||||
{
|
{
|
||||||
await using var ctx = _db.GetDbContext();
|
await using var ctx = _db.GetDbContext();
|
||||||
return await ctx.GetTable<UserXpStats>()
|
return await ctx.GetTable<UserXpStats>()
|
||||||
.Where(x => x.GuildId == guildId && userIds.Contains(x.UserId))
|
.Where(x => x.GuildId == guildId && userIds.Contains(x.UserId))
|
||||||
.UpdateAsync(old => new()
|
.UpdateAsync(old => new()
|
||||||
{
|
{
|
||||||
Xp = old.Xp + amount
|
Xp = old.Xp + amount
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task AddXpAsync(ulong userId, ulong guildId, int 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))
|
using (var tempDraw = Image.Load<Rgba32>(avatarData))
|
||||||
{
|
{
|
||||||
tempDraw.Mutate(x => x
|
tempDraw.Mutate(x => x
|
||||||
.Resize(_template.User.Icon.Size.X, _template.User.Icon.Size.Y)
|
.Resize(_template.User.Icon.Size.X, _template.User.Icon.Size.Y)
|
||||||
.ApplyRoundedCorners(Math.Max(_template.User.Icon.Size.X,
|
.ApplyRoundedCorners(Math.Max(_template.User.Icon.Size.X,
|
||||||
_template.User.Icon.Size.Y)
|
_template.User.Icon.Size.Y)
|
||||||
/ 2.0f));
|
/ 2.0f));
|
||||||
await using (var stream = await tempDraw.ToStreamAsync())
|
await using (var stream = await tempDraw.ToStreamAsync())
|
||||||
{
|
{
|
||||||
data = stream.ToArray();
|
data = stream.ToArray();
|
||||||
|
@ -1361,10 +1250,10 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
||||||
using (var tempDraw = Image.Load<Rgba32>(imgData))
|
using (var tempDraw = Image.Load<Rgba32>(imgData))
|
||||||
{
|
{
|
||||||
tempDraw.Mutate(x => x
|
tempDraw.Mutate(x => x
|
||||||
.Resize(_template.Club.Icon.Size.X, _template.Club.Icon.Size.Y)
|
.Resize(_template.Club.Icon.Size.X, _template.Club.Icon.Size.Y)
|
||||||
.ApplyRoundedCorners(Math.Max(_template.Club.Icon.Size.X,
|
.ApplyRoundedCorners(Math.Max(_template.Club.Icon.Size.X,
|
||||||
_template.Club.Icon.Size.Y)
|
_template.Club.Icon.Size.Y)
|
||||||
/ 2.0f));
|
/ 2.0f));
|
||||||
await using (var tds = await tempDraw.ToStreamAsync())
|
await using (var tds = await tempDraw.ToStreamAsync())
|
||||||
{
|
{
|
||||||
data = tds.ToArray();
|
data = tds.ToArray();
|
||||||
|
@ -1395,7 +1284,7 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
||||||
{
|
{
|
||||||
await using var uow = _db.GetDbContext();
|
await using var uow = _db.GetDbContext();
|
||||||
await uow.GetTable<UserXpStats>()
|
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)
|
public void XpReset(ulong guildId)
|
||||||
|
@ -1409,8 +1298,8 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
||||||
{
|
{
|
||||||
await using var uow = _db.GetDbContext();
|
await using var uow = _db.GetDbContext();
|
||||||
await uow.GetTable<XpSettings>()
|
await uow.GetTable<XpSettings>()
|
||||||
.Where(x => x.GuildId == guildId)
|
.Where(x => x.GuildId == guildId)
|
||||||
.DeleteAsync();
|
.DeleteAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ValueTask<Dictionary<string, XpConfig.ShopItemInfo>?> GetShopBgs()
|
public ValueTask<Dictionary<string, XpConfig.ShopItemInfo>?> GetShopBgs()
|
||||||
|
@ -1454,7 +1343,7 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (await ctx.GetTable<XpShopOwnedItem>()
|
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;
|
return BuyResult.AlreadyOwned;
|
||||||
|
|
||||||
var item = GetShopItem(type, key);
|
var item = GetShopItem(type, key);
|
||||||
|
@ -1467,14 +1356,14 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
||||||
|
|
||||||
|
|
||||||
await ctx.GetTable<XpShopOwnedItem>()
|
await ctx.GetTable<XpShopOwnedItem>()
|
||||||
.InsertAsync(() => new XpShopOwnedItem()
|
.InsertAsync(() => new XpShopOwnedItem()
|
||||||
{
|
{
|
||||||
UserId = userId,
|
UserId = userId,
|
||||||
IsUsing = false,
|
IsUsing = false,
|
||||||
ItemKey = key,
|
ItemKey = key,
|
||||||
ItemType = type,
|
ItemType = type,
|
||||||
DateAdded = DateTime.UtcNow,
|
DateAdded = DateTime.UtcNow,
|
||||||
});
|
});
|
||||||
|
|
||||||
return BuyResult.Success;
|
return BuyResult.Success;
|
||||||
}
|
}
|
||||||
|
@ -1514,9 +1403,9 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
||||||
{
|
{
|
||||||
await using var ctx = _db.GetDbContext();
|
await using var ctx = _db.GetDbContext();
|
||||||
return await ctx.GetTable<XpShopOwnedItem>()
|
return await ctx.GetTable<XpShopOwnedItem>()
|
||||||
.AnyAsyncLinqToDB(x => x.UserId == userId
|
.AnyAsyncLinqToDB(x => x.UserId == userId
|
||||||
&& x.ItemType == itemType
|
&& x.ItemType == itemType
|
||||||
&& x.ItemKey == key);
|
&& x.ItemKey == key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1527,9 +1416,9 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
||||||
{
|
{
|
||||||
await using var ctx = _db.GetDbContext();
|
await using var ctx = _db.GetDbContext();
|
||||||
return await ctx.GetTable<XpShopOwnedItem>()
|
return await ctx.GetTable<XpShopOwnedItem>()
|
||||||
.FirstOrDefaultAsyncLinqToDB(x => x.UserId == userId
|
.FirstOrDefaultAsyncLinqToDB(x => x.UserId == userId
|
||||||
&& x.ItemType == itemType
|
&& x.ItemType == itemType
|
||||||
&& x.ItemKey == key);
|
&& x.ItemKey == key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<XpShopOwnedItem?> GetItemInUse(
|
public async Task<XpShopOwnedItem?> GetItemInUse(
|
||||||
|
@ -1538,9 +1427,9 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
||||||
{
|
{
|
||||||
await using var ctx = _db.GetDbContext();
|
await using var ctx = _db.GetDbContext();
|
||||||
return await ctx.GetTable<XpShopOwnedItem>()
|
return await ctx.GetTable<XpShopOwnedItem>()
|
||||||
.FirstOrDefaultAsyncLinqToDB(x => x.UserId == userId
|
.FirstOrDefaultAsyncLinqToDB(x => x.UserId == userId
|
||||||
&& x.ItemType == itemType
|
&& x.ItemType == itemType
|
||||||
&& x.IsUsing);
|
&& x.IsUsing);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> UseShopItemAsync(ulong userId, XpShopItemType itemType, string key)
|
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))
|
if (await OwnsItemAsync(userId, itemType, key))
|
||||||
{
|
{
|
||||||
await ctx.GetTable<XpShopOwnedItem>()
|
await ctx.GetTable<XpShopOwnedItem>()
|
||||||
.Where(x => x.UserId == userId && x.ItemType == itemType)
|
.Where(x => x.UserId == userId && x.ItemType == itemType)
|
||||||
.UpdateAsync(old => new()
|
.UpdateAsync(old => new()
|
||||||
{
|
{
|
||||||
IsUsing = key == old.ItemKey
|
IsUsing = key == old.ItemKey
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -1590,9 +1479,9 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
|
||||||
{
|
{
|
||||||
await using var ctx = _db.GetDbContext();
|
await using var ctx = _db.GetDbContext();
|
||||||
return await ctx.GetTable<UserXpStats>()
|
return await ctx.GetTable<UserXpStats>()
|
||||||
.Where(x => x.GuildId == requestGuildId
|
.Where(x => x.GuildId == requestGuildId
|
||||||
&& (guildUsers == null || guildUsers.Contains(x.UserId)))
|
&& (guildUsers == null || guildUsers.Contains(x.UserId)))
|
||||||
.CountAsyncLinqToDB();
|
.CountAsyncLinqToDB();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SetLevelAsync(ulong guildId, ulong userId, int level)
|
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);
|
var lvlStats = LevelStats.CreateForLevel(level);
|
||||||
await using var ctx = _db.GetDbContext();
|
await using var ctx = _db.GetDbContext();
|
||||||
await ctx.GetTable<UserXpStats>()
|
await ctx.GetTable<UserXpStats>()
|
||||||
.InsertOrUpdateAsync(() => new()
|
.InsertOrUpdateAsync(() => new()
|
||||||
{
|
{
|
||||||
GuildId = guildId,
|
GuildId = guildId,
|
||||||
UserId = userId,
|
UserId = userId,
|
||||||
Xp = lvlStats.TotalXp,
|
Xp = lvlStats.TotalXp,
|
||||||
DateAdded = DateTime.UtcNow
|
DateAdded = DateTime.UtcNow
|
||||||
},
|
},
|
||||||
(old) => new()
|
(old) => new()
|
||||||
{
|
{
|
||||||
Xp = lvlStats.TotalXp
|
Xp = lvlStats.TotalXp
|
||||||
},
|
},
|
||||||
() => new()
|
() => new()
|
||||||
{
|
{
|
||||||
GuildId = guildId,
|
GuildId = guildId,
|
||||||
UserId = userId
|
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
Add a link
Reference in a new issue