From 569f54bd2dbeba460e0a557e935ab6eb9322ae7e Mon Sep 17 00:00:00 2001 From: Tom <167194956+Dreary@users.noreply.github.com> Date: Tue, 10 Feb 2026 02:21:36 +0000 Subject: [PATCH 1/4] Add guild trophy ranking and caching - Add GuildTrophyRankInfo record as a DTO for guild ranking data. - Implement GameStorage methods: GetGuildTrophyRankInfo(by id/name), GetGuildIdByCharacterId, GetGuildTrophyRankings, and ComputeGuildTrophy. Rankings are computed by aggregating member achievement totals, ordered, and limited to top 200. Implement batch queries to reduce per-entity EF tracking. - Update WebController to expose GuildTrophy and PersonalGuildTrophy handlers, add a 3-minute in-memory cache for rankings, and use cached data when possible. Add necessary using and cache fields. - Change InGameRankPacket.GuildTrophy to accept a list of GuildTrophyRankInfo and serialize full guild info (rank, id, name, emblem, leader, and trophy breakdown). - Persist guild emblem updates in GuildManager by saving the guild to GameStorage after changes. These changes enable querying and serving guild trophy leaderboards and individual guild ranks with basic caching and saves emblems. --- .../Model/Ranking/GuildTrophyRankInfo.cs | 11 ++ .../Storage/Game/GameStorage.Web.cs | 114 ++++++++++++++++++ .../Controllers/WebController.cs | 47 ++++++++ Maple2.Server.Web/Packet/InGameRankPacket.cs | 25 ++-- .../Containers/GuildManager.cs | 5 + 5 files changed, 190 insertions(+), 12 deletions(-) create mode 100644 Maple2.Database/Model/Ranking/GuildTrophyRankInfo.cs diff --git a/Maple2.Database/Model/Ranking/GuildTrophyRankInfo.cs b/Maple2.Database/Model/Ranking/GuildTrophyRankInfo.cs new file mode 100644 index 000000000..1271f7736 --- /dev/null +++ b/Maple2.Database/Model/Ranking/GuildTrophyRankInfo.cs @@ -0,0 +1,11 @@ +using Maple2.Model.Game; + +namespace Maple2.Database.Model.Ranking; + +public record GuildTrophyRankInfo( + int Rank, + long GuildId, + string Name, + string Emblem, + string LeaderName, + AchievementInfo Trophy); diff --git a/Maple2.Database/Storage/Game/GameStorage.Web.cs b/Maple2.Database/Storage/Game/GameStorage.Web.cs index 8bc8d3957..672a3919a 100644 --- a/Maple2.Database/Storage/Game/GameStorage.Web.cs +++ b/Maple2.Database/Storage/Game/GameStorage.Web.cs @@ -98,6 +98,120 @@ public IList GetTrophyRankings() { Trophy: r.Trophy)) .ToList(); } + public GuildTrophyRankInfo? GetGuildTrophyRankInfo(long guildId) { + var guild = Context.Guild + .Where(g => g.Id == guildId) + .Select(g => new { g.Id, g.Name, g.Emblem, g.LeaderId }) + .FirstOrDefault(); + if (guild == null) { + return null; + } + + string leaderName = Context.Character + .Where(c => c.Id == guild.LeaderId) + .Select(c => c.Name) + .FirstOrDefault() ?? string.Empty; + + AchievementInfo guildTrophy = ComputeGuildTrophy(guildId); + + // Compute all guild totals to determine rank + var allGuildIds = Context.Guild.Select(g => g.Id).ToList(); + var guildTotals = new List<(long GuildId, int Total)>(); + foreach (long id in allGuildIds) { + AchievementInfo info = ComputeGuildTrophy(id); + guildTotals.Add((id, info.Total)); + } + + guildTotals = guildTotals.OrderByDescending(g => g.Total).ToList(); + int rank = guildTotals.FindIndex(g => g.GuildId == guildId) + 1; + if (rank == 0) { + rank = guildTotals.Count + 1; + } + + return new GuildTrophyRankInfo( + Rank: rank, + GuildId: guild.Id, + Name: guild.Name, + Emblem: guild.Emblem, + LeaderName: leaderName, + Trophy: guildTrophy); + } + + public GuildTrophyRankInfo? GetGuildTrophyRankInfo(string guildName) { + long? guildId = Context.Guild + .Where(g => g.Name == guildName) + .Select(g => (long?) g.Id) + .FirstOrDefault(); + if (guildId == null) { + return null; + } + + return GetGuildTrophyRankInfo(guildId.Value); + } + + public long GetGuildIdByCharacterId(long characterId) { + return Context.GuildMember + .Where(m => m.CharacterId == characterId) + .Select(m => m.GuildId) + .FirstOrDefault(); + } + + public IList GetGuildTrophyRankings() { + // Read guild data with Select projection to avoid EF tracking issues + var guilds = Context.Guild + .Select(g => new { g.Id, g.Name, g.Emblem, g.LeaderId }) + .ToList(); + + // Batch lookup leader names + var leaderIds = guilds.Select(g => g.LeaderId).Distinct().ToList(); + var leaderNames = Context.Character + .Where(c => leaderIds.Contains(c.Id)) + .Select(c => new { c.Id, c.Name }) + .ToDictionary(c => c.Id, c => c.Name); + + var rankings = new List(); + foreach (var guild in guilds) { + AchievementInfo trophy = ComputeGuildTrophy(guild.Id); + if (trophy.Total <= 0) { + continue; + } + string leaderName = leaderNames.GetValueOrDefault(guild.LeaderId, string.Empty); + rankings.Add(new GuildTrophyRankInfo(0, guild.Id, guild.Name, guild.Emblem, leaderName, trophy)); + } + + return rankings + .OrderByDescending(r => r.Trophy.Total) + .Select((r, index) => new GuildTrophyRankInfo( + Rank: index + 1, + GuildId: r.GuildId, + Name: r.Name, + Emblem: r.Emblem, + LeaderName: r.LeaderName, + Trophy: r.Trophy)) + .Take(200) + .ToList(); + } + + private AchievementInfo ComputeGuildTrophy(long guildId) { + // Get all member character IDs and their account IDs via Select projection + var members = Context.GuildMember + .Where(m => m.GuildId == guildId) + .Select(m => m.CharacterId) + .ToList(); + + // Batch lookup account IDs + var characterInfo = Context.Character + .Where(c => members.Contains(c.Id)) + .Select(c => new { c.Id, c.AccountId }) + .ToList(); + + var total = new AchievementInfo(); + foreach (var info in characterInfo) { + total += GetAchievementInfo(info.AccountId, info.Id); + } + + return total; + } #endregion public IList GetMentorList(long accountId, long characterId) { diff --git a/Maple2.Server.Web/Controllers/WebController.cs b/Maple2.Server.Web/Controllers/WebController.cs index 7ee7834fc..047d012a8 100644 --- a/Maple2.Server.Web/Controllers/WebController.cs +++ b/Maple2.Server.Web/Controllers/WebController.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.IO.Compression; +using System.Linq; using System.Threading.Tasks; using Maple2.Database.Model.Ranking; using Maple2.Database.Storage; @@ -24,6 +25,9 @@ public class WebController : ControllerBase { private readonly WebStorage webStorage; private readonly GameStorage gameStorage; + private static IList? _guildTrophyCache; + private static DateTime _guildTrophyCacheExpiry; + public WebController(WebStorage webStorage, GameStorage gameStorage) { this.webStorage = webStorage; this.gameStorage = gameStorage; @@ -58,6 +62,8 @@ public async Task Rankings() { ByteWriter pWriter = type switch { GameRankingType.Trophy => Trophy(userName), GameRankingType.PersonalTrophy => PersonalTrophy(characterId), + GameRankingType.GuildTrophy => GuildTrophy(userName), + GameRankingType.PersonalGuildTrophy => PersonalGuildTrophy(characterId), _ => new ByteWriter(), }; @@ -299,7 +305,48 @@ public ByteWriter PersonalTrophy(long characterId) { using GameStorage.Request db = gameStorage.Context(); TrophyRankInfo? info = db.GetTrophyRankInfo(characterId); return InGameRankPacket.PersonalRank(GameRankingType.PersonalTrophy, info?.Rank ?? 0); + } + + public ByteWriter GuildTrophy(string userName) { + if (!string.IsNullOrEmpty(userName)) { + // Search by guild name, check cache first then fall back to DB + IList rankings = GetCachedGuildTrophyRankings(); + GuildTrophyRankInfo? cached = rankings.FirstOrDefault(r => + r.Name.Equals(userName, StringComparison.OrdinalIgnoreCase)); + if (cached != null) { + return InGameRankPacket.GuildTrophy(new[] { cached }); + } + + using GameStorage.Request db = gameStorage.Context(); + GuildTrophyRankInfo? info = db.GetGuildTrophyRankInfo(userName); + return InGameRankPacket.GuildTrophy(info != null ? new[] { info } : []); + } + + return InGameRankPacket.GuildTrophy(GetCachedGuildTrophyRankings()); + } + public ByteWriter PersonalGuildTrophy(long characterId) { + using GameStorage.Request db = gameStorage.Context(); + long guildId = db.GetGuildIdByCharacterId(characterId); + if (guildId == 0) { + return InGameRankPacket.PersonalRank(GameRankingType.PersonalGuildTrophy, 0); + } + + // Check cached rankings for this guild's rank + IList rankings = GetCachedGuildTrophyRankings(); + GuildTrophyRankInfo? cached = rankings.FirstOrDefault(r => r.GuildId == guildId); + return InGameRankPacket.PersonalRank(GameRankingType.PersonalGuildTrophy, cached?.Rank ?? 0); + } + + private IList GetCachedGuildTrophyRankings() { + if (_guildTrophyCache != null && DateTime.Now < _guildTrophyCacheExpiry) { + return _guildTrophyCache; + } + + using GameStorage.Request db = gameStorage.Context(); + _guildTrophyCache = db.GetGuildTrophyRankings(); + _guildTrophyCacheExpiry = DateTime.Now.AddMinutes(3); + return _guildTrophyCache; } #endregion diff --git a/Maple2.Server.Web/Packet/InGameRankPacket.cs b/Maple2.Server.Web/Packet/InGameRankPacket.cs index 2accf1af7..6ae14a122 100644 --- a/Maple2.Server.Web/Packet/InGameRankPacket.cs +++ b/Maple2.Server.Web/Packet/InGameRankPacket.cs @@ -37,23 +37,24 @@ public static ByteWriter PersonalRank(GameRankingType type, int rank) { return pWriter; } - public static ByteWriter GuildTrophy() { + public static ByteWriter GuildTrophy(IList rankInfos) { var pWriter = new ByteWriter(); pWriter.Write(GameRankingType.GuildTrophy); pWriter.WriteInt(0); pWriter.WriteUnicodeStringWithLength(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); - pWriter.WriteInt(1); // count - - pWriter.WriteInt(0); // rank - pWriter.WriteLong(0); // guildId - pWriter.WriteUnicodeStringWithLength(string.Empty); // guildName - pWriter.WriteUnicodeStringWithLength(string.Empty); // guildEmblem - pWriter.WriteLong(); // guild leader id ? - pWriter.WriteInt(); // guild trophy total - pWriter.WriteInt(0); // guild trophy combat - pWriter.WriteInt(0); // guild trophy adventure - pWriter.WriteInt(0); // guild trophy lifestyle + pWriter.WriteInt(rankInfos.Count); + foreach (GuildTrophyRankInfo info in rankInfos) { + pWriter.WriteInt(info.Rank); + pWriter.WriteLong(info.GuildId); + pWriter.WriteUnicodeStringWithLength(info.Name); + pWriter.WriteUnicodeStringWithLength(info.Emblem); + pWriter.WriteUnicodeStringWithLength(info.LeaderName); + pWriter.WriteInt(info.Trophy.Total); + pWriter.WriteInt(info.Trophy.Combat); + pWriter.WriteInt(info.Trophy.Adventure); + pWriter.WriteInt(info.Trophy.Lifestyle); + } return pWriter; } diff --git a/Maple2.Server.World/Containers/GuildManager.cs b/Maple2.Server.World/Containers/GuildManager.cs index 9e4e7f187..ef4030b2d 100644 --- a/Maple2.Server.World/Containers/GuildManager.cs +++ b/Maple2.Server.World/Containers/GuildManager.cs @@ -308,6 +308,11 @@ public GuildError UpdateEmblem(long requestorId, string emblem) { } Guild.Emblem = emblem; + + using (GameStorage.Request db = GameStorage.Context()) { + db.SaveGuild(Guild); + } + Broadcast(new GuildRequest { UpdateEmblem = new GuildRequest.Types.UpdateEmblem { RequestorName = requestor.Name, From ade23e5838e9aadc4a4f25bfa6a9b37fc122267a Mon Sep 17 00:00:00 2001 From: Tom <167194956+Dreary@users.noreply.github.com> Date: Tue, 10 Feb 2026 02:24:05 +0000 Subject: [PATCH 2/4] dotnet format --- Maple2.Database/Model/Ranking/GuildTrophyRankInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Maple2.Database/Model/Ranking/GuildTrophyRankInfo.cs b/Maple2.Database/Model/Ranking/GuildTrophyRankInfo.cs index 1271f7736..86e1553c7 100644 --- a/Maple2.Database/Model/Ranking/GuildTrophyRankInfo.cs +++ b/Maple2.Database/Model/Ranking/GuildTrophyRankInfo.cs @@ -1,4 +1,4 @@ -using Maple2.Model.Game; +using Maple2.Model.Game; namespace Maple2.Database.Model.Ranking; From c46b6ecf789b8aec94472f53ead5548698074982 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=82ngelo=20Tadeucci?= Date: Mon, 9 Feb 2026 23:47:01 -0300 Subject: [PATCH 3/4] Add memory cache for ranking endpoints Introduce IMemoryCache to cache ranking responses and reduce DB load. WebController now accepts IMemoryCache, removes the old static guild-trophy cache, and caches Trophy, PersonalTrophy, GuildTrophy, PersonalGuildTrophy and full guild rankings (GetCachedGuildTrophyRankings) using hourly TTL (RankingCacheDuration). Program.cs registers AddMemoryCache so the cache is available via DI. Cache keys follow patterns like "Trophy_{user}", "PersonalTrophy_{id}", "GuildTrophy_{name}", "PersonalGuildTrophy_{id}", and "GuildTrophyRankings". --- .../Controllers/WebController.cs | 113 +++++++++++------- Maple2.Server.Web/Program.cs | 1 + 2 files changed, 74 insertions(+), 40 deletions(-) diff --git a/Maple2.Server.Web/Controllers/WebController.cs b/Maple2.Server.Web/Controllers/WebController.cs index 047d012a8..9735b15d0 100644 --- a/Maple2.Server.Web/Controllers/WebController.cs +++ b/Maple2.Server.Web/Controllers/WebController.cs @@ -14,6 +14,7 @@ using Maple2.Tools.Extensions; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Caching.Memory; using Serilog; using Enum = System.Enum; @@ -24,13 +25,13 @@ public class WebController : ControllerBase { private readonly WebStorage webStorage; private readonly GameStorage gameStorage; + private readonly IMemoryCache cache; + private static readonly TimeSpan RankingCacheDuration = TimeSpan.FromHours(1); - private static IList? _guildTrophyCache; - private static DateTime _guildTrophyCacheExpiry; - - public WebController(WebStorage webStorage, GameStorage gameStorage) { + public WebController(WebStorage webStorage, GameStorage gameStorage, IMemoryCache cache) { this.webStorage = webStorage; this.gameStorage = gameStorage; + this.cache = cache; } [HttpPost("irrq.aspx")] @@ -286,67 +287,99 @@ private static IResult HandleUnknownMode(UgcType mode) { #region Ranking public ByteWriter Trophy(string userName) { - List rankInfos = []; - using GameStorage.Request db = gameStorage.Context(); - if (!string.IsNullOrEmpty(userName)) { - TrophyRankInfo? info = db.GetTrophyRankInfo(userName); - if (info != null) { - rankInfos.Add(info); + string cacheKey = $"Trophy_{userName ?? "all"}"; + + if (!cache.TryGetValue(cacheKey, out ByteWriter? cachedResult)) { + List rankInfos = []; + using GameStorage.Request db = gameStorage.Context(); + if (!string.IsNullOrEmpty(userName)) { + TrophyRankInfo? info = db.GetTrophyRankInfo(userName); + if (info != null) { + rankInfos.Add(info); + } + } else { + IList infos = db.GetTrophyRankings(); + rankInfos.AddRange(infos); } - } else { - IList infos = db.GetTrophyRankings(); - rankInfos.AddRange(infos); + + cachedResult = InGameRankPacket.Trophy(rankInfos); + cache.Set(cacheKey, cachedResult, RankingCacheDuration); } - return InGameRankPacket.Trophy(rankInfos); + return cachedResult!; } public ByteWriter PersonalTrophy(long characterId) { - using GameStorage.Request db = gameStorage.Context(); - TrophyRankInfo? info = db.GetTrophyRankInfo(characterId); - return InGameRankPacket.PersonalRank(GameRankingType.PersonalTrophy, info?.Rank ?? 0); + string cacheKey = $"PersonalTrophy_{characterId}"; + + if (!cache.TryGetValue(cacheKey, out ByteWriter? cachedResult)) { + using GameStorage.Request db = gameStorage.Context(); + TrophyRankInfo? info = db.GetTrophyRankInfo(characterId); + cachedResult = InGameRankPacket.PersonalRank(GameRankingType.PersonalTrophy, info?.Rank ?? 0); + cache.Set(cacheKey, cachedResult, RankingCacheDuration); + } + + return cachedResult!; } public ByteWriter GuildTrophy(string userName) { if (!string.IsNullOrEmpty(userName)) { - // Search by guild name, check cache first then fall back to DB - IList rankings = GetCachedGuildTrophyRankings(); - GuildTrophyRankInfo? cached = rankings.FirstOrDefault(r => - r.Name.Equals(userName, StringComparison.OrdinalIgnoreCase)); - if (cached != null) { - return InGameRankPacket.GuildTrophy(new[] { cached }); + string cacheKey = $"GuildTrophy_{userName}"; + + if (!cache.TryGetValue(cacheKey, out ByteWriter? cachedResult)) { + // Search by guild name, check full rankings cache first then fall back to DB + IList rankings = GetCachedGuildTrophyRankings(); + GuildTrophyRankInfo? cached = rankings.FirstOrDefault(r => + r.Name.Equals(userName, StringComparison.OrdinalIgnoreCase)); + + if (cached != null) { + cachedResult = InGameRankPacket.GuildTrophy(new[] { cached }); + } else { + using GameStorage.Request db = gameStorage.Context(); + GuildTrophyRankInfo? info = db.GetGuildTrophyRankInfo(userName); + cachedResult = InGameRankPacket.GuildTrophy(info != null ? new[] { info } : []); + } + + cache.Set(cacheKey, cachedResult, RankingCacheDuration); } - using GameStorage.Request db = gameStorage.Context(); - GuildTrophyRankInfo? info = db.GetGuildTrophyRankInfo(userName); - return InGameRankPacket.GuildTrophy(info != null ? new[] { info } : []); + return cachedResult!; } return InGameRankPacket.GuildTrophy(GetCachedGuildTrophyRankings()); } public ByteWriter PersonalGuildTrophy(long characterId) { - using GameStorage.Request db = gameStorage.Context(); - long guildId = db.GetGuildIdByCharacterId(characterId); - if (guildId == 0) { - return InGameRankPacket.PersonalRank(GameRankingType.PersonalGuildTrophy, 0); + string cacheKey = $"PersonalGuildTrophy_{characterId}"; + + if (!cache.TryGetValue(cacheKey, out ByteWriter? cachedResult)) { + using GameStorage.Request db = gameStorage.Context(); + long guildId = db.GetGuildIdByCharacterId(characterId); + if (guildId == 0) { + cachedResult = InGameRankPacket.PersonalRank(GameRankingType.PersonalGuildTrophy, 0); + } else { + // Check cached rankings for this guild's rank + IList rankings = GetCachedGuildTrophyRankings(); + GuildTrophyRankInfo? cached = rankings.FirstOrDefault(r => r.GuildId == guildId); + cachedResult = InGameRankPacket.PersonalRank(GameRankingType.PersonalGuildTrophy, cached?.Rank ?? 0); + } + + cache.Set(cacheKey, cachedResult, RankingCacheDuration); } - // Check cached rankings for this guild's rank - IList rankings = GetCachedGuildTrophyRankings(); - GuildTrophyRankInfo? cached = rankings.FirstOrDefault(r => r.GuildId == guildId); - return InGameRankPacket.PersonalRank(GameRankingType.PersonalGuildTrophy, cached?.Rank ?? 0); + return cachedResult!; } private IList GetCachedGuildTrophyRankings() { - if (_guildTrophyCache != null && DateTime.Now < _guildTrophyCacheExpiry) { - return _guildTrophyCache; + const string cacheKey = "GuildTrophyRankings"; + + if (!cache.TryGetValue(cacheKey, out IList? rankings)) { + using GameStorage.Request db = gameStorage.Context(); + rankings = db.GetGuildTrophyRankings(); + cache.Set(cacheKey, rankings, RankingCacheDuration); } - using GameStorage.Request db = gameStorage.Context(); - _guildTrophyCache = db.GetGuildTrophyRankings(); - _guildTrophyCacheExpiry = DateTime.Now.AddMinutes(3); - return _guildTrophyCache; + return rankings!; } #endregion diff --git a/Maple2.Server.Web/Program.cs b/Maple2.Server.Web/Program.cs index fa5e19ea2..6e699334e 100644 --- a/Maple2.Server.Web/Program.cs +++ b/Maple2.Server.Web/Program.cs @@ -46,6 +46,7 @@ // }); }); builder.Services.Configure(options => options.ShutdownTimeout = TimeSpan.FromSeconds(15)); +builder.Services.AddMemoryCache(); builder.Services.AddControllers(); builder.Logging.ClearProviders(); From 02cbe6941926061130e176c9639041ea67bcfa37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=82ngelo=20Tadeucci?= Date: Mon, 9 Feb 2026 23:59:30 -0300 Subject: [PATCH 4/4] Update WebController.cs --- .../Controllers/WebController.cs | 52 ++++++++++++------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/Maple2.Server.Web/Controllers/WebController.cs b/Maple2.Server.Web/Controllers/WebController.cs index 9735b15d0..84b694b69 100644 --- a/Maple2.Server.Web/Controllers/WebController.cs +++ b/Maple2.Server.Web/Controllers/WebController.cs @@ -289,7 +289,7 @@ private static IResult HandleUnknownMode(UgcType mode) { public ByteWriter Trophy(string userName) { string cacheKey = $"Trophy_{userName ?? "all"}"; - if (!cache.TryGetValue(cacheKey, out ByteWriter? cachedResult)) { + if (!cache.TryGetValue(cacheKey, out byte[]? cachedData)) { List rankInfos = []; using GameStorage.Request db = gameStorage.Context(); if (!string.IsNullOrEmpty(userName)) { @@ -302,48 +302,58 @@ public ByteWriter Trophy(string userName) { rankInfos.AddRange(infos); } - cachedResult = InGameRankPacket.Trophy(rankInfos); - cache.Set(cacheKey, cachedResult, RankingCacheDuration); + ByteWriter writer = InGameRankPacket.Trophy(rankInfos); + cachedData = writer.Buffer[..writer.Length]; + cache.Set(cacheKey, cachedData, RankingCacheDuration); } - return cachedResult!; + var result = new ByteWriter(); + result.WriteBytes(cachedData!); + return result; } public ByteWriter PersonalTrophy(long characterId) { string cacheKey = $"PersonalTrophy_{characterId}"; - if (!cache.TryGetValue(cacheKey, out ByteWriter? cachedResult)) { + if (!cache.TryGetValue(cacheKey, out byte[]? cachedData)) { using GameStorage.Request db = gameStorage.Context(); TrophyRankInfo? info = db.GetTrophyRankInfo(characterId); - cachedResult = InGameRankPacket.PersonalRank(GameRankingType.PersonalTrophy, info?.Rank ?? 0); - cache.Set(cacheKey, cachedResult, RankingCacheDuration); + ByteWriter writer = InGameRankPacket.PersonalRank(GameRankingType.PersonalTrophy, info?.Rank ?? 0); + cachedData = writer.Buffer[..writer.Length]; + cache.Set(cacheKey, cachedData, RankingCacheDuration); } - return cachedResult!; + var result = new ByteWriter(); + result.WriteBytes(cachedData!); + return result; } public ByteWriter GuildTrophy(string userName) { if (!string.IsNullOrEmpty(userName)) { string cacheKey = $"GuildTrophy_{userName}"; - if (!cache.TryGetValue(cacheKey, out ByteWriter? cachedResult)) { + if (!cache.TryGetValue(cacheKey, out byte[]? cachedData)) { // Search by guild name, check full rankings cache first then fall back to DB IList rankings = GetCachedGuildTrophyRankings(); GuildTrophyRankInfo? cached = rankings.FirstOrDefault(r => r.Name.Equals(userName, StringComparison.OrdinalIgnoreCase)); + ByteWriter writer; if (cached != null) { - cachedResult = InGameRankPacket.GuildTrophy(new[] { cached }); + writer = InGameRankPacket.GuildTrophy([cached]); } else { using GameStorage.Request db = gameStorage.Context(); GuildTrophyRankInfo? info = db.GetGuildTrophyRankInfo(userName); - cachedResult = InGameRankPacket.GuildTrophy(info != null ? new[] { info } : []); + writer = InGameRankPacket.GuildTrophy(info != null ? new[] { info } : []); } - cache.Set(cacheKey, cachedResult, RankingCacheDuration); + cachedData = writer.Buffer[..writer.Length]; + cache.Set(cacheKey, cachedData, RankingCacheDuration); } - return cachedResult!; + var result = new ByteWriter(); + result.WriteBytes(cachedData!); + return result; } return InGameRankPacket.GuildTrophy(GetCachedGuildTrophyRankings()); @@ -352,22 +362,26 @@ public ByteWriter GuildTrophy(string userName) { public ByteWriter PersonalGuildTrophy(long characterId) { string cacheKey = $"PersonalGuildTrophy_{characterId}"; - if (!cache.TryGetValue(cacheKey, out ByteWriter? cachedResult)) { + if (!cache.TryGetValue(cacheKey, out byte[]? cachedData)) { using GameStorage.Request db = gameStorage.Context(); long guildId = db.GetGuildIdByCharacterId(characterId); + ByteWriter writer; if (guildId == 0) { - cachedResult = InGameRankPacket.PersonalRank(GameRankingType.PersonalGuildTrophy, 0); + writer = InGameRankPacket.PersonalRank(GameRankingType.PersonalGuildTrophy, 0); } else { // Check cached rankings for this guild's rank IList rankings = GetCachedGuildTrophyRankings(); GuildTrophyRankInfo? cached = rankings.FirstOrDefault(r => r.GuildId == guildId); - cachedResult = InGameRankPacket.PersonalRank(GameRankingType.PersonalGuildTrophy, cached?.Rank ?? 0); + writer = InGameRankPacket.PersonalRank(GameRankingType.PersonalGuildTrophy, cached?.Rank ?? 0); } - cache.Set(cacheKey, cachedResult, RankingCacheDuration); + cachedData = writer.Buffer[..writer.Length]; + cache.Set(cacheKey, cachedData, RankingCacheDuration); } - return cachedResult!; + var result = new ByteWriter(); + result.WriteBytes(cachedData!); + return result; } private IList GetCachedGuildTrophyRankings() { @@ -387,7 +401,7 @@ public ByteWriter MenteeList(long accountId, long characterId) { using GameStorage.Request db = gameStorage.Context(); IList list = db.GetMentorList(accountId, characterId); - IList players = new List(); + IList players = []; foreach (long menteeId in list) { PlayerInfo? playerInfo = db.GetPlayerInfo(menteeId); if (playerInfo != null) {