diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs
index c5302a393c..8c9741b98b 100644
--- a/osu.Game/Online/API/APIAccess.cs
+++ b/osu.Game/Online/API/APIAccess.cs
@@ -36,6 +36,8 @@ namespace osu.Game.Online.API
public string WebsiteRootUrl { get; }
+ public int APIVersion => 20220217; // We may want to pull this from the game version eventually.
+
public Exception LastLoginError { get; private set; }
public string ProvidedUsername { get; private set; }
diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs
index 91148c177f..776ff5fd8f 100644
--- a/osu.Game/Online/API/APIRequest.cs
+++ b/osu.Game/Online/API/APIRequest.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Globalization;
using JetBrains.Annotations;
using Newtonsoft.Json;
using osu.Framework.IO.Network;
@@ -112,6 +113,9 @@ namespace osu.Game.Online.API
WebRequest = CreateWebRequest();
WebRequest.Failed += Fail;
WebRequest.AllowRetryOnTimeout = false;
+
+ WebRequest.AddHeader("x-api-version", API.APIVersion.ToString(CultureInfo.InvariantCulture));
+
if (!string.IsNullOrEmpty(API.AccessToken))
WebRequest.AddHeader("Authorization", $"Bearer {API.AccessToken}");
diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs
index 7131c3a7d4..f292e95bd1 100644
--- a/osu.Game/Online/API/DummyAPIAccess.cs
+++ b/osu.Game/Online/API/DummyAPIAccess.cs
@@ -33,6 +33,8 @@ namespace osu.Game.Online.API
public string WebsiteRootUrl => "http://localhost";
+ public int APIVersion => int.Parse(DateTime.Now.ToString("yyyyMMdd"));
+
public Exception LastLoginError { get; private set; }
///
diff --git a/osu.Game/Online/API/IAPIProvider.cs b/osu.Game/Online/API/IAPIProvider.cs
index a97eae77e3..470d46cd7f 100644
--- a/osu.Game/Online/API/IAPIProvider.cs
+++ b/osu.Game/Online/API/IAPIProvider.cs
@@ -57,6 +57,11 @@ namespace osu.Game.Online.API
///
string WebsiteRootUrl { get; }
+ ///
+ /// The version of the API.
+ ///
+ int APIVersion { get; }
+
///
/// The last login error that occurred, if any.
///
diff --git a/osu.Game/Online/API/Requests/Responses/APIUser.cs b/osu.Game/Online/API/Requests/Responses/APIUser.cs
index 2b64e5de06..a53ac1cd9b 100644
--- a/osu.Game/Online/API/Requests/Responses/APIUser.cs
+++ b/osu.Game/Online/API/Requests/Responses/APIUser.cs
@@ -12,6 +12,7 @@ using osu.Game.Users;
namespace osu.Game.Online.API.Requests.Responses
{
+ [JsonObject(MemberSerialization.OptIn)]
public class APIUser : IEquatable, IUser
{
[JsonProperty(@"id")]
diff --git a/osu.Game/Online/Rooms/PlaylistItem.cs b/osu.Game/Online/Rooms/PlaylistItem.cs
index 33718f050b..f696362cbb 100644
--- a/osu.Game/Online/Rooms/PlaylistItem.cs
+++ b/osu.Game/Online/Rooms/PlaylistItem.cs
@@ -62,6 +62,10 @@ namespace osu.Game.Online.Rooms
[JsonProperty("beatmap_id")]
private int onlineBeatmapId => Beatmap.OnlineID;
+ ///
+ /// A beatmap representing this playlist item.
+ /// In many cases, this will *not* contain any usable information apart from OnlineID.
+ ///
[JsonIgnore]
public IBeatmapInfo Beatmap { get; set; } = null!;
diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs
index a328f8e8c0..a33150fe08 100644
--- a/osu.Game/Online/Rooms/Room.cs
+++ b/osu.Game/Online/Rooms/Room.cs
@@ -3,7 +3,6 @@
using System;
using System.Linq;
-using JetBrains.Annotations;
using Newtonsoft.Json;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
@@ -15,6 +14,7 @@ using osu.Game.Utils;
namespace osu.Game.Online.Rooms
{
+ [JsonObject(MemberSerialization.OptIn)]
public class Room : IDeepCloneable
{
[Cached]
@@ -37,8 +37,19 @@ namespace osu.Game.Online.Rooms
[JsonProperty("channel_id")]
public readonly Bindable ChannelId = new Bindable();
+ [JsonProperty("current_playlist_item")]
+ [Cached]
+ public readonly Bindable CurrentPlaylistItem = new Bindable();
+
+ [JsonProperty("playlist_item_stats")]
+ [Cached]
+ public readonly Bindable PlaylistItemStats = new Bindable();
+
+ [JsonProperty("difficulty_range")]
+ [Cached]
+ public readonly Bindable DifficultyRange = new Bindable();
+
[Cached]
- [JsonIgnore]
public readonly Bindable Category = new Bindable();
// Todo: osu-framework bug (https://github.com/ppy/osu-framework/issues/4106)
@@ -51,19 +62,15 @@ namespace osu.Game.Online.Rooms
}
[Cached]
- [JsonIgnore]
public readonly Bindable MaxAttempts = new Bindable();
[Cached]
- [JsonIgnore]
public readonly Bindable Status = new Bindable(new RoomStatusOpen());
[Cached]
- [JsonIgnore]
public readonly Bindable Availability = new Bindable();
[Cached]
- [JsonIgnore]
public readonly Bindable Type = new Bindable();
// Todo: osu-framework bug (https://github.com/ppy/osu-framework/issues/4106)
@@ -76,7 +83,6 @@ namespace osu.Game.Online.Rooms
}
[Cached]
- [JsonIgnore]
public readonly Bindable QueueMode = new Bindable();
[JsonConverter(typeof(SnakeCaseStringEnumConverter))]
@@ -88,7 +94,6 @@ namespace osu.Game.Online.Rooms
}
[Cached]
- [JsonIgnore]
public readonly Bindable MaxParticipants = new Bindable();
[Cached]
@@ -113,7 +118,6 @@ namespace osu.Game.Online.Rooms
public readonly Bindable Password = new Bindable();
[Cached]
- [JsonIgnore]
public readonly Bindable Duration = new Bindable();
[JsonProperty("duration")]
@@ -158,6 +162,8 @@ namespace osu.Game.Online.Rooms
var copy = new Room();
copy.CopyFrom(this);
+
+ // ID must be unset as we use this as a marker for whether this is a client-side (not-yet-created) room or not.
copy.RoomID.Value = null;
return copy;
@@ -183,6 +189,9 @@ namespace osu.Game.Online.Rooms
EndDate.Value = other.EndDate.Value;
UserScore.Value = other.UserScore.Value;
QueueMode.Value = other.QueueMode.Value;
+ DifficultyRange.Value = other.DifficultyRange.Value;
+ PlaylistItemStats.Value = other.PlaylistItemStats.Value;
+ CurrentPlaylistItem.Value = other.CurrentPlaylistItem.Value;
if (EndDate.Value != null && DateTimeOffset.Now >= EndDate.Value)
Status.Value = new RoomStatusEnded();
@@ -211,21 +220,27 @@ namespace osu.Game.Online.Rooms
Playlist.RemoveAll(i => i.Expired);
}
- #region Newtonsoft.Json implicit ShouldSerialize() methods
+ [JsonObject(MemberSerialization.OptIn)]
+ public class RoomPlaylistItemStats
+ {
+ [JsonProperty("count_active")]
+ public int CountActive;
- // The properties in this region are used implicitly by Newtonsoft.Json to not serialise certain fields in some cases.
- // They rely on being named exactly the same as the corresponding fields (casing included) and as such should NOT be renamed
- // unless the fields are also renamed.
+ [JsonProperty("count_total")]
+ public int CountTotal;
- [UsedImplicitly]
- public bool ShouldSerializeRoomID() => false;
+ [JsonProperty("ruleset_ids")]
+ public int[] RulesetIDs;
+ }
- [UsedImplicitly]
- public bool ShouldSerializeHost() => false;
+ [JsonObject(MemberSerialization.OptIn)]
+ public class RoomDifficultyRange
+ {
+ [JsonProperty("min")]
+ public double Min;
- [UsedImplicitly]
- public bool ShouldSerializeEndDate() => false;
-
- #endregion
+ [JsonProperty("max")]
+ public double Max;
+ }
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundSprite.cs b/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundSprite.cs
index d46ff12279..2faa46e622 100644
--- a/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundSprite.cs
+++ b/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundSprite.cs
@@ -23,6 +23,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
{
InternalChild = sprite = CreateBackgroundSprite();
+ CurrentPlaylistItem.BindValueChanged(_ => updateBeatmap());
Playlist.CollectionChanged += (_, __) => updateBeatmap();
updateBeatmap();
@@ -30,7 +31,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
private void updateBeatmap()
{
- sprite.Beatmap.Value = Playlist.GetCurrentItem()?.Beatmap;
+ sprite.Beatmap.Value = CurrentPlaylistItem.Value?.Beatmap ?? Playlist.GetCurrentItem()?.Beatmap;
}
protected virtual UpdateableBeatmapBackgroundSprite CreateBackgroundSprite() => new UpdateableBeatmapBackgroundSprite(BeatmapSetCoverType) { RelativeSizeAxes = Axes.Both };
diff --git a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs
index 95ecadd21a..7425e46bd3 100644
--- a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs
+++ b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs
@@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using System;
-using System.Collections.Specialized;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
@@ -75,15 +74,29 @@ namespace osu.Game.Screens.OnlinePlay.Components
{
base.LoadComplete();
- Playlist.BindCollectionChanged(updateRange, true);
+ DifficultyRange.BindValueChanged(_ => updateRange());
+ Playlist.BindCollectionChanged((_, __) => updateRange(), true);
}
- private void updateRange(object sender, NotifyCollectionChangedEventArgs e)
+ private void updateRange()
{
- var orderedDifficulties = Playlist.Select(p => p.Beatmap).OrderBy(b => b.StarRating).ToArray();
+ StarDifficulty minDifficulty;
+ StarDifficulty maxDifficulty;
- StarDifficulty minDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[0].StarRating : 0, 0);
- StarDifficulty maxDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[^1].StarRating : 0, 0);
+ if (DifficultyRange.Value != null)
+ {
+ minDifficulty = new StarDifficulty(DifficultyRange.Value.Min, 0);
+ maxDifficulty = new StarDifficulty(DifficultyRange.Value.Max, 0);
+ }
+ else
+ {
+ // In multiplayer rooms, the beatmaps of playlist items will not be populated to a point this can be correct.
+ // Either populating them via BeatmapLookupCache or polling the API for the room's DifficultyRange will be required.
+ var orderedDifficulties = Playlist.Select(p => p.Beatmap).OrderBy(b => b.StarRating).ToArray();
+
+ minDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[0].StarRating : 0, 0);
+ maxDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[^1].StarRating : 0, 0);
+ }
minDisplay.Current.Value = minDifficulty;
maxDisplay.Current.Value = maxDifficulty;
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs
index a1a82c907a..5adce862a0 100644
--- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs
+++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs
@@ -388,7 +388,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
protected override void LoadComplete()
{
base.LoadComplete();
- SelectedItem.BindValueChanged(onSelectedItemChanged, true);
+ CurrentPlaylistItem.BindValueChanged(onSelectedItemChanged, true);
}
private CancellationTokenSource beatmapLookupCancellation;
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs
index ef2c2df4a6..a6bbcd548d 100644
--- a/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs
+++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs
@@ -1,7 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System.Collections.Specialized;
+using System.Linq;
using Humanizer;
using osu.Framework.Allocation;
using osu.Framework.Extensions.LocalisationExtensions;
@@ -41,15 +41,22 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
base.LoadComplete();
- Playlist.BindCollectionChanged(updateCount, true);
+ PlaylistItemStats.BindValueChanged(_ => updateCount());
+ Playlist.BindCollectionChanged((_, __) => updateCount(), true);
}
- private void updateCount(object sender, NotifyCollectionChangedEventArgs e)
+ private void updateCount()
{
+ int activeItems = Playlist.Count > 0 || PlaylistItemStats.Value == null
+ // For now, use the playlist as the source of truth if it has any items.
+ // This allows the count to display correctly on the room screen (after joining a room).
+ ? Playlist.Count(i => !i.Expired)
+ : PlaylistItemStats.Value.CountActive;
+
count.Clear();
- count.AddText(Playlist.Count.ToLocalisableString(), s => s.Font = s.Font.With(weight: FontWeight.Bold));
+ count.AddText(activeItems.ToLocalisableString(), s => s.Font = s.Font.With(weight: FontWeight.Bold));
count.AddText(" ");
- count.AddText("Beatmap".ToQuantity(Playlist.Count, ShowQuantityAs.None));
+ count.AddText("Beatmap".ToQuantity(activeItems, ShowQuantityAs.None));
}
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs
index 3260427192..175cd2c44e 100644
--- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs
+++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs
@@ -77,7 +77,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
bool matchingFilter = true;
- matchingFilter &= r.Room.Playlist.Count == 0 || criteria.Ruleset == null || r.Room.Playlist.Any(i => i.RulesetID == criteria.Ruleset.OnlineID);
+ matchingFilter &= criteria.Ruleset == null || r.Room.PlaylistItemStats.Value?.RulesetIDs.Any(id => id == criteria.Ruleset.OnlineID) != false;
if (!string.IsNullOrEmpty(criteria.SearchString))
matchingFilter &= r.FilterTerms.Any(term => term.Contains(criteria.SearchString, StringComparison.InvariantCultureIgnoreCase));
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs
index 7f1db733b3..be98a9d4e9 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs
@@ -343,7 +343,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
base.LoadComplete();
drawablePlaylist.Items.BindTo(Playlist);
- drawablePlaylist.SelectedItem.BindTo(SelectedItem);
+ drawablePlaylist.SelectedItem.BindTo(CurrentPlaylistItem);
}
protected override void Update()
@@ -419,7 +419,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
if (text.StartsWith(not_found_prefix, StringComparison.Ordinal))
{
ErrorText.Text = "The selected beatmap is not available online.";
- SelectedItem.Value.MarkInvalid();
+ CurrentPlaylistItem.Value.MarkInvalid();
}
else
{
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs
index 06959d942f..023af85f3b 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs
@@ -67,7 +67,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
{
base.LoadComplete();
- SelectedItem.BindValueChanged(_ => updateState());
+ CurrentPlaylistItem.BindValueChanged(_ => updateState());
}
protected override void OnRoomUpdated()
@@ -111,7 +111,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
bool enableButton =
Room?.State == MultiplayerRoomState.Open
- && SelectedItem.Value?.ID == Room.Settings.PlaylistItemId
+ && CurrentPlaylistItem.Value?.ID == Room.Settings.PlaylistItemId
&& !Room.Playlist.Single(i => i.ID == Room.Settings.PlaylistItemId).Expired
&& !operationInProgress.Value;
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs
index 7b90532cce..eeafebfec0 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs
@@ -52,14 +52,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist
queueList = new MultiplayerQueueList
{
RelativeSizeAxes = Axes.Both,
- SelectedItem = { BindTarget = SelectedItem },
+ SelectedItem = { BindTarget = CurrentPlaylistItem },
RequestEdit = item => RequestEdit?.Invoke(item)
},
historyList = new MultiplayerHistoryList
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
- SelectedItem = { BindTarget = SelectedItem }
+ SelectedItem = { BindTarget = CurrentPlaylistItem }
}
}
}
diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs
index c833621fbc..95d9b2af15 100644
--- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs
+++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs
@@ -32,9 +32,22 @@ namespace osu.Game.Screens.OnlinePlay
[Resolved(typeof(Room))]
protected Bindable Type { get; private set; }
+ ///
+ /// The currently selected item in the , or the current item from
+ /// if this is not within a .
+ ///
+ [Resolved(typeof(Room))]
+ protected Bindable CurrentPlaylistItem { get; private set; }
+
+ [Resolved(typeof(Room))]
+ protected Bindable PlaylistItemStats { get; private set; }
+
[Resolved(typeof(Room))]
protected BindableList Playlist { get; private set; }
+ [Resolved(typeof(Room))]
+ protected Bindable DifficultyRange { get; private set; }
+
[Resolved(typeof(Room))]
protected Bindable Category { get; private set; }
@@ -71,12 +84,6 @@ namespace osu.Game.Screens.OnlinePlay
[Resolved(CanBeNull = true)]
private IBindable subScreenSelectedItem { get; set; }
- ///
- /// The currently selected item in the , or the current item from
- /// if this is not within a .
- ///
- protected readonly Bindable SelectedItem = new Bindable();
-
protected override void LoadComplete()
{
base.LoadComplete();
@@ -85,9 +92,13 @@ namespace osu.Game.Screens.OnlinePlay
Playlist.BindCollectionChanged((_, __) => UpdateSelectedItem(), true);
}
- protected virtual void UpdateSelectedItem()
- => SelectedItem.Value = RoomID.Value == null || subScreenSelectedItem == null
- ? Playlist.GetCurrentItem()
- : subScreenSelectedItem.Value;
+ protected void UpdateSelectedItem()
+ {
+ // null room ID means this is a room in the process of being created.
+ if (RoomID.Value == null)
+ CurrentPlaylistItem.Value = Playlist.GetCurrentItem();
+ else if (subScreenSelectedItem != null)
+ CurrentPlaylistItem.Value = subScreenSelectedItem.Value;
+ }
}
}
diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs
index 6abcb2924c..3de4e7afd9 100644
--- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs
+++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomManager.cs
@@ -43,6 +43,11 @@ namespace osu.Game.Tests.Visual.OnlinePlay
if (ruleset != null)
{
+ room.PlaylistItemStats.Value = new Room.RoomPlaylistItemStats
+ {
+ RulesetIDs = new[] { ruleset.OnlineID },
+ };
+
room.Playlist.Add(new PlaylistItem(new BeatmapInfo { Metadata = new BeatmapMetadata() })
{
RulesetID = ruleset.OnlineID,