Skip to content

Commit

Permalink
Implement friend leaderboards
Browse files Browse the repository at this point in the history
  • Loading branch information
Beyley committed Jul 26, 2024
1 parent 865f952 commit c65e7a6
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 4 deletions.
23 changes: 19 additions & 4 deletions Refresh.GameServer/Database/GameDatabaseContext.Leaderboard.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,6 @@ public GameSubmittedScore SubmitScore(SerializedScore score, GameUser user, Game
return newScore;
}

[UsedImplicitly] private record ScoreLevelWithPlayer(GameLevel Level, GameUser Player);

public DatabaseList<GameSubmittedScore> GetTopScoresForLevel(GameLevel level, int count, int skip, byte type, bool showDuplicates = false)
{
IEnumerable<GameSubmittedScore> scores = this.GameSubmittedScores
Expand All @@ -70,7 +68,7 @@ public DatabaseList<GameSubmittedScore> 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<GameSubmittedScore>(scores, skip, count);
}
Expand All @@ -86,13 +84,30 @@ public IEnumerable<ScoreWithRank> 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<ScoreWithRank> GetLevelTopScoresByFriends(GameUser user, GameLevel level, int count, byte type)
{
IEnumerable<GameUser> mutuals = this.GetUsersMutuals(user);

IEnumerable<GameSubmittedScore> 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")]
Expand Down
21 changes: 21 additions & 0 deletions Refresh.GameServer/Endpoints/Game/Levels/LeaderboardEndpoints.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -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)]
Expand Down
13 changes: 13 additions & 0 deletions Refresh.GameServer/Types/Scores/FriendScoresRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.Xml.Serialization;

namespace Refresh.GameServer.Types.Scores;

[XmlRoot("playRecord")]
public class FriendScoresRequest
{
[XmlElement("playerIds")]
public List<string> Usernames { get; set; }

Check warning on line 9 in Refresh.GameServer/Types/Scores/FriendScoresRequest.cs

View workflow job for this annotation

GitHub Actions / Build, Test, and Upload Builds

Non-nullable property 'Usernames' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 9 in Refresh.GameServer/Types/Scores/FriendScoresRequest.cs

View workflow job for this annotation

GitHub Actions / Build, Test, and Upload Builds

Non-nullable property 'Usernames' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

[XmlElement("type")]
public byte Type { get; set; }
}

0 comments on commit c65e7a6

Please sign in to comment.