finalized rewritten xp loop, updated xp.yml

This commit is contained in:
Toastie 2025-02-08 16:33:05 +13:00
parent dbc312dd9d
commit 06970eb9d3
Signed by: toastie_t0ast
GPG key ID: 0861BE54AD481DC7
15 changed files with 487 additions and 526 deletions

View file

@ -0,0 +1,14 @@
#nullable disable warnings
using System.ComponentModel.DataAnnotations;
namespace EllieBot.Modules.Xp.Services;
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;
}

View file

@ -31,11 +31,13 @@ public partial class Xp : EllieModule<XpService>
private readonly DownloadTracker _tracker; private readonly DownloadTracker _tracker;
private readonly ICurrencyProvider _gss; private readonly ICurrencyProvider _gss;
private readonly XpTemplateService _templateService;
public Xp(DownloadTracker tracker, ICurrencyProvider gss) public Xp(DownloadTracker tracker, ICurrencyProvider gss, XpTemplateService templateService)
{ {
_tracker = tracker; _tracker = tracker;
_gss = gss; _gss = gss;
_templateService = templateService;
} }
[Cmd] [Cmd]
@ -325,7 +327,7 @@ public partial class Xp : EllieModule<XpService>
[OwnerOnly] [OwnerOnly]
public async Task XpTemplateReload() public async Task XpTemplateReload()
{ {
_service.ReloadXpTemplate(); _templateService.ReloadXpTemplate();
await Task.Delay(1000); await Task.Delay(1000);
await Response().Confirm(strs.template_reloaded).SendAsync(); await Response().Confirm(strs.template_reloaded).SendAsync();
} }

View file

