diff --git a/src/EllieBot/Db/Extensions/UserXpExtensions.cs b/src/EllieBot/Db/Extensions/UserXpExtensions.cs index 5241f3f..d137868 100644 --- a/src/EllieBot/Db/Extensions/UserXpExtensions.cs +++ b/src/EllieBot/Db/Extensions/UserXpExtensions.cs @@ -31,17 +31,17 @@ public static class UserXpExtensions public static async Task> GetTopUserXps(this DbSet xps, ulong guildId, int count) => await xps.ToLinqToDBTable() .Where(x => x.GuildId == guildId) - .OrderByDescending(x => x.Xp + x.AwardedXp) + .OrderByDescending(x => x.Xp) .Take(count) .ToListAsyncLinqToDB(); public static async Task GetUserGuildRanking(this DbSet xps, ulong userId, ulong guildId) => await xps.ToLinqToDBTable() .Where(x => x.GuildId == guildId - && x.Xp + x.AwardedXp + && x.Xp > xps.AsQueryable() .Where(y => y.UserId == userId && y.GuildId == guildId) - .Select(y => y.Xp + y.AwardedXp) + .Select(y => y.Xp) .FirstOrDefault()) .CountAsyncLinqToDB() + 1; @@ -53,6 +53,6 @@ public static class UserXpExtensions => await userXp .Where(x => x.GuildId == guildId && x.UserId == userId) .FirstOrDefaultAsyncLinqToDB() is UserXpStats uxs - ? new(uxs.Xp + uxs.AwardedXp) + ? new(uxs.Xp) : new(0); } \ No newline at end of file diff --git a/src/EllieBot/Db/LevelStats.cs b/src/EllieBot/Db/LevelStats.cs index 0f3e02c..f859d8a 100644 --- a/src/EllieBot/Db/LevelStats.cs +++ b/src/EllieBot/Db/LevelStats.cs @@ -3,38 +3,28 @@ namespace EllieBot.Db; public readonly struct LevelStats { - public const int XP_REQUIRED_LVL_1 = 36; - public long Level { get; } public long LevelXp { get; } public long RequiredXp { get; } public long TotalXp { get; } - public LevelStats(long xp) + public LevelStats(long totalXp) { - if (xp < 0) - xp = 0; + if (totalXp < 0) + totalXp = 0; - TotalXp = xp; - - const int baseXp = XP_REQUIRED_LVL_1; - - var required = baseXp; - var totalXp = 0; - var lvl = 1; - while (true) - { - required = (int)(baseXp + (baseXp / 4.0 * (lvl - 1))); - - if (required + totalXp > xp) - break; - - totalXp += required; - lvl++; - } - - Level = lvl - 1; - LevelXp = xp - totalXp; - RequiredXp = required; + TotalXp = totalXp; + Level = GetLevelByTotalXp(totalXp); + LevelXp = totalXp - GetTotalXpReqForLevel(Level); + RequiredXp = (9 * (Level + 1)) + 27; } + + public static LevelStats CreateForLevel(long level) + => new(GetTotalXpReqForLevel(level)); + + public static long GetTotalXpReqForLevel(long level) + => ((9 * level * level) + (63 * level)) / 2; + + public static long GetLevelByTotalXp(long totalXp) + => (long)((-7.0 / 2) + (1 / 6.0 * Math.Sqrt((8 * totalXp) + 441))); } \ No newline at end of file diff --git a/src/EllieBot/Modules/Administration/Self/SelfCommands.cs b/src/EllieBot/Modules/Administration/Self/SelfCommands.cs index 440bd25..54a037a 100644 --- a/src/EllieBot/Modules/Administration/Self/SelfCommands.cs +++ b/src/EllieBot/Modules/Administration/Self/SelfCommands.cs @@ -66,9 +66,9 @@ public partial class Administration await message.ModifyAsync(x => x.Embed = CreateEmbed() - .WithDescription(GetText(strs.cache_users_done(added, updated))) - .WithOkColor() - .Build() + .WithDescription(GetText(strs.cache_users_done(added, updated))) + .WithOkColor() + .Build() ); } @@ -119,13 +119,13 @@ public partial class Administration await Response() .Embed(CreateEmbed() - .WithOkColor() - .WithTitle(GetText(strs.scadd)) - .AddField(GetText(strs.server), - cmd.GuildId is null ? "-" : $"{cmd.GuildName}/{cmd.GuildId}", - true) - .AddField(GetText(strs.channel), $"{cmd.ChannelName}/{cmd.ChannelId}", true) - .AddField(GetText(strs.command_text), cmdText)) + .WithOkColor() + .WithTitle(GetText(strs.scadd)) + .AddField(GetText(strs.server), + cmd.GuildId is null ? "-" : $"{cmd.GuildName}/{cmd.GuildId}", + true) + .AddField(GetText(strs.channel), $"{cmd.ChannelName}/{cmd.ChannelId}", true) + .AddField(GetText(strs.command_text), cmdText)) .SendAsync(); } @@ -323,16 +323,16 @@ public partial class Administration .ToArray()); var allShardStrings = statuses.Select(st => - { - var timeDiff = DateTime.UtcNow - st.LastUpdate; - var stateStr = ConnectionStateToEmoji(st); - var maxGuildCountLength = - statuses.Max(x => x.GuildCount).ToString().Length; - return $"`{stateStr} " - + $"| #{st.ShardId.ToString().PadBoth(3)} " - + $"| {timeDiff:mm\\:ss} " - + $"| {st.GuildCount.ToString().PadBoth(maxGuildCountLength)} `"; - }) + { + var timeDiff = DateTime.UtcNow - st.LastUpdate; + var stateStr = ConnectionStateToEmoji(st); + var maxGuildCountLength = + statuses.Max(x => x.GuildCount).ToString().Length; + return $"`{stateStr} " + + $"| #{st.ShardId.ToString().PadBoth(3)} " + + $"| {timeDiff:mm\\:ss} " + + $"| {st.GuildCount.ToString().PadBoth(maxGuildCountLength)} `"; + }) .ToArray(); await Response() .Paginated() @@ -440,7 +440,8 @@ public partial class Administration return; } - try { await Response().Confirm(strs.restarting).SendAsync(); } + try + { await Response().Confirm(strs.restarting).SendAsync(); } catch { } } diff --git a/src/EllieBot/Modules/Utility/GuildColorsCommands.cs b/src/EllieBot/Modules/Utility/GuildColorsCommands.cs index da17070..ec2a991 100644 --- a/src/EllieBot/Modules/Utility/GuildColorsCommands.cs +++ b/src/EllieBot/Modules/Utility/GuildColorsCommands.cs @@ -12,17 +12,21 @@ public partial class Utility [RequireContext(ContextType.Guild)] public async Task ServerColorsShow() { + var colors = _service.GetColors(ctx.Guild.Id); + var okHex = colors?.Ok?.RawValue.ToString("X8"); + var warnHex = colors?.Warn?.RawValue.ToString("X8"); + var errHex = colors?.Error?.RawValue.ToString("X8"); EmbedBuilder[] ebs = [ CreateEmbed() .WithOkColor() - .WithDescription("\\✅"), + .WithDescription($"\\✅ {okHex}"), CreateEmbed() .WithPendingColor() - .WithDescription("\\⏳\\⚠️"), + .WithDescription($"\\⏳\\⚠️ {warnHex}"), CreateEmbed() .WithErrorColor() - .WithDescription("\\❌") + .WithDescription($"\\❌ {errHex}") ]; await Response() diff --git a/src/EllieBot/Modules/Xp/Xp.cs b/src/EllieBot/Modules/Xp/Xp.cs index 75f5a64..f723813 100644 --- a/src/EllieBot/Modules/Xp/Xp.cs +++ b/src/EllieBot/Modules/Xp/Xp.cs @@ -59,9 +59,9 @@ public partial class Xp : EllieModule var serverSetting = _service.GetNotificationType(ctx.User.Id, ctx.Guild.Id); var embed = CreateEmbed() - .WithOkColor() - .AddField(GetText(strs.xpn_setting_global), GetNotifLocationString(globalSetting)) - .AddField(GetText(strs.xpn_setting_server), GetNotifLocationString(serverSetting)); + .WithOkColor() + .AddField(GetText(strs.xpn_setting_global), GetNotifLocationString(globalSetting)) + .AddField(GetText(strs.xpn_setting_server), GetNotifLocationString(serverSetting)); await Response().Embed(embed).SendAsync(); } @@ -154,9 +154,9 @@ public partial class Xp : EllieModule .Page((items, _) => { var embed = CreateEmbed() - .WithTitle(GetText(strs.exclusion_list)) - .WithDescription(string.Join('\n', items)) - .WithOkColor(); + .WithTitle(GetText(strs.exclusion_list)) + .WithDescription(string.Join('\n', items)) + .WithOkColor(); return embed; }) @@ -214,16 +214,12 @@ public partial class Xp : EllieModule for (var i = 0; i < users.Count; i++) { - var levelStats = new LevelStats(users[i].Xp + users[i].AwardedXp); + var levelStats = new LevelStats(users[i].Xp); var user = ((SocketGuild)ctx.Guild).GetUser(users[i].UserId); var userXpData = users[i]; var awardStr = string.Empty; - if (userXpData.AwardedXp > 0) - awardStr = $"(+{userXpData.AwardedXp})"; - else if (userXpData.AwardedXp < 0) - awardStr = $"({userXpData.AwardedXp})"; embed.AddField($"#{i + 1 + (curPage * 10)} {user?.ToString() ?? users[i].UserId.ToString()}", $"{GetText(strs.level_x(levelStats.Level))} - {levelStats.TotalXp}xp {awardStr}"); @@ -266,8 +262,8 @@ public partial class Xp : EllieModule .Page((users, curPage) => { var embed = CreateEmbed() - .WithOkColor() - .WithTitle(GetText(strs.global_leaderboard)); + .WithOkColor() + .WithTitle(GetText(strs.global_leaderboard)); if (!users.Any()) { @@ -287,6 +283,28 @@ public partial class Xp : EllieModule .SendAsync(); } + [Cmd] + [RequireContext(ContextType.Guild)] + [UserPerm(GuildPerm.Administrator)] + [Priority(1)] + public Task XpLevelSet(int level, IGuildUser user) + => XpLevelSet(level, user.Id); + + [Cmd] + [RequireContext(ContextType.Guild)] + [UserPerm(GuildPerm.Administrator)] + [Priority(0)] + public async Task XpLevelSet(int level, ulong userId) + { + if (level < 0) + return; + + await _service.SetLevelAsync(ctx.Guild.Id, userId, level); + await Response() + .Confirm(strs.level_set($"<@{userId}>", Format.Bold(level.ToString()))) + .SendAsync(); + } + [Cmd] [RequireContext(ContextType.Guild)] [UserPerm(GuildPerm.Administrator)] @@ -351,8 +369,8 @@ public partial class Xp : EllieModule public async Task XpReset(ulong userId) { var embed = CreateEmbed() - .WithTitle(GetText(strs.reset)) - .WithDescription(GetText(strs.reset_user_confirm)); + .WithTitle(GetText(strs.reset)) + .WithDescription(GetText(strs.reset_user_confirm)); if (!await PromptUserConfirmAsync(embed)) return; @@ -368,8 +386,8 @@ public partial class Xp : EllieModule public async Task XpReset() { var embed = CreateEmbed() - .WithTitle(GetText(strs.reset)) - .WithDescription(GetText(strs.reset_server_confirm)); + .WithTitle(GetText(strs.reset)) + .WithDescription(GetText(strs.reset_server_confirm)); if (!await PromptUserConfirmAsync(embed)) return; @@ -446,20 +464,20 @@ public partial class Xp : EllieModule { if (!items.Any()) return CreateEmbed() - .WithDescription(GetText(strs.not_found)) - .WithErrorColor(); + .WithDescription(GetText(strs.not_found)) + .WithErrorColor(); var (key, item) = items.FirstOrDefault(); var eb = CreateEmbed() - .WithOkColor() - .WithTitle(item.Name) - .AddField(GetText(strs.price), - CurrencyHelper.N(item.Price, Culture, _gss.GetCurrencySign()), - true) - .WithImageUrl(string.IsNullOrWhiteSpace(item.Preview) - ? item.Url - : item.Preview); + .WithOkColor() + .WithTitle(item.Name) + .AddField(GetText(strs.price), + CurrencyHelper.N(item.Price, Culture, _gss.GetCurrencySign()), + true) + .WithImageUrl(string.IsNullOrWhiteSpace(item.Preview) + ? item.Url + : item.Preview); if (!string.IsNullOrWhiteSpace(item.Desc)) eb.AddField(GetText(strs.desc), item.Desc); diff --git a/src/EllieBot/Modules/Xp/XpService.cs b/src/EllieBot/Modules/Xp/XpService.cs index e0af343..db18eb0 100644 --- a/src/EllieBot/Modules/Xp/XpService.cs +++ b/src/EllieBot/Modules/Xp/XpService.cs @@ -261,7 +261,6 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand GuildId = guildId, Xp = group.Key, DateAdded = DateTime.UtcNow, - AwardedXp = 0, NotifyOnLevelUp = XpNotificationLocation.None }, _ => new() @@ -310,8 +309,8 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand if (guildToAdd.TryGetValue(du.GuildId, out var users) && users.TryGetValue(du.UserId, out var xpGainData)) { - var oldLevel = new LevelStats(du.Xp - xpGainData.XpAmount + du.AwardedXp); - var newLevel = new LevelStats(du.Xp + du.AwardedXp); + var oldLevel = new LevelStats(du.Xp - xpGainData.XpAmount); + var newLevel = new LevelStats(du.Xp); if (oldLevel.Level < newLevel.Level) { @@ -595,7 +594,7 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand return await uow .UserXpStats .Where(x => x.GuildId == guildId) - .OrderByDescending(x => x.Xp + x.AwardedXp) + .OrderByDescending(x => x.Xp) .Skip(page * 10) .Take(10) .ToArrayAsyncLinqToDB(); @@ -606,7 +605,7 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand await using var uow = _db.GetDbContext(); return await uow.Set() .Where(x => x.GuildId == guildId && x.UserId.In(users)) - .OrderByDescending(x => x.Xp + x.AwardedXp) + .OrderByDescending(x => x.Xp) .Skip(page * 10) .Take(10) .ToArrayAsyncLinqToDB(); @@ -903,7 +902,7 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand using var uow = _db.GetDbContext(); var usr = uow.GetOrCreateUserXpStats(guildId, userId); - usr.AwardedXp += amount; + usr.Xp += amount; uow.SaveChanges(); } @@ -949,7 +948,7 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand return new(du, stats, new(totalXp), - new(stats.Xp + stats.AwardedXp), + new(stats.Xp), globalRank, guildRank); } @@ -1192,19 +1191,6 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand outlinePen)); } - if (stats.FullGuildStats.AwardedXp != 0 && template.User.Xp.Awarded.Show) - { - var sign = stats.FullGuildStats.AwardedXp > 0 ? "+ " : ""; - var awX = template.User.Xp.Awarded.Pos.X - - (Math.Max(0, stats.FullGuildStats.AwardedXp.ToString().Length - 2) * 5); - var awY = template.User.Xp.Awarded.Pos.Y; - img.Mutate(x => x.DrawText($"({sign}{stats.FullGuildStats.AwardedXp})", - _fonts.NotoSans.CreateFont(template.User.Xp.Awarded.FontSize, FontStyle.Bold), - Brushes.Solid(template.User.Xp.Awarded.Color), - outlinePen, - new(awX, awY))); - } - var rankPen = new SolidPen(Color.White, 1); //ranking if (template.User.GlobalRank.Show) @@ -1671,6 +1657,29 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand && (guildUsers == null || guildUsers.Contains(x.UserId))) .CountAsyncLinqToDB(); } + + public async Task SetLevelAsync(ulong guildId, ulong userId, int level) + { + var lvlStats = LevelStats.CreateForLevel(level); + await using var ctx = _db.GetDbContext(); + await ctx.GetTable() + .InsertOrUpdateAsync(() => new() + { + GuildId = guildId, + UserId = userId, + AwardedXp = 0, + Xp = lvlStats.TotalXp, + NotifyOnLevelUp = XpNotificationLocation.None, + DateAdded = DateTime.UtcNow + }, (old) => new() + { + Xp = lvlStats.TotalXp + }, () => new() + { + GuildId = guildId, + UserId = userId + }); + } } public enum BuyResult diff --git a/src/EllieBot/_common/Sender/MessageSenderService.cs b/src/EllieBot/_common/Sender/MessageSenderService.cs index f6e3205..2e96beb 100644 --- a/src/EllieBot/_common/Sender/MessageSenderService.cs +++ b/src/EllieBot/_common/Sender/MessageSenderService.cs @@ -51,7 +51,7 @@ public class EllieEmbedBuilder : EmbedBuilder var bcColors = bcsData.Data.Color; _okColor = guildColors?.Ok ?? bcColors.Ok.ToDiscordColor(); _errorColor = guildColors?.Error ?? bcColors.Error.ToDiscordColor(); - _pendingColor = guildColors?.Pending ?? bcColors.Pending.ToDiscordColor(); + _pendingColor = guildColors?.Warn ?? bcColors.Pending.ToDiscordColor(); } public EmbedBuilder WithOkColor() diff --git a/src/EllieBot/_common/Services/Impl/GuildColorsService.cs b/src/EllieBot/_common/Services/Impl/GuildColorsService.cs index 464382d..3c0ff4a 100644 --- a/src/EllieBot/_common/Services/Impl/GuildColorsService.cs +++ b/src/EllieBot/_common/Services/Impl/GuildColorsService.cs @@ -108,7 +108,7 @@ public sealed class GuildColorsService : IReadyExecutor, IGuildColorsService, IE { _colors[guildId] = _colors[guildId] with { - Pending = color?.ToDiscordColor() + Warn = color?.ToDiscordColor() }; } } diff --git a/src/EllieBot/_common/Services/Impl/IGuildColorsService.cs b/src/EllieBot/_common/Services/Impl/IGuildColorsService.cs index 6369b38..5fce9e1 100644 --- a/src/EllieBot/_common/Services/Impl/IGuildColorsService.cs +++ b/src/EllieBot/_common/Services/Impl/IGuildColorsService.cs @@ -10,4 +10,4 @@ public interface IGuildColorsService Task SetPendingColor(ulong guildId, Rgba32? color); } -public record struct Colors(Color? Ok, Color? Pending, Color? Error); +public record struct Colors(Color? Ok, Color? Warn, Color? Error); diff --git a/src/EllieBot/data/aliases.yml b/src/EllieBot/data/aliases.yml index fd20da5..a635a75 100644 --- a/src/EllieBot/data/aliases.yml +++ b/src/EllieBot/data/aliases.yml @@ -1121,6 +1121,8 @@ xpshopbuy: - xpshopbuy xpshopuse: - xpshopuse +xplevelset: + - xplevelset clubcreate: - clubcreate clubtransfer: diff --git a/src/EllieBot/data/strings/commands/commands.en-US.yml b/src/EllieBot/data/strings/commands/commands.en-US.yml index 8539adf..e269191 100644 --- a/src/EllieBot/data/strings/commands/commands.en-US.yml +++ b/src/EllieBot/data/strings/commands/commands.en-US.yml @@ -4820,4 +4820,14 @@ servercolorpending: - '#ffff00' params: - color: - desc: "The hex of the color to set" \ No newline at end of file + desc: "The hex of the color to set" +xplevelset: + desc: |- + Sets the level of the user you specify. + ex: + - '10 @User' + params: + - level: + desc: "The level to set the user to." + - user: + desc: "The user to set the level of." \ No newline at end of file diff --git a/src/EllieBot/data/strings/responses/responses.en-US.json b/src/EllieBot/data/strings/responses/responses.en-US.json index 50b74f7..a15504d 100644 --- a/src/EllieBot/data/strings/responses/responses.en-US.json +++ b/src/EllieBot/data/strings/responses/responses.en-US.json @@ -1137,5 +1137,6 @@ "server_not_found": "Server not found.", "server_color_set": "Successfully set a new server color.", "lasts_until": "Lasts Until", - "winners_count": "Winners" + "winners_count": "Winners", + "level_set": "Level of user {0} set to {1} on this server." }