diff --git a/osu.Game/Online/Metadata/IMetadataClient.cs b/osu.Game/Online/Metadata/IMetadataClient.cs
index ee7a726bfc..97c1bbde5f 100644
--- a/osu.Game/Online/Metadata/IMetadataClient.cs
+++ b/osu.Game/Online/Metadata/IMetadataClient.cs
@@ -26,5 +26,11 @@ namespace osu.Game.Online.Metadata
/// Null value means there is no "daily challenge" currently active.
///
Task DailyChallengeUpdated(DailyChallengeInfo? info);
+
+ ///
+ /// Delivers information that a multiplayer score was set in a watched room.
+ /// To receive these, the client must call for a given room first.
+ ///
+ Task MultiplayerRoomScoreSet(MultiplayerRoomScoreSetEvent roomScoreSetEvent);
}
}
diff --git a/osu.Game/Online/Metadata/IMetadataServer.cs b/osu.Game/Online/Metadata/IMetadataServer.cs
index 8bf3f8f56b..79ed8b5634 100644
--- a/osu.Game/Online/Metadata/IMetadataServer.cs
+++ b/osu.Game/Online/Metadata/IMetadataServer.cs
@@ -43,5 +43,15 @@ namespace osu.Game.Online.Metadata
/// Signals to the server that the current user would like to stop receiving updates on other users' online presence.
///
Task EndWatchingUserPresence();
+
+ ///
+ /// Signals to the server that the current user would like to begin receiving updates about the state of the multiplayer room with the given .
+ ///
+ Task BeginWatchingMultiplayerRoom(long id);
+
+ ///
+ /// Signals to the server that the current user would like to stop receiving updates about the state of the multiplayer room with the given .
+ ///
+ Task EndWatchingMultiplayerRoom(long id);
}
}
diff --git a/osu.Game/Online/Metadata/MetadataClient.cs b/osu.Game/Online/Metadata/MetadataClient.cs
index b619970494..8a5fe1733e 100644
--- a/osu.Game/Online/Metadata/MetadataClient.cs
+++ b/osu.Game/Online/Metadata/MetadataClient.cs
@@ -68,6 +68,24 @@ namespace osu.Game.Online.Metadata
#endregion
+ #region Multiplayer room watching
+
+ public abstract Task BeginWatchingMultiplayerRoom(long id);
+
+ public abstract Task EndWatchingMultiplayerRoom(long id);
+
+ public event Action? MultiplayerRoomScoreSet;
+
+ Task IMetadataClient.MultiplayerRoomScoreSet(MultiplayerRoomScoreSetEvent roomScoreSetEvent)
+ {
+ if (MultiplayerRoomScoreSet != null)
+ Schedule(MultiplayerRoomScoreSet, roomScoreSetEvent);
+
+ return Task.CompletedTask;
+ }
+
+ #endregion
+
#region Disconnection handling
public event Action? Disconnecting;
diff --git a/osu.Game/Online/Metadata/MultiplayerPlaylistItemStats.cs b/osu.Game/Online/Metadata/MultiplayerPlaylistItemStats.cs
new file mode 100644
index 0000000000..d13705bf5b
--- /dev/null
+++ b/osu.Game/Online/Metadata/MultiplayerPlaylistItemStats.cs
@@ -0,0 +1,29 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using MessagePack;
+
+namespace osu.Game.Online.Metadata
+{
+ [MessagePackObject]
+ [Serializable]
+ public class MultiplayerPlaylistItemStats
+ {
+ public const int TOTAL_SCORE_DISTRIBUTION_BINS = 13;
+
+ ///
+ /// The ID of the playlist item which these stats pertain to.
+ ///
+ [Key(0)]
+ public long PlaylistItemID { get; set; }
+
+ ///
+ /// The count of scores with given total ranges in the room.
+ /// The ranges are bracketed into bins, each of 100,000 score width.
+ /// The last bin will contain count of all scores with total of 1,200,000 or larger.
+ ///
+ [Key(1)]
+ public long[] TotalScoreDistribution { get; set; } = new long[TOTAL_SCORE_DISTRIBUTION_BINS];
+ }
+}
diff --git a/osu.Game/Online/Metadata/MultiplayerRoomScoreSetEvent.cs b/osu.Game/Online/Metadata/MultiplayerRoomScoreSetEvent.cs
new file mode 100644
index 0000000000..00bc5dc840
--- /dev/null
+++ b/osu.Game/Online/Metadata/MultiplayerRoomScoreSetEvent.cs
@@ -0,0 +1,50 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using MessagePack;
+
+namespace osu.Game.Online.Metadata
+{
+ [Serializable]
+ [MessagePackObject]
+ public class MultiplayerRoomScoreSetEvent
+ {
+ ///
+ /// The ID of the room in which the score was set.
+ ///
+ [Key(0)]
+ public long RoomID { get; set; }
+
+ ///
+ /// The ID of the playlist item on which the score was set.
+ ///
+ [Key(1)]
+ public long PlaylistItemID { get; set; }
+
+ ///
+ /// The ID of the score set.
+ ///
+ [Key(2)]
+ public long ScoreID { get; set; }
+
+ ///
+ /// The ID of the user who set the score.
+ ///
+ [Key(3)]
+ public int UserID { get; set; }
+
+ ///
+ /// The total score set by the player.
+ ///
+ [Key(4)]
+ public long TotalScore { get; set; }
+
+ ///
+ /// If the set score is the user's new best on a playlist item, this member will contain the user's new rank in the room overall.
+ /// Otherwise, it will contain .
+ ///
+ [Key(5)]
+ public int? NewRank { get; set; }
+ }
+}
diff --git a/osu.Game/Online/Metadata/OnlineMetadataClient.cs b/osu.Game/Online/Metadata/OnlineMetadataClient.cs
index b94f26a71d..80fcf7571d 100644
--- a/osu.Game/Online/Metadata/OnlineMetadataClient.cs
+++ b/osu.Game/Online/Metadata/OnlineMetadataClient.cs
@@ -62,6 +62,7 @@ namespace osu.Game.Online.Metadata
connection.On(nameof(IMetadataClient.BeatmapSetsUpdated), ((IMetadataClient)this).BeatmapSetsUpdated);
connection.On(nameof(IMetadataClient.UserPresenceUpdated), ((IMetadataClient)this).UserPresenceUpdated);
connection.On(nameof(IMetadataClient.DailyChallengeUpdated), ((IMetadataClient)this).DailyChallengeUpdated);
+ connection.On(nameof(IMetadataClient.MultiplayerRoomScoreSet), ((IMetadataClient)this).MultiplayerRoomScoreSet);
connection.On(nameof(IStatefulUserHubClient.DisconnectRequested), ((IMetadataClient)this).DisconnectRequested);
};
@@ -240,6 +241,24 @@ namespace osu.Game.Online.Metadata
return Task.CompletedTask;
}
+ public override async Task BeginWatchingMultiplayerRoom(long id)
+ {
+ if (connector?.IsConnected.Value != true)
+ throw new OperationCanceledException();
+
+ Debug.Assert(connection != null);
+ return await connection.InvokeAsync(nameof(IMetadataServer.BeginWatchingMultiplayerRoom), id).ConfigureAwait(false);
+ }
+
+ public override async Task EndWatchingMultiplayerRoom(long id)
+ {
+ if (connector?.IsConnected.Value != true)
+ throw new OperationCanceledException();
+
+ Debug.Assert(connection != null);
+ await connection.InvokeAsync(nameof(IMetadataServer.EndWatchingMultiplayerRoom)).ConfigureAwait(false);
+ }
+
public override async Task DisconnectRequested()
{
await base.DisconnectRequested().ConfigureAwait(false);
diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs
index 3d4f27c44b..d251a10f9a 100644
--- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs
+++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs
@@ -11,6 +11,7 @@ using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Online.Metadata;
using osu.Game.Overlays;
using osu.Game.Scoring;
using osuTK;
@@ -21,7 +22,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
{
private FillFlowContainer barsContainer = null!;
- private const int bin_count = 13;
+ private const int bin_count = MultiplayerPlaylistItemStats.TOTAL_SCORE_DISTRIBUTION_BINS;
private long[] bins = new long[bin_count];
[BackgroundDependencyLoader]
diff --git a/osu.Game/Tests/Visual/Metadata/TestMetadataClient.cs b/osu.Game/Tests/Visual/Metadata/TestMetadataClient.cs
index b589e66d8b..fa64a83352 100644
--- a/osu.Game/Tests/Visual/Metadata/TestMetadataClient.cs
+++ b/osu.Game/Tests/Visual/Metadata/TestMetadataClient.cs
@@ -86,5 +86,10 @@ namespace osu.Game.Tests.Visual.Metadata
dailyChallengeInfo.Value = info;
return Task.CompletedTask;
}
+
+ public override Task BeginWatchingMultiplayerRoom(long id)
+ => Task.FromResult(new MultiplayerPlaylistItemStats[MultiplayerPlaylistItemStats.TOTAL_SCORE_DISTRIBUTION_BINS]);
+
+ public override Task EndWatchingMultiplayerRoom(long id) => Task.CompletedTask;
}
}