From c65e7a6ef324e544a7fa1c79716e92a95aa79507 Mon Sep 17 00:00:00 2001 From: Beyley Thomas Date: Fri, 26 Jul 2024 00:22:06 -0700 Subject: [PATCH 1/2] Implement friend leaderboards --- .../GameDatabaseContext.Leaderboard.cs | 23 +++++++++++++++---- .../Game/Levels/LeaderboardEndpoints.cs | 21 +++++++++++++++++ .../Types/Scores/FriendScoresRequest.cs | 13 +++++++++++ 3 files changed, 53 insertions(+), 4 deletions(-) create mode 100644 Refresh.GameServer/Types/Scores/FriendScoresRequest.cs diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Leaderboard.cs b/Refresh.GameServer/Database/GameDatabaseContext.Leaderboard.cs index 7b95d6c9..e948e24d 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Leaderboard.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Leaderboard.cs @@ -60,8 +60,6 @@ public GameSubmittedScore SubmitScore(SerializedScore score, GameUser user, Game return newScore; } - [UsedImplicitly] private record ScoreLevelWithPlayer(GameLevel Level, GameUser Player); - public DatabaseList GetTopScoresForLevel(GameLevel level, int count, int skip, byte type, bool showDuplicates = false) { IEnumerable scores = this.GameSubmittedScores @@ -70,7 +68,7 @@ public DatabaseList GetTopScoresForLevel(GameLevel level, in .AsEnumerable(); if (!showDuplicates) - scores = scores.DistinctBy(s => new ScoreLevelWithPlayer(s.Level, s.Players[0])); + scores = scores.DistinctBy(s => s.Players[0]); return new DatabaseList(scores, skip, count); } @@ -86,13 +84,30 @@ public IEnumerable GetRankedScoresAroundScore(GameSubmittedScore .AsEnumerable() .ToList(); - scores = scores.DistinctBy(s => new ScoreLevelWithPlayer(s.Level, s.Players[0])) + scores = scores.DistinctBy(s => s.Players[0]) .ToList(); return scores.Select((s, i) => new ScoreWithRank(s, i + 1)) .Skip(Math.Min(scores.Count, scores.IndexOf(score) - count / 2)) // center user's score around other scores .Take(count); } + + public IEnumerable GetLevelTopScoresByFriends(GameUser user, GameLevel level, int count, byte type) + { + IEnumerable mutuals = this.GetUsersMutuals(user); + + IEnumerable scores = this.GameSubmittedScores + .Where(s => s.ScoreType == type && s.Level == level) + .OrderByDescending(s => s.Score) + .AsEnumerable() + .DistinctBy(s => s.Players[0].UserId) + //TODO: THIS CALL IS EXTREMELY INEFFECIENT!!! once we are in postgres land, figure out a way to do this effeciently + .Where(s => s.Players.Any(p => p.UserId == user.UserId || mutuals.Contains(p))) + .Take(10) + .ToList(); + + return scores.Select((s, i) => new ScoreWithRank(s, i + 1)); + } [Pure] [ContractAnnotation("null => null; notnull => canbenull")] diff --git a/Refresh.GameServer/Endpoints/Game/Levels/LeaderboardEndpoints.cs b/Refresh.GameServer/Endpoints/Game/Levels/LeaderboardEndpoints.cs index 3753bb94..cc430261 100644 --- a/Refresh.GameServer/Endpoints/Game/Levels/LeaderboardEndpoints.cs +++ b/Refresh.GameServer/Endpoints/Game/Levels/LeaderboardEndpoints.cs @@ -1,6 +1,7 @@ using System.Diagnostics; using Bunkum.Core; using Bunkum.Core.Endpoints; +using Bunkum.Core.Endpoints.Debugging; using Bunkum.Core.RateLimit; using Bunkum.Core.Responses; using Bunkum.Listener.Protocol; @@ -11,6 +12,7 @@ using Refresh.GameServer.Types.Levels; using Refresh.GameServer.Types.Lists; using Refresh.GameServer.Types.Roles; +using Refresh.GameServer.Types.Scores; using Refresh.GameServer.Types.UserData; using Refresh.GameServer.Types.UserData.Leaderboard; @@ -66,6 +68,25 @@ public Response GetUserScores(RequestContext context, GameUser user, GameDatabas return new Response(SerializedMultiLeaderboardResponse.FromOld(multiLeaderboard), ContentType.Xml); } + + [GameEndpoint("scoreboard/friends/{slotType}/{id}", HttpMethods.Post, ContentType.Xml)] + [RateLimitSettings(RequestTimeoutDuration, MaxRequestAmount, RequestBlockDuration, BucketName)] + [DebugRequestBody] + [DebugResponseBody] + [NullStatusCode(NotFound)] + public SerializedScoreLeaderboardList? GetLevelFriendLeaderboard( + RequestContext context, + GameUser user, + GameDatabaseContext database, + string slotType, + int id, + FriendScoresRequest body) + { + GameLevel? level = database.GetLevelByIdAndType(slotType, id); + if (level == null) return null; + + return SerializedScoreLeaderboardList.FromSubmittedEnumerable(database.GetLevelTopScoresByFriends(user, level, 10, body.Type)); + } [GameEndpoint("scoreboard/{slotType}/{id}", ContentType.Xml, HttpMethods.Post)] [RateLimitSettings(RequestTimeoutDuration, MaxRequestAmount, RequestBlockDuration, BucketName)] diff --git a/Refresh.GameServer/Types/Scores/FriendScoresRequest.cs b/Refresh.GameServer/Types/Scores/FriendScoresRequest.cs new file mode 100644 index 00000000..a02c2e53 --- /dev/null +++ b/Refresh.GameServer/Types/Scores/FriendScoresRequest.cs @@ -0,0 +1,13 @@ +using System.Xml.Serialization; + +namespace Refresh.GameServer.Types.Scores; + +[XmlRoot("playRecord")] +public class FriendScoresRequest +{ + [XmlElement("playerIds")] + public List Usernames { get; set; } + + [XmlElement("type")] + public byte Type { get; set; } +} \ No newline at end of file From 143941365298f2ed6ce6b9a8b6c6d2d66ac8e080 Mon Sep 17 00:00:00 2001 From: Beyley Thomas Date: Sat, 27 Jul 2024 21:15:14 -0700 Subject: [PATCH 2/2] Remove debug request/response body --- .../Endpoints/Game/Levels/LeaderboardEndpoints.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Refresh.GameServer/Endpoints/Game/Levels/LeaderboardEndpoints.cs b/Refresh.GameServer/Endpoints/Game/Levels/LeaderboardEndpoints.cs index cc430261..0e0a2f88 100644 --- a/Refresh.GameServer/Endpoints/Game/Levels/LeaderboardEndpoints.cs +++ b/Refresh.GameServer/Endpoints/Game/Levels/LeaderboardEndpoints.cs @@ -71,8 +71,6 @@ public Response GetUserScores(RequestContext context, GameUser user, GameDatabas [GameEndpoint("scoreboard/friends/{slotType}/{id}", HttpMethods.Post, ContentType.Xml)] [RateLimitSettings(RequestTimeoutDuration, MaxRequestAmount, RequestBlockDuration, BucketName)] - [DebugRequestBody] - [DebugResponseBody] [NullStatusCode(NotFound)] public SerializedScoreLeaderboardList? GetLevelFriendLeaderboard( RequestContext context,