@ -10,25 +10,19 @@ namespace EllieBot.Modules.Xp;
public sealed partial class XpConfig : ICloneable<XpConfig> public sealed partial class XpConfig : ICloneable<XpConfig>
{ {
[Comment("""DO NOT CHANGE""")] [Comment("""DO NOT CHANGE""")]
public int Version { get; set; } = 7; public int Version { get; set; } = 10;
[Comment("""How much XP will the users receive per message""")] [Comment("""How much XP will the users receive per message""")]
public int XpPerMessage { get; set; } = 3; public int TextXpPerMessage { get; set; } = 3;
[Comment("""How often can the users receive XP, in seconds""")] [Comment("""How often can the users receive XP, in seconds""")]
public float MessageXpCooldown { get; set; } = 300; public int TextXpCooldown { get; set; } = 300;
[Comment("""Amount of xp users gain from posting an image""")] [Comment("""Amount of xp users gain from posting an image""")]
public int XpFromImage { get; set; } = 0; public int TextXpFromImage { get; set; } = 3;
[Comment("""Average amount of xp earned per minute in VC""")] [Comment("""Average amount of xp earned per minute in VC""")]
public double VoiceXpPerMinute { get; set; } = 0; public int VoiceXpPerMinute { get; set; } = 3;
[Comment("""The maximum amount of minutes the bot will keep track of a user in a voice channel""")]
public int VoiceMaxMinutes { get; set; } = 720;
[Comment("""The amount of currency users will receive for each point of global xp that they earn""")]
public float CurrencyPerXp { get; set; } = 0;
[Comment("""Xp Shop config""")] [Comment("""Xp Shop config""")]
public ShopConfig Shop { get; set; } = new(); public ShopConfig Shop { get; set; } = new();
@ -36,44 +30,44 @@ public sealed partial class XpConfig : ICloneable<XpConfig>
public sealed class ShopConfig public sealed class ShopConfig
{ {
[Comment(""" [Comment("""
Whether the xp shop is enabled Whether the xp shop is enabled
True -> Users can access the xp shop using .xpshop command True -> Users can access the xp shop using .xpshop command
False -> Users can't access the xp shop False -> Users can't access the xp shop
""")] """)]
public bool IsEnabled { get; set; } = false; public bool IsEnabled { get; set; } = false;
[Comment(""" [Comment("""
Which patron tier do users need in order to use the .xpshop bgs command Which patron tier do users need in order to use the .xpshop bgs command
Leave at 'None' if patron system is disabled or you don't want any restrictions Leave at 'None' if patron system is disabled or you don't want any restrictions
""")] """)]
public PatronTier BgsTierRequirement { get; set; } = PatronTier.None; public PatronTier BgsTierRequirement { get; set; } = PatronTier.None;
[Comment(""" [Comment("""
Which patron tier do users need in order to use the .xpshop frames command Which patron tier do users need in order to use the .xpshop frames command
Leave at 'None' if patron system is disabled or you don't want any restrictions Leave at 'None' if patron system is disabled or you don't want any restrictions
""")] """)]
public PatronTier FramesTierRequirement { get; set; } = PatronTier.None; public PatronTier FramesTierRequirement { get; set; } = PatronTier.None;
[Comment(""" [Comment("""
Frames available for sale. Keys are unique IDs. Frames available for sale. Keys are unique IDs.
Do not change keys as they are not publicly visible. Only change properties (name, price, id) Do not change keys as they are not publicly visible. Only change properties (name, price, id)
Removing a key which previously existed means that all previous purchases will also be unusable. Removing a key which previously existed means that all previous purchases will also be unusable.
To remove an item from the shop, but keep previous purchases, set the price to -1 To remove an item from the shop, but keep previous purchases, set the price to -1
""")] """)]
public Dictionary<string, ShopItemInfo>? Frames { get; set; } = new() public Dictionary<string, ShopItemInfo>? Frames { get; set; } = new()
{ {
{"default", new() {Name = "No frame", Price = 0, Url = string.Empty}} { "default", new() { Name = "No frame", Price = 0, Url = string.Empty } }
}; };
[Comment(""" [Comment("""
Backgrounds available for sale. Keys are unique IDs. Backgrounds available for sale. Keys are unique IDs.
Do not change keys as they are not publicly visible. Only change properties (name, price, id) Do not change keys as they are not publicly visible. Only change properties (name, price, id)
Removing a key which previously existed means that all previous purchases will also be unusable. Removing a key which previously existed means that all previous purchases will also be unusable.
To remove an item from the shop, but keep previous purchases, set the price to -1 To remove an item from the shop, but keep previous purchases, set the price to -1
""")] """)]
public Dictionary<string, ShopItemInfo>? Bgs { get; set; } = new() public Dictionary<string, ShopItemInfo>? Bgs { get; set; } = new()
{ {
{"default", new() {Name = "Default Background", Price = 0, Url = string.Empty}} { "default", new() { Name = "Default Background", Price = 0, Url = string.Empty } }
}; };
} }
@ -81,16 +75,17 @@ public sealed partial class XpConfig : ICloneable<XpConfig>
{ {
[Comment("""Visible name of the item""")] [Comment("""Visible name of the item""")]
public string Name { get; set; } public string Name { get; set; }
[Comment("""Price of the item. Set to -1 if you no longer want to sell the item but want the users to be able to keep their old purchase""")] [Comment(
"""Price of the item. Set to -1 if you no longer want to sell the item but want the users to be able to keep their old purchase""")]
public int Price { get; set; } public int Price { get; set; }
[Comment("""Direct url to the .png image which will be applied to the user's XP card""")] [Comment("""Direct url to the .png image which will be applied to the user's XP card""")]
public string Url { get; set; } public string Url { get; set; }
[Comment("""Optional preview url which will show instead of the real URL in the shop """)] [Comment("""Optional preview url which will show instead of the real URL in the shop """)]
public string Preview { get; set; } public string Preview { get; set; }
[Comment("""Optional description of the item""")] [Comment("""Optional description of the item""")]
public string Desc { get; set; } public string Desc { get; set; }
} }

View file

@ -15,24 +15,29 @@ public sealed class XpConfigService : ConfigServiceBase<XpConfig>
: base(FILE_PATH, serializer, pubSub, _changeKey) : base(FILE_PATH, serializer, pubSub, _changeKey)
{ {
AddParsedProp("txt.cooldown", AddParsedProp("txt.cooldown",
conf => conf.MessageXpCooldown, conf => conf.TextXpCooldown,
float.TryParse, int.TryParse,
(f) => f.ToString("F2"), (f) => f.ToString("F2"),
x => x > 0); x => x > 0);
AddParsedProp("txt.per_msg", conf => conf.XpPerMessage, int.TryParse, ConfigPrinters.ToString, x => x >= 0);
AddParsedProp("txt.per_image", conf => conf.XpFromImage, int.TryParse, ConfigPrinters.ToString, x => x > 0);
AddParsedProp("voice.per_minute", AddParsedProp("txt.permsg",
conf => conf.VoiceXpPerMinute, conf => conf.TextXpPerMessage,
double.TryParse, int.TryParse,
ConfigPrinters.ToString, ConfigPrinters.ToString,
x => x >= 0); x => x >= 0);
AddParsedProp("voice.max_minutes",
conf => conf.VoiceMaxMinutes, AddParsedProp("txt.perimage",
conf => conf.TextXpFromImage,
int.TryParse, int.TryParse,
ConfigPrinters.ToString, ConfigPrinters.ToString,
x => x > 0); x => x > 0);
AddParsedProp("voice.perminute",
conf => conf.VoiceXpPerMinute,
int.TryParse,
ConfigPrinters.ToString,
x => x >= 0);
AddParsedProp("shop.is_enabled", AddParsedProp("shop.is_enabled",
conf => conf.Shop.IsEnabled, conf => conf.Shop.IsEnabled,
bool.TryParse, bool.TryParse,
@ -43,21 +48,11 @@ public sealed class XpConfigService : ConfigServiceBase<XpConfig>
private void Migrate() private void Migrate()
{ {
if (data.Version < 2) if (data.Version < 10)
{ {
ModifyConfig(c => ModifyConfig(c =>
{ {
c.Version = 2; c.Version = 10;
c.XpFromImage = 0;
});
}
if (data.Version < 7)
{
ModifyConfig(c =>
{
c.Version = 7;
c.MessageXpCooldown *= 60;
}); });
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -7,8 +7,8 @@ namespace EllieBot.Modules.Xp;
public class XpTemplate public class XpTemplate
{ {
public int Version { get; set; } = 0; public int Version { get; set; } = 2;
[JsonProperty("output_size")] [JsonProperty("output_size")]
public XpTemplatePos OutputSize { get; set; } = new() public XpTemplatePos OutputSize { get; set; } = new()
{ {

View file

@ -217,7 +217,7 @@ public class XpSvc : GrpcXp.GrpcXpBase, IGrpcSvc, IEService
throw new RpcException(new Status(StatusCode.NotFound, "Guild not found")); throw new RpcException(new Status(StatusCode.NotFound, "Guild not found"));
var data = await _xp.GetGuildUserXps(request.GuildId, request.Page - 1); var data = await _xp.GetGuildUserXps(request.GuildId, request.Page - 1);
var total = await _xp.GetTotalGuildUsers(request.GuildId); var total = await _xp.GetGuildXpUsersCountAsync(request.GuildId);
var reply = new GetXpLbReply var reply = new GetXpLbReply
{ {

View file

@ -9,12 +9,12 @@ namespace Ellie.Common;
public sealed class MemoryBotCache : IBotCache public sealed class MemoryBotCache : IBotCache
{ {
// needed for overwrites and Delete return value // needed for overwrites and Delete return value
private readonly object _cacheLock = new object(); private readonly ConcurrentDictionary<string, object> _locks = new();
private readonly MemoryCache _cache; private readonly MemoryCache _cache;
public MemoryBotCache() public MemoryBotCache()
{ {
_cache = new MemoryCache(new MemoryCacheOptions()); _cache = new(new MemoryCacheOptions());
} }
public ValueTask<bool> AddAsync<T>(TypedKey<T> key, T value, TimeSpan? expiry = null, bool overwrite = true) public ValueTask<bool> AddAsync<T>(TypedKey<T> key, T value, TimeSpan? expiry = null, bool overwrite = true)
@ -26,12 +26,14 @@ public sealed class MemoryBotCache : IBotCache
item.AbsoluteExpirationRelativeToNow = expiry; item.AbsoluteExpirationRelativeToNow = expiry;
return new(true); return new(true);
} }
lock (_cacheLock) var cacheLock = _locks.GetOrAdd(key.Key, static _ => new());
lock (cacheLock)
{ {
if (_cache.TryGetValue(key.Key, out var old) && old is not null) if (_cache.TryGetValue(key.Key, out var old) && old is not null)
return new(false); return new(false);
using var item = _cache.CreateEntry(key.Key); using var item = _cache.CreateEntry(key.Key);
item.Value = value; item.Value = value;
item.AbsoluteExpirationRelativeToNow = expiry; item.AbsoluteExpirationRelativeToNow = expiry;
@ -61,9 +63,10 @@ public sealed class MemoryBotCache : IBotCache
public ValueTask<bool> RemoveAsync<T>(TypedKey<T> key) public ValueTask<bool> RemoveAsync<T>(TypedKey<T> key)
{ {
lock (_cacheLock) var cacheLock = _locks.GetOrAdd(key.Key, static _ => new());
lock (cacheLock)
{ {
var toReturn = _cache.TryGetValue(key.Key, out var old ) && old is not null; var toReturn = _cache.TryGetValue(key.Key, out var old) && old is not null;
_cache.Remove(key.Key); _cache.Remove(key.Key);
return new(toReturn); return new(toReturn);
} }

View file

@ -1,17 +1,13 @@
# DO NOT CHANGE # DO NOT CHANGE
version: 7 version: 10
# How much XP will the users receive per message # How much XP will the users receive per message
xpPerMessage: 3 textXpPerMessage: 3
# How often can the users receive XP, in seconds # How often can the users receive XP, in seconds
messageXpCooldown: 300 textXpCooldown: 300
# Amount of xp users gain from posting an image # Amount of xp users gain from posting an image
xpFromImage: 0 textXpFromImage: 3
# Average amount of xp earned per minute in VC # Average amount of xp earned per minute in VC
voiceXpPerMinute: 0 voiceXpPerMinute: 0
# The maximum amount of minutes the bot will keep track of a user in a voice channel
voiceMaxMinutes: 720
# The amount of currency users will receive for each point of global xp that they earn
currencyPerXp: 0
# Xp Shop config # Xp Shop config
shop: shop:
# Whether the xp shop is enabled # Whether the xp shop is enabled

View file

@ -1,5 +1,5 @@
{ {
"Version": 1, "Version": 2,
"output_size": { "output_size": {
"X": 800, "X": 800,
"Y": 392 "Y": 392