diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index cb45447ed5..d75f09f184 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -114,7 +114,7 @@ jobs:
dotnet-version: "8.0.x"
- name: Install .NET workloads
- run: dotnet workload install maui-android
+ run: dotnet workload install android
- name: Compile
run: dotnet build -c Debug osu.Android.slnf
diff --git a/osu.Android.props b/osu.Android.props
index 0ebb6be7a1..4699beeac0 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -10,7 +10,7 @@
true
-
+
System.ArgumentOutOfRangeException : Index was out of range. Must be non-negative and less than the size of the collection. (Parameter 'index')
- * --TearDown
- * at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)
- * at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken)
- * at osu.Framework.Extensions.TaskExtensions.WaitSafely(Task task)
- * at osu.Framework.Testing.TestScene.checkForErrors()
- * at osu.Framework.Testing.TestScene.RunTestsFromNUnit()
- *--ArgumentOutOfRangeException
- * at osu.Framework.Bindables.BindableList`1.removeAt(Int32 index, BindableList`1 caller)
- * at osu.Framework.Bindables.BindableList`1.removeAt(Int32 index, BindableList`1 caller)
- * at osu.Framework.Bindables.BindableList`1.removeAt(Int32 index, BindableList`1 caller)
- * at osu.Game.Online.Multiplayer.MultiplayerClient.<>c__DisplayClass106_0.b__0() in C:\BuildAgent\work\ecd860037212ac52\osu.Game\Online\Multiplayer\MultiplayerClient .cs:line 702
- * at osu.Framework.Threading.ScheduledDelegate.RunTaskInternal()
- */
public void TestCreatedRoom()
{
AddStep("add playlist item", () =>
{
- SelectedRoom.Value.Playlist.Add(new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
- {
- RulesetID = new OsuRuleset().RulesetInfo.OnlineID
- });
+ SelectedRoom.Value!.Playlist =
+ [
+ new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
+ {
+ RulesetID = new OsuRuleset().RulesetInfo.OnlineID
+ }
+ ];
});
ClickButtonWhenEnabled();
@@ -112,16 +92,18 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
[Test]
- [FlakyTest] // See above
public void TestTaikoOnlyMod()
{
AddStep("add playlist item", () =>
{
- SelectedRoom.Value.Playlist.Add(new PlaylistItem(new TestBeatmap(new TaikoRuleset().RulesetInfo).BeatmapInfo)
- {
- RulesetID = new TaikoRuleset().RulesetInfo.OnlineID,
- AllowedMods = new[] { new APIMod(new TaikoModSwap()) }
- });
+ SelectedRoom.Value!.Playlist =
+ [
+ new PlaylistItem(new TestBeatmap(new TaikoRuleset().RulesetInfo).BeatmapInfo)
+ {
+ RulesetID = new TaikoRuleset().RulesetInfo.OnlineID,
+ AllowedMods = new[] { new APIMod(new TaikoModSwap()) }
+ }
+ ];
});
ClickButtonWhenEnabled();
@@ -133,32 +115,36 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
[Test]
- [FlakyTest] // See above
public void TestSettingValidity()
{
AddAssert("create button not enabled", () => !this.ChildrenOfType().Single().Enabled.Value);
AddStep("set playlist", () =>
{
- SelectedRoom.Value.Playlist.Add(new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
- {
- RulesetID = new OsuRuleset().RulesetInfo.OnlineID
- });
+ SelectedRoom.Value!.Playlist =
+ [
+ new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
+ {
+ RulesetID = new OsuRuleset().RulesetInfo.OnlineID
+ }
+ ];
});
AddAssert("create button enabled", () => this.ChildrenOfType().Single().Enabled.Value);
}
[Test]
- [FlakyTest] // See above
public void TestStartMatchWhileSpectating()
{
AddStep("set playlist", () =>
{
- SelectedRoom.Value.Playlist.Add(new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()).BeatmapInfo)
- {
- RulesetID = new OsuRuleset().RulesetInfo.OnlineID
- });
+ SelectedRoom.Value!.Playlist =
+ [
+ new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()).BeatmapInfo)
+ {
+ RulesetID = new OsuRuleset().RulesetInfo.OnlineID
+ }
+ ];
});
ClickButtonWhenEnabled();
@@ -179,16 +165,18 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
[Test]
- [FlakyTest] // See above
public void TestFreeModSelectionHasAllowedMods()
{
AddStep("add playlist item with allowed mod", () =>
{
- SelectedRoom.Value.Playlist.Add(new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
- {
- RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
- AllowedMods = new[] { new APIMod(new OsuModDoubleTime()) }
- });
+ SelectedRoom.Value!.Playlist =
+ [
+ new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
+ {
+ RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
+ AllowedMods = new[] { new APIMod(new OsuModDoubleTime()) }
+ }
+ ];
});
ClickButtonWhenEnabled();
@@ -206,16 +194,18 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
[Test]
- [FlakyTest] // See above
public void TestModSelectKeyWithAllowedMods()
{
AddStep("add playlist item with allowed mod", () =>
{
- SelectedRoom.Value.Playlist.Add(new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
- {
- RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
- AllowedMods = new[] { new APIMod(new OsuModDoubleTime()) }
- });
+ SelectedRoom.Value!.Playlist =
+ [
+ new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
+ {
+ RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
+ AllowedMods = new[] { new APIMod(new OsuModDoubleTime()) }
+ }
+ ];
});
ClickButtonWhenEnabled();
@@ -228,15 +218,17 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
[Test]
- [FlakyTest] // See above
public void TestModSelectKeyWithNoAllowedMods()
{
AddStep("add playlist item with no allowed mods", () =>
{
- SelectedRoom.Value.Playlist.Add(new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
- {
- RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
- });
+ SelectedRoom.Value!.Playlist =
+ [
+ new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
+ {
+ RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
+ }
+ ];
});
ClickButtonWhenEnabled();
@@ -249,13 +241,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
[Test]
- [FlakyTest] // See above
public void TestNextPlaylistItemSelectedAfterCompletion()
{
AddStep("add two playlist items", () =>
{
- SelectedRoom.Value.Playlist.AddRange(new[]
- {
+ SelectedRoom.Value!.Playlist =
+ [
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
@@ -264,7 +255,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
}
- });
+ ];
});
ClickButtonWhenEnabled();
@@ -286,24 +277,26 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
[Test]
- [FlakyTest] // See above
public void TestModSelectOverlay()
{
AddStep("add playlist item", () =>
{
- SelectedRoom.Value.Playlist.Add(new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
- {
- RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
- RequiredMods = new[]
+ SelectedRoom.Value!.Playlist =
+ [
+ new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
{
- new APIMod(new OsuModDoubleTime { SpeedChange = { Value = 2.0 } }),
- new APIMod(new OsuModStrictTracking()),
- },
- AllowedMods = new[]
- {
- new APIMod(new OsuModFlashlight()),
+ RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
+ RequiredMods = new[]
+ {
+ new APIMod(new OsuModDoubleTime { SpeedChange = { Value = 2.0 } }),
+ new APIMod(new OsuModStrictTracking()),
+ },
+ AllowedMods = new[]
+ {
+ new APIMod(new OsuModFlashlight()),
+ }
}
- });
+ ];
});
ClickButtonWhenEnabled();
@@ -323,8 +316,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
private partial class TestMultiplayerMatchSubScreen : MultiplayerMatchSubScreen
{
[Resolved(canBeNull: true)]
- [CanBeNull]
- private IDialogOverlay dialogOverlay { get; set; }
+ private IDialogOverlay? dialogOverlay { get; set; }
public TestMultiplayerMatchSubScreen(Room room)
: base(room)
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs
index aaf85dab7c..94dd114c32 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using System.Collections.Generic;
using System.Linq;
@@ -22,7 +20,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public partial class TestSceneMultiplayerPlayer : MultiplayerTestScene
{
- private MultiplayerPlayer player;
+ private MultiplayerPlayer player = null!;
[Test]
public void TestGameplay()
@@ -49,7 +47,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("score changed", () => player.GameplayState.ScoreProcessor.TotalScore.Value > 0);
}
- private void setup(Func> mods = null)
+ private void setup(Func>? mods = null)
{
AddStep("set beatmap", () =>
{
@@ -64,10 +62,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("initialise gameplay", () =>
{
- Stack.Push(player = new MultiplayerPlayer(MultiplayerClient.ServerAPIRoom, new PlaylistItem(Beatmap.Value.BeatmapInfo)
+ Stack.Push(player = new MultiplayerPlayer(MultiplayerClient.ServerAPIRoom!, new PlaylistItem(Beatmap.Value.BeatmapInfo)
{
RulesetID = Beatmap.Value.BeatmapInfo.Ruleset.OnlineID,
- }, MultiplayerClient.ServerRoom?.Users.ToArray()));
+ }, MultiplayerClient.ServerRoom!.Users.ToArray()));
});
AddUntilStep("wait for player to be current", () => player.IsCurrentScreen() && player.IsLoaded);
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs
index 3baabecd84..36f5bba384 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs
@@ -28,9 +28,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public partial class TestSceneMultiplayerPlaylist : MultiplayerTestScene
{
- [Cached(typeof(IBindable))]
- private readonly Bindable currentItem = new Bindable();
-
private MultiplayerPlaylist list = null!;
private BeatmapManager beatmaps = null!;
private BeatmapSetInfo importedSet = null!;
@@ -51,12 +48,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("create list", () =>
{
- Child = list = new MultiplayerPlaylist
+ Child = list = new MultiplayerPlaylist(SelectedRoom.Value!)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
- Size = new Vector2(0.4f, 0.8f)
+ Size = new Vector2(0.4f, 0.8f),
+ SelectedItem = new Bindable()
};
});
@@ -166,9 +164,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
RoomManager.CreateRoom(new Room
{
- Name = { Value = "test name" },
+ Name = "test name",
Playlist =
- {
+ [
new PlaylistItem(new TestBeatmap(Ruleset.Value).BeatmapInfo)
{
RulesetID = Ruleset.Value.OnlineID
@@ -178,7 +176,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
RulesetID = Ruleset.Value.OnlineID,
Expired = true
}
- }
+ ]
});
});
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs
index 47fb4e06ea..3ef2e4ecf4 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using System.Linq;
using NUnit.Framework;
@@ -27,10 +25,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public partial class TestSceneMultiplayerQueueList : MultiplayerTestScene
{
- private MultiplayerQueueList playlist;
- private BeatmapManager beatmaps;
- private BeatmapSetInfo importedSet;
- private BeatmapInfo importedBeatmap;
+ private MultiplayerQueueList playlist = null!;
+ private BeatmapManager beatmaps = null!;
+ private BeatmapSetInfo importedSet = null!;
+ private BeatmapInfo importedBeatmap = null!;
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
@@ -46,12 +44,17 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("create playlist", () =>
{
- Child = playlist = new MultiplayerQueueList
+ Child = playlist = new MultiplayerQueueList(SelectedRoom.Value!)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(500, 300),
- Items = { BindTarget = MultiplayerClient.ClientAPIRoom!.Playlist }
+ };
+
+ MultiplayerClient.ClientAPIRoom!.PropertyChanged += (_, e) =>
+ {
+ if (e.PropertyName == nameof(Room.Playlist))
+ playlist.Items.ReplaceRange(0, playlist.Items.Count, MultiplayerClient.ClientAPIRoom.Playlist);
};
});
@@ -69,7 +72,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
public void TestDeleteButtonAlwaysVisibleForHost()
{
AddStep("set all players queue mode", () => MultiplayerClient.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }).WaitSafely());
- AddUntilStep("wait for queue mode change", () => MultiplayerClient.ClientAPIRoom?.QueueMode.Value == QueueMode.AllPlayers);
+ AddUntilStep("wait for queue mode change", () => MultiplayerClient.ClientAPIRoom?.QueueMode == QueueMode.AllPlayers);
addPlaylistItem(() => API.LocalUser.Value.OnlineID);
assertDeleteButtonVisibility(1, true);
@@ -81,7 +84,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
public void TestDeleteButtonOnlyVisibleForItemOwnerIfNotHost()
{
AddStep("set all players queue mode", () => MultiplayerClient.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }).WaitSafely());
- AddUntilStep("wait for queue mode change", () => MultiplayerClient.ClientAPIRoom?.QueueMode.Value == QueueMode.AllPlayers);
+ AddUntilStep("wait for queue mode change", () => MultiplayerClient.ClientAPIRoom?.QueueMode == QueueMode.AllPlayers);
AddStep("join other user", () => MultiplayerClient.AddUser(new APIUser { Id = 1234 }));
AddStep("set other user as host", () => MultiplayerClient.TransferHost(1234));
@@ -100,7 +103,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
public void TestSingleItemDoesNotHaveDeleteButton()
{
AddStep("set all players queue mode", () => MultiplayerClient.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }).WaitSafely());
- AddUntilStep("wait for queue mode change", () => MultiplayerClient.ClientAPIRoom?.QueueMode.Value == QueueMode.AllPlayers);
+ AddUntilStep("wait for queue mode change", () => MultiplayerClient.ClientAPIRoom?.QueueMode == QueueMode.AllPlayers);
assertDeleteButtonVisibility(0, false);
}
@@ -109,7 +112,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
public void TestCurrentItemHasDeleteButtonIfNotSingle()
{
AddStep("set all players queue mode", () => MultiplayerClient.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }).WaitSafely());
- AddUntilStep("wait for queue mode change", () => MultiplayerClient.ClientAPIRoom?.QueueMode.Value == QueueMode.AllPlayers);
+ AddUntilStep("wait for queue mode change", () => MultiplayerClient.ClientAPIRoom?.QueueMode == QueueMode.AllPlayers);
addPlaylistItem(() => API.LocalUser.Value.OnlineID);
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerResults.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerResults.cs
index f030466fff..076c2c3cdd 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerResults.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerResults.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using NUnit.Framework;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets.Osu;
@@ -16,7 +14,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestDisplayResults()
{
- MultiplayerResultsScreen screen = null;
+ MultiplayerResultsScreen screen = null!;
AddStep("show results screen", () =>
{
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs
index 5ae5d1e228..1429f86164 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs
@@ -26,9 +26,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public partial class TestSceneMultiplayerSpectateButton : MultiplayerTestScene
{
- [Cached(typeof(IBindable))]
- private readonly Bindable currentItem = new Bindable();
-
private MultiplayerSpectateButton spectateButton = null!;
private MatchStartControl startControl = null!;
@@ -51,13 +48,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("create button", () =>
{
- AvailabilityTracker.SelectedItem.BindTo(currentItem);
+ PlaylistItem item = SelectedRoom.Value!.Playlist.First();
+
+ AvailabilityTracker.SelectedItem.Value = item;
importedSet = beatmaps.GetAllUsableBeatmapSets().First();
Beatmap.Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First());
- currentItem.Value = SelectedRoom.Value.Playlist.First();
-
Child = new PopoverContainer
{
RelativeSizeAxes = Axes.Both,
@@ -72,12 +69,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(200, 50),
+ SelectedItem = new Bindable(item)
},
startControl = new MatchStartControl
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(200, 50),
+ SelectedItem = new Bindable(item)
}
}
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectatorPlayerGrid.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectatorPlayerGrid.cs
index 8fd05dcaa9..2f461ad706 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectatorPlayerGrid.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectatorPlayerGrid.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
@@ -16,7 +14,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public partial class TestSceneMultiplayerSpectatorPlayerGrid : OsuManualInputManagerTestScene
{
- private PlayerGrid grid;
+ private PlayerGrid grid = null!;
[SetUp]
public void Setup() => Schedule(() =>
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs
index 68fd39a066..f77b6e8c68 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Bindables;
@@ -32,7 +30,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
[TestCase(1048576, 1048576)]
public void TestDisplayTeamResults(int team1Score, int team2Score)
{
- MultiplayerResultsScreen screen = null;
+ MultiplayerResultsScreen screen = null!;
AddStep("show results screen", () =>
{
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs
index ae27db0dd1..cd41884ba7 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using System.Collections.Generic;
using System.Linq;
@@ -28,18 +26,18 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public partial class TestScenePlaylistsRoomSettingsPlaylist : OnlinePlayTestScene
{
- private TestPlaylist playlist;
+ private TestPlaylist playlist = null!;
[Test]
public void TestItemRemovedOnDeletion()
{
- PlaylistItem selectedItem = null;
+ PlaylistItem selectedItem = null!;
createPlaylist();
moveToItem(0);
AddStep("click", () => InputManager.Click(MouseButton.Left));
- AddStep("retrieve selection", () => selectedItem = playlist.SelectedItem.Value);
+ AddStep("retrieve selection", () => selectedItem = playlist.SelectedItem.Value!);
moveToDeleteButton(0);
AddStep("click delete button", () => InputManager.Click(MouseButton.Left));
@@ -122,7 +120,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
InputManager.MoveMouseTo(item.ChildrenOfType().ElementAt(0), offset);
});
- private void createPlaylist(Action setupPlaylist = null)
+ private void createPlaylist(Action? setupPlaylist = null)
{
AddStep("create playlist", () =>
{
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs
index cc78bed5de..fa1909254a 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using System.Linq;
using NUnit.Framework;
@@ -27,9 +25,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public partial class TestScenePlaylistsSongSelect : OnlinePlayTestScene
{
- private BeatmapManager manager;
-
- private TestPlaylistsSongSelect songSelect;
+ private BeatmapManager manager = null!;
+ private TestPlaylistsSongSelect songSelect = null!;
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
@@ -60,7 +57,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
SelectedMods.Value = Array.Empty();
});
- AddStep("create song select", () => LoadScreen(songSelect = new TestPlaylistsSongSelect(SelectedRoom.Value)));
+ AddStep("create song select", () => LoadScreen(songSelect = new TestPlaylistsSongSelect(SelectedRoom.Value!)));
AddUntilStep("wait for present", () => songSelect.IsCurrentScreen() && songSelect.BeatmapSetsLoaded);
}
@@ -68,46 +65,41 @@ namespace osu.Game.Tests.Visual.Multiplayer
public void TestItemAddedIfEmptyOnStart()
{
AddStep("finalise selection", () => songSelect.FinaliseSelection());
- AddAssert("playlist has 1 item", () => SelectedRoom.Value.Playlist.Count == 1);
+ AddAssert("playlist has 1 item", () => SelectedRoom.Value!.Playlist.Count == 1);
}
[Test]
public void TestItemAddedWhenCreateNewItemClicked()
{
- AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem());
- AddAssert("playlist has 1 item", () => SelectedRoom.Value.Playlist.Count == 1);
+ AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!());
+ AddAssert("playlist has 1 item", () => SelectedRoom.Value!.Playlist.Count == 1);
}
[Test]
public void TestItemNotAddedIfExistingOnStart()
{
- AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem());
+ AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!());
AddStep("finalise selection", () => songSelect.FinaliseSelection());
- AddAssert("playlist has 1 item", () => SelectedRoom.Value.Playlist.Count == 1);
+ AddAssert("playlist has 1 item", () => SelectedRoom.Value!.Playlist.Count == 1);
}
[Test]
public void TestAddSameItemMultipleTimes()
{
- AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem());
- AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem());
- AddAssert("playlist has 2 items", () => SelectedRoom.Value.Playlist.Count == 2);
+ AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!());
+ AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!());
+ AddAssert("playlist has 2 items", () => SelectedRoom.Value!.Playlist.Count == 2);
}
[Test]
public void TestAddItemAfterRearrangement()
{
- AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem());
- AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem());
- AddStep("rearrange", () =>
- {
- var item = SelectedRoom.Value.Playlist[0];
- SelectedRoom.Value.Playlist.RemoveAt(0);
- SelectedRoom.Value.Playlist.Add(item);
- });
+ AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!());
+ AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!());
+ AddStep("rearrange", () => SelectedRoom.Value!.Playlist = SelectedRoom.Value!.Playlist.Skip(1).Append(SelectedRoom.Value!.Playlist[0]).ToArray());
- AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem());
- AddAssert("new item has id 2", () => SelectedRoom.Value.Playlist.Last().ID == 2);
+ AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!());
+ AddAssert("new item has id 2", () => SelectedRoom.Value!.Playlist.Last().ID == 2);
}
///
@@ -117,19 +109,19 @@ namespace osu.Game.Tests.Visual.Multiplayer
public void TestNewItemHasNewModInstances()
{
AddStep("set dt mod", () => SelectedMods.Value = new[] { new OsuModDoubleTime() });
- AddStep("create item", () => songSelect.BeatmapDetails.CreateNewItem());
+ AddStep("create item", () => songSelect.BeatmapDetails.CreateNewItem!());
AddStep("change mod rate", () => ((OsuModDoubleTime)SelectedMods.Value[0]).SpeedChange.Value = 2);
- AddStep("create item", () => songSelect.BeatmapDetails.CreateNewItem());
+ AddStep("create item", () => songSelect.BeatmapDetails.CreateNewItem!());
AddAssert("item 1 has rate 1.5", () =>
{
- var mod = (OsuModDoubleTime)SelectedRoom.Value.Playlist.First().RequiredMods[0].ToMod(new OsuRuleset());
+ var mod = (OsuModDoubleTime)SelectedRoom.Value!.Playlist.First().RequiredMods[0].ToMod(new OsuRuleset());
return Precision.AlmostEquals(1.5, mod.SpeedChange.Value);
});
AddAssert("item 2 has rate 2", () =>
{
- var mod = (OsuModDoubleTime)SelectedRoom.Value.Playlist.Last().RequiredMods[0].ToMod(new OsuRuleset());
+ var mod = (OsuModDoubleTime)SelectedRoom.Value!.Playlist.Last().RequiredMods[0].ToMod(new OsuRuleset());
return Precision.AlmostEquals(2, mod.SpeedChange.Value);
});
}
@@ -140,7 +132,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestGlobalModInstancesNotRetained()
{
- OsuModDoubleTime mod = null;
+ OsuModDoubleTime mod = null!;
AddStep("set dt mod and store", () =>
{
@@ -150,12 +142,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
mod = (OsuModDoubleTime)SelectedMods.Value[0];
});
- AddStep("create item", () => songSelect.BeatmapDetails.CreateNewItem());
+ AddStep("create item", () => songSelect.BeatmapDetails.CreateNewItem!());
AddStep("change stored mod rate", () => mod.SpeedChange.Value = 2);
AddAssert("item has rate 1.5", () =>
{
- var m = (OsuModDoubleTime)SelectedRoom.Value.Playlist.First().RequiredMods[0].ToMod(new OsuRuleset());
+ var m = (OsuModDoubleTime)SelectedRoom.Value!.Playlist.First().RequiredMods[0].ToMod(new OsuRuleset());
return Precision.AlmostEquals(1.5, m.SpeedChange.Value);
});
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneRankRangePill.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneRankRangePill.cs
index d5f53bc354..9420ddf807 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneRankRangePill.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneRankRangePill.cs
@@ -9,7 +9,6 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Multiplayer;
-using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Lounge.Components;
namespace osu.Game.Tests.Visual.Multiplayer
@@ -18,10 +17,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
private readonly Mock multiplayerClient = new Mock();
- protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) =>
- // not used directly in component, but required due to it inheriting from OnlinePlayComposite.
- new CachedModelDependencyContainer(base.CreateChildDependencies(parent));
-
[BackgroundDependencyLoader]
private void load()
{
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs
index b53a61f881..88afef7de2 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs
@@ -20,7 +20,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
SelectedRoom.Value = new Room();
- Child = new StarRatingRangeDisplay
+ Child = new StarRatingRangeDisplay(SelectedRoom.Value)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre
@@ -33,11 +33,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("set playlist", () =>
{
- SelectedRoom.Value.Playlist.AddRange(new[]
- {
+ SelectedRoom.Value!.Playlist =
+ [
new PlaylistItem(new BeatmapInfo { StarRating = min }),
new PlaylistItem(new BeatmapInfo { StarRating = max }),
- });
+ ];
});
}
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs
index 32e90153d8..05136ebee1 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using System.Linq;
using NUnit.Framework;
@@ -29,10 +27,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public partial class TestSceneTeamVersus : ScreenTestScene
{
- private BeatmapManager beatmaps;
- private BeatmapSetInfo importedSet;
+ private BeatmapManager beatmaps = null!;
+ private BeatmapSetInfo importedSet = null!;
- private TestMultiplayerComponents multiplayerComponents;
+ private TestMultiplayerComponents multiplayerComponents = null!;
private TestMultiplayerClient multiplayerClient => multiplayerComponents.MultiplayerClient;
@@ -64,15 +62,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
createRoom(() => new Room
{
- Name = { Value = "Test Room" },
- Type = { Value = MatchType.TeamVersus },
+ Name = "Test Room",
+ Type = MatchType.TeamVersus,
Playlist =
- {
+ [
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
}
- }
+ ]
});
AddUntilStep("room type is team vs", () => multiplayerClient.ClientRoom?.Settings.MatchType == MatchType.TeamVersus);
@@ -84,15 +82,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
createRoom(() => new Room
{
- Name = { Value = "Test Room" },
- Type = { Value = MatchType.TeamVersus },
+ Name = "Test Room",
+ Type = MatchType.TeamVersus,
Playlist =
- {
+ [
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
}
- }
+ ]
});
AddUntilStep("user on team 0", () => (multiplayerClient.ClientRoom?.Users.FirstOrDefault()?.MatchState as TeamVersusUserState)?.TeamID == 0);
@@ -121,25 +119,25 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
createRoom(() => new Room
{
- Name = { Value = "Test Room" },
- Type = { Value = MatchType.HeadToHead },
+ Name = "Test Room",
+ Type = MatchType.HeadToHead,
Playlist =
- {
+ [
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
}
- }
+ ]
});
- AddUntilStep("match type head to head", () => multiplayerClient.ClientAPIRoom?.Type.Value == MatchType.HeadToHead);
+ AddUntilStep("match type head to head", () => multiplayerClient.ClientAPIRoom?.Type == MatchType.HeadToHead);
AddStep("change match type", () => multiplayerClient.ChangeSettings(new MultiplayerRoomSettings
{
MatchType = MatchType.TeamVersus
}).WaitSafely());
- AddUntilStep("api room updated to team versus", () => multiplayerClient.ClientAPIRoom?.Type.Value == MatchType.TeamVersus);
+ AddUntilStep("api room updated to team versus", () => multiplayerClient.ClientAPIRoom?.Type == MatchType.TeamVersus);
}
[Test]
@@ -147,14 +145,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
createRoom(() => new Room
{
- Name = { Value = "Test Room" },
+ Name = "Test Room",
Playlist =
- {
+ [
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
}
- }
+ ]
});
AddUntilStep("room type is head to head", () => multiplayerClient.ClientRoom?.Settings.MatchType == MatchType.HeadToHead);
diff --git a/osu.Game.Tests/Visual/Online/TestSceneLocalUserStatisticsProvider.cs b/osu.Game.Tests/Visual/Online/TestSceneLocalUserStatisticsProvider.cs
new file mode 100644
index 0000000000..f24a9333c1
--- /dev/null
+++ b/osu.Game.Tests/Visual/Online/TestSceneLocalUserStatisticsProvider.cs
@@ -0,0 +1,179 @@
+// 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.Generic;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Testing;
+using osu.Game.Graphics.Containers;
+using osu.Game.Online;
+using osu.Game.Online.API;
+using osu.Game.Online.API.Requests;
+using osu.Game.Online.API.Requests.Responses;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Catch;
+using osu.Game.Rulesets.Mania;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.Taiko;
+using osu.Game.Users;
+
+namespace osu.Game.Tests.Visual.Online
+{
+ public partial class TestSceneLocalUserStatisticsProvider : OsuTestScene
+ {
+ private LocalUserStatisticsProvider statisticsProvider = null!;
+
+ private readonly Dictionary<(int userId, string rulesetName), UserStatistics> serverSideStatistics = new Dictionary<(int userId, string rulesetName), UserStatistics>();
+
+ [SetUpSteps]
+ public void SetUpSteps()
+ {
+ AddStep("clear statistics", () => serverSideStatistics.Clear());
+
+ setUser(1000);
+
+ AddStep("setup provider", () =>
+ {
+ OsuTextFlowContainer text;
+
+ ((DummyAPIAccess)API).HandleRequest = r =>
+ {
+ switch (r)
+ {
+ case GetUserRequest userRequest:
+ int userId = int.Parse(userRequest.Lookup);
+ string rulesetName = userRequest.Ruleset!.ShortName;
+ var response = new APIUser
+ {
+ Id = userId,
+ Statistics = tryGetStatistics(userId, rulesetName)
+ };
+
+ userRequest.TriggerSuccess(response);
+ return true;
+
+ default:
+ return false;
+ }
+ };
+
+ Clear();
+ Add(statisticsProvider = new LocalUserStatisticsProvider());
+ Add(text = new OsuTextFlowContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ });
+
+ statisticsProvider.StatisticsUpdated += update =>
+ {
+ text.Clear();
+
+ foreach (var ruleset in Dependencies.Get().AvailableRulesets)
+ {
+ text.AddText(statisticsProvider.GetStatisticsFor(ruleset) is UserStatistics statistics
+ ? $"{ruleset.Name} statistics: (total score: {statistics.TotalScore})"
+ : $"{ruleset.Name} statistics: (null)");
+ text.NewLine();
+ }
+
+ text.AddText($"latest update: {update.Ruleset}"
+ + $" ({(update.OldStatistics?.TotalScore.ToString() ?? "null")} -> {update.NewStatistics.TotalScore})");
+ };
+
+ Ruleset.Value = new OsuRuleset().RulesetInfo;
+ });
+ }
+
+ [Test]
+ public void TestInitialStatistics()
+ {
+ AddAssert("osu statistics populated", () => statisticsProvider.GetStatisticsFor(new OsuRuleset().RulesetInfo)!.TotalScore, () => Is.EqualTo(4_000_000));
+ AddAssert("taiko statistics populated", () => statisticsProvider.GetStatisticsFor(new TaikoRuleset().RulesetInfo)!.TotalScore, () => Is.EqualTo(3_000_000));
+ AddAssert("catch statistics populated", () => statisticsProvider.GetStatisticsFor(new CatchRuleset().RulesetInfo)!.TotalScore, () => Is.EqualTo(2_000_000));
+ AddAssert("mania statistics populated", () => statisticsProvider.GetStatisticsFor(new ManiaRuleset().RulesetInfo)!.TotalScore, () => Is.EqualTo(1_000_000));
+ }
+
+ [Test]
+ public void TestUserChanges()
+ {
+ setUser(1001);
+
+ AddStep("update statistics for user 1000", () =>
+ {
+ serverSideStatistics[(1000, "osu")] = new UserStatistics { TotalScore = 5_000_000 };
+ serverSideStatistics[(1000, "taiko")] = new UserStatistics { TotalScore = 6_000_000 };
+ });
+
+ AddAssert("statistics matches user 1001 in osu",
+ () => statisticsProvider.GetStatisticsFor(new OsuRuleset().RulesetInfo)!.TotalScore,
+ () => Is.EqualTo(4_000_000));
+
+ AddAssert("statistics matches user 1001 in taiko",
+ () => statisticsProvider.GetStatisticsFor(new TaikoRuleset().RulesetInfo)!.TotalScore,
+ () => Is.EqualTo(3_000_000));
+
+ setUser(1000, false);
+
+ AddAssert("statistics matches user 1000 in osu",
+ () => statisticsProvider.GetStatisticsFor(new OsuRuleset().RulesetInfo)!.TotalScore,
+ () => Is.EqualTo(5_000_000));
+
+ AddAssert("statistics matches user 1000 in taiko",
+ () => statisticsProvider.GetStatisticsFor(new TaikoRuleset().RulesetInfo)!.TotalScore,
+ () => Is.EqualTo(6_000_000));
+ }
+
+ [Test]
+ public void TestRefetchStatistics()
+ {
+ UserStatisticsUpdate? update = null;
+
+ setUser(1001);
+
+ AddStep("update statistics server side",
+ () => serverSideStatistics[(1001, "osu")] = new UserStatistics { TotalScore = 9_000_000 });
+
+ AddAssert("statistics match old score",
+ () => statisticsProvider.GetStatisticsFor(new OsuRuleset().RulesetInfo)!.TotalScore,
+ () => Is.EqualTo(4_000_000));
+
+ AddStep("setup event", () =>
+ {
+ update = null;
+ statisticsProvider.StatisticsUpdated -= onStatisticsUpdated;
+ statisticsProvider.StatisticsUpdated += onStatisticsUpdated;
+ });
+
+ AddStep("request refetch", () => statisticsProvider.RefetchStatistics(new OsuRuleset().RulesetInfo));
+ AddUntilStep("statistics update raised",
+ () => update?.NewStatistics.TotalScore,
+ () => Is.EqualTo(9_000_000));
+ AddAssert("statistics match new score",
+ () => statisticsProvider.GetStatisticsFor(new OsuRuleset().RulesetInfo)!.TotalScore,
+ () => Is.EqualTo(9_000_000));
+
+ void onStatisticsUpdated(UserStatisticsUpdate u) => update = u;
+ }
+
+ private UserStatistics tryGetStatistics(int userId, string rulesetName)
+ => serverSideStatistics.TryGetValue((userId, rulesetName), out var stats) ? stats : new UserStatistics();
+
+ private void setUser(int userId, bool generateStatistics = true)
+ {
+ AddStep($"set local user to {userId}", () =>
+ {
+ if (generateStatistics)
+ {
+ serverSideStatistics[(userId, "osu")] = new UserStatistics { TotalScore = 4_000_000 };
+ serverSideStatistics[(userId, "taiko")] = new UserStatistics { TotalScore = 3_000_000 };
+ serverSideStatistics[(userId, "fruits")] = new UserStatistics { TotalScore = 2_000_000 };
+ serverSideStatistics[(userId, "mania")] = new UserStatistics { TotalScore = 1_000_000 };
+ }
+
+ ((DummyAPIAccess)API).LocalUser.Value = new APIUser { Id = userId };
+ });
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneReplayMissingBeatmap.cs b/osu.Game.Tests/Visual/Online/TestSceneReplayMissingBeatmap.cs
index 60197e0eb7..b986901dcf 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneReplayMissingBeatmap.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneReplayMissingBeatmap.cs
@@ -40,15 +40,13 @@ namespace osu.Game.Tests.Visual.Online
AddStep("import score", () =>
{
- using (var resourceStream = TestResources.OpenResource("Replays/mania-replay.osr"))
- {
- var importTask = new ImportTask(resourceStream, "replay.osr");
+ var resourceStream = TestResources.OpenResource("Replays/mania-replay.osr");
+ var importTask = new ImportTask(resourceStream, "replay.osr");
- Game.ScoreManager.Import(new[] { importTask });
- }
+ Game.ScoreManager.Import(new[] { importTask });
});
- AddUntilStep("Replay missing notification show", () => Game.Notifications.ChildrenOfType().Any());
+ AddUntilStep("Replay missing notification shown", () => Game.Notifications.ChildrenOfType().Any());
}
[Test]
@@ -58,15 +56,13 @@ namespace osu.Game.Tests.Visual.Online
AddStep("import score", () =>
{
- using (var resourceStream = TestResources.OpenResource("Replays/mania-replay.osr"))
- {
- var importTask = new ImportTask(resourceStream, "replay.osr");
+ var resourceStream = TestResources.OpenResource("Replays/mania-replay.osr");
+ var importTask = new ImportTask(resourceStream, "replay.osr");
- Game.ScoreManager.Import(new[] { importTask });
- }
+ Game.ScoreManager.Import(new[] { importTask });
});
- AddUntilStep("Replay missing notification not show", () => !Game.Notifications.ChildrenOfType().Any());
+ AddUntilStep("Replay missing notification not shown", () => !Game.Notifications.ChildrenOfType().Any());
}
private void setupBeatmapResponse(APIBeatmap b)
diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs
index ab3d5907ef..3f1d961588 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using NUnit.Framework;
using osu.Framework.Allocation;
@@ -11,6 +9,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
+using osu.Game.Online;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays;
using osu.Game.Rulesets;
@@ -24,17 +23,20 @@ namespace osu.Game.Tests.Visual.Online
[TestFixture]
public partial class TestSceneUserPanel : OsuTestScene
{
- private readonly Bindable activity = new Bindable();
+ private readonly Bindable activity = new Bindable();
private readonly Bindable status = new Bindable();
- private UserGridPanel boundPanel1;
- private TestUserListPanel boundPanel2;
+ private UserGridPanel boundPanel1 = null!;
+ private TestUserListPanel boundPanel2 = null!;
[Cached]
- private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
+ private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
+
+ [Cached(typeof(LocalUserStatisticsProvider))]
+ private readonly TestUserStatisticsProvider statisticsProvider = new TestUserStatisticsProvider();
[Resolved]
- private IRulesetStore rulesetStore { get; set; }
+ private IRulesetStore rulesetStore { get; set; } = null!;
[SetUp]
public void SetUp() => Schedule(() =>
@@ -42,7 +44,11 @@ namespace osu.Game.Tests.Visual.Online
activity.Value = null;
status.Value = null;
- Child = new FillFlowContainer
+ Remove(statisticsProvider, false);
+ Clear();
+ Add(statisticsProvider);
+
+ Add(new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -108,7 +114,7 @@ namespace osu.Game.Tests.Visual.Online
Statistics = new UserStatistics { GlobalRank = null, CountryRank = null }
}) { Width = 300 }
}
- };
+ });
boundPanel1.Status.BindTo(status);
boundPanel1.Activity.BindTo(activity);
@@ -162,24 +168,21 @@ namespace osu.Game.Tests.Visual.Online
{
AddStep("update statistics", () =>
{
- API.UpdateStatistics(new UserStatistics
+ statisticsProvider.UpdateStatistics(new UserStatistics
{
GlobalRank = RNG.Next(100000),
CountryRank = RNG.Next(100000)
- });
+ }, Ruleset.Value);
});
AddStep("set statistics to something big", () =>
{
- API.UpdateStatistics(new UserStatistics
+ statisticsProvider.UpdateStatistics(new UserStatistics
{
GlobalRank = RNG.Next(1_000_000, 100_000_000),
CountryRank = RNG.Next(1_000_000, 100_000_000)
- });
- });
- AddStep("set statistics to empty", () =>
- {
- API.UpdateStatistics(new UserStatistics());
+ }, Ruleset.Value);
});
+ AddStep("set statistics to empty", () => statisticsProvider.UpdateStatistics(new UserStatistics(), Ruleset.Value));
}
private UserActivity soloGameStatusForRuleset(int rulesetId) => new UserActivity.InSoloGame(new BeatmapInfo(), rulesetStore.GetRuleset(rulesetId)!);
@@ -201,5 +204,11 @@ namespace osu.Game.Tests.Visual.Online
public new TextFlowContainer LastVisitMessage => base.LastVisitMessage;
}
+
+ public partial class TestUserStatisticsProvider : LocalUserStatisticsProvider
+ {
+ public new void UpdateStatistics(UserStatistics newStatistics, RulesetInfo ruleset, Action? callback = null)
+ => base.UpdateStatistics(newStatistics, ruleset, callback);
+ }
}
}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs
index 006610dccd..d16ed46bd2 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
@@ -58,6 +59,16 @@ namespace osu.Game.Tests.Visual.Online
return true;
}
+ if (req is GetUserBeatmapsRequest getUserBeatmapsRequest)
+ {
+ getUserBeatmapsRequest.TriggerSuccess(new List
+ {
+ CreateAPIBeatmapSet(),
+ CreateAPIBeatmapSet()
+ });
+ return true;
+ }
+
return false;
};
});
diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserStatisticsWatcher.cs b/osu.Game.Tests/Visual/Online/TestSceneUserStatisticsWatcher.cs
index e7ad07041c..d410b7f3a4 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneUserStatisticsWatcher.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneUserStatisticsWatcher.cs
@@ -25,6 +25,7 @@ namespace osu.Game.Tests.Visual.Online
{
protected override bool UseOnlineAPI => false;
+ private LocalUserStatisticsProvider statisticsProvider = null!;
private UserStatisticsWatcher watcher = null!;
[Resolved]
@@ -107,7 +108,9 @@ namespace osu.Game.Tests.Visual.Online
AddStep("create watcher", () =>
{
- Child = watcher = new UserStatisticsWatcher();
+ Clear();
+ Add(statisticsProvider = new LocalUserStatisticsProvider());
+ Add(watcher = new UserStatisticsWatcher(statisticsProvider));
});
}
@@ -123,7 +126,7 @@ namespace osu.Game.Tests.Visual.Online
var ruleset = new OsuRuleset().RulesetInfo;
- UserStatisticsUpdate? update = null;
+ ScoreBasedUserStatisticsUpdate? update = null;
registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate);
feignScoreProcessing(userId, ruleset, 5_000_000);
@@ -146,7 +149,7 @@ namespace osu.Game.Tests.Visual.Online
// note ordering - in this test processing completes *before* the registration is added.
feignScoreProcessing(userId, ruleset, 5_000_000);
- UserStatisticsUpdate? update = null;
+ ScoreBasedUserStatisticsUpdate? update = null;
registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate);
AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, scoreId));
@@ -164,7 +167,7 @@ namespace osu.Game.Tests.Visual.Online
long scoreId = getScoreId();
var ruleset = new OsuRuleset().RulesetInfo;
- UserStatisticsUpdate? update = null;
+ ScoreBasedUserStatisticsUpdate? update = null;
registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate);
feignScoreProcessing(userId, ruleset, 5_000_000);
@@ -191,7 +194,7 @@ namespace osu.Game.Tests.Visual.Online
long scoreId = getScoreId();
var ruleset = new OsuRuleset().RulesetInfo;
- UserStatisticsUpdate? update = null;
+ ScoreBasedUserStatisticsUpdate? update = null;
registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate);
feignScoreProcessing(userId, ruleset, 5_000_000);
@@ -212,7 +215,7 @@ namespace osu.Game.Tests.Visual.Online
long scoreId = getScoreId();
var ruleset = new OsuRuleset().RulesetInfo;
- UserStatisticsUpdate? update = null;
+ ScoreBasedUserStatisticsUpdate? update = null;
registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate);
feignScoreProcessing(userId, ruleset, 5_000_000);
@@ -241,7 +244,7 @@ namespace osu.Game.Tests.Visual.Online
feignScoreProcessing(userId, ruleset, 6_000_000);
- UserStatisticsUpdate? update = null;
+ ScoreBasedUserStatisticsUpdate? update = null;
registerForUpdates(secondScoreId, ruleset, receivedUpdate => update = receivedUpdate);
AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, secondScoreId));
@@ -259,15 +262,14 @@ namespace osu.Game.Tests.Visual.Online
var ruleset = new OsuRuleset().RulesetInfo;
- UserStatisticsUpdate? update = null;
+ ScoreBasedUserStatisticsUpdate? update = null;
registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate);
feignScoreProcessing(userId, ruleset, 5_000_000);
AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, scoreId));
AddUntilStep("update received", () => update != null);
- AddAssert("local user values are correct", () => dummyAPI.LocalUser.Value.Statistics.TotalScore, () => Is.EqualTo(5_000_000));
- AddAssert("statistics values are correct", () => dummyAPI.Statistics.Value!.TotalScore, () => Is.EqualTo(5_000_000));
+ AddAssert("statistics values are correct", () => statisticsProvider.GetStatisticsFor(ruleset)!.TotalScore, () => Is.EqualTo(5_000_000));
}
private int nextUserId = 2000;
@@ -289,7 +291,7 @@ namespace osu.Game.Tests.Visual.Online
});
}
- private void registerForUpdates(long scoreId, RulesetInfo rulesetInfo, Action onUpdateReady) =>
+ private void registerForUpdates(long scoreId, RulesetInfo rulesetInfo, Action onUpdateReady) =>
AddStep("register for updates", () =>
{
watcher.RegisterForStatisticsUpdateAfter(
diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs
index 0c536cb1d4..8c8dc8d69a 100644
--- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs
+++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System.Linq;
using NUnit.Framework;
using osu.Framework.Bindables;
@@ -21,7 +19,7 @@ namespace osu.Game.Tests.Visual.Playlists
{
protected new TestRoomManager RoomManager => (TestRoomManager)base.RoomManager;
- private TestLoungeSubScreen loungeScreen;
+ private TestLoungeSubScreen loungeScreen = null!;
public override void SetUpSteps()
{
@@ -97,7 +95,7 @@ namespace osu.Game.Tests.Visual.Playlists
private partial class TestLoungeSubScreen : PlaylistsLoungeSubScreen
{
- public new Bindable SelectedRoom => base.SelectedRoom;
+ public new Bindable SelectedRoom => base.SelectedRoom;
}
}
}
diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs
index 9f7b20ad43..5868331451 100644
--- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs
+++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using NUnit.Framework;
using osu.Framework.Bindables;
@@ -22,7 +20,7 @@ namespace osu.Game.Tests.Visual.Playlists
{
protected new TestRoomManager RoomManager => (TestRoomManager)base.RoomManager;
- private TestRoomSettings settings;
+ private TestRoomSettings settings = null!;
protected override OnlinePlayTestSceneDependencies CreateOnlinePlayDependencies() => new TestDependencies();
@@ -34,7 +32,7 @@ namespace osu.Game.Tests.Visual.Playlists
{
SelectedRoom.Value = new Room();
- Child = settings = new TestRoomSettings(SelectedRoom.Value)
+ Child = settings = new TestRoomSettings(SelectedRoom.Value!)
{
RelativeSizeAxes = Axes.Both,
State = { Value = Visibility.Visible }
@@ -47,19 +45,19 @@ namespace osu.Game.Tests.Visual.Playlists
{
AddStep("clear name and beatmap", () =>
{
- SelectedRoom.Value.Name.Value = "";
- SelectedRoom.Value.Playlist.Clear();
+ SelectedRoom.Value!.Name = "";
+ SelectedRoom.Value!.Playlist = [];
});
AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value);
- AddStep("set name", () => SelectedRoom.Value.Name.Value = "Room name");
+ AddStep("set name", () => SelectedRoom.Value!.Name = "Room name");
AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value);
- AddStep("set beatmap", () => SelectedRoom.Value.Playlist.Add(new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo)));
+ AddStep("set beatmap", () => SelectedRoom.Value!.Playlist = [new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo)]);
AddAssert("button enabled", () => settings.ApplyButton.Enabled.Value);
- AddStep("clear name", () => SelectedRoom.Value.Name.Value = "");
+ AddStep("clear name", () => SelectedRoom.Value!.Name = "");
AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value);
}
@@ -69,13 +67,13 @@ namespace osu.Game.Tests.Visual.Playlists
const string expected_name = "expected name";
TimeSpan expectedDuration = TimeSpan.FromMinutes(15);
- Room createdRoom = null;
+ Room createdRoom = null!;
AddStep("setup", () =>
{
settings.NameField.Current.Value = expected_name;
settings.DurationField.Current.Value = expectedDuration;
- SelectedRoom.Value.Playlist.Add(new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo));
+ SelectedRoom.Value!.Playlist = [new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo)];
RoomManager.CreateRequested = r =>
{
@@ -85,8 +83,8 @@ namespace osu.Game.Tests.Visual.Playlists
});
AddStep("create room", () => settings.ApplyButton.Action.Invoke());
- AddAssert("has correct name", () => createdRoom.Name.Value == expected_name);
- AddAssert("has correct duration", () => createdRoom.Duration.Value == expectedDuration);
+ AddAssert("has correct name", () => createdRoom.Name == expected_name);
+ AddAssert("has correct duration", () => createdRoom.Duration == expectedDuration);
}
[Test]
@@ -94,14 +92,14 @@ namespace osu.Game.Tests.Visual.Playlists
{
const string not_found_prefix = "beatmaps not found:";
- string errorMessage = null;
+ string errorMessage = null!;
AddStep("setup", () =>
{
var beatmap = CreateBeatmap(Ruleset.Value).BeatmapInfo;
- SelectedRoom.Value.Name.Value = "Test Room";
- SelectedRoom.Value.Playlist.Add(new PlaylistItem(beatmap));
+ SelectedRoom.Value!.Name = "Test Room";
+ SelectedRoom.Value!.Playlist = [new PlaylistItem(beatmap)];
errorMessage = $"{not_found_prefix} {beatmap.OnlineID}";
@@ -109,13 +107,13 @@ namespace osu.Game.Tests.Visual.Playlists
});
AddAssert("error not displayed", () => !settings.ErrorText.IsPresent);
- AddAssert("playlist item valid", () => SelectedRoom.Value.Playlist[0].Valid.Value);
+ AddAssert("playlist item valid", () => SelectedRoom.Value!.Playlist[0].Valid.Value);
AddStep("create room", () => settings.ApplyButton.Action.Invoke());
AddAssert("error displayed", () => settings.ErrorText.IsPresent);
AddAssert("error has custom text", () => settings.ErrorText.Text != errorMessage);
- AddAssert("playlist item marked invalid", () => !SelectedRoom.Value.Playlist[0].Valid.Value);
+ AddAssert("playlist item marked invalid", () => !SelectedRoom.Value!.Playlist[0].Valid.Value);
}
[Test]
@@ -127,8 +125,8 @@ namespace osu.Game.Tests.Visual.Playlists
AddStep("setup", () =>
{
- SelectedRoom.Value.Name.Value = "Test Room";
- SelectedRoom.Value.Playlist.Add(new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo));
+ SelectedRoom.Value!.Name = "Test Room";
+ SelectedRoom.Value!.Playlist = [new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo)];
RoomManager.CreateRequested = _ => failText;
});
@@ -169,7 +167,7 @@ namespace osu.Game.Tests.Visual.Playlists
protected class TestRoomManager : IRoomManager
{
- public Func CreateRequested;
+ public Func? CreateRequested;
public event Action RoomsUpdated
{
@@ -187,7 +185,7 @@ namespace osu.Game.Tests.Visual.Playlists
public void ClearRooms() => throw new NotImplementedException();
- public void CreateRoom(Room room, Action onSuccess = null, Action onError = null)
+ public void CreateRoom(Room room, Action? onSuccess = null, Action? onError = null)
{
if (CreateRequested == null)
return;
@@ -200,7 +198,7 @@ namespace osu.Game.Tests.Visual.Playlists
onSuccess?.Invoke(room);
}
- public void JoinRoom(Room room, string password, Action onSuccess = null, Action onError = null) => throw new NotImplementedException();
+ public void JoinRoom(Room room, string? password, Action? onSuccess = null, Action? onError = null) => throw new NotImplementedException();
public void PartRoom() => throw new NotImplementedException();
}
diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs
index 3b60c28dc0..c60b208ffc 100644
--- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs
+++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs
@@ -1,6 +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.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Game.Online.API.Requests.Responses;
@@ -19,17 +20,16 @@ namespace osu.Game.Tests.Visual.Playlists
AddStep("create list", () =>
{
- SelectedRoom.Value = new Room { RoomID = { Value = 7 } };
-
- for (int i = 0; i < 50; i++)
+ SelectedRoom.Value = new Room
{
- SelectedRoom.Value.RecentParticipants.Add(new APIUser
+ RoomID = 7,
+ RecentParticipants = Enumerable.Range(0, 50).Select(_ => new APIUser
{
Username = "peppy",
Statistics = new UserStatistics { GlobalRank = 1234 },
Id = 2
- });
- }
+ }).ToArray()
+ };
});
}
@@ -38,7 +38,7 @@ namespace osu.Game.Tests.Visual.Playlists
{
AddStep("create component", () =>
{
- Child = new ParticipantsDisplay(Direction.Horizontal)
+ Child = new ParticipantsDisplay(SelectedRoom.Value!, Direction.Horizontal)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -52,7 +52,7 @@ namespace osu.Game.Tests.Visual.Playlists
{
AddStep("create component", () =>
{
- Child = new ParticipantsDisplay(Direction.Vertical)
+ Child = new ParticipantsDisplay(SelectedRoom.Value!, Direction.Vertical)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs
index 7527647b9c..5977e67b0e 100644
--- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs
+++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs
@@ -1,13 +1,10 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
-using JetBrains.Annotations;
using Newtonsoft.Json.Linq;
using NUnit.Framework;
using osu.Framework.Graphics.Containers;
@@ -34,14 +31,14 @@ namespace osu.Game.Tests.Visual.Playlists
private const int scores_per_result = 10;
private const int real_user_position = 200;
- private TestResultsScreen resultsScreen;
+ private TestResultsScreen resultsScreen = null!;
private int lowestScoreId; // Score ID of the lowest score in the list.
private int highestScoreId; // Score ID of the highest score in the list.
private bool requestComplete;
private int totalCount;
- private ScoreInfo userScore;
+ private ScoreInfo userScore = null!;
[SetUpSteps]
public override void SetUpSteps()
@@ -205,7 +202,7 @@ namespace osu.Game.Tests.Visual.Playlists
AddAssert("placeholder shown", () => this.ChildrenOfType().Count(), () => Is.EqualTo(1));
}
- private void createResults(Func getScore = null)
+ private void createResults(Func? getScore = null)
{
AddStep("load results", () =>
{
@@ -229,7 +226,7 @@ namespace osu.Game.Tests.Visual.Playlists
AddWaitStep("wait for display", 5);
}
- private void bindHandler(bool delayed = false, ScoreInfo userScore = null, bool failRequests = false, bool noScores = false) => ((DummyAPIAccess)API).HandleRequest = request =>
+ private void bindHandler(bool delayed = false, ScoreInfo? userScore = null, bool failRequests = false, bool noScores = false) => ((DummyAPIAccess)API).HandleRequest = request =>
{
// pre-check for requests we should be handling (as they are scheduled below).
switch (request)
@@ -286,7 +283,7 @@ namespace osu.Game.Tests.Visual.Playlists
req.TriggerFailure(new WebException("Failed."));
}
- private MultiplayerScore createUserResponse([NotNull] ScoreInfo userScore)
+ private MultiplayerScore createUserResponse(ScoreInfo userScore)
{
var multiplayerUserScore = new MultiplayerScore
{
@@ -420,7 +417,7 @@ namespace osu.Game.Tests.Visual.Playlists
public new LoadingSpinner RightSpinner => base.RightSpinner;
public new ScorePanelList ScorePanelList => base.ScorePanelList;
- public TestResultsScreen([CanBeNull] ScoreInfo score, int roomId, PlaylistItem playlistItem)
+ public TestResultsScreen(ScoreInfo? score, int roomId, PlaylistItem playlistItem)
: base(score, roomId, playlistItem)
{
AllowRetry = true;
diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs
index 1636a3d4b8..0270840597 100644
--- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs
+++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs
@@ -1,12 +1,9 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using System.Diagnostics;
using System.Linq;
-using JetBrains.Annotations;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
@@ -35,11 +32,9 @@ namespace osu.Game.Tests.Visual.Playlists
{
public partial class TestScenePlaylistsRoomCreation : OnlinePlayTestScene
{
- private BeatmapManager manager;
-
- private TestPlaylistsRoomSubScreen match;
-
- private BeatmapSetInfo importedBeatmap;
+ private BeatmapManager manager = null!;
+ private TestPlaylistsRoomSubScreen match = null!;
+ private BeatmapSetInfo importedBeatmap = null!;
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
@@ -52,11 +47,11 @@ namespace osu.Game.Tests.Visual.Playlists
[SetUpSteps]
public void SetupSteps()
{
- AddStep("set room", () => SelectedRoom!.Value = new Room());
+ AddStep("set room", () => SelectedRoom.Value = new Room());
importBeatmap();
- AddStep("load match", () => LoadScreen(match = new TestPlaylistsRoomSubScreen(SelectedRoom!.Value)));
+ AddStep("load match", () => LoadScreen(match = new TestPlaylistsRoomSubScreen(SelectedRoom.Value!)));
AddUntilStep("wait for load", () => match.IsCurrentScreen());
}
@@ -65,14 +60,17 @@ namespace osu.Game.Tests.Visual.Playlists
{
setupAndCreateRoom(room =>
{
- room.Name.Value = "my awesome room";
- room.Host.Value = API.LocalUser.Value;
- room.RecentParticipants.Add(room.Host.Value);
- room.EndDate.Value = DateTimeOffset.Now.AddMinutes(5);
- room.Playlist.Add(new PlaylistItem(importedBeatmap.Beatmaps.First())
- {
- RulesetID = new OsuRuleset().RulesetInfo.OnlineID
- });
+ room.Name = "my awesome room";
+ room.Host = API.LocalUser.Value;
+ room.RecentParticipants = [room.Host];
+ room.EndDate = DateTimeOffset.Now.AddMinutes(5);
+ room.Playlist =
+ [
+ new PlaylistItem(importedBeatmap.Beatmaps.First())
+ {
+ RulesetID = new OsuRuleset().RulesetInfo.OnlineID
+ }
+ ];
});
AddUntilStep("Progress details are hidden", () => match.ChildrenOfType().FirstOrDefault()?.Parent!.Alpha == 0);
@@ -88,15 +86,18 @@ namespace osu.Game.Tests.Visual.Playlists
{
setupAndCreateRoom(room =>
{
- room.Name.Value = "my awesome room";
- room.MaxAttempts.Value = 5;
- room.Host.Value = API.LocalUser.Value;
- room.RecentParticipants.Add(room.Host.Value);
- room.EndDate.Value = DateTimeOffset.Now.AddMinutes(5);
- room.Playlist.Add(new PlaylistItem(importedBeatmap.Beatmaps.First())
- {
- RulesetID = new OsuRuleset().RulesetInfo.OnlineID
- });
+ room.Name = "my awesome room";
+ room.MaxAttempts = 5;
+ room.Host = API.LocalUser.Value;
+ room.RecentParticipants = [room.Host];
+ room.EndDate = DateTimeOffset.Now.AddMinutes(5);
+ room.Playlist =
+ [
+ new PlaylistItem(importedBeatmap.Beatmaps.First())
+ {
+ RulesetID = new OsuRuleset().RulesetInfo.OnlineID
+ }
+ ];
});
AddUntilStep("Progress details are visible", () => match.ChildrenOfType().FirstOrDefault()?.Parent!.Alpha == 1);
@@ -107,21 +108,24 @@ namespace osu.Game.Tests.Visual.Playlists
{
setupAndCreateRoom(room =>
{
- room.Name.Value = "my awesome room";
- room.Host.Value = API.LocalUser.Value;
- room.Playlist.Add(new PlaylistItem(importedBeatmap.Beatmaps.First())
- {
- RulesetID = new OsuRuleset().RulesetInfo.OnlineID
- });
+ room.Name = "my awesome room";
+ room.Host = API.LocalUser.Value;
+ room.Playlist =
+ [
+ new PlaylistItem(importedBeatmap.Beatmaps.First())
+ {
+ RulesetID = new OsuRuleset().RulesetInfo.OnlineID
+ }
+ ];
});
- AddAssert("first playlist item selected", () => match.SelectedItem.Value == SelectedRoom!.Value.Playlist[0]);
+ AddAssert("first playlist item selected", () => match.SelectedItem.Value == SelectedRoom.Value!.Playlist[0]);
}
[Test]
public void TestBeatmapUpdatedOnReImport()
{
- string realHash = null;
+ string realHash = null!;
int realOnlineId = 0;
int realOnlineSetId = 0;
@@ -139,40 +143,40 @@ namespace osu.Game.Tests.Visual.Playlists
BeatmapInfo =
{
OnlineID = realOnlineId,
- Metadata = new BeatmapMetadata(),
- BeatmapSet =
- {
- OnlineID = realOnlineSetId
- }
+ Metadata = new BeatmapMetadata()
},
};
+ Debug.Assert(modifiedBeatmap.BeatmapInfo.BeatmapSet != null);
+ modifiedBeatmap.BeatmapInfo.BeatmapSet!.OnlineID = realOnlineSetId;
+
modifiedBeatmap.HitObjects.Clear();
modifiedBeatmap.HitObjects.Add(new HitCircle { StartTime = 5000 });
- Debug.Assert(modifiedBeatmap.BeatmapInfo.BeatmapSet != null);
-
manager.Import(modifiedBeatmap.BeatmapInfo.BeatmapSet);
});
// Create the room using the real beatmap values.
setupAndCreateRoom(room =>
{
- room.Name.Value = "my awesome room";
- room.Host.Value = API.LocalUser.Value;
- room.Playlist.Add(new PlaylistItem(new BeatmapInfo
- {
- MD5Hash = realHash,
- OnlineID = realOnlineId,
- Metadata = new BeatmapMetadata(),
- BeatmapSet = new BeatmapSetInfo
+ room.Name = "my awesome room";
+ room.Host = API.LocalUser.Value;
+ room.Playlist =
+ [
+ new PlaylistItem(new BeatmapInfo
{
- OnlineID = realOnlineSetId,
+ MD5Hash = realHash,
+ OnlineID = realOnlineId,
+ Metadata = new BeatmapMetadata(),
+ BeatmapSet = new BeatmapSetInfo
+ {
+ OnlineID = realOnlineSetId,
+ }
+ })
+ {
+ RulesetID = new OsuRuleset().RulesetInfo.OnlineID
}
- })
- {
- RulesetID = new OsuRuleset().RulesetInfo.OnlineID
- });
+ ];
});
AddAssert("match has default beatmap", () => match.Beatmap.IsDefault);
@@ -181,17 +185,11 @@ namespace osu.Game.Tests.Visual.Playlists
{
var originalBeatmap = new TestBeatmap(new OsuRuleset().RulesetInfo)
{
- BeatmapInfo =
- {
- OnlineID = realOnlineId,
- BeatmapSet =
- {
- OnlineID = realOnlineSetId
- }
- },
+ BeatmapInfo = { OnlineID = realOnlineId },
};
Debug.Assert(originalBeatmap.BeatmapInfo.BeatmapSet != null);
+ originalBeatmap.BeatmapInfo.BeatmapSet.OnlineID = realOnlineSetId;
manager.Import(originalBeatmap.BeatmapInfo.BeatmapSet);
});
@@ -201,7 +199,7 @@ namespace osu.Game.Tests.Visual.Playlists
private void setupAndCreateRoom(Action room)
{
- AddStep("setup room", () => room(SelectedRoom!.Value));
+ AddStep("setup room", () => room(SelectedRoom.Value!));
AddStep("click create button", () =>
{
@@ -215,19 +213,17 @@ namespace osu.Game.Tests.Visual.Playlists
var beatmap = CreateBeatmap(new OsuRuleset().RulesetInfo);
Debug.Assert(beatmap.BeatmapInfo.BeatmapSet != null);
-
- importedBeatmap = manager.Import(beatmap.BeatmapInfo.BeatmapSet)?.Value.Detach();
+ importedBeatmap = manager.Import(beatmap.BeatmapInfo.BeatmapSet)!.Value.Detach();
});
private partial class TestPlaylistsRoomSubScreen : PlaylistsRoomSubScreen
{
- public new Bindable SelectedItem => base.SelectedItem;
+ public new Bindable SelectedItem => base.SelectedItem;
public new Bindable Beatmap => base.Beatmap;
[Resolved(canBeNull: true)]
- [CanBeNull]
- private IDialogOverlay dialogOverlay { get; set; }
+ private IDialogOverlay? dialogOverlay { get; set; }
public TestPlaylistsRoomSubScreen(Room room)
: base(room)
diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs
new file mode 100644
index 0000000000..4306fc1e6a
--- /dev/null
+++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs
@@ -0,0 +1,41 @@
+// 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 NUnit.Framework;
+using osu.Framework.Screens;
+using osu.Game.Online.API.Requests.Responses;
+using osu.Game.Online.Rooms;
+using osu.Game.Online.Rooms.RoomStatuses;
+using osu.Game.Screens.OnlinePlay.Playlists;
+using osu.Game.Tests.Visual.OnlinePlay;
+
+namespace osu.Game.Tests.Visual.Playlists
+{
+ public partial class TestScenePlaylistsRoomSubScreen : OnlinePlayTestScene
+ {
+ protected new TestRoomManager RoomManager => (TestRoomManager)base.RoomManager;
+
+ [Test]
+ public void TestStatusUpdateOnEnter()
+ {
+ Room room = null!;
+ PlaylistsRoomSubScreen roomScreen = null!;
+
+ AddStep("create room", () =>
+ {
+ RoomManager.AddRoom(room = new Room
+ {
+ Name = @"Test Room",
+ Host = new APIUser { Username = @"Host" },
+ Category = RoomCategory.Normal,
+ EndDate = DateTimeOffset.Now.AddMinutes(-1)
+ });
+ });
+
+ AddStep("push screen", () => LoadScreen(roomScreen = new PlaylistsRoomSubScreen(room)));
+ AddUntilStep("wait for screen load", () => roomScreen.IsCurrentScreen());
+ AddAssert("status is still ended", () => roomScreen.Room.Status, Is.TypeOf);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs b/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs
index ffc7d88a34..b406ea369f 100644
--- a/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs
+++ b/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs
@@ -112,6 +112,6 @@ namespace osu.Game.Tests.Visual.Ranking
});
private void displayUpdate(UserStatistics before, UserStatistics after) =>
- AddStep("display update", () => overallRanking.StatisticsUpdate.Value = new UserStatisticsUpdate(new ScoreInfo(), before, after));
+ AddStep("display update", () => overallRanking.StatisticsUpdate.Value = new ScoreBasedUserStatisticsUpdate(new ScoreInfo(), before, after));
}
}
diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs
index f46f76cbb8..c12b9d29bc 100644
--- a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs
+++ b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs
@@ -91,12 +91,12 @@ namespace osu.Game.Tests.Visual.Ranking
UserStatisticsWatcher userStatisticsWatcher = null!;
ScoreInfo score = null!;
- AddStep("create user statistics watcher", () => Add(userStatisticsWatcher = new UserStatisticsWatcher()));
+ AddStep("create user statistics watcher", () => Add(userStatisticsWatcher = new UserStatisticsWatcher(new LocalUserStatisticsProvider())));
AddStep("set user statistics update", () =>
{
score = TestResources.CreateTestScoreInfo();
score.OnlineID = 1234;
- ((Bindable)userStatisticsWatcher.LatestUpdate).Value = new UserStatisticsUpdate(score,
+ ((Bindable)userStatisticsWatcher.LatestUpdate).Value = new ScoreBasedUserStatisticsUpdate(score,
new UserStatistics
{
Level = new UserStatistics.LevelInfo
@@ -157,7 +157,7 @@ namespace osu.Game.Tests.Visual.Ranking
Score = { Value = score },
DisplayedUserStatisticsUpdate =
{
- Value = new UserStatisticsUpdate(score, new UserStatistics
+ Value = new ScoreBasedUserStatisticsUpdate(score, new UserStatistics
{
Level = new UserStatistics.LevelInfo
{
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs
index 66862e1b78..bd5c43d242 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs
@@ -8,14 +8,14 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using NUnit.Framework;
-using osu.Framework.Allocation;
-using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Platform;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Database;
using osu.Game.Extensions;
using osu.Game.Online.API;
+using osu.Game.Online.API.Requests;
+using osu.Game.Online.API.Requests.Responses;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Mania;
@@ -28,25 +28,31 @@ namespace osu.Game.Tests.Visual.SongSelect
{
public partial class TestSceneBeatmapRecommendations : OsuGameTestScene
{
- [Resolved]
- private IRulesetStore rulesetStore { get; set; }
-
[SetUpSteps]
public override void SetUpSteps()
{
AddStep("populate ruleset statistics", () =>
{
- Dictionary rulesetStatistics = new Dictionary();
-
- rulesetStore.AvailableRulesets.Where(ruleset => ruleset.IsLegacyRuleset()).ForEach(rulesetInfo =>
+ ((DummyAPIAccess)API).HandleRequest = r =>
{
- rulesetStatistics[rulesetInfo.ShortName] = new UserStatistics
+ switch (r)
{
- PP = getNecessaryPP(rulesetInfo.OnlineID)
- };
- });
+ case GetUserRequest userRequest:
+ userRequest.TriggerSuccess(new APIUser
+ {
+ Id = 99,
+ Statistics = new UserStatistics
+ {
+ PP = getNecessaryPP(userRequest.Ruleset?.OnlineID ?? 0)
+ }
+ });
- API.LocalUser.Value.RulesetsStatistics = rulesetStatistics;
+ return true;
+
+ default:
+ return false;
+ }
+ };
});
decimal getNecessaryPP(int? rulesetID)
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapAttributeText.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapAttributeText.cs
index e3a6fca319..5acd6cb084 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapAttributeText.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapAttributeText.cs
@@ -73,10 +73,10 @@ namespace osu.Game.Tests.Visual.UserInterface
});
});
- [TestCase(BeatmapAttribute.CircleSize, "Circle Size: 1.00")]
- [TestCase(BeatmapAttribute.HPDrain, "HP Drain: 2.00")]
- [TestCase(BeatmapAttribute.Accuracy, "Accuracy: 3.00")]
- [TestCase(BeatmapAttribute.ApproachRate, "Approach Rate: 4.00")]
+ [TestCase(BeatmapAttribute.CircleSize, "Circle Size: 1")]
+ [TestCase(BeatmapAttribute.HPDrain, "HP Drain: 2")]
+ [TestCase(BeatmapAttribute.Accuracy, "Accuracy: 3")]
+ [TestCase(BeatmapAttribute.ApproachRate, "Approach Rate: 4")]
[TestCase(BeatmapAttribute.Title, "Title: _Title")]
[TestCase(BeatmapAttribute.Artist, "Artist: _Artist")]
[TestCase(BeatmapAttribute.Creator, "Creator: _Creator")]
@@ -121,15 +121,15 @@ namespace osu.Game.Tests.Visual.UserInterface
Difficulty =
{
ApproachRate = 10,
- CircleSize = 9
+ CircleSize = 9.5f
}
}
}));
- test(BeatmapAttribute.BPM, new OsuModDoubleTime(), "BPM: 100.00", "BPM: 150.00");
+ test(BeatmapAttribute.BPM, new OsuModDoubleTime(), "BPM: 100", "BPM: 150");
test(BeatmapAttribute.Length, new OsuModDoubleTime(), "Length: 00:30", "Length: 00:20");
- test(BeatmapAttribute.ApproachRate, new OsuModDoubleTime(), "Approach Rate: 10.00", "Approach Rate: 11.00");
- test(BeatmapAttribute.CircleSize, new OsuModHardRock(), "Circle Size: 9.00", "Circle Size: 10.00");
+ test(BeatmapAttribute.ApproachRate, new OsuModDoubleTime(), "Approach Rate: 10", "Approach Rate: 11");
+ test(BeatmapAttribute.CircleSize, new OsuModHardRock(), "Circle Size: 9.5", "Circle Size: 10");
void test(BeatmapAttribute attribute, Mod mod, string before, string after)
{
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs
index 4925facd8a..c091c089cf 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs
@@ -53,13 +53,13 @@ namespace osu.Game.Tests.Visual.UserInterface
beatmap.OnlineID = 1001;
getRoomRequest.TriggerSuccess(new Room
{
- RoomID = { Value = 1234 },
+ RoomID = 1234,
Playlist =
- {
+ [
new PlaylistItem(beatmap)
- },
- StartDate = { Value = DateTimeOffset.Now.AddMinutes(-5) },
- EndDate = { Value = DateTimeOffset.Now.AddSeconds(30) }
+ ],
+ StartDate = DateTimeOffset.Now.AddMinutes(-5),
+ EndDate = DateTimeOffset.Now.AddSeconds(30)
});
return true;
@@ -131,13 +131,13 @@ namespace osu.Game.Tests.Visual.UserInterface
beatmap.OnlineID = 1001;
getRoomRequest.TriggerSuccess(new Room
{
- RoomID = { Value = 1234 },
+ RoomID = 1234,
Playlist =
- {
+ [
new PlaylistItem(beatmap)
- },
- StartDate = { Value = DateTimeOffset.Now.AddMinutes(-50) },
- EndDate = { Value = DateTimeOffset.Now.AddSeconds(30) }
+ ],
+ StartDate = DateTimeOffset.Now.AddMinutes(-50),
+ EndDate = DateTimeOffset.Now.AddSeconds(30)
});
return true;
diff --git a/osu.Game/.idea/.idea.osu.dir/.idea/.name b/osu.Game/.idea/.idea.osu.dir/.idea/.name
new file mode 100644
index 0000000000..21cb4db60e
--- /dev/null
+++ b/osu.Game/.idea/.idea.osu.dir/.idea/.name
@@ -0,0 +1 @@
+osu
\ No newline at end of file
diff --git a/osu.Game/.idea/.idea.osu.dir/.idea/indexLayout.xml b/osu.Game/.idea/.idea.osu.dir/.idea/indexLayout.xml
new file mode 100644
index 0000000000..7b08163ceb
--- /dev/null
+++ b/osu.Game/.idea/.idea.osu.dir/.idea/indexLayout.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/osu.Game/.idea/.idea.osu.dir/.idea/vcs.xml b/osu.Game/.idea/.idea.osu.dir/.idea/vcs.xml
new file mode 100644
index 0000000000..6c0b863585
--- /dev/null
+++ b/osu.Game/.idea/.idea.osu.dir/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/osu.Game/.idea/.idea.osu.dir/.idea/workspace.xml b/osu.Game/.idea/.idea.osu.dir/.idea/workspace.xml
new file mode 100644
index 0000000000..4be7e05a9a
--- /dev/null
+++ b/osu.Game/.idea/.idea.osu.dir/.idea/workspace.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs
index f1463eb632..d94c09d40f 100644
--- a/osu.Game/Beatmaps/BeatmapInfo.cs
+++ b/osu.Game/Beatmaps/BeatmapInfo.cs
@@ -150,7 +150,7 @@ namespace osu.Game.Beatmaps
public bool EpilepsyWarning { get; set; }
- public bool SamplesMatchPlaybackRate { get; set; } = true;
+ public bool SamplesMatchPlaybackRate { get; set; }
///
/// The time at which this beatmap was last played by the local user.
@@ -181,7 +181,7 @@ namespace osu.Game.Beatmaps
public double? EditorTimestamp { get; set; }
[Ignored]
- public CountdownType Countdown { get; set; } = CountdownType.Normal;
+ public CountdownType Countdown { get; set; } = CountdownType.None;
///
/// The number of beats to move the countdown backwards (compared to its default location).
diff --git a/osu.Game/Beatmaps/DifficultyRecommender.cs b/osu.Game/Beatmaps/DifficultyRecommender.cs
index ec00756fd9..d132b86052 100644
--- a/osu.Game/Beatmaps/DifficultyRecommender.cs
+++ b/osu.Game/Beatmaps/DifficultyRecommender.cs
@@ -9,9 +9,11 @@ using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
+using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
-using osu.Game.Online.API;
+using osu.Game.Online;
using osu.Game.Rulesets;
+using osu.Game.Users;
namespace osu.Game.Beatmaps
{
@@ -21,18 +23,63 @@ namespace osu.Game.Beatmaps
///
public partial class DifficultyRecommender : Component
{
- [Resolved]
- private IAPIProvider api { get; set; }
+ private readonly LocalUserStatisticsProvider statisticsProvider;
[Resolved]
- private Bindable ruleset { get; set; }
+ private Bindable gameRuleset { get; set; }
+
+ [Resolved]
+ private RulesetStore rulesets { get; set; } = null!;
private readonly Dictionary recommendedDifficultyMapping = new Dictionary();
+ ///
+ /// Rulesets ordered descending by their respective recommended difficulties.
+ /// The currently selected ruleset will always be first.
+ ///
+ private IEnumerable orderedRulesets
+ {
+ get
+ {
+ if (LoadState < LoadState.Ready || gameRuleset.Value == null)
+ return Enumerable.Empty();
+
+ return recommendedDifficultyMapping
+ .OrderByDescending(pair => pair.Value)
+ .Select(pair => pair.Key)
+ .Where(r => !r.Equals(gameRuleset.Value.ShortName, StringComparison.Ordinal))
+ .Prepend(gameRuleset.Value.ShortName);
+ }
+ }
+
+ public DifficultyRecommender(LocalUserStatisticsProvider statisticsProvider)
+ {
+ this.statisticsProvider = statisticsProvider;
+ }
+
[BackgroundDependencyLoader]
private void load()
{
- api.LocalUser.BindValueChanged(_ => populateValues(), true);
+ foreach (var ruleset in rulesets.AvailableRulesets)
+ {
+ if (statisticsProvider.GetStatisticsFor(ruleset) is UserStatistics statistics)
+ updateMapping(ruleset, statistics);
+ }
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ statisticsProvider.StatisticsUpdated += onStatisticsUpdated;
+ }
+
+ private void onStatisticsUpdated(UserStatisticsUpdate update) => updateMapping(update.Ruleset, update.NewStatistics);
+
+ private void updateMapping(RulesetInfo ruleset, UserStatistics statistics)
+ {
+ // algorithm taken from https://github.com/ppy/osu-web/blob/e6e2825516449e3d0f3f5e1852c6bdd3428c3437/app/Models/User.php#L1505
+ recommendedDifficultyMapping[ruleset.ShortName] = Math.Pow((double)(statistics.PP ?? 0), 0.4) * 0.195;
}
///
@@ -64,35 +111,12 @@ namespace osu.Game.Beatmaps
return null;
}
- private void populateValues()
+ protected override void Dispose(bool isDisposing)
{
- if (api.LocalUser.Value.RulesetsStatistics == null)
- return;
+ if (statisticsProvider.IsNotNull())
+ statisticsProvider.StatisticsUpdated -= onStatisticsUpdated;
- foreach (var kvp in api.LocalUser.Value.RulesetsStatistics)
- {
- // algorithm taken from https://github.com/ppy/osu-web/blob/e6e2825516449e3d0f3f5e1852c6bdd3428c3437/app/Models/User.php#L1505
- recommendedDifficultyMapping[kvp.Key] = Math.Pow((double)(kvp.Value.PP ?? 0), 0.4) * 0.195;
- }
- }
-
- ///
- /// Rulesets ordered descending by their respective recommended difficulties.
- /// The currently selected ruleset will always be first.
- ///
- private IEnumerable orderedRulesets
- {
- get
- {
- if (LoadState < LoadState.Ready || ruleset.Value == null)
- return Enumerable.Empty();
-
- return recommendedDifficultyMapping
- .OrderByDescending(pair => pair.Value)
- .Select(pair => pair.Key)
- .Where(r => !r.Equals(ruleset.Value.ShortName, StringComparison.Ordinal))
- .Prepend(ruleset.Value.ShortName);
- }
+ base.Dispose(isDisposing);
}
}
}
diff --git a/osu.Game/Beatmaps/Drawables/BeatmapSetOnlineStatusPill.cs b/osu.Game/Beatmaps/Drawables/BeatmapSetOnlineStatusPill.cs
index f18355505a..599d1b380a 100644
--- a/osu.Game/Beatmaps/Drawables/BeatmapSetOnlineStatusPill.cs
+++ b/osu.Game/Beatmaps/Drawables/BeatmapSetOnlineStatusPill.cs
@@ -86,7 +86,7 @@ namespace osu.Game.Beatmaps.Drawables
};
Status = BeatmapOnlineStatus.None;
- TextPadding = new MarginPadding { Horizontal = 5, Bottom = 1 };
+ TextPadding = new MarginPadding { Horizontal = 4, Bottom = 1 };
}
protected override void LoadComplete()
diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs
index 25e42bcbf7..56103c1d6d 100644
--- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs
+++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs
@@ -20,9 +20,9 @@ namespace osu.Game.Beatmaps.Drawables.Cards
public abstract partial class BeatmapCard : OsuClickableContainer, IHasContextMenu
{
public const float TRANSITION_DURATION = 340;
- public const float CORNER_RADIUS = 10;
+ public const float CORNER_RADIUS = 8;
- protected const float WIDTH = 430;
+ protected const float WIDTH = 345;
public IBindable Expanded { get; }
diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs
index 2c2761ff0c..ebd0113379 100644
--- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs
+++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs
@@ -22,7 +22,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
protected override Drawable IdleContent => idleBottomContent;
protected override Drawable DownloadInProgressContent => downloadProgressBar;
- private const float height = 140;
+ private const float height = 112;
[Cached]
private readonly BeatmapCardContent content;
@@ -68,7 +68,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
Padding = new MarginPadding { Right = CORNER_RADIUS },
Child = leftIconArea = new FillFlowContainer
{
- Margin = new MarginPadding(5),
+ Margin = new MarginPadding(4),
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(1)
@@ -80,7 +80,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
Width = WIDTH - height + CORNER_RADIUS,
FavouriteState = { BindTarget = FavouriteState },
ButtonsCollapsedWidth = CORNER_RADIUS,
- ButtonsExpandedWidth = 30,
+ ButtonsExpandedWidth = 24,
Children = new Drawable[]
{
new FillFlowContainer
@@ -109,7 +109,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
new TruncatingSpriteText
{
Text = new RomanisableString(BeatmapSet.TitleUnicode, BeatmapSet.Title),
- Font = OsuFont.Default.With(size: 22.5f, weight: FontWeight.SemiBold),
+ Font = OsuFont.Default.With(size: 18f, weight: FontWeight.SemiBold),
RelativeSizeAxes = Axes.X,
},
titleBadgeArea = new FillFlowContainer
@@ -142,7 +142,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
new TruncatingSpriteText
{
Text = createArtistText(),
- Font = OsuFont.Default.With(size: 17.5f, weight: FontWeight.SemiBold),
+ Font = OsuFont.Default.With(size: 14f, weight: FontWeight.SemiBold),
RelativeSizeAxes = Axes.X,
},
Empty()
@@ -154,7 +154,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
RelativeSizeAxes = Axes.X,
Text = BeatmapSet.Source,
Shadow = false,
- Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold),
+ Font = OsuFont.GetFont(size: 11f, weight: FontWeight.SemiBold),
Colour = colourProvider.Content2
},
}
@@ -173,18 +173,18 @@ namespace osu.Game.Beatmaps.Drawables.Cards
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
- Spacing = new Vector2(0, 3),
+ Spacing = new Vector2(0, 2),
AlwaysPresent = true,
Children = new Drawable[]
{
new LinkFlowContainer(s =>
{
s.Shadow = false;
- s.Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold);
+ s.Font = OsuFont.GetFont(size: 11f, weight: FontWeight.SemiBold);
}).With(d =>
{
d.AutoSizeAxes = Axes.Both;
- d.Margin = new MarginPadding { Top = 2 };
+ d.Margin = new MarginPadding { Top = 1 };
d.AddText("mapped by ", t => t.Colour = colourProvider.Content2);
d.AddUserLink(BeatmapSet.Author);
}),
@@ -215,7 +215,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
downloadProgressBar = new BeatmapCardDownloadProgressBar
{
RelativeSizeAxes = Axes.X,
- Height = 6,
+ Height = 5,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
State = { BindTarget = DownloadTracker.State },
@@ -231,17 +231,17 @@ namespace osu.Game.Beatmaps.Drawables.Cards
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
- Padding = new MarginPadding { Horizontal = 10, Vertical = 13 },
+ Padding = new MarginPadding { Horizontal = 8, Vertical = 10 },
Child = new BeatmapCardDifficultyList(BeatmapSet)
};
c.Expanded.BindTarget = Expanded;
});
if (BeatmapSet.HasVideo)
- leftIconArea.Add(new VideoIconPill { IconSize = new Vector2(20) });
+ leftIconArea.Add(new VideoIconPill { IconSize = new Vector2(16) });
if (BeatmapSet.HasStoryboard)
- leftIconArea.Add(new StoryboardIconPill { IconSize = new Vector2(20) });
+ leftIconArea.Add(new StoryboardIconPill { IconSize = new Vector2(16) });
if (BeatmapSet.FeaturedInSpotlight)
{
@@ -249,7 +249,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
{
Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight,
- Margin = new MarginPadding { Left = 5 }
+ Margin = new MarginPadding { Left = 4 }
});
}
@@ -259,7 +259,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
{
Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight,
- Margin = new MarginPadding { Left = 5 }
+ Margin = new MarginPadding { Left = 4 }
});
}
@@ -269,7 +269,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
{
Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight,
- Margin = new MarginPadding { Left = 5 }
+ Margin = new MarginPadding { Left = 4 }
};
}
@@ -288,7 +288,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
{
BeatmapCardStatistic withMargin(BeatmapCardStatistic original)
{
- original.Margin = new MarginPadding { Right = 10 };
+ original.Margin = new MarginPadding { Right = 8 };
return original;
}
diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtraInfoRow.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtraInfoRow.cs
index 3a1b8f7e86..a11ef0f95c 100644
--- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtraInfoRow.cs
+++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtraInfoRow.cs
@@ -25,7 +25,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Horizontal,
- Spacing = new Vector2(4, 0),
+ Spacing = new Vector2(3, 0),
Children = new Drawable[]
{
new BeatmapSetOnlineStatusPill
@@ -33,13 +33,14 @@ namespace osu.Game.Beatmaps.Drawables.Cards
AutoSizeAxes = Axes.Both,
Status = beatmapSet.Status,
Anchor = Anchor.CentreLeft,
- Origin = Anchor.CentreLeft
+ Origin = Anchor.CentreLeft,
+ TextSize = 13f
},
new DifficultySpectrumDisplay(beatmapSet)
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
- DotSize = new Vector2(6, 12)
+ DotSize = new Vector2(5, 10)
}
}
};
diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNormal.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNormal.cs
index 46ab7ec5f6..724919f3bd 100644
--- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNormal.cs
+++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNormal.cs
@@ -23,7 +23,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
protected override Drawable IdleContent => idleBottomContent;
protected override Drawable DownloadInProgressContent => downloadProgressBar;
- public const float HEIGHT = 100;
+ public const float HEIGHT = 80;
[Cached]
private readonly BeatmapCardContent content;
@@ -69,7 +69,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
Padding = new MarginPadding { Right = CORNER_RADIUS },
Child = leftIconArea = new FillFlowContainer
{
- Margin = new MarginPadding(5),
+ Margin = new MarginPadding(4),
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(1)
@@ -81,7 +81,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
Width = WIDTH - HEIGHT + CORNER_RADIUS,
FavouriteState = { BindTarget = FavouriteState },
ButtonsCollapsedWidth = CORNER_RADIUS,
- ButtonsExpandedWidth = 30,
+ ButtonsExpandedWidth = 24,
Children = new Drawable[]
{
new FillFlowContainer
@@ -110,7 +110,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
new TruncatingSpriteText
{
Text = new RomanisableString(BeatmapSet.TitleUnicode, BeatmapSet.Title),
- Font = OsuFont.Default.With(size: 22.5f, weight: FontWeight.SemiBold),
+ Font = OsuFont.Default.With(size: 18f, weight: FontWeight.SemiBold),
RelativeSizeAxes = Axes.X,
},
titleBadgeArea = new FillFlowContainer
@@ -143,7 +143,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
new TruncatingSpriteText
{
Text = createArtistText(),
- Font = OsuFont.Default.With(size: 17.5f, weight: FontWeight.SemiBold),
+ Font = OsuFont.Default.With(size: 14f, weight: FontWeight.SemiBold),
RelativeSizeAxes = Axes.X,
},
Empty()
@@ -153,11 +153,11 @@ namespace osu.Game.Beatmaps.Drawables.Cards
new LinkFlowContainer(s =>
{
s.Shadow = false;
- s.Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold);
+ s.Font = OsuFont.GetFont(size: 11f, weight: FontWeight.SemiBold);
}).With(d =>
{
d.AutoSizeAxes = Axes.Both;
- d.Margin = new MarginPadding { Top = 2 };
+ d.Margin = new MarginPadding { Top = 1 };
d.AddText("mapped by ", t => t.Colour = colourProvider.Content2);
d.AddUserLink(BeatmapSet.Author);
}),
@@ -177,7 +177,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
- Spacing = new Vector2(0, 3),
+ Spacing = new Vector2(0, 2),
AlwaysPresent = true,
Children = new Drawable[]
{
@@ -186,7 +186,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Horizontal,
- Spacing = new Vector2(10, 0),
+ Spacing = new Vector2(8, 0),
Alpha = 0,
AlwaysPresent = true,
ChildrenEnumerable = createStatistics()
@@ -197,7 +197,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
downloadProgressBar = new BeatmapCardDownloadProgressBar
{
RelativeSizeAxes = Axes.X,
- Height = 6,
+ Height = 5,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
State = { BindTarget = DownloadTracker.State },
@@ -213,17 +213,17 @@ namespace osu.Game.Beatmaps.Drawables.Cards
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
- Padding = new MarginPadding { Horizontal = 10, Vertical = 13 },
+ Padding = new MarginPadding { Horizontal = 8, Vertical = 10 },
Child = new BeatmapCardDifficultyList(BeatmapSet)
};
c.Expanded.BindTarget = Expanded;
});
if (BeatmapSet.HasVideo)
- leftIconArea.Add(new VideoIconPill { IconSize = new Vector2(20) });
+ leftIconArea.Add(new VideoIconPill { IconSize = new Vector2(16) });
if (BeatmapSet.HasStoryboard)
- leftIconArea.Add(new StoryboardIconPill { IconSize = new Vector2(20) });
+ leftIconArea.Add(new StoryboardIconPill { IconSize = new Vector2(16) });
if (BeatmapSet.FeaturedInSpotlight)
{
@@ -231,7 +231,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
{
Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight,
- Margin = new MarginPadding { Left = 5 }
+ Margin = new MarginPadding { Left = 4 }
});
}
@@ -241,7 +241,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
{
Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight,
- Margin = new MarginPadding { Left = 5 }
+ Margin = new MarginPadding { Left = 4 }
});
}
@@ -251,7 +251,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
{
Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight,
- Margin = new MarginPadding { Left = 5 }
+ Margin = new MarginPadding { Left = 4 }
};
}
}
diff --git a/osu.Game/Beatmaps/Drawables/Cards/Statistics/BeatmapCardStatistic.cs b/osu.Game/Beatmaps/Drawables/Cards/Statistics/BeatmapCardStatistic.cs
index 6fd7142c05..ece52d0fa9 100644
--- a/osu.Game/Beatmaps/Drawables/Cards/Statistics/BeatmapCardStatistic.cs
+++ b/osu.Game/Beatmaps/Drawables/Cards/Statistics/BeatmapCardStatistic.cs
@@ -46,21 +46,21 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Statistics
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
- Spacing = new Vector2(5, 0),
+ Spacing = new Vector2(4, 0),
Children = new Drawable[]
{
spriteIcon = new SpriteIcon
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
- Size = new Vector2(10),
+ Size = new Vector2(8),
Margin = new MarginPadding { Top = 1 }
},
spriteText = new OsuSpriteText
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
- Font = OsuFont.Default.With(size: 14)
+ Font = OsuFont.Default.With(size: 11)
}
}
};
diff --git a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs
index 6f71fa90b8..be6ca43f4b 100644
--- a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs
+++ b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs
@@ -14,9 +14,17 @@ namespace osu.Game.Beatmaps.Drawables
///
public partial class UpdateableBeatmapBackgroundSprite : ModelBackedDrawable
{
- public readonly Bindable Beatmap = new Bindable();
+ public readonly Bindable Beatmap = new Bindable();
- protected override double LoadDelay => 500;
+ ///
+ /// Delay before the background is loaded while on-screen.
+ ///
+ public double BackgroundLoadDelay { get; set; } = 500;
+
+ ///
+ /// Delay before the background is unloaded while off-screen.
+ ///
+ public double BackgroundUnloadDelay { get; set; } = 10000;
[Resolved]
private BeatmapManager beatmaps { get; set; } = null!;
@@ -29,10 +37,9 @@ namespace osu.Game.Beatmaps.Drawables
this.beatmapSetCoverType = beatmapSetCoverType;
}
- ///
- /// Delay before the background is unloaded while off-screen.
- ///
- protected virtual double UnloadDelay => 10000;
+ protected override double LoadDelay => BackgroundLoadDelay;
+
+ protected virtual double UnloadDelay => BackgroundUnloadDelay;
protected override DelayedLoadWrapper CreateDelayedLoadWrapper(Func createContentFunc, double timeBeforeLoad) =>
new DelayedLoadUnloadWrapper(createContentFunc, timeBeforeLoad, UnloadDelay) { RelativeSizeAxes = Axes.Both };
diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
index 3d8c8a6e7a..4d7ac355e0 100644
--- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
@@ -192,7 +192,6 @@ namespace osu.Game.Beatmaps.Formats
private static void applyLegacyDefaults(BeatmapInfo beatmapInfo)
{
beatmapInfo.WidescreenStoryboard = false;
- beatmapInfo.SamplesMatchPlaybackRate = false;
}
protected override void ParseLine(Beatmap beatmap, Section section, string line)
diff --git a/osu.Game/Collections/DrawableCollectionList.cs b/osu.Game/Collections/DrawableCollectionList.cs
index 164ec558a4..85af1d383d 100644
--- a/osu.Game/Collections/DrawableCollectionList.cs
+++ b/osu.Game/Collections/DrawableCollectionList.cs
@@ -21,6 +21,12 @@ namespace osu.Game.Collections
///
public partial class DrawableCollectionList : OsuRearrangeableListContainer>
{
+ public new MarginPadding Padding
+ {
+ get => base.Padding;
+ set => base.Padding = value;
+ }
+
protected override ScrollContainer CreateScrollContainer() => scroll = new Scroll();
[Resolved]
@@ -34,6 +40,12 @@ namespace osu.Game.Collections
public IEnumerable OrderedItems => flow.FlowingChildren;
+ public string SearchTerm
+ {
+ get => flow.SearchTerm;
+ set => flow.SearchTerm = value;
+ }
+
protected override FillFlowContainer>> CreateListFillFlowContainer() => flow = new Flow
{
DragActive = { BindTarget = DragActive }
@@ -46,6 +58,26 @@ namespace osu.Game.Collections
realmSubscription = realm.RegisterForNotifications(r => r.All().OrderBy(c => c.Name), collectionsChanged);
}
+ ///
+ /// When non-null, signifies that a new collection was created and should be presented to the user.
+ ///
+ private Guid? lastCreated;
+
+ protected override void OnItemsChanged()
+ {
+ base.OnItemsChanged();
+
+ if (lastCreated != null)
+ {
+ var createdItem = flow.Children.SingleOrDefault(item => item.Model.Value.ID == lastCreated);
+
+ if (createdItem != null)
+ scroll.ScrollTo(createdItem);
+
+ lastCreated = null;
+ }
+ }
+
private void collectionsChanged(IRealmCollection collections, ChangeSet? changes)
{
if (changes == null)
@@ -60,7 +92,11 @@ namespace osu.Game.Collections
foreach (int i in changes.InsertedIndices)
Items.Insert(i, collections[i].ToLive(realm));
+ if (changes.InsertedIndices.Length == 1)
+ lastCreated = collections[changes.InsertedIndices[0]].ID;
+
foreach (int i in changes.NewModifiedIndices)
+
{
var updatedItem = collections[i];
@@ -104,8 +140,7 @@ namespace osu.Game.Collections
public Scroll()
{
- ScrollbarVisible = false;
- Padding = new MarginPadding(10);
+ ScrollbarOverlapsContent = false;
base.Content.Add(new FillFlowContainer
{
@@ -133,7 +168,7 @@ namespace osu.Game.Collections
base.Update();
// AutoSizeAxes cannot be used as the height should represent the post-layout-transform height at all times, so that the placeholder doesn't bounce around.
- content.Height = ((Flow)Child).Children.Sum(c => c.DrawHeight + 5);
+ content.Height = ((Flow)Child).Children.Sum(c => c.IsPresent ? c.DrawHeight + 5 : 0);
}
///
@@ -179,7 +214,7 @@ namespace osu.Game.Collections
///
/// The flow of . Disables layout easing unless a drag is in progress.
///
- private partial class Flow : FillFlowContainer>>
+ private partial class Flow : SearchContainer>>
{
public readonly IBindable DragActive = new Bindable();
@@ -187,6 +222,8 @@ namespace osu.Game.Collections
{
Spacing = new Vector2(0, 5);
LayoutEasing = Easing.OutQuint;
+
+ Padding = new MarginPadding { Right = 5 };
}
protected override void LoadComplete()
diff --git a/osu.Game/Collections/DrawableCollectionListItem.cs b/osu.Game/Collections/DrawableCollectionListItem.cs
index f07ec87353..e86254329f 100644
--- a/osu.Game/Collections/DrawableCollectionListItem.cs
+++ b/osu.Game/Collections/DrawableCollectionListItem.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
@@ -10,6 +11,7 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
+using osu.Framework.Localisation;
using osu.Game.Database;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
@@ -23,7 +25,7 @@ namespace osu.Game.Collections
///
/// Visualises a inside a .
///
- public partial class DrawableCollectionListItem : OsuRearrangeableListItem>
+ public partial class DrawableCollectionListItem : OsuRearrangeableListItem>, IFilterable
{
private const float item_height = 35;
private const float button_width = item_height * 0.75f;
@@ -207,5 +209,25 @@ namespace osu.Game.Collections
private void deleteCollection() => collection.PerformWrite(c => c.Realm!.Remove(c));
}
+
+ public IEnumerable FilterTerms => [(LocalisableString)Model.Value.Name];
+
+ private bool matchingFilter = true;
+
+ public bool MatchingFilter
+ {
+ get => matchingFilter;
+ set
+ {
+ matchingFilter = value;
+
+ if (matchingFilter)
+ this.FadeIn(200);
+ else
+ Hide();
+ }
+ }
+
+ public bool FilteringActive { get; set; }
}
}
diff --git a/osu.Game/Collections/ManageCollectionsDialog.cs b/osu.Game/Collections/ManageCollectionsDialog.cs
index 9f8158af53..a738ae66cb 100644
--- a/osu.Game/Collections/ManageCollectionsDialog.cs
+++ b/osu.Game/Collections/ManageCollectionsDialog.cs
@@ -12,6 +12,7 @@ using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays;
+using osu.Game.Resources.Localisation.Web;
using osuTK;
namespace osu.Game.Collections
@@ -26,6 +27,9 @@ namespace osu.Game.Collections
private IDisposable? duckOperation;
+ private BasicSearchTextBox searchTextBox = null!;
+ private DrawableCollectionList list = null!;
+
[Resolved]
private MusicController? musicController { get; set; }
@@ -104,10 +108,31 @@ namespace osu.Game.Collections
RelativeSizeAxes = Axes.Both,
Colour = colours.GreySeaFoamDarker
},
- new DrawableCollectionList
+ new Container
{
RelativeSizeAxes = Axes.Both,
- }
+ Padding = new MarginPadding(10),
+ Children = new Drawable[]
+ {
+ searchTextBox = new BasicSearchTextBox
+ {
+ RelativeSizeAxes = Axes.X,
+ Y = 10,
+ Height = 40,
+ ReleaseFocusOnCommit = false,
+ HoldFocus = true,
+ PlaceholderText = HomeStrings.SearchPlaceholder,
+ },
+ list = new DrawableCollectionList
+ {
+ Padding = new MarginPadding
+ {
+ Top = 60,
+ },
+ RelativeSizeAxes = Axes.Both,
+ }
+ }
+ },
}
}
},
@@ -117,6 +142,16 @@ namespace osu.Game.Collections
};
}
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ searchTextBox.Current.BindValueChanged(_ =>
+ {
+ list.SearchTerm = searchTextBox.Current.Value;
+ });
+ }
+
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs
index af6fd61a3d..33d99e9b0f 100644
--- a/osu.Game/Configuration/OsuConfigManager.cs
+++ b/osu.Game/Configuration/OsuConfigManager.cs
@@ -54,7 +54,7 @@ namespace osu.Game.Configuration
SetDefault(OsuSetting.ModSelectHotkeyStyle, ModSelectHotkeyStyle.Sequential);
SetDefault(OsuSetting.ModSelectTextSearchStartsActive, true);
- SetDefault(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2f, 1f);
+ SetDefault(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2f, 1f, 0.01f);
SetDefault(OsuSetting.BeatmapListingCardSize, BeatmapCardSize.Normal);
@@ -132,7 +132,7 @@ namespace osu.Game.Configuration
SetDefault(OsuSetting.Prefer24HourTime, !CultureInfoHelper.SystemCulture.DateTimeFormat.ShortTimePattern.Contains(@"tt"));
// Gameplay
- SetDefault(OsuSetting.PositionalHitsoundsLevel, 0.2f, 0, 1);
+ SetDefault(OsuSetting.PositionalHitsoundsLevel, 0.2f, 0, 1, 0.01f);
SetDefault(OsuSetting.DimLevel, 0.7, 0, 1, 0.01);
SetDefault(OsuSetting.BlurLevel, 0, 0, 1, 0.01);
SetDefault(OsuSetting.LightenDuringBreaks, true);
@@ -169,13 +169,13 @@ namespace osu.Game.Configuration
SetDefault(OsuSetting.Scaling, ScalingMode.Off);
SetDefault(OsuSetting.SafeAreaConsiderations, true);
- SetDefault(OsuSetting.ScalingBackgroundDim, 0.9f, 0.5f, 1f);
+ SetDefault(OsuSetting.ScalingBackgroundDim, 0.9f, 0.5f, 1f, 0.01f);
- SetDefault(OsuSetting.ScalingSizeX, 0.8f, 0.2f, 1f);
- SetDefault(OsuSetting.ScalingSizeY, 0.8f, 0.2f, 1f);
+ SetDefault(OsuSetting.ScalingSizeX, 0.8f, 0.2f, 1f, 0.01f);
+ SetDefault(OsuSetting.ScalingSizeY, 0.8f, 0.2f, 1f, 0.01f);
- SetDefault(OsuSetting.ScalingPositionX, 0.5f, 0f, 1f);
- SetDefault(OsuSetting.ScalingPositionY, 0.5f, 0f, 1f);
+ SetDefault(OsuSetting.ScalingPositionX, 0.5f, 0f, 1f, 0.01f);
+ SetDefault(OsuSetting.ScalingPositionY, 0.5f, 0f, 1f, 0.01f);
SetDefault(OsuSetting.UIScale, 1f, 0.8f, 1.6f, 0.01f);
@@ -214,6 +214,7 @@ namespace osu.Game.Configuration
SetDefault(OsuSetting.EditorContractSidebars, false);
SetDefault(OsuSetting.AlwaysShowHoldForMenuButton, false);
+ SetDefault(OsuSetting.AlwaysRequireHoldingForPause, false);
}
protected override bool CheckLookupContainsPrivateInformation(OsuSetting lookup)
@@ -444,5 +445,6 @@ namespace osu.Game.Configuration
EditorRotationOrigin,
EditorTimelineShowBreaks,
EditorAdjustExistingObjectsOnTimingChanges,
+ AlwaysRequireHoldingForPause
}
}
diff --git a/osu.Game/Database/ModelDownloader.cs b/osu.Game/Database/ModelDownloader.cs
index 8aece748a8..dfeec259fe 100644
--- a/osu.Game/Database/ModelDownloader.cs
+++ b/osu.Game/Database/ModelDownloader.cs
@@ -68,18 +68,23 @@ namespace osu.Game.Database
{
Task.Factory.StartNew(async () =>
{
- bool importSuccessful;
+ bool importSuccessful = false;
- if (originalModel != null)
- importSuccessful = (await importer.ImportAsUpdate(notification, new ImportTask(filename), originalModel).ConfigureAwait(false)) != null;
- else
- importSuccessful = (await importer.Import(notification, new[] { new ImportTask(filename) }).ConfigureAwait(false)).Any();
+ try
+ {
+ if (originalModel != null)
+ importSuccessful = (await importer.ImportAsUpdate(notification, new ImportTask(filename), originalModel).ConfigureAwait(false)) != null;
+ else
+ importSuccessful = (await importer.Import(notification, new[] { new ImportTask(filename) }).ConfigureAwait(false)).Any();
+ }
+ finally
+ {
+ // for now a failed import will be marked as a failed download for simplicity.
+ if (!importSuccessful)
+ DownloadFailed?.Invoke(request);
- // for now a failed import will be marked as a failed download for simplicity.
- if (!importSuccessful)
- DownloadFailed?.Invoke(request);
-
- CurrentDownloads.Remove(request);
+ CurrentDownloads.Remove(request);
+ }
}, TaskCreationOptions.LongRunning);
};
diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs
index 75462797f8..e538530b79 100644
--- a/osu.Game/Database/RealmArchiveModelImporter.cs
+++ b/osu.Game/Database/RealmArchiveModelImporter.cs
@@ -105,7 +105,6 @@ namespace osu.Game.Database
}
notification.Progress = 0;
- notification.Text = $"{HumanisedModelName.Humanize(LetterCasing.Title)} import is initialising...";
int current = 0;
@@ -113,65 +112,78 @@ namespace osu.Game.Database
parameters.Batch |= tasks.Length >= minimum_items_considered_batch_import;
- await Task.WhenAll(tasks.Select(async task =>
+ notification.Text = $"{HumanisedModelName.Humanize(LetterCasing.Title)} import is initialising...";
+ notification.State = ProgressNotificationState.Active;
+
+ await pauseIfNecessaryAsync(parameters, notification, notification.CancellationToken).ConfigureAwait(false);
+
+ try
{
- if (notification.CancellationToken.IsCancellationRequested)
- return;
-
- try
+ await Parallel.ForEachAsync(tasks, notification.CancellationToken, async (task, cancellation) =>
{
- var model = await Import(task, parameters, notification.CancellationToken).ConfigureAwait(false);
+ cancellation.ThrowIfCancellationRequested();
- lock (imported)
+ try
{
- if (model != null)
- imported.Add(model);
- current++;
+ await pauseIfNecessaryAsync(parameters, notification, cancellation).ConfigureAwait(false);
- notification.Text = $"Imported {current} of {tasks.Length} {HumanisedModelName}s";
- notification.Progress = (float)current / tasks.Length;
+ var model = await Import(task, parameters, cancellation).ConfigureAwait(false);
+
+ lock (imported)
+ {
+ if (model != null)
+ imported.Add(model);
+ current++;
+
+ notification.Text = $"Imported {current} of {tasks.Length} {HumanisedModelName}s";
+ notification.Progress = (float)current / tasks.Length;
+ }
+ }
+ catch (OperationCanceledException)
+ {
+ throw;
+ }
+ catch (Exception e)
+ {
+ Logger.Error(e, $@"Could not import ({task})", LoggingTarget.Database);
+ }
+ }).ConfigureAwait(false);
+ }
+ finally
+ {
+ if (imported.Count == 0)
+ {
+ if (notification.CancellationToken.IsCancellationRequested)
+ {
+ notification.State = ProgressNotificationState.Cancelled;
+ }
+ else
+ {
+ notification.Text = $"{HumanisedModelName.Humanize(LetterCasing.Title)} import failed! Check logs for more information.";
+ notification.State = ProgressNotificationState.Cancelled;
}
}
- catch (OperationCanceledException)
- {
- }
- catch (Exception e)
- {
- Logger.Error(e, $@"Could not import ({task})", LoggingTarget.Database);
- }
- })).ConfigureAwait(false);
-
- if (imported.Count == 0)
- {
- if (notification.CancellationToken.IsCancellationRequested)
- {
- notification.State = ProgressNotificationState.Cancelled;
- return imported;
- }
-
- notification.Text = $"{HumanisedModelName.Humanize(LetterCasing.Title)} import failed! Check logs for more information.";
- notification.State = ProgressNotificationState.Cancelled;
- }
- else
- {
- if (tasks.Length > imported.Count)
- notification.CompletionText = $"Imported {imported.Count} of {tasks.Length} {HumanisedModelName}s.";
- else if (imported.Count > 1)
- notification.CompletionText = $"Imported {imported.Count} {HumanisedModelName}s!";
else
- notification.CompletionText = $"Imported {imported.First().GetDisplayString()}!";
-
- if (imported.Count > 0 && PresentImport != null)
{
- notification.CompletionText += " Click to view.";
- notification.CompletionClickAction = () =>
- {
- PresentImport?.Invoke(imported);
- return true;
- };
- }
+ if (tasks.Length > imported.Count)
+ notification.CompletionText = $"Imported {imported.Count} of {tasks.Length} {HumanisedModelName}s.";
+ else if (imported.Count > 1)
+ notification.CompletionText = $"Imported {imported.Count} {HumanisedModelName}s!";
+ else
+ notification.CompletionText = $"Imported {imported.First().GetDisplayString()}!";
- notification.State = ProgressNotificationState.Completed;
+ if (imported.Count > 0 && PresentImport != null)
+ {
+ notification.CompletionText += " Click to view.";
+ notification.CompletionClickAction = () =>
+ {
+ PresentImport?.Invoke(imported);
+ return true;
+ };
+ }
+
+ notification.State = ProgressNotificationState.Completed;
+ }
}
return imported;
@@ -286,8 +298,6 @@ namespace osu.Game.Database
/// An optional cancellation token.
public virtual Live? ImportModel(TModel item, ArchiveReader? archive = null, ImportParameters parameters = default, CancellationToken cancellationToken = default) => Realm.Run(realm =>
{
- pauseIfNecessary(parameters, cancellationToken);
-
TModel? existing;
if (parameters.Batch && archive != null)
@@ -528,7 +538,8 @@ namespace osu.Game.Database
/// The new model proposed for import.
/// The current realm context.
/// An existing model which matches the criteria to skip importing, else null.
- protected TModel? CheckForExisting(TModel model, Realm realm) => string.IsNullOrEmpty(model.Hash) ? null : realm.All().OrderBy(b => b.DeletePending).FirstOrDefault(b => b.Hash == model.Hash);
+ protected TModel? CheckForExisting(TModel model, Realm realm) =>
+ string.IsNullOrEmpty(model.Hash) ? null : realm.All().OrderBy(b => b.DeletePending).FirstOrDefault(b => b.Hash == model.Hash);
///
/// Whether import can be skipped after finding an existing import early in the process.
@@ -575,21 +586,29 @@ namespace osu.Game.Database
/// Whether to perform deletion.
protected virtual bool ShouldDeleteArchive(string path) => false;
- private void pauseIfNecessary(ImportParameters importParameters, CancellationToken cancellationToken)
+ private async Task pauseIfNecessaryAsync(ImportParameters importParameters, ProgressNotification notification, CancellationToken cancellationToken)
{
if (!PauseImports || importParameters.ImportImmediately)
return;
Logger.Log($@"{GetType().Name} is being paused.");
+ // A paused state could obviously be entered mid-import (during the `Task.WhenAll` below),
+ // but in order to keep things simple let's focus on the most common scenario.
+ notification.Text = $"{HumanisedModelName.Humanize(LetterCasing.Title)} import is paused due to gameplay...";
+ notification.State = ProgressNotificationState.Queued;
+
while (PauseImports)
{
cancellationToken.ThrowIfCancellationRequested();
- Thread.Sleep(500);
+ await Task.Delay(500, cancellationToken).ConfigureAwait(false);
}
cancellationToken.ThrowIfCancellationRequested();
Logger.Log($@"{GetType().Name} is being resumed.");
+
+ notification.Text = $"{HumanisedModelName.Humanize(LetterCasing.Title)} import is resuming...";
+ notification.State = ProgressNotificationState.Active;
}
private IEnumerable getIDs(IEnumerable files)
diff --git a/osu.Game/Graphics/Containers/OsuScrollContainer.cs b/osu.Game/Graphics/Containers/OsuScrollContainer.cs
index ffd28957ef..a3cd5a4902 100644
--- a/osu.Game/Graphics/Containers/OsuScrollContainer.cs
+++ b/osu.Game/Graphics/Containers/OsuScrollContainer.cs
@@ -42,8 +42,6 @@ namespace osu.Game.Graphics.Containers
///
public double DistanceDecayOnRightMouseScrollbar = 0.02;
- private bool shouldPerformRightMouseScroll(MouseButtonEvent e) => RightMouseScrollbar && e.Button == MouseButton.Right;
-
private bool rightMouseDragging;
protected override bool IsDragging => base.IsDragging || rightMouseDragging;
@@ -126,8 +124,15 @@ namespace osu.Game.Graphics.Containers
return base.OnScroll(e);
}
- protected virtual void ScrollFromMouseEvent(MouseEvent e) =>
- ScrollTo(Clamp(ToLocalSpace(e.ScreenSpaceMousePosition)[ScrollDim] / DrawSize[ScrollDim] * Content.DrawSize[ScrollDim]), true, DistanceDecayOnRightMouseScrollbar);
+ protected virtual void ScrollFromMouseEvent(MouseEvent e)
+ {
+ float fromScrollbarPosition = FromScrollbarPosition(ToLocalSpace(e.ScreenSpaceMousePosition)[ScrollDim]);
+ float scrollbarCentreOffset = FromScrollbarPosition(Scrollbar.DrawHeight) * 0.5f;
+
+ ScrollTo(Clamp(fromScrollbarPosition - scrollbarCentreOffset), true, DistanceDecayOnRightMouseScrollbar);
+ }
+
+ private bool shouldPerformRightMouseScroll(MouseButtonEvent e) => RightMouseScrollbar && e.Button == MouseButton.Right;
protected override ScrollbarContainer CreateScrollbar(Direction direction) => new OsuScrollbar(direction);
diff --git a/osu.Game/Localisation/GameplaySettingsStrings.cs b/osu.Game/Localisation/GameplaySettingsStrings.cs
index 6de61f7ebe..ff6a6102a7 100644
--- a/osu.Game/Localisation/GameplaySettingsStrings.cs
+++ b/osu.Game/Localisation/GameplaySettingsStrings.cs
@@ -89,6 +89,11 @@ namespace osu.Game.Localisation
///
public static LocalisableString AlwaysShowHoldForMenuButton => new TranslatableString(getKey(@"always_show_hold_for_menu_button"), @"Always show hold for menu button");
+ ///
+ /// "Require holding key to pause gameplay"
+ ///
+ public static LocalisableString AlwaysRequireHoldForMenu => new TranslatableString(getKey(@"require_holding_key_to_pause_gameplay"), @"Require holding key to pause gameplay");
+
///
/// "Always play first combo break sound"
///
diff --git a/osu.Game/Localisation/MenuTipStrings.cs b/osu.Game/Localisation/MenuTipStrings.cs
new file mode 100644
index 0000000000..f97ad5fa2c
--- /dev/null
+++ b/osu.Game/Localisation/MenuTipStrings.cs
@@ -0,0 +1,154 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Localisation;
+
+namespace osu.Game.Localisation
+{
+ public static class MenuTipStrings
+ {
+ private const string prefix = @"osu.Game.Resources.Localisation.MenuTip";
+
+ ///
+ /// "Press Ctrl-T anywhere in the game to toggle the toolbar!"
+ ///
+ public static LocalisableString ToggleToolbarShortcut => new TranslatableString(getKey(@"toggle_toolbar_shortcut"), @"Press Ctrl-T anywhere in the game to toggle the toolbar!");
+
+ ///
+ /// "Press Ctrl-O anywhere in the game to access settings!"
+ ///
+ public static LocalisableString GameSettingsShortcut => new TranslatableString(getKey(@"game_settings_shortcut"), @"Press Ctrl-O anywhere in the game to access settings!");
+
+ ///
+ /// "All settings are dynamic and take effect in real-time. Try changing the skin while watching autoplay!"
+ ///
+ public static LocalisableString DynamicSettings => new TranslatableString(getKey(@"dynamic_settings"), @"All settings are dynamic and take effect in real-time. Try changing the skin while watching autoplay!");
+
+ ///
+ /// "New features are coming online every update. Make sure to stay up-to-date!"
+ ///
+ public static LocalisableString NewFeaturesAreComingOnline => new TranslatableString(getKey(@"new_features_are_coming_online"), @"New features are coming online every update. Make sure to stay up-to-date!");
+
+ ///
+ /// "If you find the UI too large or small, try adjusting UI scale in settings!"
+ ///
+ public static LocalisableString UIScalingSettings => new TranslatableString(getKey(@"ui_scaling_settings"), @"If you find the UI too large or small, try adjusting UI scale in settings!");
+
+ ///
+ /// "Try adjusting the "Screen Scaling" mode to change your gameplay or UI area, even in fullscreen!"
+ ///
+ public static LocalisableString ScreenScalingSettings => new TranslatableString(getKey(@"screen_scaling_settings"), @"Try adjusting the ""Screen Scaling"" mode to change your gameplay or UI area, even in fullscreen!");
+
+ ///
+ /// "What used to be "osu!direct" is available to all users just like on the website. You can access it anywhere using Ctrl-B!"
+ ///
+ public static LocalisableString FreeOsuDirect => new TranslatableString(getKey(@"free_osu_direct"), @"What used to be ""osu!direct"" is available to all users just like on the website. You can access it anywhere using Ctrl-B!");
+
+ ///
+ /// "Seeking in replays is available by dragging on the progress bar at the bottom of the screen or by using the left and right arrow keys!"
+ ///
+ public static LocalisableString ReplaySeeking => new TranslatableString(getKey(@"replay_seeking"), @"Seeking in replays is available by dragging on the progress bar at the bottom of the screen or by using the left and right arrow keys!");
+
+ ///
+ /// "Try scrolling right in mod select to find a bunch of new fun mods!"
+ ///
+ public static LocalisableString TryNewMods => new TranslatableString(getKey(@"try_new_mods"), @"Try scrolling right in mod select to find a bunch of new fun mods!");
+
+ ///
+ /// "Most of the web content (profiles, rankings, etc.) are available natively in-game from the icons on the toolbar!"
+ ///
+ public static LocalisableString EmbeddedWebContent => new TranslatableString(getKey(@"embedded_web_content"), @"Most of the web content (profiles, rankings, etc.) are available natively in-game from the icons on the toolbar!");
+
+ ///
+ /// "Get more details, hide or delete a beatmap by right-clicking on its panel at song select!"
+ ///
+ public static LocalisableString BeatmapRightClick => new TranslatableString(getKey(@"beatmap_right_click"), @"Get more details, hide or delete a beatmap by right-clicking on its panel at song select!");
+
+ ///
+ /// "Check out the "playlists" system, which lets users create their own custom and permanent leaderboards!"
+ ///
+ public static LocalisableString DiscoverPlaylists => new TranslatableString(getKey(@"discover_playlists"), @"Check out the ""playlists"" system, which lets users create their own custom and permanent leaderboards!");
+
+ ///
+ /// "Toggle advanced frame / thread statistics with Ctrl-F11!"
+ ///
+ public static LocalisableString ToggleAdvancedFPSCounter => new TranslatableString(getKey(@"toggle_advanced_fps_counter"), @"Toggle advanced frame / thread statistics with Ctrl-F11!");
+
+ ///
+ /// "You can pause during a replay by pressing Space!"
+ ///
+ public static LocalisableString ReplayPausing => new TranslatableString(getKey(@"replay_pausing"), @"You can pause during a replay by pressing Space!");
+
+ ///
+ /// "Most of the hotkeys in the game are configurable and can be changed to anything you want. Check the bindings panel under input settings!"
+ ///
+ public static LocalisableString ConfigurableHotkeys => new TranslatableString(getKey(@"configurable_hotkeys"), @"Most of the hotkeys in the game are configurable and can be changed to anything you want. Check the bindings panel under input settings!");
+
+ ///
+ /// "Your gameplay HUD can be customised by using the skin layout editor. Open it at any time via Ctrl-Shift-S!"
+ ///
+ public static LocalisableString SkinEditor => new TranslatableString(getKey(@"skin_editor"), @"Your gameplay HUD can be customised by using the skin layout editor. Open it at any time via Ctrl-Shift-S!");
+
+ ///
+ /// "You can create mod presets to make toggling your favourite mod combinations easier!"
+ ///
+ public static LocalisableString ModPresets => new TranslatableString(getKey(@"mod_presets"), @"You can create mod presets to make toggling your favourite mod combinations easier!");
+
+ ///
+ /// "Many mods have customisation settings that drastically change how they function. Click the Customise button in mod select to view settings!"
+ ///
+ public static LocalisableString ModCustomisationSettings => new TranslatableString(getKey(@"mod_customisation_settings"), @"Many mods have customisation settings that drastically change how they function. Click the Customise button in mod select to view settings!");
+
+ ///
+ /// "Press Ctrl-Shift-R to switch to a random skin!"
+ ///
+ public static LocalisableString RandomSkinShortcut => new TranslatableString(getKey(@"random_skin_shortcut"), @"Press Ctrl-Shift-R to switch to a random skin!");
+
+ ///
+ /// "While watching a replay, press Ctrl-H to toggle replay settings!"
+ ///
+ public static LocalisableString ToggleReplaySettingsShortcut => new TranslatableString(getKey(@"toggle_replay_settings_shortcut"), @"While watching a replay, press Ctrl-H to toggle replay settings!");
+
+ ///
+ /// "You can easily copy the mods from scores on a leaderboard by right-clicking on them!"
+ ///
+ public static LocalisableString CopyModsFromScore => new TranslatableString(getKey(@"copy_mods_from_score"), @"You can easily copy the mods from scores on a leaderboard by right-clicking on them!");
+
+ ///
+ /// "Ctrl-Enter at song select will start a beatmap in autoplay mode!"
+ ///
+ public static LocalisableString AutoplayBeatmapShortcut => new TranslatableString(getKey(@"autoplay_beatmap_shortcut"), @"Ctrl-Enter at song select will start a beatmap in autoplay mode!");
+
+ ///
+ /// "Multithreading support means that even with low "FPS" your input and judgements will be accurate!"
+ ///
+ public static LocalisableString MultithreadingSupport => new TranslatableString(getKey(@"multithreading_support"), @"Multithreading support means that even with low ""FPS"" your input and judgements will be accurate!");
+
+ ///
+ /// "All delete operations are temporary until exiting. Restore accidentally deleted content from the maintenance settings!"
+ ///
+ public static LocalisableString TemporaryDeleteOperations => new TranslatableString(getKey(@"temporary_delete_operations"), @"All delete operations are temporary until exiting. Restore accidentally deleted content from the maintenance settings!");
+
+ ///
+ /// "Take a look under the hood at performance counters and enable verbose performance logging with Ctrl-F2!"
+ ///
+ public static LocalisableString GlobalStatisticsShortcut => new TranslatableString(getKey(@"global_statistics_shortcut"), @"Take a look under the hood at performance counters and enable verbose performance logging with Ctrl-F2!");
+
+ ///
+ /// "When your gameplay HUD is hidden, you can press and hold Ctrl to view it temporarily!"
+ ///
+ public static LocalisableString PeekHUDWhenHidden => new TranslatableString(getKey(@"peek_hud_when_hidden"), @"When your gameplay HUD is hidden, you can press and hold Ctrl to view it temporarily!");
+
+ ///
+ /// "Drag and drop any image into the skin editor to load it in quickly!"
+ ///
+ public static LocalisableString DragAndDropImageInSkinEditor => new TranslatableString(getKey(@"drag_and_drop_image_in_skin_editor"), @"Drag and drop any image into the skin editor to load it in quickly!");
+
+ ///
+ /// "a tip for you:"
+ ///
+ public static LocalisableString MenuTipTitle => new TranslatableString(getKey(@"menu_tip_title"), @"a tip for you:");
+
+ private static string getKey(string key) => $@"{prefix}:{key}";
+ }
+}
diff --git a/osu.Game/Localisation/RulesetSettingsStrings.cs b/osu.Game/Localisation/RulesetSettingsStrings.cs
index e3d51f1124..9434cd53de 100644
--- a/osu.Game/Localisation/RulesetSettingsStrings.cs
+++ b/osu.Game/Localisation/RulesetSettingsStrings.cs
@@ -80,9 +80,9 @@ namespace osu.Game.Localisation
public static LocalisableString TimingBasedColouring => new TranslatableString(getKey(@"Timing_based_colouring"), @"Timing-based note colouring");
///
- /// "{0}ms (speed {1})"
+ /// "{0}ms (speed {1:N1})"
///
- public static LocalisableString ScrollSpeedTooltip(int scrollTime, int scrollSpeed) => new TranslatableString(getKey(@"ruleset"), @"{0}ms (speed {1})", scrollTime, scrollSpeed);
+ public static LocalisableString ScrollSpeedTooltip(int scrollTime, double scrollSpeed) => new TranslatableString(getKey(@"ruleset"), @"{0}ms (speed {1:N1})", scrollTime, scrollSpeed);
///
/// "Touch control scheme"
diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs
index c8992c108e..ec48fa2436 100644
--- a/osu.Game/Online/API/APIAccess.cs
+++ b/osu.Game/Online/API/APIAccess.cs
@@ -59,7 +59,6 @@ namespace osu.Game.Online.API
public IBindable LocalUser => localUser;
public IBindableList Friends => friends;
public IBindable Activity => activity;
- public IBindable Statistics => statistics;
public INotificationsClient NotificationsClient { get; }
@@ -74,8 +73,6 @@ namespace osu.Game.Online.API
private Bindable configStatus { get; } = new Bindable();
private Bindable localUserStatus { get; } = new Bindable();
- private Bindable statistics { get; } = new Bindable();
-
protected bool HasLogin => authentication.Token.Value != null || (!string.IsNullOrEmpty(ProvidedUsername) && !string.IsNullOrEmpty(password));
private readonly CancellationTokenSource cancellationToken = new CancellationTokenSource();
@@ -604,14 +601,6 @@ namespace osu.Game.Online.API
flushQueue();
}
- public void UpdateStatistics(UserStatistics newStatistics)
- {
- statistics.Value = newStatistics;
-
- if (IsLoggedIn)
- localUser.Value.Statistics = newStatistics;
- }
-
public void UpdateLocalFriends()
{
if (!IsLoggedIn)
@@ -630,11 +619,7 @@ namespace osu.Game.Online.API
private static APIUser createGuestUser() => new GuestUser();
- private void setLocalUser(APIUser user) => Scheduler.Add(() =>
- {
- localUser.Value = user;
- statistics.Value = user.Statistics;
- }, false);
+ private void setLocalUser(APIUser user) => Scheduler.Add(() => localUser.Value = user, false);
protected override void Dispose(bool isDisposing)
{
diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs
index f0da0c25da..5d63c04925 100644
--- a/osu.Game/Online/API/DummyAPIAccess.cs
+++ b/osu.Game/Online/API/DummyAPIAccess.cs
@@ -30,8 +30,6 @@ namespace osu.Game.Online.API
public Bindable Activity { get; } = new Bindable();
- public Bindable Statistics { get; } = new Bindable();
-
public DummyNotificationsClient NotificationsClient { get; } = new DummyNotificationsClient();
INotificationsClient IAPIProvider.NotificationsClient => NotificationsClient;
@@ -178,11 +176,6 @@ namespace osu.Game.Online.API
private void onSuccessfulLogin()
{
state.Value = APIState.Online;
- Statistics.Value = new UserStatistics
- {
- GlobalRank = 1,
- CountryRank = 1
- };
}
public void Logout()
@@ -193,14 +186,6 @@ namespace osu.Game.Online.API
LocalUser.Value = new GuestUser();
}
- public void UpdateStatistics(UserStatistics newStatistics)
- {
- Statistics.Value = newStatistics;
-
- if (IsLoggedIn)
- LocalUser.Value.Statistics = newStatistics;
- }
-
public void UpdateLocalFriends()
{
}
@@ -220,7 +205,6 @@ namespace osu.Game.Online.API
IBindable IAPIProvider.LocalUser => LocalUser;
IBindableList IAPIProvider.Friends => Friends;
IBindable IAPIProvider.Activity => Activity;
- IBindable IAPIProvider.Statistics => Statistics;
///
/// Skip 2FA requirement for next login.
diff --git a/osu.Game/Online/API/IAPIProvider.cs b/osu.Game/Online/API/IAPIProvider.cs
index 4b1aed236d..1c4b2da742 100644
--- a/osu.Game/Online/API/IAPIProvider.cs
+++ b/osu.Game/Online/API/IAPIProvider.cs
@@ -29,11 +29,6 @@ namespace osu.Game.Online.API
///
IBindable Activity { get; }
- ///
- /// The current user's online statistics.
- ///
- IBindable Statistics { get; }
-
///
/// The language supplied by this provider to API requests.
///
@@ -129,11 +124,6 @@ namespace osu.Game.Online.API
///
void Logout();
- ///
- /// Sets Statistics bindable.
- ///
- void UpdateStatistics(UserStatistics newStatistics);
-
///
/// Update the friends status of the current user.
///
diff --git a/osu.Game/Online/API/Requests/Responses/APIUser.cs b/osu.Game/Online/API/Requests/Responses/APIUser.cs
index 5d80fde515..a829484506 100644
--- a/osu.Game/Online/API/Requests/Responses/APIUser.cs
+++ b/osu.Game/Online/API/Requests/Responses/APIUser.cs
@@ -223,8 +223,10 @@ namespace osu.Game.Online.API.Requests.Responses
///
/// User statistics for the requested ruleset (in the case of a or response).
- /// Otherwise empty.
///
+ ///
+ /// This returns null when accessed from . Use instead.
+ ///
[JsonProperty(@"statistics")]
public UserStatistics Statistics
{
diff --git a/osu.Game/Online/LocalUserStatisticsProvider.cs b/osu.Game/Online/LocalUserStatisticsProvider.cs
new file mode 100644
index 0000000000..22d5788c87
--- /dev/null
+++ b/osu.Game/Online/LocalUserStatisticsProvider.cs
@@ -0,0 +1,92 @@
+// 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 System.Collections.Generic;
+using System.Linq;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Game.Extensions;
+using osu.Game.Online.API;
+using osu.Game.Online.API.Requests;
+using osu.Game.Rulesets;
+using osu.Game.Users;
+
+namespace osu.Game.Online
+{
+ ///
+ /// A component that keeps track of the latest statistics for the local user.
+ ///
+ public partial class LocalUserStatisticsProvider : Component
+ {
+ ///
+ /// Invoked whenever a change occured to the statistics of any ruleset,
+ /// either due to change in local user (log out and log in) or as a result of score submission.
+ ///
+ ///
+ /// This does not guarantee the presence of the old statistics,
+ /// specifically in the case of initial population or change in local user.
+ ///
+ public event Action? StatisticsUpdated;
+
+ [Resolved]
+ private RulesetStore rulesets { get; set; } = null!;
+
+ [Resolved]
+ private IAPIProvider api { get; set; } = null!;
+
+ private readonly Dictionary statisticsCache = new Dictionary();
+
+ ///
+ /// Returns the currently available for the given ruleset.
+ /// This may return null if the requested statistics has not been fetched before yet.
+ ///
+ /// The ruleset to return the corresponding for.
+ public UserStatistics? GetStatisticsFor(RulesetInfo ruleset) => statisticsCache.GetValueOrDefault(ruleset.ShortName);
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ api.LocalUser.BindValueChanged(_ =>
+ {
+ // queuing up requests directly on user change is unsafe, as the API status may have not been updated yet.
+ // schedule a frame to allow the API to be in its correct state sending requests.
+ Schedule(initialiseStatistics);
+ }, true);
+ }
+
+ private void initialiseStatistics()
+ {
+ statisticsCache.Clear();
+
+ if (api.LocalUser.Value == null || api.LocalUser.Value.Id <= 1)
+ return;
+
+ foreach (var ruleset in rulesets.AvailableRulesets.Where(r => r.IsLegacyRuleset()))
+ RefetchStatistics(ruleset);
+ }
+
+ public void RefetchStatistics(RulesetInfo ruleset, Action? callback = null)
+ {
+ if (!ruleset.IsLegacyRuleset())
+ throw new InvalidOperationException($@"Retrieving statistics is not supported for ruleset {ruleset.ShortName}");
+
+ var request = new GetUserRequest(api.LocalUser.Value.Id, ruleset);
+ request.Success += u => UpdateStatistics(u.Statistics, ruleset, callback);
+ api.Queue(request);
+ }
+
+ protected void UpdateStatistics(UserStatistics newStatistics, RulesetInfo ruleset, Action? callback = null)
+ {
+ var oldStatistics = statisticsCache.GetValueOrDefault(ruleset.ShortName);
+ statisticsCache[ruleset.ShortName] = newStatistics;
+
+ var update = new UserStatisticsUpdate(ruleset, oldStatistics, newStatistics);
+ callback?.Invoke(update);
+ StatisticsUpdated?.Invoke(update);
+ }
+ }
+
+ public record UserStatisticsUpdate(RulesetInfo Ruleset, UserStatistics? OldStatistics, UserStatistics NewStatistics);
+}
diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs
index ff147aba10..998a34931d 100644
--- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs
+++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs
@@ -5,7 +5,6 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
-using System.Text;
using System.Threading;
using System.Threading.Tasks;
using osu.Framework.Allocation;
@@ -181,10 +180,10 @@ namespace osu.Game.Online.Multiplayer
await joinOrLeaveTaskChain.Add(async () =>
{
- Debug.Assert(room.RoomID.Value != null);
+ Debug.Assert(room.RoomID != null);
// Join the server-side room.
- var joinedRoom = await JoinRoom(room.RoomID.Value.Value, password ?? room.Password.Value).ConfigureAwait(false);
+ var joinedRoom = await JoinRoom(room.RoomID.Value, password ?? room.Password).ConfigureAwait(false);
Debug.Assert(joinedRoom != null);
// Populate users.
@@ -201,12 +200,11 @@ namespace osu.Game.Online.Multiplayer
Debug.Assert(joinedRoom.Playlist.Count > 0);
- APIRoom.Playlist.Clear();
- APIRoom.Playlist.AddRange(joinedRoom.Playlist.Select(item => new PlaylistItem(item)));
- APIRoom.CurrentPlaylistItem.Value = APIRoom.Playlist.Single(item => item.ID == joinedRoom.Settings.PlaylistItemId);
+ APIRoom.Playlist = joinedRoom.Playlist.Select(item => new PlaylistItem(item)).ToArray();
+ APIRoom.CurrentPlaylistItem = APIRoom.Playlist.Single(item => item.ID == joinedRoom.Settings.PlaylistItemId);
// The server will null out the end date upon the host joining the room, but the null value is never communicated to the client.
- APIRoom.EndDate.Value = null;
+ APIRoom.EndDate = null;
Debug.Assert(LocalUser != null);
addUserToAPIRoom(LocalUser);
@@ -397,15 +395,15 @@ namespace osu.Game.Online.Multiplayer
switch (state)
{
case MultiplayerRoomState.Open:
- APIRoom.Status.Value = APIRoom.HasPassword.Value ? new RoomStatusOpenPrivate() : new RoomStatusOpen();
+ APIRoom.Status = APIRoom.HasPassword ? new RoomStatusOpenPrivate() : new RoomStatusOpen();
break;
case MultiplayerRoomState.Playing:
- APIRoom.Status.Value = new RoomStatusPlaying();
+ APIRoom.Status = new RoomStatusPlaying();
break;
case MultiplayerRoomState.Closed:
- APIRoom.Status.Value = new RoomStatusEnded();
+ APIRoom.Status = new RoomStatusEnded();
break;
}
@@ -459,7 +457,7 @@ namespace osu.Game.Online.Multiplayer
if (apiUser == null || apiRoom == null) return;
PostNotification?.Invoke(
- new UserAvatarNotification(apiUser, NotificationsStrings.InvitedYouToTheMultiplayer(apiUser.Username, apiRoom.Name.Value))
+ new UserAvatarNotification(apiUser, NotificationsStrings.InvitedYouToTheMultiplayer(apiUser.Username, apiRoom.Name))
{
Activated = () =>
{
@@ -487,12 +485,12 @@ namespace osu.Game.Online.Multiplayer
{
Debug.Assert(APIRoom != null);
- APIRoom.RecentParticipants.Add(user.User ?? new APIUser
+ APIRoom.RecentParticipants = APIRoom.RecentParticipants.Append(user.User ?? new APIUser
{
Id = user.UserID,
Username = "[Unresolved]"
- });
- APIRoom.ParticipantCount.Value++;
+ }).ToArray();
+ APIRoom.ParticipantCount++;
}
private Task handleUserLeft(MultiplayerRoomUser user, Action? callback)
@@ -506,8 +504,8 @@ namespace osu.Game.Online.Multiplayer
PlayingUserIds.Remove(user.UserID);
Debug.Assert(APIRoom != null);
- APIRoom.RecentParticipants.RemoveAll(u => u.Id == user.UserID);
- APIRoom.ParticipantCount.Value--;
+ APIRoom.RecentParticipants = APIRoom.RecentParticipants.Where(u => u.Id != user.UserID).ToArray();
+ APIRoom.ParticipantCount--;
callback?.Invoke(user);
RoomUpdated?.Invoke();
@@ -528,7 +526,7 @@ namespace osu.Game.Online.Multiplayer
var user = Room.Users.FirstOrDefault(u => u.UserID == userId);
Room.Host = user;
- APIRoom.Host.Value = user?.User;
+ APIRoom.Host = user?.User;
RoomUpdated?.Invoke();
}, false);
@@ -734,7 +732,7 @@ namespace osu.Game.Online.Multiplayer
Debug.Assert(APIRoom != null);
Room.Playlist.Add(item);
- APIRoom.Playlist.Add(new PlaylistItem(item));
+ APIRoom.Playlist = APIRoom.Playlist.Append(new PlaylistItem(item)).ToArray();
ItemAdded?.Invoke(item);
RoomUpdated?.Invoke();
@@ -753,7 +751,7 @@ namespace osu.Game.Online.Multiplayer
Debug.Assert(APIRoom != null);
Room.Playlist.Remove(Room.Playlist.Single(existing => existing.ID == playlistItemId));
- APIRoom.Playlist.RemoveAll(existing => existing.ID == playlistItemId);
+ APIRoom.Playlist = APIRoom.Playlist.Where(i => i.ID != playlistItemId).ToArray();
Debug.Assert(Room.Playlist.Count > 0);
@@ -771,30 +769,10 @@ namespace osu.Game.Online.Multiplayer
if (Room == null)
return;
- try
- {
- Debug.Assert(APIRoom != null);
+ Debug.Assert(APIRoom != null);
- Room.Playlist[Room.Playlist.IndexOf(Room.Playlist.Single(existing => existing.ID == item.ID))] = item;
-
- int existingIndex = APIRoom.Playlist.IndexOf(APIRoom.Playlist.Single(existing => existing.ID == item.ID));
-
- APIRoom.Playlist.RemoveAt(existingIndex);
- APIRoom.Playlist.Insert(existingIndex, new PlaylistItem(item));
- }
- catch (Exception ex)
- {
- // Temporary code to attempt to figure out long-term failing tests.
- StringBuilder exceptionText = new StringBuilder();
-
- exceptionText.AppendLine("MultiplayerClient test failure investigation");
- exceptionText.AppendLine($"Exception : {ex.ToString()}");
- exceptionText.AppendLine($"Lookup : {item.ID}");
- exceptionText.AppendLine($"Items in Room.Playlist : {string.Join(',', Room.Playlist.Select(i => i.ID))}");
- exceptionText.AppendLine($"Items in APIRoom.Playlist: {string.Join(',', APIRoom!.Playlist.Select(i => i.ID))}");
-
- throw new AggregateException(exceptionText.ToString());
- }
+ Room.Playlist[Room.Playlist.IndexOf(Room.Playlist.Single(existing => existing.ID == item.ID))] = item;
+ APIRoom.Playlist = APIRoom.Playlist.Select((pi, i) => pi.ID == item.ID ? new PlaylistItem(item) : APIRoom.Playlist[i]).ToArray();
ItemChanged?.Invoke(item);
RoomUpdated?.Invoke();
@@ -841,14 +819,14 @@ namespace osu.Game.Online.Multiplayer
// Update a few properties of the room instantaneously.
Room.Settings = settings;
- APIRoom.Name.Value = Room.Settings.Name;
- APIRoom.Password.Value = Room.Settings.Password;
- APIRoom.Status.Value = string.IsNullOrEmpty(Room.Settings.Password) ? new RoomStatusOpen() : new RoomStatusOpenPrivate();
- APIRoom.Type.Value = Room.Settings.MatchType;
- APIRoom.QueueMode.Value = Room.Settings.QueueMode;
- APIRoom.AutoStartDuration.Value = Room.Settings.AutoStartDuration;
- APIRoom.CurrentPlaylistItem.Value = APIRoom.Playlist.Single(item => item.ID == settings.PlaylistItemId);
- APIRoom.AutoSkip.Value = Room.Settings.AutoSkip;
+ APIRoom.Name = Room.Settings.Name;
+ APIRoom.Password = Room.Settings.Password;
+ APIRoom.Status = string.IsNullOrEmpty(Room.Settings.Password) ? new RoomStatusOpen() : new RoomStatusOpenPrivate();
+ APIRoom.Type = Room.Settings.MatchType;
+ APIRoom.QueueMode = Room.Settings.QueueMode;
+ APIRoom.AutoStartDuration = Room.Settings.AutoStartDuration;
+ APIRoom.CurrentPlaylistItem = APIRoom.Playlist.Single(item => item.ID == settings.PlaylistItemId);
+ APIRoom.AutoSkip = Room.Settings.AutoSkip;
RoomUpdated?.Invoke();
}
diff --git a/osu.Game/Online/Rooms/GetRoomsRequest.cs b/osu.Game/Online/Rooms/GetRoomsRequest.cs
index 1b5e08c729..7feb709acb 100644
--- a/osu.Game/Online/Rooms/GetRoomsRequest.cs
+++ b/osu.Game/Online/Rooms/GetRoomsRequest.cs
@@ -1,12 +1,10 @@
// 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 System.Collections.Generic;
using osu.Framework.IO.Network;
using osu.Game.Extensions;
using osu.Game.Online.API;
-using osu.Game.Online.Rooms.RoomStatuses;
using osu.Game.Screens.OnlinePlay.Lounge.Components;
namespace osu.Game.Online.Rooms
@@ -35,25 +33,6 @@ namespace osu.Game.Online.Rooms
return req;
}
- protected override void PostProcess()
- {
- base.PostProcess();
-
- if (Response != null)
- {
- // API doesn't populate status so let's do it here.
- foreach (var room in Response)
- {
- if (room.EndDate.Value != null && DateTimeOffset.Now >= room.EndDate.Value)
- room.Status.Value = new RoomStatusEnded();
- else if (room.HasPassword.Value)
- room.Status.Value = new RoomStatusOpenPrivate();
- else
- room.Status.Value = new RoomStatusOpen();
- }
- }
- }
-
protected override string Target => "rooms";
}
}
diff --git a/osu.Game/Online/Rooms/JoinRoomRequest.cs b/osu.Game/Online/Rooms/JoinRoomRequest.cs
index 9a73104b60..dfc7a53fb2 100644
--- a/osu.Game/Online/Rooms/JoinRoomRequest.cs
+++ b/osu.Game/Online/Rooms/JoinRoomRequest.cs
@@ -7,7 +7,7 @@ using osu.Game.Online.API;
namespace osu.Game.Online.Rooms
{
- public class JoinRoomRequest : APIRequest
+ public class JoinRoomRequest : APIRequest
{
public readonly Room Room;
public readonly string? Password;
@@ -27,6 +27,6 @@ namespace osu.Game.Online.Rooms
return req;
}
- protected override string Target => $@"rooms/{Room.RoomID.Value}/users/{User!.Id}";
+ protected override string Target => $@"rooms/{Room.RoomID}/users/{User!.Id}";
}
}
diff --git a/osu.Game/Online/Rooms/PartRoomRequest.cs b/osu.Game/Online/Rooms/PartRoomRequest.cs
index 2416833a1e..77b5619efb 100644
--- a/osu.Game/Online/Rooms/PartRoomRequest.cs
+++ b/osu.Game/Online/Rooms/PartRoomRequest.cs
@@ -23,6 +23,6 @@ namespace osu.Game.Online.Rooms
return req;
}
- protected override string Target => $"rooms/{room.RoomID.Value}/users/{User!.Id}";
+ protected override string Target => $"rooms/{room.RoomID}/users/{User!.Id}";
}
}
diff --git a/osu.Game/Online/Rooms/PlaylistExtensions.cs b/osu.Game/Online/Rooms/PlaylistExtensions.cs
index 8591b5bb47..8afa7d90f8 100644
--- a/osu.Game/Online/Rooms/PlaylistExtensions.cs
+++ b/osu.Game/Online/Rooms/PlaylistExtensions.cs
@@ -5,7 +5,6 @@ using System.Collections.Generic;
using System.Linq;
using Humanizer;
using Humanizer.Localisation;
-using osu.Framework.Bindables;
using osu.Game.Rulesets;
using osu.Game.Utils;
@@ -30,7 +29,7 @@ namespace osu.Game.Online.Rooms
/// or the last-played if all items are expired,
/// or if was empty.
///
- public static PlaylistItem? GetCurrentItem(this ICollection playlist)
+ public static PlaylistItem? GetCurrentItem(this IReadOnlyCollection playlist)
{
if (playlist.Count == 0)
return null;
@@ -43,7 +42,7 @@ namespace osu.Game.Online.Rooms
///
/// Returns the total duration from the in playlist order from the supplied ,
///
- public static string GetTotalDuration(this BindableList playlist, RulesetStore rulesetStore) =>
+ public static string GetTotalDuration(this IReadOnlyList playlist, RulesetStore rulesetStore) =>
playlist.Select(p =>
{
double rate = 1;
diff --git a/osu.Game/Online/Rooms/PlaylistItem.cs b/osu.Game/Online/Rooms/PlaylistItem.cs
index a900d8f3d7..47d4e163bf 100644
--- a/osu.Game/Online/Rooms/PlaylistItem.cs
+++ b/osu.Game/Online/Rooms/PlaylistItem.cs
@@ -120,18 +120,21 @@ namespace osu.Game.Online.Rooms
#endregion
- public PlaylistItem With(Optional beatmap = default, Optional playlistOrder = default) => new PlaylistItem(beatmap.GetOr(Beatmap))
+ public PlaylistItem With(Optional id = default, Optional beatmap = default, Optional playlistOrder = default)
{
- ID = ID,
- OwnerID = OwnerID,
- RulesetID = RulesetID,
- Expired = Expired,
- PlaylistOrder = playlistOrder.GetOr(PlaylistOrder),
- PlayedAt = PlayedAt,
- AllowedMods = AllowedMods,
- RequiredMods = RequiredMods,
- valid = { Value = Valid.Value },
- };
+ return new PlaylistItem(beatmap.GetOr(Beatmap))
+ {
+ ID = id.GetOr(ID),
+ OwnerID = OwnerID,
+ RulesetID = RulesetID,
+ Expired = Expired,
+ PlaylistOrder = playlistOrder.GetOr(PlaylistOrder),
+ PlayedAt = PlayedAt,
+ AllowedMods = AllowedMods,
+ RequiredMods = RequiredMods,
+ valid = { Value = Valid.Value },
+ };
+ }
public bool Equals(PlaylistItem? other)
=> ID == other?.ID
diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs
index c39932c3bf..e1813c7e4e 100644
--- a/osu.Game/Online/Rooms/Room.cs
+++ b/osu.Game/Online/Rooms/Room.cs
@@ -1,13 +1,13 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
+using System.Collections.Generic;
+using System.ComponentModel;
using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Runtime.Serialization;
using Newtonsoft.Json;
-using osu.Framework.Allocation;
-using osu.Framework.Bindables;
using osu.Game.IO.Serialization.Converters;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Multiplayer;
@@ -16,162 +16,344 @@ using osu.Game.Online.Rooms.RoomStatuses;
namespace osu.Game.Online.Rooms
{
[JsonObject(MemberSerialization.OptIn)]
- public partial class Room : IDependencyInjectionCandidate
+ public partial class Room : INotifyPropertyChanged
{
- [Cached]
- [JsonProperty("id")]
- public readonly Bindable RoomID = new Bindable();
+ public event PropertyChangedEventHandler? PropertyChanged;
- [Cached]
- [JsonProperty("name")]
- public readonly Bindable Name = new Bindable();
-
- [Cached]
- [JsonProperty("host")]
- public readonly Bindable Host = new Bindable();
-
- [Cached]
- [JsonProperty("playlist")]
- public readonly BindableList Playlist = new BindableList();
-
- [Cached]
- [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]
- public readonly Bindable Category = new Bindable();
-
- // Todo: osu-framework bug (https://github.com/ppy/osu-framework/issues/4106)
- [JsonProperty("category")]
- [JsonConverter(typeof(SnakeCaseStringEnumConverter))]
- private RoomCategory category
+ ///
+ /// The online room ID. Will be null while the room has not yet been created.
+ ///
+ public long? RoomID
{
- get => Category.Value;
- set => Category.Value = value;
+ get => roomId;
+ set => SetField(ref roomId, value);
}
- [Cached]
- public readonly Bindable MaxAttempts = new Bindable();
-
- [Cached]
- public readonly Bindable Status = new Bindable(new RoomStatusOpen());
-
- [Cached]
- public readonly Bindable Availability = new Bindable();
-
- [Cached]
- public readonly Bindable Type = new Bindable();
-
- // Todo: osu-framework bug (https://github.com/ppy/osu-framework/issues/4106)
- [JsonConverter(typeof(SnakeCaseStringEnumConverter))]
- [JsonProperty("type")]
- private MatchType type
+ ///
+ /// The room name.
+ ///
+ public string Name
{
- get => Type.Value;
- set => Type.Value = value;
+ get => name;
+ set => SetField(ref name, value);
}
- [Cached]
- public readonly Bindable QueueMode = new Bindable();
-
- [JsonConverter(typeof(SnakeCaseStringEnumConverter))]
- [JsonProperty("queue_mode")]
- private QueueMode queueMode
+ ///
+ /// Sets the room password. Will be null after the room is created.
+ ///
+ ///
+ /// To check if the room has a password, use .
+ ///
+ public string? Password
{
- get => QueueMode.Value;
- set => QueueMode.Value = value;
- }
-
- [Cached]
- public readonly Bindable AutoStartDuration = new Bindable();
-
- [JsonProperty("auto_start_duration")]
- private ushort autoStartDuration
- {
- get => (ushort)AutoStartDuration.Value.TotalSeconds;
- set => AutoStartDuration.Value = TimeSpan.FromSeconds(value);
- }
-
- [Cached]
- public readonly Bindable MaxParticipants = new Bindable();
-
- [Cached]
- [JsonProperty("current_user_score")]
- public readonly Bindable UserScore = new Bindable();
-
- [JsonProperty("has_password")]
- public readonly Bindable HasPassword = new Bindable();
-
- [Cached]
- [JsonProperty("recent_participants")]
- public readonly BindableList RecentParticipants = new BindableList();
-
- [Cached]
- [JsonProperty("participant_count")]
- public readonly Bindable ParticipantCount = new Bindable();
-
- #region Properties only used for room creation request
-
- [Cached(Name = nameof(Password))]
- [JsonProperty("password")]
- public readonly Bindable Password = new Bindable();
-
- [Cached]
- public readonly Bindable Duration = new Bindable();
-
- [JsonProperty("duration")]
- private int? duration
- {
- get => (int?)Duration.Value?.TotalMinutes;
+ get => password;
set
{
- if (value == null)
- Duration.Value = null;
- else
- Duration.Value = TimeSpan.FromMinutes(value.Value);
+ SetField(ref password, value);
+ HasPassword = !string.IsNullOrEmpty(value);
}
}
- #endregion
+ ///
+ /// Whether the room has a password.
+ ///
+ ///
+ /// To set a password, use .
+ ///
+ [JsonProperty("has_password")]
+ public bool HasPassword
+ {
+ get => hasPassword;
+ private set => SetField(ref hasPassword, value);
+ }
+
+ ///
+ /// The room host. Will be null while the room has not yet been created.
+ ///
+ public APIUser? Host
+ {
+ get => host;
+ set => SetField(ref host, value);
+ }
+
+ ///
+ /// The room category.
+ ///
+ public RoomCategory Category
+ {
+ get => category;
+ set => SetField(ref category, value);
+ }
+
+ ///
+ /// The duration for which the room will be open. Will be null after the room is created.
+ ///
+ ///
+ /// To check the room end time, use .
+ ///
+ public TimeSpan? Duration
+ {
+ get => duration == null ? null : TimeSpan.FromMinutes(duration.Value);
+ set => SetField(ref duration, value == null ? null : (int)value.Value.TotalMinutes);
+ }
+
+ ///
+ /// The date at which the room was opened. Will be null while the room has not yet been created.
+ ///
+ public DateTimeOffset? StartDate
+ {
+ get => startDate;
+ set => SetField(ref startDate, value);
+ }
+
+ ///
+ /// The date at which the room will be closed.
+ ///
+ ///
+ /// To set the room duration, use .
+ ///
+ public DateTimeOffset? EndDate
+ {
+ get => endDate;
+ set => SetField(ref endDate, value);
+ }
+
+ ///
+ /// The maximum number of users allowed in the room.
+ ///
+ public int? MaxParticipants
+ {
+ get => maxParticipants;
+ set => SetField(ref maxParticipants, value);
+ }
+
+ ///
+ /// The current number of users in the room.
+ ///
+ public int ParticipantCount
+ {
+ get => participantCount;
+ set => SetField(ref participantCount, value);
+ }
+
+ ///
+ /// The set of most recent participants in the room.
+ ///
+ public IReadOnlyList RecentParticipants
+ {
+ get => recentParticipants;
+ set => SetList(ref recentParticipants, value);
+ }
+
+ ///
+ /// The match type.
+ ///
+ public MatchType Type
+ {
+ get => type;
+ set => SetField(ref type, value);
+ }
+
+ ///
+ /// The maximum number of attempts on the playlist. Only valid for playlist rooms.
+ ///
+ public int? MaxAttempts
+ {
+ get => maxAttempts;
+ set => SetField(ref maxAttempts, value);
+ }
+
+ ///
+ /// The room playlist.
+ ///
+ public IReadOnlyList Playlist
+ {
+ get => playlist;
+ set => SetList(ref playlist, value);
+ }
+
+ ///
+ /// Describes the items in the playlist.
+ ///
+ public RoomPlaylistItemStats? PlaylistItemStats
+ {
+ get => playlistItemStats;
+ set => SetField(ref playlistItemStats, value);
+ }
+
+ ///
+ /// Describes the range of difficulty of the room.
+ ///
+ public RoomDifficultyRange? DifficultyRange
+ {
+ get => difficultyRange;
+ set => SetField(ref difficultyRange, value);
+ }
+
+ ///
+ /// The playlist queueing mode. Only valid for multiplayer rooms.
+ ///
+ public QueueMode QueueMode
+ {
+ get => queueMode;
+ set => SetField(ref queueMode, value);
+ }
+
+ ///
+ /// Whether to automatically skip map intros. Only valid for multiplayer rooms.
+ ///
+ public bool AutoSkip
+ {
+ get => autoSkip;
+ set => SetField(ref autoSkip, value);
+ }
+
+ ///
+ /// The amount of time before the match is automatically started. Only valid for multiplayer rooms.
+ ///
+ public TimeSpan AutoStartDuration
+ {
+ get => TimeSpan.FromSeconds(autoStartDuration);
+ set => SetField(ref autoStartDuration, (ushort)value.TotalSeconds);
+ }
+
+ ///
+ /// Provides some extra scoring statistics for the local user in the room.
+ ///
+ public PlaylistAggregateScore? UserScore
+ {
+ get => userScore;
+ set => SetField(ref userScore, value);
+ }
+
+ ///
+ /// Represents the current item selected within the room.
+ ///
+ ///
+ /// Only valid for room listing requests (i.e. in the lounge screen), and may not be valid while inside the room.
+ ///
+ public PlaylistItem? CurrentPlaylistItem
+ {
+ get => currentPlaylistItem;
+ set => SetField(ref currentPlaylistItem, value);
+ }
+
+ ///
+ /// The chat channel id for the room. Will be 0 while the room has not yet been created.
+ ///
+ public int ChannelId
+ {
+ get => channelId;
+ private set => SetField(ref channelId, value);
+ }
+
+ ///
+ /// The current room status.
+ ///
+ public RoomStatus Status
+ {
+ get => status;
+ set => SetField(ref status, value);
+ }
+
+ ///
+ /// Describes which players are able to join the room.
+ ///
+ public RoomAvailability Availability
+ {
+ get => availability;
+ set => SetField(ref availability, value);
+ }
+
+ [OnDeserialized]
+ private void onDeserialised(StreamingContext context)
+ {
+ // API doesn't populate status so let's do it here.
+ if (EndDate != null && DateTimeOffset.Now >= EndDate)
+ Status = new RoomStatusEnded();
+ else if (HasPassword)
+ Status = new RoomStatusOpenPrivate();
+ else
+ Status = new RoomStatusOpen();
+ }
+
+ [JsonProperty("id")]
+ private long? roomId;
+
+ [JsonProperty("name")]
+ private string name = string.Empty;
+
+ [JsonProperty("password")]
+ private string? password;
+
+ // Not serialised (internal use only).
+ private bool hasPassword;
+
+ [JsonProperty("host")]
+ private APIUser? host;
+
+ [JsonProperty("category")]
+ [JsonConverter(typeof(SnakeCaseStringEnumConverter))]
+ private RoomCategory category;
+
+ [JsonProperty("duration")]
+ private int? duration;
- // Only supports retrieval for now
- [Cached]
[JsonProperty("starts_at")]
- public readonly Bindable StartDate = new Bindable();
+ private DateTimeOffset? startDate;
- // Only supports retrieval for now
- [Cached]
[JsonProperty("ends_at")]
- public readonly Bindable EndDate = new Bindable();
+ private DateTimeOffset? endDate;
+
+ // Not yet serialised (not implemented).
+ private int? maxParticipants;
+
+ [JsonProperty("participant_count")]
+ private int participantCount;
+
+ [JsonProperty("recent_participants")]
+ private IReadOnlyList recentParticipants = [];
- // Todo: Find a better way to do this (https://github.com/ppy/osu-framework/issues/1930)
[JsonProperty("max_attempts", DefaultValueHandling = DefaultValueHandling.Ignore)]
- private int? maxAttempts
- {
- get => MaxAttempts.Value;
- set => MaxAttempts.Value = value;
- }
+ private int? maxAttempts;
+
+ [JsonProperty("playlist")]
+ private IReadOnlyList playlist = [];
+
+ [JsonProperty("playlist_item_stats")]
+ private RoomPlaylistItemStats? playlistItemStats;
+
+ [JsonProperty("difficulty_range")]
+ private RoomDifficultyRange? difficultyRange;
+
+ [JsonConverter(typeof(SnakeCaseStringEnumConverter))]
+ [JsonProperty("type")]
+ private MatchType type;
+
+ [JsonConverter(typeof(SnakeCaseStringEnumConverter))]
+ [JsonProperty("queue_mode")]
+ private QueueMode queueMode;
- [Cached]
[JsonProperty("auto_skip")]
- public readonly Bindable AutoSkip = new Bindable();
+ private bool autoSkip;
- public Room()
- {
- Password.BindValueChanged(p => HasPassword.Value = !string.IsNullOrEmpty(p.NewValue));
- }
+ [JsonProperty("auto_start_duration")]
+ private ushort autoStartDuration;
+
+ [JsonProperty("current_user_score")]
+ private PlaylistAggregateScore? userScore;
+
+ [JsonProperty("current_playlist_item")]
+ private PlaylistItem? currentPlaylistItem;
+
+ [JsonProperty("channel_id")]
+ private int channelId;
+
+ // Not serialised (see: GetRoomsRequest).
+ private RoomStatus status = new RoomStatusOpen();
+
+ // Not yet serialised (not implemented).
+ private RoomAvailability availability;
///
/// Copies values from another into this one.
@@ -182,52 +364,27 @@ namespace osu.Game.Online.Rooms
/// The to copy values from.
public void CopyFrom(Room other)
{
- RoomID.Value = other.RoomID.Value;
- Name.Value = other.Name.Value;
-
- Category.Value = other.Category.Value;
-
- if (other.Host.Value != null && Host.Value?.Id != other.Host.Value.Id)
- Host.Value = other.Host.Value;
-
- ChannelId.Value = other.ChannelId.Value;
- Status.Value = other.Status.Value;
- Availability.Value = other.Availability.Value;
- HasPassword.Value = other.HasPassword.Value;
- Type.Value = other.Type.Value;
- MaxParticipants.Value = other.MaxParticipants.Value;
- ParticipantCount.Value = other.ParticipantCount.Value;
- EndDate.Value = other.EndDate.Value;
- UserScore.Value = other.UserScore.Value;
- QueueMode.Value = other.QueueMode.Value;
- AutoStartDuration.Value = other.AutoStartDuration.Value;
- DifficultyRange.Value = other.DifficultyRange.Value;
- PlaylistItemStats.Value = other.PlaylistItemStats.Value;
- CurrentPlaylistItem.Value = other.CurrentPlaylistItem.Value;
- AutoSkip.Value = other.AutoSkip.Value;
-
- other.RemoveExpiredPlaylistItems();
-
- if (!Playlist.SequenceEqual(other.Playlist))
- {
- Playlist.Clear();
- Playlist.AddRange(other.Playlist);
- }
-
- if (!RecentParticipants.SequenceEqual(other.RecentParticipants))
- {
- RecentParticipants.Clear();
- RecentParticipants.AddRange(other.RecentParticipants);
- }
- }
-
- public void RemoveExpiredPlaylistItems()
- {
- // Todo: This is not the best way/place to do this, but the intention is to display all playlist items when the room has ended,
- // and display only the non-expired playlist items while the room is still active. In order to achieve this, all expired items are removed from the source Room.
- // More refactoring is required before this can be done locally instead - DrawableRoomPlaylist is currently directly bound to the playlist to display items in the room.
- if (!(Status.Value is RoomStatusEnded))
- Playlist.RemoveAll(i => i.Expired);
+ RoomID = other.RoomID;
+ Name = other.Name;
+ Category = other.Category;
+ Host = other.Host;
+ ChannelId = other.ChannelId;
+ Status = other.Status;
+ Availability = other.Availability;
+ HasPassword = other.HasPassword;
+ Type = other.Type;
+ MaxParticipants = other.MaxParticipants;
+ ParticipantCount = other.ParticipantCount;
+ EndDate = other.EndDate;
+ UserScore = other.UserScore;
+ QueueMode = other.QueueMode;
+ AutoStartDuration = other.AutoStartDuration;
+ DifficultyRange = other.DifficultyRange;
+ PlaylistItemStats = other.PlaylistItemStats;
+ CurrentPlaylistItem = other.CurrentPlaylistItem;
+ AutoSkip = other.AutoSkip;
+ Playlist = other.Playlist;
+ RecentParticipants = other.RecentParticipants;
}
[JsonObject(MemberSerialization.OptIn)]
@@ -240,7 +397,7 @@ namespace osu.Game.Online.Rooms
public int CountTotal;
[JsonProperty("ruleset_ids")]
- public int[] RulesetIDs;
+ public int[] RulesetIDs = [];
}
[JsonObject(MemberSerialization.OptIn)]
@@ -252,5 +409,28 @@ namespace osu.Game.Online.Rooms
[JsonProperty("max")]
public double Max;
}
+
+ protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null!)
+ => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+
+ protected bool SetList(ref IReadOnlyList list, IReadOnlyList value, [CallerMemberName] string propertyName = null!)
+ {
+ if (list.SequenceEqual(value))
+ return false;
+
+ list = value;
+ OnPropertyChanged(propertyName);
+ return true;
+ }
+
+ protected bool SetField(ref T field, T value, [CallerMemberName] string propertyName = null!)
+ {
+ if (EqualityComparer.Default.Equals(field, value))
+ return false;
+
+ field = value;
+ OnPropertyChanged(propertyName);
+ return true;
+ }
}
}
diff --git a/osu.Game/Online/UserStatisticsUpdate.cs b/osu.Game/Online/ScoreBasedUserStatisticsUpdate.cs
similarity index 84%
rename from osu.Game/Online/UserStatisticsUpdate.cs
rename to osu.Game/Online/ScoreBasedUserStatisticsUpdate.cs
index f85b219ef0..dc55c57c68 100644
--- a/osu.Game/Online/UserStatisticsUpdate.cs
+++ b/osu.Game/Online/ScoreBasedUserStatisticsUpdate.cs
@@ -9,7 +9,7 @@ namespace osu.Game.Online
///
/// Contains data about the change in a user's profile statistics after completing a score.
///
- public class UserStatisticsUpdate
+ public class ScoreBasedUserStatisticsUpdate
{
///
/// The score set by the user that triggered the update.
@@ -27,12 +27,12 @@ namespace osu.Game.Online
public UserStatistics After { get; }
///
- /// Creates a new .
+ /// Creates a new .
///
/// The score set by the user that triggered the update.
/// The user's profile statistics prior to the score being set.
/// The user's profile statistics after the score was set.
- public UserStatisticsUpdate(ScoreInfo score, UserStatistics before, UserStatistics after)
+ public ScoreBasedUserStatisticsUpdate(ScoreInfo score, UserStatistics before, UserStatistics after)
{
Score = score;
Before = before;
diff --git a/osu.Game/Online/UserStatisticsWatcher.cs b/osu.Game/Online/UserStatisticsWatcher.cs
index af32e86ae4..73ca3c9f53 100644
--- a/osu.Game/Online/UserStatisticsWatcher.cs
+++ b/osu.Game/Online/UserStatisticsWatcher.cs
@@ -2,18 +2,14 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
-using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Game.Extensions;
using osu.Game.Online.API;
-using osu.Game.Online.API.Requests;
-using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Spectator;
using osu.Game.Scoring;
-using osu.Game.Users;
namespace osu.Game.Online
{
@@ -22,8 +18,10 @@ namespace osu.Game.Online
///
public partial class UserStatisticsWatcher : Component
{
- public IBindable LatestUpdate => latestUpdate;
- private readonly Bindable latestUpdate = new Bindable();
+ private readonly LocalUserStatisticsProvider statisticsProvider;
+
+ public IBindable LatestUpdate => latestUpdate;
+ private readonly Bindable latestUpdate = new Bindable();
[Resolved]
private SpectatorClient spectatorClient { get; set; } = null!;
@@ -33,13 +31,15 @@ namespace osu.Game.Online
private readonly Dictionary watchedScores = new Dictionary();
- private Dictionary? latestStatistics;
+ public UserStatisticsWatcher(LocalUserStatisticsProvider statisticsProvider)
+ {
+ this.statisticsProvider = statisticsProvider;
+ }
protected override void LoadComplete()
{
base.LoadComplete();
- api.LocalUser.BindValueChanged(user => onUserChanged(user.NewValue), true);
spectatorClient.OnUserScoreProcessed += userScoreProcessed;
}
@@ -61,35 +61,6 @@ namespace osu.Game.Online
});
}
- private void onUserChanged(APIUser? localUser) => Schedule(() =>
- {
- latestStatistics = null;
-
- if (localUser == null || localUser.OnlineID <= 1)
- return;
-
- var userRequest = new GetUsersRequest(new[] { localUser.OnlineID });
- userRequest.Success += initialiseUserStatistics;
- api.Queue(userRequest);
- });
-
- private void initialiseUserStatistics(GetUsersResponse response) => Schedule(() =>
- {
- var user = response.Users.SingleOrDefault();
-
- // possible if the user is restricted or similar.
- if (user == null)
- return;
-
- latestStatistics = new Dictionary();
-
- if (user.RulesetsStatistics != null)
- {
- foreach (var rulesetStats in user.RulesetsStatistics)
- latestStatistics.Add(rulesetStats.Key, rulesetStats.Value);
- }
- });
-
private void userScoreProcessed(int userId, long scoreId)
{
if (userId != api.LocalUser.Value?.OnlineID)
@@ -98,30 +69,11 @@ namespace osu.Game.Online
if (!watchedScores.Remove(scoreId, out var scoreInfo))
return;
- requestStatisticsUpdate(userId, scoreInfo);
- }
-
- private void requestStatisticsUpdate(int userId, ScoreInfo scoreInfo)
- {
- var request = new GetUserRequest(userId, scoreInfo.Ruleset);
- request.Success += user => Schedule(() => dispatchStatisticsUpdate(scoreInfo, user.Statistics));
- api.Queue(request);
- }
-
- private void dispatchStatisticsUpdate(ScoreInfo scoreInfo, UserStatistics updatedStatistics)
- {
- string rulesetName = scoreInfo.Ruleset.ShortName;
-
- api.UpdateStatistics(updatedStatistics);
-
- if (latestStatistics == null)
- return;
-
- latestStatistics.TryGetValue(rulesetName, out UserStatistics? latestRulesetStatistics);
- latestRulesetStatistics ??= new UserStatistics();
-
- latestUpdate.Value = new UserStatisticsUpdate(scoreInfo, latestRulesetStatistics, updatedStatistics);
- latestStatistics[rulesetName] = updatedStatistics;
+ statisticsProvider.RefetchStatistics(scoreInfo.Ruleset, u => Schedule(() =>
+ {
+ if (u.OldStatistics != null)
+ latestUpdate.Value = new ScoreBasedUserStatisticsUpdate(scoreInfo, u.OldStatistics, u.NewStatistics);
+ }));
}
protected override void Dispose(bool isDisposing)
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index dce24c6ee7..a92b1f4d36 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -148,8 +148,7 @@ namespace osu.Game
[Resolved]
private FrameworkConfigManager frameworkConfig { get; set; }
- [Cached]
- private readonly DifficultyRecommender difficultyRecommender = new DifficultyRecommender();
+ private DifficultyRecommender difficultyRecommender;
[Cached]
private readonly LegacyImportManager legacyImportManager = new LegacyImportManager();
@@ -1069,7 +1068,11 @@ namespace osu.Game
ScreenStack.Push(CreateLoader().With(l => l.RelativeSizeAxes = Axes.Both));
});
- loadComponentSingleFile(new UserStatisticsWatcher(), Add, true);
+ LocalUserStatisticsProvider statisticsProvider;
+
+ loadComponentSingleFile(statisticsProvider = new LocalUserStatisticsProvider(), Add, true);
+ loadComponentSingleFile(difficultyRecommender = new DifficultyRecommender(statisticsProvider), Add, true);
+ loadComponentSingleFile(new UserStatisticsWatcher(statisticsProvider), Add, true);
loadComponentSingleFile(Toolbar = new Toolbar
{
OnHome = delegate
@@ -1139,7 +1142,6 @@ namespace osu.Game
loadComponentSingleFile(new BackgroundDataStoreProcessor(), Add);
loadComponentSingleFile(new DetachedBeatmapStore(), Add, true);
- Add(difficultyRecommender);
Add(externalLinkOpener = new ExternalLinkOpener());
Add(new MusicKeyBindingHandler());
Add(new OnlineStatusNotifier(() => ScreenStack.CurrentScreen));
diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs
index b47e2b82c0..f83368fa41 100644
--- a/osu.Game/Overlays/BeatmapListingOverlay.cs
+++ b/osu.Game/Overlays/BeatmapListingOverlay.cs
@@ -198,7 +198,6 @@ namespace osu.Game.Overlays
{
c.Anchor = Anchor.TopCentre;
c.Origin = Anchor.TopCentre;
- c.Scale = new Vector2(0.8f);
})).ToArray();
private static ReverseChildIDFillFlowContainer createCardContainerFor(IEnumerable newCards)
diff --git a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs
index b237a0ee05..df657aa55b 100644
--- a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs
+++ b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs
@@ -71,7 +71,7 @@ namespace osu.Game.Overlays.Profile.Sections.Beatmaps
? new BeatmapCardNormal(model)
{
Anchor = Anchor.TopCentre,
- Origin = Anchor.TopCentre,
+ Origin = Anchor.TopCentre
}
: null;
}
diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/HUDSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/HUDSettings.cs
index f4dd319152..b4caaf7983 100644
--- a/osu.Game/Overlays/Settings/Sections/Gameplay/HUDSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Gameplay/HUDSettings.cs
@@ -41,6 +41,11 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
Current = config.GetBindable(OsuSetting.GameplayLeaderboard),
},
new SettingsCheckbox
+ {
+ LabelText = GameplaySettingsStrings.AlwaysRequireHoldForMenu,
+ Current = config.GetBindable(OsuSetting.AlwaysRequireHoldingForPause),
+ },
+ new SettingsCheckbox
{
LabelText = GameplaySettingsStrings.AlwaysShowHoldForMenuButton,
Current = config.GetBindable(OsuSetting.AlwaysShowHoldForMenuButton),
diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
index ce087f1807..f40a4c941f 100644
--- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
@@ -268,7 +268,8 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
private void updateScreenModeWarning()
{
- if (RuntimeInfo.OS == RuntimeInfo.Platform.macOS)
+ // Can be removed once we stop supporting SDL2.
+ if (RuntimeInfo.OS == RuntimeInfo.Platform.macOS && !FrameworkEnvironment.UseSDL3)
{
if (windowModeDropdown.Current.Value == WindowMode.Fullscreen)
windowModeDropdown.SetNoticeText(LayoutSettingsStrings.FullscreenMacOSNote, true);
diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs
index 42908f7102..d0ee2ccd71 100644
--- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs
+++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs
@@ -34,11 +34,12 @@ using osu.Game.Screens.Edit.Components;
using osu.Game.Screens.Edit.Components.Menus;
using osu.Game.Skinning;
using osu.Framework.Graphics.Cursor;
+using osu.Game.Input.Bindings;
namespace osu.Game.Overlays.SkinEditor
{
[Cached(typeof(SkinEditor))]
- public partial class SkinEditor : VisibilityContainer, ICanAcceptFiles, IKeyBindingHandler, IEditorChangeHandler
+ public partial class SkinEditor : VisibilityContainer, ICanAcceptFiles, IKeyBindingHandler, IKeyBindingHandler, IEditorChangeHandler
{
public const double TRANSITION_DURATION = 300;
@@ -155,7 +156,7 @@ namespace osu.Game.Overlays.SkinEditor
{
Items = new OsuMenuItem[]
{
- new EditorMenuItem(Web.CommonStrings.ButtonsSave, MenuItemType.Standard, () => Save()),
+ new EditorMenuItem(Web.CommonStrings.ButtonsSave, MenuItemType.Standard, () => Save()) { Hotkey = new Hotkey(PlatformAction.Save) },
new EditorMenuItem(CommonStrings.Export, MenuItemType.Standard, () => skins.ExportCurrentSkin()) { Action = { Disabled = !RuntimeInfo.IsDesktop } },
new OsuMenuItemSpacer(),
new EditorMenuItem(CommonStrings.RevertToDefault, MenuItemType.Destructive, () => dialogOverlay?.Push(new RevertConfirmDialog(revert))),
@@ -167,13 +168,13 @@ namespace osu.Game.Overlays.SkinEditor
{
Items = new OsuMenuItem[]
{
- undoMenuItem = new EditorMenuItem(CommonStrings.Undo, MenuItemType.Standard, Undo),
- redoMenuItem = new EditorMenuItem(CommonStrings.Redo, MenuItemType.Standard, Redo),
+ undoMenuItem = new EditorMenuItem(CommonStrings.Undo, MenuItemType.Standard, Undo) { Hotkey = new Hotkey(PlatformAction.Undo) },
+ redoMenuItem = new EditorMenuItem(CommonStrings.Redo, MenuItemType.Standard, Redo) { Hotkey = new Hotkey(PlatformAction.Redo) },
new OsuMenuItemSpacer(),
- cutMenuItem = new EditorMenuItem(CommonStrings.Cut, MenuItemType.Standard, Cut),
- copyMenuItem = new EditorMenuItem(CommonStrings.Copy, MenuItemType.Standard, Copy),
- pasteMenuItem = new EditorMenuItem(CommonStrings.Paste, MenuItemType.Standard, Paste),
- cloneMenuItem = new EditorMenuItem(CommonStrings.Clone, MenuItemType.Standard, Clone),
+ cutMenuItem = new EditorMenuItem(CommonStrings.Cut, MenuItemType.Standard, Cut) { Hotkey = new Hotkey(PlatformAction.Cut) },
+ copyMenuItem = new EditorMenuItem(CommonStrings.Copy, MenuItemType.Standard, Copy) { Hotkey = new Hotkey(PlatformAction.Copy) },
+ pasteMenuItem = new EditorMenuItem(CommonStrings.Paste, MenuItemType.Standard, Paste) { Hotkey = new Hotkey(PlatformAction.Paste) },
+ cloneMenuItem = new EditorMenuItem(CommonStrings.Clone, MenuItemType.Standard, Clone) { Hotkey = new Hotkey(GlobalAction.EditorCloneSelection) },
}
},
}
@@ -313,6 +314,25 @@ namespace osu.Game.Overlays.SkinEditor
{
}
+ public bool OnPressed(KeyBindingPressEvent e)
+ {
+ if (e.Repeat)
+ return false;
+
+ switch (e.Action)
+ {
+ case GlobalAction.EditorCloneSelection:
+ Clone();
+ return true;
+ }
+
+ return false;
+ }
+
+ public void OnReleased(KeyBindingReleaseEvent e)
+ {
+ }
+
public void UpdateTargetScreen(Drawable targetScreen)
{
this.targetScreen = targetScreen;
diff --git a/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs b/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs
index 07c2e72774..d5891da936 100644
--- a/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs
+++ b/osu.Game/Overlays/Toolbar/TransientUserStatisticsUpdateDisplay.cs
@@ -21,7 +21,7 @@ namespace osu.Game.Overlays.Toolbar
{
public partial class TransientUserStatisticsUpdateDisplay : CompositeDrawable
{
- public Bindable LatestUpdate { get; } = new Bindable();
+ public Bindable LatestUpdate { get; } = new Bindable();
private Statistic globalRank = null!;
private Statistic pp = null!;
@@ -48,7 +48,7 @@ namespace osu.Game.Overlays.Toolbar
};
if (userStatisticsWatcher != null)
- ((IBindable)LatestUpdate).BindTo(userStatisticsWatcher.LatestUpdate);
+ ((IBindable)LatestUpdate).BindTo(userStatisticsWatcher.LatestUpdate);
}
protected override void LoadComplete()
diff --git a/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs b/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs
index cf41c8e108..5bf15aee8b 100644
--- a/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs
+++ b/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs
@@ -163,7 +163,7 @@ namespace osu.Game.Rulesets.Edit
return (lastBefore, firstAfter);
}
- protected abstract double ReadCurrentDistanceSnap(HitObject before, HitObject after);
+ public abstract double ReadCurrentDistanceSnap(HitObject before, HitObject after);
protected override void Update()
{
diff --git a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs
index 19554b6504..4ca937bf86 100644
--- a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs
+++ b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs
@@ -205,7 +205,7 @@ namespace osu.Game.Rulesets.Mods
{
foreach (var hitObject in hitObjects)
{
- if (!(hitObject.HitWindows is HitWindows.EmptyHitWindows))
+ if (hitObject.HitWindows != HitWindows.Empty)
yield return hitObject;
foreach (HitObject nested in getAllApplicableHitObjects(hitObject.NestedHitObjects))
diff --git a/osu.Game/Rulesets/Scoring/HitEventExtensions.cs b/osu.Game/Rulesets/Scoring/HitEventExtensions.cs
index fc4eef13ba..269342460f 100644
--- a/osu.Game/Rulesets/Scoring/HitEventExtensions.cs
+++ b/osu.Game/Rulesets/Scoring/HitEventExtensions.cs
@@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
+using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Scoring
{
@@ -20,32 +21,36 @@ namespace osu.Game.Rulesets.Scoring
/// A non-null value if unstable rate could be calculated,
/// and if unstable rate cannot be calculated due to being empty.
///
- public static double? CalculateUnstableRate(this IEnumerable hitEvents)
+ public static UnstableRateCalculationResult? CalculateUnstableRate(this IReadOnlyList hitEvents, UnstableRateCalculationResult? result = null)
{
Debug.Assert(hitEvents.All(ev => ev.GameplayRate != null));
- int count = 0;
- double mean = 0;
- double sumOfSquares = 0;
+ result ??= new UnstableRateCalculationResult();
- foreach (var e in hitEvents)
+ // Handle rewinding in the simplest way possible.
+ if (hitEvents.Count < result.EventCount + 1)
+ result = new UnstableRateCalculationResult();
+
+ for (int i = result.EventCount; i < hitEvents.Count; i++)
{
+ HitEvent e = hitEvents[i];
+
if (!AffectsUnstableRate(e))
continue;
- count++;
+ result.EventCount++;
// Division by gameplay rate is to account for TimeOffset scaling with gameplay rate.
double currentValue = e.TimeOffset / e.GameplayRate!.Value;
- double nextMean = mean + (currentValue - mean) / count;
- sumOfSquares += (currentValue - mean) * (currentValue - nextMean);
- mean = nextMean;
+ double nextMean = result.Mean + (currentValue - result.Mean) / result.EventCount;
+ result.SumOfSquares += (currentValue - result.Mean) * (currentValue - nextMean);
+ result.Mean = nextMean;
}
- if (count == 0)
+ if (result.EventCount == 0)
return null;
- return 10.0 * Math.Sqrt(sumOfSquares / count);
+ return result;
}
///
@@ -65,6 +70,39 @@ namespace osu.Game.Rulesets.Scoring
return timeOffsets.Average();
}
- public static bool AffectsUnstableRate(HitEvent e) => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result.IsHit();
+ public static bool AffectsUnstableRate(HitEvent e) => AffectsUnstableRate(e.HitObject, e.Result);
+ public static bool AffectsUnstableRate(HitObject hitObject, HitResult result) => hitObject.HitWindows != HitWindows.Empty && result.IsHit();
+
+ ///
+ /// Data type returned by which allows efficient incremental processing.
+ ///
+ ///
+ /// This should be passed back into future calls as a parameter.
+ ///
+ /// The optimisations used here rely on hit events being a consecutive sequence from a single gameplay session.
+ /// When a new gameplay session is started, any existing results should be disposed.
+ ///
+ public class UnstableRateCalculationResult
+ {
+ ///
+ /// Total events processed. For internal incremental calculation use.
+ ///
+ public int EventCount;
+
+ ///
+ /// Last sum-of-squares value. For internal incremental calculation use.
+ ///
+ public double SumOfSquares;
+
+ ///
+ /// Last mean value. For internal incremental calculation use.
+ ///
+ public double Mean;
+
+ ///
+ /// The unstable rate.
+ ///
+ public double Result => EventCount == 0 ? 0 : 10.0 * Math.Sqrt(SumOfSquares / EventCount);
+ }
}
}
diff --git a/osu.Game/Rulesets/Scoring/HitWindows.cs b/osu.Game/Rulesets/Scoring/HitWindows.cs
index 2d008b58ba..a6a268fc78 100644
--- a/osu.Game/Rulesets/Scoring/HitWindows.cs
+++ b/osu.Game/Rulesets/Scoring/HitWindows.cs
@@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Scoring
/// An empty with only and .
/// No time values are provided (meaning instantaneous hit or miss).
///
- public static HitWindows Empty => new EmptyHitWindows();
+ public static HitWindows Empty { get; } = new EmptyHitWindows();
public HitWindows()
{
@@ -182,7 +182,7 @@ namespace osu.Game.Rulesets.Scoring
///
protected virtual DifficultyRange[] GetRanges() => base_ranges;
- public class EmptyHitWindows : HitWindows
+ private class EmptyHitWindows : HitWindows
{
private static readonly DifficultyRange[] ranges =
{
diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs
index a9dbfc29a9..78cee2c1cf 100644
--- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs
@@ -258,8 +258,10 @@ namespace osu.Game.Screens.Edit.Compose.Components
private void resetTernaryStates()
{
- if (SelectionNewComboState.Value == TernaryState.Indeterminate)
- SelectionNewComboState.Value = TernaryState.False;
+ if (SelectedItems.Count > 0)
+ return;
+
+ SelectionNewComboState.Value = TernaryState.False;
AutoSelectionBankEnabled.Value = true;
SelectionAdditionBanksEnabled.Value = true;
SelectionBankStates[HIT_BANK_AUTO].Value = TernaryState.True;
diff --git a/osu.Game/Screens/Menu/DailyChallengeButton.cs b/osu.Game/Screens/Menu/DailyChallengeButton.cs
index 44a53efa7b..be22fc3c30 100644
--- a/osu.Game/Screens/Menu/DailyChallengeButton.cs
+++ b/osu.Game/Screens/Menu/DailyChallengeButton.cs
@@ -156,15 +156,15 @@ namespace osu.Game.Screens.Menu
Room = room;
cover.OnlineInfo = TooltipContent = room.Playlist.FirstOrDefault()?.Beatmap.BeatmapSet as APIBeatmapSet;
- if (room.StartDate.Value != null && room.RoomID.Value != lastDailyChallengeRoomID)
+ if (room.StartDate != null && room.RoomID != lastDailyChallengeRoomID)
{
- lastDailyChallengeRoomID = room.RoomID.Value;
+ lastDailyChallengeRoomID = room.RoomID;
// new challenge is live, reset intro played static.
statics.SetValue(Static.DailyChallengeIntroPlayed, false);
// we only want to notify the user if the new challenge just went live.
- if (Math.Abs((DateTimeOffset.Now - room.StartDate.Value!.Value).TotalSeconds) < 1800)
+ if (Math.Abs((DateTimeOffset.Now - room.StartDate.Value).TotalSeconds) < 1800)
notificationOverlay?.Post(new NewDailyChallengeNotification(room));
}
@@ -180,7 +180,7 @@ namespace osu.Game.Screens.Menu
if (Room == null)
return;
- var remaining = (Room.EndDate.Value - DateTimeOffset.Now) ?? TimeSpan.Zero;
+ var remaining = (Room.EndDate - DateTimeOffset.Now) ?? TimeSpan.Zero;
if (remaining <= TimeSpan.Zero)
{
diff --git a/osu.Game/Screens/Menu/MenuTip.cs b/osu.Game/Screens/Menu/MenuTip.cs
index da349373c3..3fc5fe57fb 100644
--- a/osu.Game/Screens/Menu/MenuTip.cs
+++ b/osu.Game/Screens/Menu/MenuTip.cs
@@ -7,12 +7,14 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
+using osu.Framework.Localisation;
using osu.Framework.Utils;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osuTK;
using osuTK.Graphics;
+using osu.Game.Localisation;
namespace osu.Game.Screens.Menu
{
@@ -78,50 +80,49 @@ namespace osu.Game.Screens.Menu
static void formatRegular(SpriteText t) => t.Font = OsuFont.GetFont(size: 16, weight: FontWeight.Regular);
static void formatSemiBold(SpriteText t) => t.Font = OsuFont.GetFont(size: 16, weight: FontWeight.SemiBold);
- string tip = getRandomTip();
+ var tip = getRandomTip();
textFlow.Clear();
- textFlow.AddParagraph("a tip for you:", formatSemiBold);
+ textFlow.AddParagraph(MenuTipStrings.MenuTipTitle, formatSemiBold);
textFlow.AddParagraph(tip, formatRegular);
this.FadeInFromZero(200, Easing.OutQuint)
- .Delay(1000 + 80 * tip.Length)
+ .Delay(1000 + 80 * tip.ToString().Length)
.Then()
.FadeOutFromOne(2000, Easing.OutQuint);
}
- private string getRandomTip()
+ private LocalisableString getRandomTip()
{
- string[] tips =
+ LocalisableString[] tips =
{
- "Press Ctrl-T anywhere in the game to toggle the toolbar!",
- "Press Ctrl-O anywhere in the game to access options!",
- "All settings are dynamic and take effect in real-time. Try changing the skin while watching autoplay!",
- "New features are coming online every update. Make sure to stay up-to-date!",
- "If you find the UI too large or small, try adjusting UI scale in settings!",
- "Try adjusting the \"Screen Scaling\" mode to change your gameplay or UI area, even in fullscreen!",
- "What used to be \"osu!direct\" is available to all users just like on the website. You can access it anywhere using Ctrl-B!",
- "Seeking in replays is available by dragging on the progress bar at the bottom of the screen or by using the left and right arrow keys!",
- "Multithreading support means that even with low \"FPS\" your input and judgements will be accurate!",
- "Try scrolling right in mod select to find a bunch of new fun mods!",
- "Most of the web content (profiles, rankings, etc.) are available natively in-game from the icons on the toolbar!",
- "Get more details, hide or delete a beatmap by right-clicking on its panel at song select!",
- "All delete operations are temporary until exiting. Restore accidentally deleted content from the maintenance settings!",
- "Check out the \"playlists\" system, which lets users create their own custom and permanent leaderboards!",
- "Toggle advanced frame / thread statistics with Ctrl-F11!",
- "Take a look under the hood at performance counters and enable verbose performance logging with Ctrl-F2!",
- "You can pause during a replay by pressing Space!",
- "Most of the hotkeys in the game are configurable and can be changed to anything you want. Check the bindings panel under input settings!",
- "When your gameplay HUD is hidden, you can press and hold Ctrl to view it temporarily!",
- "Your gameplay HUD can be customized by using the skin layout editor. Open it at any time via Ctrl-Shift-S!",
- "Drag and drop any image into the skin editor to load it in quickly!",
- "You can create mod presets to make toggling your favorite mod combinations easier!",
- "Many mods have customisation settings that drastically change how they function. Click the Mod Customisation button in mod select to view settings!",
- "Press Ctrl-Shift-R to switch to a random skin!",
- "Press Ctrl-Shift-F to toggle the FPS Counter. But make sure not to pay too much attention to it!",
- "While watching a replay, press Ctrl-H to toggle replay settings!",
- "You can easily copy the mods from scores on a leaderboard by right-clicking on them!",
- "Ctrl-Enter at song select will start a beatmap in autoplay mode!"
+ MenuTipStrings.ToggleToolbarShortcut,
+ MenuTipStrings.GameSettingsShortcut,
+ MenuTipStrings.DynamicSettings,
+ MenuTipStrings.NewFeaturesAreComingOnline,
+ MenuTipStrings.UIScalingSettings,
+ MenuTipStrings.ScreenScalingSettings,
+ MenuTipStrings.FreeOsuDirect,
+ MenuTipStrings.ReplaySeeking,
+ MenuTipStrings.MultithreadingSupport,
+ MenuTipStrings.TryNewMods,
+ MenuTipStrings.EmbeddedWebContent,
+ MenuTipStrings.BeatmapRightClick,
+ MenuTipStrings.TemporaryDeleteOperations,
+ MenuTipStrings.DiscoverPlaylists,
+ MenuTipStrings.ToggleAdvancedFPSCounter,
+ MenuTipStrings.GlobalStatisticsShortcut,
+ MenuTipStrings.ReplayPausing,
+ MenuTipStrings.ConfigurableHotkeys,
+ MenuTipStrings.PeekHUDWhenHidden,
+ MenuTipStrings.SkinEditor,
+ MenuTipStrings.DragAndDropImageInSkinEditor,
+ MenuTipStrings.ModPresets,
+ MenuTipStrings.ModCustomisationSettings,
+ MenuTipStrings.RandomSkinShortcut,
+ MenuTipStrings.ToggleReplaySettingsShortcut,
+ MenuTipStrings.CopyModsFromScore,
+ MenuTipStrings.AutoplayBeatmapShortcut
};
return tips[RNG.Next(0, tips.Length)];
diff --git a/osu.Game/Screens/Menu/SongTicker.cs b/osu.Game/Screens/Menu/SongTicker.cs
index 3bdc0efe19..3aac365eee 100644
--- a/osu.Game/Screens/Menu/SongTicker.cs
+++ b/osu.Game/Screens/Menu/SongTicker.cs
@@ -8,6 +8,9 @@ using osu.Game.Graphics.Sprites;
using osuTK;
using osu.Game.Graphics;
using osu.Framework.Bindables;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics.Effects;
+using osu.Framework.Graphics.Shapes;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
@@ -27,26 +30,60 @@ namespace osu.Game.Screens.Menu
public SongTicker()
{
AutoSizeAxes = Axes.Both;
- Child = new FillFlowContainer
+ InternalChildren = new Drawable[]
{
- AutoSizeAxes = Axes.Both,
- Direction = FillDirection.Vertical,
- Spacing = new Vector2(0, 3),
- Children = new Drawable[]
+ new Container
{
- title = new OsuSpriteText
+ Anchor = Anchor.TopRight,
+ Origin = Anchor.TopRight,
+ Position = new Vector2(5, -5),
+ Padding = new MarginPadding(-5),
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
{
- Anchor = Anchor.TopRight,
- Origin = Anchor.TopRight,
- Font = OsuFont.GetFont(size: 24, weight: FontWeight.Light, italics: true)
- },
- artist = new OsuSpriteText
- {
- Anchor = Anchor.TopRight,
- Origin = Anchor.TopRight,
- Font = OsuFont.GetFont(size: 16)
+ new CircularContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ EdgeEffect = new EdgeEffectParameters
+ {
+ Radius = 75,
+ Type = EdgeEffectType.Shadow,
+ Colour = OsuColour.Gray(0.04f).Opacity(0.3f),
+ },
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ AlwaysPresent = true,
+ Alpha = 0,
+ },
+ }
+ },
}
- }
+ },
+ new FillFlowContainer
+ {
+ AutoSizeAxes = Axes.Both,
+ Direction = FillDirection.Vertical,
+ Spacing = new Vector2(0, 3),
+ Children = new Drawable[]
+ {
+ title = new OsuSpriteText
+ {
+ Anchor = Anchor.TopRight,
+ Origin = Anchor.TopRight,
+ Font = OsuFont.GetFont(size: 24, weight: FontWeight.Light, italics: true)
+ },
+ artist = new OsuSpriteText
+ {
+ Anchor = Anchor.TopRight,
+ Origin = Anchor.TopRight,
+ Font = OsuFont.GetFont(size: 16)
+ }
+ }
+ },
};
}
diff --git a/osu.Game/Screens/OnlinePlay/Components/BeatmapTitle.cs b/osu.Game/Screens/OnlinePlay/Components/BeatmapTitle.cs
index 7c57f5b4f5..5c8ac5ce73 100644
--- a/osu.Game/Screens/OnlinePlay/Components/BeatmapTitle.cs
+++ b/osu.Game/Screens/OnlinePlay/Components/BeatmapTitle.cs
@@ -1,32 +1,40 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.ComponentModel;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Online.Chat;
+using osu.Game.Online.Rooms;
namespace osu.Game.Screens.OnlinePlay.Components
{
- public partial class BeatmapTitle : OnlinePlayComposite
+ public partial class BeatmapTitle : CompositeDrawable
{
+ private readonly Room room;
private readonly LinkFlowContainer textFlow;
- public BeatmapTitle()
- {
- AutoSizeAxes = Axes.Both;
+ [Resolved]
+ private OsuColour colours { get; set; } = null!;
+ public BeatmapTitle(Room room)
+ {
+ this.room = room;
+
+ AutoSizeAxes = Axes.Both;
InternalChild = textFlow = new LinkFlowContainer { AutoSizeAxes = Axes.Both };
}
- [BackgroundDependencyLoader]
- private void load()
+ protected override void LoadComplete()
{
- Playlist.CollectionChanged += (_, _) => updateText();
+ base.LoadComplete();
+ room.PropertyChanged += onRoomPropertyChanged;
updateText();
}
@@ -46,8 +54,11 @@ namespace osu.Game.Screens.OnlinePlay.Components
}
}
- [Resolved]
- private OsuColour colours { get; set; } = null!;
+ private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == nameof(Room.Playlist))
+ updateText();
+ }
private void updateText()
{
@@ -56,7 +67,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
textFlow.Clear();
- var beatmap = Playlist.FirstOrDefault()?.Beatmap;
+ var beatmap = room.Playlist.FirstOrDefault()?.Beatmap;
if (beatmap == null)
{
@@ -78,5 +89,11 @@ namespace osu.Game.Screens.OnlinePlay.Components
textFlow.AddLink(title, LinkAction.OpenBeatmap, beatmap.OnlineID.ToString(), "Open beatmap");
}
}
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+ room.PropertyChanged -= onRoomPropertyChanged;
+ }
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs b/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs
index 4b38ea68b3..b213d424df 100644
--- a/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs
+++ b/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System.Linq;
using System.Threading.Tasks;
using osu.Framework.Allocation;
@@ -20,7 +18,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
public IBindable InitialRoomsReceived => initialRoomsReceived;
private readonly Bindable initialRoomsReceived = new Bindable();
- public readonly Bindable Filter = new Bindable();
+ public readonly Bindable Filter = new Bindable();
[BackgroundDependencyLoader]
private void load()
@@ -35,7 +33,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
});
}
- private GetRoomsRequest lastPollRequest;
+ private GetRoomsRequest? lastPollRequest;
protected override Task Poll()
{
@@ -53,19 +51,16 @@ namespace osu.Game.Screens.OnlinePlay.Components
req.Success += result =>
{
- result = result.Where(r => r.Category.Value != RoomCategory.DailyChallenge).ToList();
+ result = result.Where(r => r.Category != RoomCategory.DailyChallenge).ToList();
foreach (var existing in RoomManager.Rooms.ToArray())
{
- if (result.All(r => r.RoomID.Value != existing.RoomID.Value))
+ if (result.All(r => r.RoomID != existing.RoomID))
RoomManager.RemoveRoom(existing);
}
foreach (var incoming in result)
- {
- incoming.RemoveExpiredPlaylistItems();
RoomManager.AddOrUpdateRoom(incoming);
- }
initialRoomsReceived.Value = true;
tcs.SetResult(true);
diff --git a/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs b/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs
index b0ede8d9b5..1f2b2e3fc2 100644
--- a/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs
+++ b/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs
@@ -1,12 +1,9 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
+using System.ComponentModel;
using System.Linq;
-using osu.Framework.Allocation;
-using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics.UserInterfaceV2;
@@ -14,23 +11,22 @@ using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Playlists;
using osu.Game.Screens.Select;
using osuTK;
+using Container = osu.Framework.Graphics.Containers.Container;
namespace osu.Game.Screens.OnlinePlay.Components
{
public partial class MatchBeatmapDetailArea : BeatmapDetailArea
{
- public Action CreateNewItem;
-
- public readonly Bindable SelectedItem = new Bindable();
-
- [Resolved(typeof(Room))]
- protected BindableList Playlist { get; private set; }
+ public Action? CreateNewItem;
+ private readonly Room room;
private readonly GridContainer playlistArea;
private readonly DrawableRoomPlaylist playlist;
- public MatchBeatmapDetailArea()
+ public MatchBeatmapDetailArea(Room room)
{
+ this.room = room;
+
Add(playlistArea = new GridContainer
{
RelativeSizeAxes = Axes.Both,
@@ -72,10 +68,21 @@ namespace osu.Game.Screens.OnlinePlay.Components
{
base.LoadComplete();
- playlist.Items.BindTo(Playlist);
- playlist.SelectedItem.BindTo(SelectedItem);
+ playlist.Items.BindCollectionChanged((_, __) => room.Playlist = playlist.Items.ToArray());
+
+ room.PropertyChanged += onRoomPropertyChanged;
+ updateRoomPlaylist();
}
+ private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == nameof(Room.Playlist))
+ updateRoomPlaylist();
+ }
+
+ private void updateRoomPlaylist()
+ => playlist.Items.ReplaceRange(0, playlist.Items.Count, room.Playlist);
+
protected override void OnTabChanged(BeatmapDetailAreaTabItem tab, bool selectedMods)
{
base.OnTabChanged(tab, selectedMods);
@@ -93,5 +100,11 @@ namespace osu.Game.Screens.OnlinePlay.Components
}
protected override BeatmapDetailAreaTabItem[] CreateTabItems() => base.CreateTabItems().Prepend(new BeatmapDetailAreaPlaylistTabItem()).ToArray();
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+ room.PropertyChanged -= onRoomPropertyChanged;
+ }
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundSprite.cs b/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundSprite.cs
deleted file mode 100644
index 0d4cd30090..0000000000
--- a/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundSprite.cs
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-#nullable disable
-
-using osu.Framework.Allocation;
-using osu.Framework.Graphics;
-using osu.Game.Beatmaps.Drawables;
-using osu.Game.Online.Rooms;
-
-namespace osu.Game.Screens.OnlinePlay.Components
-{
- public partial class OnlinePlayBackgroundSprite : OnlinePlayComposite
- {
- protected readonly BeatmapSetCoverType BeatmapSetCoverType;
- private UpdateableBeatmapBackgroundSprite sprite;
-
- public OnlinePlayBackgroundSprite(BeatmapSetCoverType beatmapSetCoverType = BeatmapSetCoverType.Cover)
- {
- BeatmapSetCoverType = beatmapSetCoverType;
- }
-
- [BackgroundDependencyLoader]
- private void load()
- {
- InternalChild = sprite = CreateBackgroundSprite();
-
- CurrentPlaylistItem.BindValueChanged(_ => updateBeatmap());
- Playlist.CollectionChanged += (_, _) => updateBeatmap();
-
- updateBeatmap();
- }
-
- private void updateBeatmap()
- {
- 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/OverlinedHeader.cs b/osu.Game/Screens/OnlinePlay/Components/OverlinedHeader.cs
index 09a3602cdd..d9cdcac7d7 100644
--- a/osu.Game/Screens/OnlinePlay/Components/OverlinedHeader.cs
+++ b/osu.Game/Screens/OnlinePlay/Components/OverlinedHeader.cs
@@ -16,7 +16,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
///
/// A header used in the multiplayer interface which shows text / details beneath a line.
///
- public partial class OverlinedHeader : OnlinePlayComposite
+ public partial class OverlinedHeader : CompositeDrawable
{
private bool showLine = true;
diff --git a/osu.Game/Screens/OnlinePlay/Components/OverlinedPlaylistHeader.cs b/osu.Game/Screens/OnlinePlay/Components/OverlinedPlaylistHeader.cs
index dd728e460b..55d9f273e9 100644
--- a/osu.Game/Screens/OnlinePlay/Components/OverlinedPlaylistHeader.cs
+++ b/osu.Game/Screens/OnlinePlay/Components/OverlinedPlaylistHeader.cs
@@ -1,6 +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.ComponentModel;
using osu.Framework.Allocation;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets;
@@ -9,19 +10,38 @@ namespace osu.Game.Screens.OnlinePlay.Components
{
public partial class OverlinedPlaylistHeader : OverlinedHeader
{
+ private readonly Room room;
+
[Resolved]
private RulesetStore rulesets { get; set; } = null!;
- public OverlinedPlaylistHeader()
+ public OverlinedPlaylistHeader(Room room)
: base("Playlist")
{
+ this.room = room;
}
protected override void LoadComplete()
{
base.LoadComplete();
- Playlist.BindCollectionChanged((_, _) => Details.Value = Playlist.GetTotalDuration(rulesets), true);
+ room.PropertyChanged += onRoomPropertyChanged;
+ updateDuration();
+ }
+
+ private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == nameof(Room.Playlist))
+ updateDuration();
+ }
+
+ private void updateDuration()
+ => Details.Value = room.Playlist.GetTotalDuration(rulesets);
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+ room.PropertyChanged -= onRoomPropertyChanged;
}
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Components/ParticipantCountDisplay.cs b/osu.Game/Screens/OnlinePlay/Components/ParticipantCountDisplay.cs
index 9f7e700ab3..db9cf3f92d 100644
--- a/osu.Game/Screens/OnlinePlay/Components/ParticipantCountDisplay.cs
+++ b/osu.Game/Screens/OnlinePlay/Components/ParticipantCountDisplay.cs
@@ -1,33 +1,36 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
+using System.ComponentModel;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
+using osu.Game.Online.Rooms;
namespace osu.Game.Screens.OnlinePlay.Components
{
- public partial class ParticipantCountDisplay : OnlinePlayComposite
+ public partial class ParticipantCountDisplay : CompositeDrawable
{
private const float text_size = 30;
private const float transition_duration = 100;
- private OsuSpriteText slash, maxText;
+ private readonly Room room;
- public ParticipantCountDisplay()
+ private OsuSpriteText slash = null!;
+ private OsuSpriteText maxText = null!;
+ private OsuSpriteText count = null!;
+
+ public ParticipantCountDisplay(Room room)
{
+ this.room = room;
AutoSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load()
{
- OsuSpriteText count;
-
InternalChild = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
@@ -50,14 +53,33 @@ namespace osu.Game.Screens.OnlinePlay.Components
},
}
};
-
- MaxParticipants.BindValueChanged(_ => updateMax(), true);
- ParticipantCount.BindValueChanged(c => count.Text = c.NewValue.ToString("#,0"), true);
}
- private void updateMax()
+ protected override void LoadComplete()
{
- if (MaxParticipants.Value == null)
+ base.LoadComplete();
+
+ room.PropertyChanged += onRoomPropertyChanged;
+ updateRoomParticipantCount();
+ }
+
+ private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
+ {
+ switch (e.PropertyName)
+ {
+ case nameof(Room.MaxParticipants):
+ updateRoomMaxParticipants();
+ break;
+
+ case nameof(Room.ParticipantCount):
+ updateRoomParticipantCount();
+ break;
+ }
+ }
+
+ private void updateRoomMaxParticipants()
+ {
+ if (room.MaxParticipants == null)
{
slash.FadeOut(transition_duration);
maxText.FadeOut(transition_duration);
@@ -65,9 +87,18 @@ namespace osu.Game.Screens.OnlinePlay.Components
else
{
slash.FadeIn(transition_duration);
- maxText.Text = MaxParticipants.Value.ToString();
+ maxText.Text = room.MaxParticipants.ToString()!;
maxText.FadeIn(transition_duration);
}
}
+
+ private void updateRoomParticipantCount()
+ => count.Text = room.ParticipantCount.ToString("#,0");
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+ room.PropertyChanged -= onRoomPropertyChanged;
+ }
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Components/ParticipantsDisplay.cs b/osu.Game/Screens/OnlinePlay/Components/ParticipantsDisplay.cs
index 5128bc4c14..a12d843b0a 100644
--- a/osu.Game/Screens/OnlinePlay/Components/ParticipantsDisplay.cs
+++ b/osu.Game/Screens/OnlinePlay/Components/ParticipantsDisplay.cs
@@ -1,25 +1,30 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Framework.Allocation;
+using System.ComponentModel;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
using osu.Game.Graphics.Containers;
+using osu.Game.Online.Rooms;
namespace osu.Game.Screens.OnlinePlay.Components
{
- public partial class ParticipantsDisplay : OnlinePlayComposite
+ public partial class ParticipantsDisplay : CompositeDrawable
{
- public Bindable Details = new Bindable();
+ public readonly Bindable Details = new Bindable();
- public ParticipantsDisplay(Direction direction)
+ private readonly Room room;
+
+ public ParticipantsDisplay(Room room, Direction direction)
{
+ this.room = room;
OsuScrollContainer scroll;
ParticipantsList list;
AddInternal(scroll = new OsuScrollContainer(direction)
{
- Child = list = new ParticipantsList()
+ Child = list = new ParticipantsList(room)
});
switch (direction)
@@ -46,14 +51,32 @@ namespace osu.Game.Screens.OnlinePlay.Components
}
}
- [BackgroundDependencyLoader]
- private void load()
+ protected override void LoadComplete()
{
- ParticipantCount.BindValueChanged(_ => setParticipantCount());
- MaxParticipants.BindValueChanged(_ => setParticipantCount(), true);
+ base.LoadComplete();
+
+ room.PropertyChanged += onRoomPropertyChanged;
+ updateRoomParticipantCount();
}
- private void setParticipantCount() =>
- Details.Value = MaxParticipants.Value != null ? $"{ParticipantCount.Value}/{MaxParticipants.Value}" : ParticipantCount.Value.ToString();
+ private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
+ {
+ switch (e.PropertyName)
+ {
+ case nameof(Room.MaxParticipants):
+ case nameof(Room.ParticipantCount):
+ updateRoomParticipantCount();
+ break;
+ }
+ }
+
+ private void updateRoomParticipantCount()
+ => Details.Value = room.MaxParticipants != null ? $"{room.ParticipantCount}/{room.MaxParticipants}" : room.ParticipantCount.ToString();
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+ room.PropertyChanged -= onRoomPropertyChanged;
+ }
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Components/ParticipantsList.cs b/osu.Game/Screens/OnlinePlay/Components/ParticipantsList.cs
index c4aefe4f99..79084a5285 100644
--- a/osu.Game/Screens/OnlinePlay/Components/ParticipantsList.cs
+++ b/osu.Game/Screens/OnlinePlay/Components/ParticipantsList.cs
@@ -1,21 +1,20 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
-using osu.Framework.Allocation;
+using System.ComponentModel;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Threading;
using osu.Game.Online.API.Requests.Responses;
+using osu.Game.Online.Rooms;
using osu.Game.Users.Drawables;
using osuTK;
namespace osu.Game.Screens.OnlinePlay.Components
{
- public partial class ParticipantsList : OnlinePlayComposite
+ public partial class ParticipantsList : CompositeDrawable
{
public const float TILE_SIZE = 35;
@@ -57,15 +56,29 @@ namespace osu.Game.Screens.OnlinePlay.Components
}
}
- [BackgroundDependencyLoader]
- private void load()
+ private readonly Room room;
+
+ public ParticipantsList(Room room)
{
- RecentParticipants.CollectionChanged += (_, _) => updateParticipants();
+ this.room = room;
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ room.PropertyChanged += onRoomPropertyChanged;
updateParticipants();
}
- private ScheduledDelegate scheduledUpdate;
- private FillFlowContainer tiles;
+ private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == nameof(Room.RecentParticipants))
+ updateParticipants();
+ }
+
+ private ScheduledDelegate? scheduledUpdate;
+ private FillFlowContainer? tiles;
private void updateParticipants()
{
@@ -83,8 +96,8 @@ namespace osu.Game.Screens.OnlinePlay.Components
Spacing = Vector2.One
};
- for (int i = 0; i < RecentParticipants.Count; i++)
- tiles.Add(new UserTile { User = RecentParticipants[i] });
+ for (int i = 0; i < room.RecentParticipants.Count; i++)
+ tiles.Add(new UserTile { User = room.RecentParticipants[i] });
AddInternal(tiles);
@@ -92,9 +105,15 @@ namespace osu.Game.Screens.OnlinePlay.Components
});
}
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+ room.PropertyChanged -= onRoomPropertyChanged;
+ }
+
private partial class UserTile : CompositeDrawable
{
- public APIUser User
+ public APIUser? User
{
get => avatar.User;
set => avatar.User = value;
diff --git a/osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs b/osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs
index 0c3b53266c..39b5edbd26 100644
--- a/osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs
+++ b/osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs
@@ -1,26 +1,28 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
+using System.ComponentModel;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
+using osu.Game.Online.Rooms;
namespace osu.Game.Screens.OnlinePlay.Components
{
- public partial class RoomLocalUserInfo : OnlinePlayComposite
+ public partial class RoomLocalUserInfo : CompositeDrawable
{
- private OsuSpriteText attemptDisplay;
+ private readonly Room room;
+ private OsuSpriteText attemptDisplay = null!;
[Resolved]
- private OsuColour colours { get; set; }
+ private OsuColour colours { get; set; } = null!;
- public RoomLocalUserInfo()
+ public RoomLocalUserInfo(Room room)
{
+ this.room = room;
AutoSizeAxes = Axes.Both;
}
@@ -45,19 +47,30 @@ namespace osu.Game.Screens.OnlinePlay.Components
{
base.LoadComplete();
- MaxAttempts.BindValueChanged(_ => updateAttempts());
- UserScore.BindValueChanged(_ => updateAttempts(), true);
+ room.PropertyChanged += onRoomPropertyChanged;
+ updateAttempts();
+ }
+
+ private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
+ {
+ switch (e.PropertyName)
+ {
+ case nameof(Room.UserScore):
+ case nameof(Room.MaxAttempts):
+ updateAttempts();
+ break;
+ }
}
private void updateAttempts()
{
- if (MaxAttempts.Value != null)
+ if (room.MaxAttempts != null)
{
- attemptDisplay.Text = $"Maximum attempts: {MaxAttempts.Value:N0}";
+ attemptDisplay.Text = $"Maximum attempts: {room.MaxAttempts:N0}";
- if (UserScore.Value != null)
+ if (room.UserScore != null)
{
- int remaining = MaxAttempts.Value.Value - UserScore.Value.PlaylistItemAttempts.Sum(a => a.Attempts);
+ int remaining = room.MaxAttempts.Value - room.UserScore.PlaylistItemAttempts.Sum(a => a.Attempts);
attemptDisplay.Text += $" ({remaining} remaining)";
if (remaining == 0)
@@ -69,5 +82,11 @@ namespace osu.Game.Screens.OnlinePlay.Components
attemptDisplay.Text = string.Empty;
}
}
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+ room.PropertyChanged -= onRoomPropertyChanged;
+ }
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs
index cb27d1ee61..73f980f0a3 100644
--- a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs
+++ b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs
@@ -1,13 +1,10 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
-using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Development;
@@ -20,18 +17,17 @@ namespace osu.Game.Screens.OnlinePlay.Components
{
public partial class RoomManager : Component, IRoomManager
{
- [CanBeNull]
- public event Action RoomsUpdated;
+ public event Action? RoomsUpdated;
private readonly BindableList rooms = new BindableList();
public IBindableList Rooms => rooms;
- protected IBindable JoinedRoom => joinedRoom;
- private readonly Bindable joinedRoom = new Bindable();
+ protected IBindable JoinedRoom => joinedRoom;
+ private readonly Bindable joinedRoom = new Bindable();
[Resolved]
- private IAPIProvider api { get; set; }
+ private IAPIProvider api { get; set; } = null!;
public RoomManager()
{
@@ -44,9 +40,9 @@ namespace osu.Game.Screens.OnlinePlay.Components
PartRoom();
}
- public virtual void CreateRoom(Room room, Action onSuccess = null, Action onError = null)
+ public virtual void CreateRoom(Room room, Action? onSuccess = null, Action? onError = null)
{
- room.Host.Value = api.LocalUser.Value;
+ room.Host = api.LocalUser.Value;
var req = new CreateRoomRequest(room);
@@ -69,16 +65,20 @@ namespace osu.Game.Screens.OnlinePlay.Components
api.Queue(req);
}
- private JoinRoomRequest currentJoinRoomRequest;
+ private JoinRoomRequest? currentJoinRoomRequest;
- public virtual void JoinRoom(Room room, string password = null, Action onSuccess = null, Action onError = null)
+ public virtual void JoinRoom(Room room, string? password = null, Action? onSuccess = null, Action? onError = null)
{
currentJoinRoomRequest?.Cancel();
currentJoinRoomRequest = new JoinRoomRequest(room, password);
- currentJoinRoomRequest.Success += () =>
+ currentJoinRoomRequest.Success += result =>
{
joinedRoom.Value = room;
+
+ AddOrUpdateRoom(result);
+ room.CopyFrom(result); // Also copy back to the source model, since this is likely to have been stored elsewhere.
+
onSuccess?.Invoke(room);
};
@@ -97,7 +97,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
{
currentJoinRoomRequest?.Cancel();
- if (JoinedRoom.Value == null)
+ if (joinedRoom.Value == null)
return;
if (api.State.Value == APIState.Online)
@@ -111,14 +111,14 @@ namespace osu.Game.Screens.OnlinePlay.Components
public void AddOrUpdateRoom(Room room)
{
Debug.Assert(ThreadSafety.IsUpdateThread);
- Debug.Assert(room.RoomID.Value != null);
+ Debug.Assert(room.RoomID != null);
- if (ignoredRooms.Contains(room.RoomID.Value.Value))
+ if (ignoredRooms.Contains(room.RoomID.Value))
return;
try
{
- var existing = rooms.FirstOrDefault(e => e.RoomID.Value == room.RoomID.Value);
+ var existing = rooms.FirstOrDefault(e => e.RoomID == room.RoomID);
if (existing == null)
rooms.Add(room);
else
@@ -126,9 +126,9 @@ namespace osu.Game.Screens.OnlinePlay.Components
}
catch (Exception ex)
{
- Logger.Error(ex, $"Failed to update room: {room.Name.Value}.");
+ Logger.Error(ex, $"Failed to update room: {room.Name}.");
- ignoredRooms.Add(room.RoomID.Value.Value);
+ ignoredRooms.Add(room.RoomID.Value);
rooms.Remove(room);
}
diff --git a/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.cs b/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.cs
index 780ee29e41..7cee8b3546 100644
--- a/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.cs
+++ b/osu.Game/Screens/OnlinePlay/Components/SelectionPollingComponent.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System.Threading.Tasks;
using osu.Game.Online.Rooms;
@@ -20,25 +18,24 @@ namespace osu.Game.Screens.OnlinePlay.Components
this.room = room;
}
- private GetRoomRequest lastPollRequest;
+ private GetRoomRequest? lastPollRequest;
protected override Task Poll()
{
if (!API.IsLoggedIn)
return base.Poll();
- if (room.RoomID.Value == null)
+ if (room.RoomID == null)
return base.Poll();
var tcs = new TaskCompletionSource();
lastPollRequest?.Cancel();
- var req = new GetRoomRequest(room.RoomID.Value.Value);
+ var req = new GetRoomRequest(room.RoomID.Value);
req.Success += result =>
{
- result.RemoveExpiredPlaylistItems();
RoomManager.AddOrUpdateRoom(result);
tcs.SetResult(true);
};
diff --git a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs
index 2ee3bb30dd..2bdb41ce12 100644
--- a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs
+++ b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs
@@ -1,9 +1,8 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
+using System.ComponentModel;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
@@ -13,22 +12,27 @@ using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics;
+using osu.Game.Online.Rooms;
using osuTK;
+using Container = osu.Framework.Graphics.Containers.Container;
namespace osu.Game.Screens.OnlinePlay.Components
{
- public partial class StarRatingRangeDisplay : OnlinePlayComposite
+ public partial class StarRatingRangeDisplay : CompositeDrawable
{
+ private readonly Room room;
+
[Resolved]
- private OsuColour colours { get; set; }
+ private OsuColour colours { get; set; } = null!;
- private StarRatingDisplay minDisplay;
- private Drawable minBackground;
- private StarRatingDisplay maxDisplay;
- private Drawable maxBackground;
+ private StarRatingDisplay minDisplay = null!;
+ private Drawable minBackground = null!;
+ private StarRatingDisplay maxDisplay = null!;
+ private Drawable maxBackground = null!;
- public StarRatingRangeDisplay()
+ public StarRatingRangeDisplay(Room room)
{
+ this.room = room;
AutoSizeAxes = Axes.Both;
}
@@ -76,8 +80,19 @@ namespace osu.Game.Screens.OnlinePlay.Components
{
base.LoadComplete();
- DifficultyRange.BindValueChanged(_ => updateRange());
- Playlist.BindCollectionChanged((_, _) => updateRange(), true);
+ room.PropertyChanged += onRoomPropertyChanged;
+ updateRange();
+ }
+
+ private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
+ {
+ switch (e.PropertyName)
+ {
+ case nameof(Room.Playlist):
+ case nameof(Room.DifficultyRange):
+ updateRange();
+ break;
+ }
}
private void updateRange()
@@ -85,16 +100,16 @@ namespace osu.Game.Screens.OnlinePlay.Components
StarDifficulty minDifficulty;
StarDifficulty maxDifficulty;
- if (DifficultyRange.Value != null && Playlist.Count == 0)
+ if (room.DifficultyRange != null && room.Playlist.Count == 0)
{
// When Playlist is empty (in lounge) we take retrieved range
- minDifficulty = new StarDifficulty(DifficultyRange.Value.Min, 0);
- maxDifficulty = new StarDifficulty(DifficultyRange.Value.Max, 0);
+ minDifficulty = new StarDifficulty(room.DifficultyRange.Min, 0);
+ maxDifficulty = new StarDifficulty(room.DifficultyRange.Max, 0);
}
else
{
// When Playlist is not empty (in room) we compute actual range
- var orderedDifficulties = Playlist.Select(p => p.Beatmap).OrderBy(b => b.StarRating).ToArray();
+ var orderedDifficulties = room.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);
@@ -107,5 +122,11 @@ namespace osu.Game.Screens.OnlinePlay.Components
minBackground.Colour = colours.ForStarDifficulty(minDifficulty.Stars);
maxBackground.Colour = colours.ForStarDifficulty(maxDifficulty.Stars);
}
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+ room.PropertyChanged -= onRoomPropertyChanged;
+ }
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Components/StatusColouredContainer.cs b/osu.Game/Screens/OnlinePlay/Components/StatusColouredContainer.cs
index ed39021a73..2b1233506f 100644
--- a/osu.Game/Screens/OnlinePlay/Components/StatusColouredContainer.cs
+++ b/osu.Game/Screens/OnlinePlay/Components/StatusColouredContainer.cs
@@ -1,39 +1,52 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
+using System.ComponentModel;
using osu.Framework.Allocation;
-using osu.Framework.Bindables;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Online.Rooms;
+using Container = osu.Framework.Graphics.Containers.Container;
namespace osu.Game.Screens.OnlinePlay.Components
{
public partial class StatusColouredContainer : Container
{
+ [Resolved]
+ private OsuColour colours { get; set; } = null!;
+
private readonly double transitionDuration;
+ private readonly Room room;
- [Resolved(typeof(Room), nameof(Room.Status))]
- private Bindable status { get; set; }
-
- [Resolved(typeof(Room), nameof(Room.Category))]
- private Bindable category { get; set; }
-
- public StatusColouredContainer(double transitionDuration = 100)
+ public StatusColouredContainer(Room room, double transitionDuration = 100)
{
+ this.room = room;
this.transitionDuration = transitionDuration;
}
- [BackgroundDependencyLoader]
- private void load(OsuColour colours)
+ protected override void LoadComplete()
{
- status.BindValueChanged(s =>
- {
- this.FadeColour(colours.ForRoomCategory(category.Value) ?? s.NewValue.GetAppropriateColour(colours), transitionDuration);
- }, true);
+ base.LoadComplete();
+
+ room.PropertyChanged += onRoomPropertyChanged;
+ updateRoomStatus();
+ }
+
+ private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == nameof(Room.Status))
+ updateRoomStatus();
+ }
+
+ private void updateRoomStatus()
+ {
+ this.FadeColour(colours.ForRoomCategory(room.Category) ?? room.Status.GetAppropriateColour(colours), transitionDuration);
+ }
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+ room.PropertyChanged -= onRoomPropertyChanged;
}
}
}
diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs
index 1aaf0a4321..0dc7e7930a 100644
--- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs
+++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs
@@ -119,14 +119,6 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
Padding = new MarginPadding { Horizontal = -HORIZONTAL_OVERFLOW_PADDING };
}
- protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
- {
- return new CachedModelDependencyContainer(base.CreateChildDependencies(parent))
- {
- Model = { Value = room }
- };
- }
-
[BackgroundDependencyLoader]
private void load(AudioManager audio)
{
@@ -228,7 +220,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
Origin = Anchor.Centre,
Children = new Drawable[]
{
- new DailyChallengeTimeRemainingRing(),
+ new DailyChallengeTimeRemainingRing(room),
breakdown = new DailyChallengeScoreBreakdown(),
totals = new DailyChallengeTotalsDisplay(),
}
@@ -301,7 +293,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
Spacing = new Vector2(10),
Children = new Drawable[]
{
- new PlaylistsReadyButton
+ new PlaylistsReadyButton(room)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -353,12 +345,12 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
private void presentScore(long id)
{
if (this.IsCurrentScreen())
- this.Push(new PlaylistItemScoreResultsScreen(room.RoomID.Value!.Value, playlistItem, id));
+ this.Push(new PlaylistItemScoreResultsScreen(room.RoomID!.Value, playlistItem, id));
}
private void onRoomScoreSet(MultiplayerRoomScoreSetEvent e)
{
- if (e.RoomID != room.RoomID.Value || e.PlaylistItemID != playlistItem.ID)
+ if (e.RoomID != room.RoomID || e.PlaylistItemID != playlistItem.ID)
return;
userLookupCache.GetUserAsync(e.UserID).ContinueWith(t =>
@@ -410,7 +402,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
private void dailyChallengeChanged(ValueChangedEvent change)
{
- if (change.OldValue?.RoomID == room.RoomID.Value && change.NewValue == null && metadataClient.IsConnected.Value)
+ if (change.OldValue?.RoomID == room.RoomID && change.NewValue == null && metadataClient.IsConnected.Value)
{
notificationOverlay?.Post(new SimpleNotification { Text = DailyChallengeStrings.ChallengeEndedNotification });
}
@@ -437,7 +429,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
roomManager.JoinRoom(room);
startLoopingTrack(this, musicController);
- metadataClient.BeginWatchingMultiplayerRoom(room.RoomID.Value!.Value).ContinueWith(t =>
+ metadataClient.BeginWatchingMultiplayerRoom(room.RoomID!.Value).ContinueWith(t =>
{
if (t.Exception != null)
{
@@ -489,7 +481,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
this.Delay(WaveContainer.DISAPPEAR_DURATION).FadeOut();
roomManager.PartRoom();
- metadataClient.EndWatchingMultiplayerRoom(room.RoomID.Value!.Value).FireAndForget();
+ metadataClient.EndWatchingMultiplayerRoom(room.RoomID!.Value).FireAndForget();
return base.OnExiting(e);
}
diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs
index 7f0f26097c..7fddb8d1c4 100644
--- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs
+++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs
@@ -169,7 +169,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
- Text = room.Name.Value.Split(':', StringSplitOptions.TrimEntries).Last(),
+ Text = room.Name.Split(':', StringSplitOptions.TrimEntries).Last(),
Margin = new MarginPadding { Horizontal = 10f, Vertical = 5f },
Shear = new Vector2(-OsuGame.SHEAR, 0f),
Font = OsuFont.GetFont(size: 32, weight: FontWeight.Light, typeface: Typeface.TorusAlternate),
diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs
index c9152393e7..9fe2b70a5a 100644
--- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs
+++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs
@@ -138,7 +138,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
if (request?.CompletionState == APIRequestCompletionState.Waiting)
return;
- request = new IndexPlaylistScoresRequest(room.RoomID.Value!.Value, playlistItem.ID);
+ request = new IndexPlaylistScoresRequest(room.RoomID!.Value, playlistItem.ID);
request.Success += req => Schedule(() =>
{
diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeTimeRemainingRing.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeTimeRemainingRing.cs
index e86f26ad6b..bf01ee6b52 100644
--- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeTimeRemainingRing.cs
+++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeTimeRemainingRing.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.ComponentModel;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -10,15 +11,15 @@ using osu.Framework.Threading;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Online.Rooms;
using osu.Game.Overlays;
using osuTK;
namespace osu.Game.Screens.OnlinePlay.DailyChallenge
{
- public partial class DailyChallengeTimeRemainingRing : OnlinePlayComposite
+ public partial class DailyChallengeTimeRemainingRing : CompositeDrawable
{
- private CircularProgress progress = null!;
- private OsuSpriteText timeText = null!;
+ private readonly Room room;
[Resolved]
private OverlayColourProvider colourProvider { get; set; } = null!;
@@ -26,6 +27,14 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
[Resolved]
private OsuColour colours { get; set; } = null!;
+ private CircularProgress progress = null!;
+ private OsuSpriteText timeText = null!;
+
+ public DailyChallengeTimeRemainingRing(Room room)
+ {
+ this.room = room;
+ }
+
[BackgroundDependencyLoader]
private void load()
{
@@ -90,12 +99,23 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
{
base.LoadComplete();
- StartDate.BindValueChanged(_ => Scheduler.AddOnce(updateState));
- EndDate.BindValueChanged(_ => Scheduler.AddOnce(updateState));
+ room.PropertyChanged += onRoomPropertyChanged;
updateState();
+
FinishTransforms(true);
}
+ private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
+ {
+ switch (e.PropertyName)
+ {
+ case nameof(Room.StartDate):
+ case nameof(Room.EndDate):
+ Scheduler.AddOnce(updateState);
+ break;
+ }
+ }
+
private ScheduledDelegate? scheduledUpdate;
private void updateState()
@@ -105,7 +125,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
const float transition_duration = 300;
- if (StartDate.Value == null || EndDate.Value == null || EndDate.Value < DateTimeOffset.Now)
+ if (room.StartDate == null || room.EndDate == null || room.EndDate < DateTimeOffset.Now)
{
timeText.Text = TimeSpan.Zero.ToString(@"hh\:mm\:ss");
progress.Progress = 0;
@@ -114,8 +134,8 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
return;
}
- var roomDuration = EndDate.Value.Value - StartDate.Value.Value;
- var remaining = EndDate.Value.Value - DateTimeOffset.Now;
+ var roomDuration = room.EndDate.Value - room.StartDate.Value;
+ var remaining = room.EndDate.Value - DateTimeOffset.Now;
timeText.Text = remaining.ToString(@"hh\:mm\:ss");
progress.Progress = remaining.TotalSeconds / roomDuration.TotalSeconds;
@@ -138,5 +158,11 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
scheduledUpdate = Scheduler.AddDelayed(updateState, 1000);
}
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+ room.PropertyChanged -= onRoomPropertyChanged;
+ }
}
}
diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs
index 5a1648c91f..207e0bdf55 100644
--- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs
+++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using System.Linq;
using osu.Framework.Bindables;
@@ -26,22 +24,22 @@ namespace osu.Game.Screens.OnlinePlay
/// The currently-selected item. Selection is visually represented with a border.
/// May be updated by clicking playlist items if is true.
///
- public readonly Bindable SelectedItem = new Bindable();
+ public readonly Bindable SelectedItem = new Bindable();
///
/// Invoked when an item is requested to be deleted.
///
- public Action RequestDeletion;
+ public Action? RequestDeletion;
///
/// Invoked when an item requests its results to be shown.
///
- public Action RequestResults;
+ public Action? RequestResults;
///
/// Invoked when an item requests to be edited.
///
- public Action RequestEdit;
+ public Action? RequestEdit;
private bool allowReordering;
@@ -235,7 +233,7 @@ namespace osu.Game.Screens.OnlinePlay
{
var visibleItems = ListContainer.AsEnumerable().Where(r => r.IsPresent);
- PlaylistItem item;
+ PlaylistItem? item;
if (SelectedItem.Value == null)
item = visibleItems.FirstOrDefault()?.Model;
diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs
index 43ffaf947e..7a773bb116 100644
--- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs
+++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using System.Collections.Generic;
using System.Linq;
@@ -54,23 +52,23 @@ namespace osu.Game.Screens.OnlinePlay
///
/// Invoked when this item requests to be deleted.
///
- public Action RequestDeletion;
+ public Action? RequestDeletion;
///
/// Invoked when this item requests its results to be shown.
///
- public Action RequestResults;
+ public Action? RequestResults;
///
/// Invoked when this item requests to be edited.
///
- public Action RequestEdit;
+ public Action? RequestEdit;
///
/// The currently-selected item, used to show a border around this item.
/// May be updated by this item if is true.
///
- public readonly Bindable SelectedItem = new Bindable();
+ public readonly Bindable SelectedItem = new Bindable();
public readonly PlaylistItem Item;
@@ -79,48 +77,48 @@ namespace osu.Game.Screens.OnlinePlay
private readonly DelayedLoadWrapper onScreenLoader = new DelayedLoadWrapper(Empty) { RelativeSizeAxes = Axes.Both };
private readonly IBindable valid = new Bindable();
- private IBeatmapInfo beatmap;
- private IRulesetInfo ruleset;
+ private IBeatmapInfo? beatmap;
+ private IRulesetInfo? ruleset;
private Mod[] requiredMods = Array.Empty();
- private Container borderContainer;
- private FillFlowContainer difficultyIconContainer;
- private LinkFlowContainer beatmapText;
- private LinkFlowContainer authorText;
- private ExplicitContentBeatmapBadge explicitContent;
- private ModDisplay modDisplay;
- private FillFlowContainer buttonsFlow;
- private UpdateableAvatar ownerAvatar;
- private Drawable showResultsButton;
- private Drawable editButton;
- private Drawable removeButton;
- private PanelBackground panelBackground;
- private FillFlowContainer mainFillFlow;
- private BeatmapCardThumbnail thumbnail;
+ private Container? borderContainer;
+ private FillFlowContainer? difficultyIconContainer;
+ private LinkFlowContainer? beatmapText;
+ private LinkFlowContainer? authorText;
+ private ExplicitContentBeatmapBadge? explicitContent;
+ private ModDisplay? modDisplay;
+ private FillFlowContainer? buttonsFlow;
+ private UpdateableAvatar? ownerAvatar;
+ private Drawable? showResultsButton;
+ private Drawable? editButton;
+ private Drawable? removeButton;
+ private PanelBackground? panelBackground;
+ private FillFlowContainer? mainFillFlow;
+ private BeatmapCardThumbnail? thumbnail;
[Resolved]
- private RealmAccess realm { get; set; }
+ private RealmAccess realm { get; set; } = null!;
[Resolved]
- private RulesetStore rulesets { get; set; }
+ private RulesetStore rulesets { get; set; } = null!;
[Resolved]
- private BeatmapManager beatmaps { get; set; }
+ private BeatmapManager beatmaps { get; set; } = null!;
[Resolved]
- private OsuColour colours { get; set; }
+ private OsuColour colours { get; set; } = null!;
[Resolved]
- private UserLookupCache userLookupCache { get; set; }
+ private UserLookupCache userLookupCache { get; set; } = null!;
[Resolved]
- private BeatmapLookupCache beatmapLookupCache { get; set; }
+ private BeatmapLookupCache beatmapLookupCache { get; set; } = null!;
[Resolved(CanBeNull = true)]
- private BeatmapSetOverlay beatmapOverlay { get; set; }
+ private BeatmapSetOverlay? beatmapOverlay { get; set; }
[Resolved(CanBeNull = true)]
- private ManageCollectionsDialog manageCollectionsDialog { get; set; }
+ private ManageCollectionsDialog? manageCollectionsDialog { get; set; }
public DrawableRoomPlaylistItem(PlaylistItem item)
: base(item)
@@ -136,7 +134,8 @@ namespace osu.Game.Screens.OnlinePlay
[BackgroundDependencyLoader]
private void load()
{
- borderContainer.BorderColour = colours.Yellow;
+ if (borderContainer != null)
+ borderContainer.BorderColour = colours.Yellow;
ruleset = rulesets.GetRuleset(Item.RulesetID);
var rulesetInstance = ruleset?.CreateInstance();
@@ -163,7 +162,8 @@ namespace osu.Game.Screens.OnlinePlay
return;
}
- borderContainer.BorderThickness = IsSelectedItem ? border_thickness : 0;
+ if (borderContainer != null)
+ borderContainer.BorderThickness = IsSelectedItem ? border_thickness : 0;
}, true);
valid.BindValueChanged(_ => Scheduler.AddOnce(refresh));
@@ -177,7 +177,11 @@ namespace osu.Game.Screens.OnlinePlay
if (showItemOwner)
{
var foundUser = await userLookupCache.GetUserAsync(Item.OwnerID).ConfigureAwait(false);
- Schedule(() => ownerAvatar.User = foundUser);
+ Schedule(() =>
+ {
+ if (ownerAvatar != null)
+ ownerAvatar.User = foundUser;
+ });
}
beatmap = await beatmapLookupCache.GetBeatmapAsync(Item.Beatmap.OnlineID).ConfigureAwait(false);
@@ -278,69 +282,89 @@ namespace osu.Game.Screens.OnlinePlay
private void refresh()
{
- if (!valid.Value)
+ if (borderContainer != null)
{
- borderContainer.BorderThickness = border_thickness;
- borderContainer.BorderColour = colours.Red;
- }
-
- if (beatmap != null)
- {
- difficultyIconContainer.Children = new Drawable[]
+ if (!valid.Value)
{
- thumbnail = new BeatmapCardThumbnail(beatmap.BeatmapSet!, (IBeatmapSetOnlineInfo)beatmap.BeatmapSet!)
- {
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.CentreLeft,
- Width = 60,
- Masking = true,
- CornerRadius = 10,
- RelativeSizeAxes = Axes.Y,
- Dimmed = { Value = IsHovered }
- },
- new DifficultyIcon(beatmap, ruleset, requiredMods)
- {
- Size = new Vector2(24),
- TooltipType = DifficultyIconTooltipType.Extended,
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.CentreLeft,
- },
- };
+ borderContainer.BorderThickness = border_thickness;
+ borderContainer.BorderColour = colours.Red;
+ }
}
- else
- difficultyIconContainer.Clear();
- panelBackground.Beatmap.Value = beatmap;
-
- beatmapText.Clear();
-
- if (beatmap != null)
+ if (difficultyIconContainer != null)
{
- beatmapText.AddLink(beatmap.GetDisplayTitleRomanisable(includeCreator: false),
- LinkAction.OpenBeatmap,
- beatmap.OnlineID.ToString(),
- null,
- text =>
+ if (beatmap != null)
+ {
+ difficultyIconContainer.Children = new Drawable[]
{
- text.Truncate = true;
- });
+ thumbnail = new BeatmapCardThumbnail(beatmap.BeatmapSet!, (IBeatmapSetOnlineInfo)beatmap.BeatmapSet!)
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ Width = 60,
+ Masking = true,
+ CornerRadius = 10,
+ RelativeSizeAxes = Axes.Y,
+ Dimmed = { Value = IsHovered }
+ },
+ new DifficultyIcon(beatmap, ruleset, requiredMods)
+ {
+ Size = new Vector2(24),
+ TooltipType = DifficultyIconTooltipType.Extended,
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ },
+ };
+ }
+ else
+ difficultyIconContainer.Clear();
}
- authorText.Clear();
+ if (panelBackground != null)
+ panelBackground.Beatmap.Value = beatmap;
- if (!string.IsNullOrEmpty(beatmap?.Metadata.Author.Username))
+ if (beatmapText != null)
{
- authorText.AddText("mapped by ");
- authorText.AddUserLink(beatmap.Metadata.Author);
+ beatmapText.Clear();
+
+ if (beatmap != null)
+ {
+ beatmapText.AddLink(beatmap.GetDisplayTitleRomanisable(includeCreator: false),
+ LinkAction.OpenBeatmap,
+ beatmap.OnlineID.ToString(),
+ null,
+ text =>
+ {
+ text.Truncate = true;
+ });
+ }
}
- bool hasExplicitContent = (beatmap?.BeatmapSet as IBeatmapSetOnlineInfo)?.HasExplicitContent == true;
- explicitContent.Alpha = hasExplicitContent ? 1 : 0;
+ if (authorText != null)
+ {
+ authorText.Clear();
- modDisplay.Current.Value = requiredMods.ToArray();
+ if (!string.IsNullOrEmpty(beatmap?.Metadata.Author.Username))
+ {
+ authorText.AddText("mapped by ");
+ authorText.AddUserLink(beatmap.Metadata.Author);
+ }
+ }
- buttonsFlow.Clear();
- buttonsFlow.ChildrenEnumerable = createButtons();
+ if (explicitContent != null)
+ {
+ bool hasExplicitContent = (beatmap?.BeatmapSet as IBeatmapSetOnlineInfo)?.HasExplicitContent == true;
+ explicitContent.Alpha = hasExplicitContent ? 1 : 0;
+ }
+
+ if (modDisplay != null)
+ modDisplay.Current.Value = requiredMods.ToArray();
+
+ if (buttonsFlow != null)
+ {
+ buttonsFlow.Clear();
+ buttonsFlow.ChildrenEnumerable = createButtons();
+ }
difficultyIconContainer.FadeInFromZero(500, Easing.OutQuint);
mainFillFlow.FadeInFromZero(500, Easing.OutQuint);
@@ -601,7 +625,7 @@ namespace osu.Game.Screens.OnlinePlay
private readonly IBeatmapInfo beatmap;
[Resolved]
- private BeatmapManager beatmapManager { get; set; }
+ private BeatmapManager beatmapManager { get; set; } = null!;
// required for download tracking, as this button hides itself. can probably be removed with a bit of consideration.
public override bool IsPresent => true;
@@ -656,7 +680,7 @@ namespace osu.Game.Screens.OnlinePlay
// For now, this is the same implementation as in PanelBackground, but supports a beatmap info rather than a working beatmap
private partial class PanelBackground : Container // todo: should be a buffered container (https://github.com/ppy/osu-framework/issues/3222)
{
- public readonly Bindable Beatmap = new Bindable();
+ public readonly Bindable Beatmap = new Bindable();
public PanelBackground()
{
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs
index ef06d21655..c39ca347c7 100644
--- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs
+++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs
@@ -1,9 +1,9 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics;
using System.Threading;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
@@ -16,6 +16,7 @@ using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.Drawables;
using osu.Game.Database;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
@@ -26,30 +27,33 @@ using osu.Game.Overlays;
using osu.Game.Screens.OnlinePlay.Components;
using osuTK;
using osuTK.Graphics;
+using Container = osu.Framework.Graphics.Containers.Container;
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
- public partial class DrawableRoom : CompositeDrawable
+ public abstract partial class DrawableRoom : CompositeDrawable
{
protected const float CORNER_RADIUS = 10;
private const float height = 100;
public readonly Room Room;
- protected Container ButtonsContainer { get; private set; }
+ protected readonly Bindable SelectedItem = new Bindable();
+ protected Container ButtonsContainer { get; private set; } = null!;
private readonly Bindable roomType = new Bindable();
private readonly Bindable roomCategory = new Bindable();
private readonly Bindable hasPassword = new Bindable();
- private DrawableRoomParticipantsList drawableRoomParticipantsList;
- private RoomSpecialCategoryPill specialCategoryPill;
- private PasswordProtectedIcon passwordIcon;
- private EndDateInfo endDateInfo;
+ private DrawableRoomParticipantsList? drawableRoomParticipantsList;
+ private RoomSpecialCategoryPill? specialCategoryPill;
+ private PasswordProtectedIcon? passwordIcon;
+ private EndDateInfo? endDateInfo;
+ private SpriteText? roomName;
+ private UpdateableBeatmapBackgroundSprite background = null!;
+ private DelayedLoadWrapper wrapper = null!;
- private DelayedLoadWrapper wrapper;
-
- public DrawableRoom(Room room)
+ protected DrawableRoom(Room room)
{
Room = room;
@@ -77,7 +81,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
AutoSizeAxes = Axes.X
};
- InternalChildren = new[]
+ InternalChildren = new Drawable[]
{
// This resolves internal 1px gaps due to applying the (parenting) corner radius and masking across multiple filling background sprites.
new Box
@@ -85,7 +89,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
RelativeSizeAxes = Axes.Both,
Colour = colours.Background5,
},
- CreateBackground().With(d =>
+ background = CreateBackground().With(d =>
{
d.RelativeSizeAxes = Axes.Both;
}),
@@ -155,17 +159,17 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
Spacing = new Vector2(5),
Children = new Drawable[]
{
- new RoomStatusPill
+ new RoomStatusPill(Room)
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft
},
- specialCategoryPill = new RoomSpecialCategoryPill
+ specialCategoryPill = new RoomSpecialCategoryPill(Room)
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft
},
- endDateInfo = new EndDateInfo
+ endDateInfo = new EndDateInfo(Room)
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
@@ -180,13 +184,15 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
- new TruncatingSpriteText
+ roomName = new TruncatingSpriteText
{
RelativeSizeAxes = Axes.X,
- Font = OsuFont.GetFont(size: 28),
- Current = { BindTarget = Room.Name }
+ Font = OsuFont.GetFont(size: 28)
},
- new RoomStatusText()
+ new RoomStatusText(Room)
+ {
+ SelectedItem = { BindTarget = SelectedItem }
+ }
}
}
},
@@ -218,7 +224,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
Children = new Drawable[]
{
ButtonsContainer,
- drawableRoomParticipantsList = new DrawableRoomParticipantsList
+ drawableRoomParticipantsList = new DrawableRoomParticipantsList(Room)
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
@@ -243,36 +249,71 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
base.LoadComplete();
+ Room.PropertyChanged += onRoomPropertyChanged;
+
wrapper.DelayedLoadComplete += _ =>
{
+ Debug.Assert(specialCategoryPill != null);
+ Debug.Assert(endDateInfo != null);
+ Debug.Assert(passwordIcon != null);
+
wrapper.FadeInFromZero(200);
- roomCategory.BindTo(Room.Category);
- roomCategory.BindValueChanged(c =>
- {
- if (c.NewValue > RoomCategory.Normal)
- specialCategoryPill.Show();
- else
- specialCategoryPill.Hide();
- }, true);
-
- roomType.BindTo(Room.Type);
- roomType.BindValueChanged(t =>
- {
- endDateInfo.Alpha = t.NewValue == MatchType.Playlists ? 1 : 0;
- }, true);
-
- hasPassword.BindTo(Room.HasPassword);
- hasPassword.BindValueChanged(v => passwordIcon.Alpha = v.NewValue ? 1 : 0, true);
+ updateRoomName();
+ updateRoomCategory();
+ updateRoomType();
+ updateRoomHasPassword();
};
+
+ SelectedItem.BindValueChanged(item => background.Beatmap.Value = item.NewValue?.Beatmap, true);
}
- protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
+ private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
- return new CachedModelDependencyContainer(base.CreateChildDependencies(parent))
+ switch (e.PropertyName)
{
- Model = { Value = Room }
- };
+ case nameof(Room.Name):
+ updateRoomName();
+ break;
+
+ case nameof(Room.Category):
+ updateRoomCategory();
+ break;
+
+ case nameof(Room.Type):
+ updateRoomType();
+ break;
+
+ case nameof(Room.HasPassword):
+ updateRoomHasPassword();
+ break;
+ }
+ }
+
+ private void updateRoomName()
+ {
+ if (roomName != null)
+ roomName.Text = Room.Name;
+ }
+
+ private void updateRoomCategory()
+ {
+ if (Room.Category > RoomCategory.Normal)
+ specialCategoryPill?.Show();
+ else
+ specialCategoryPill?.Hide();
+ }
+
+ private void updateRoomType()
+ {
+ if (endDateInfo != null)
+ endDateInfo.Alpha = Room.Type == MatchType.Playlists ? 1 : 0;
+ }
+
+ private void updateRoomHasPassword()
+ {
+ if (passwordIcon != null)
+ passwordIcon.Alpha = Room.HasPassword ? 1 : 0;
}
private int numberOfAvatars = 7;
@@ -289,29 +330,29 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
}
}
- protected virtual Drawable CreateBackground() => new OnlinePlayBackgroundSprite();
+ protected virtual UpdateableBeatmapBackgroundSprite CreateBackground() => new UpdateableBeatmapBackgroundSprite();
protected virtual IEnumerable CreateBottomDetails()
{
var pills = new List();
- if (Room.Type.Value != MatchType.Playlists)
+ if (Room.Type != MatchType.Playlists)
{
- pills.AddRange(new OnlinePlayComposite[]
+ pills.AddRange(new Drawable[]
{
- new MatchTypePill(),
- new QueueModePill(),
+ new MatchTypePill(Room),
+ new QueueModePill(Room),
});
}
pills.AddRange(new Drawable[]
{
- new PlaylistCountPill
+ new PlaylistCountPill(Room)
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
},
- new StarRatingRangeDisplay
+ new StarRatingRangeDisplay(Room)
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
@@ -322,19 +363,29 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
return pills;
}
- private partial class RoomStatusText : OnlinePlayComposite
+ protected override void Dispose(bool isDisposing)
{
- [Resolved]
- private OsuColour colours { get; set; }
+ base.Dispose(isDisposing);
+ Room.PropertyChanged -= onRoomPropertyChanged;
+ }
+
+ private partial class RoomStatusText : CompositeDrawable
+ {
+ public readonly IBindable SelectedItem = new Bindable();
[Resolved]
- private BeatmapLookupCache beatmapLookupCache { get; set; }
+ private OsuColour colours { get; set; } = null!;
- private SpriteText statusText;
- private LinkFlowContainer beatmapText;
+ [Resolved]
+ private BeatmapLookupCache beatmapLookupCache { get; set; } = null!;
- public RoomStatusText()
+ private readonly Room room;
+ private SpriteText statusText = null!;
+ private LinkFlowContainer beatmapText = null!;
+
+ public RoomStatusText(Room room)
{
+ this.room = room;
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
}
@@ -383,17 +434,17 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
protected override void LoadComplete()
{
base.LoadComplete();
- CurrentPlaylistItem.BindValueChanged(onSelectedItemChanged, true);
+ SelectedItem.BindValueChanged(onSelectedItemChanged, true);
}
- private CancellationTokenSource beatmapLookupCancellation;
+ private CancellationTokenSource? beatmapLookupCancellation;
- private void onSelectedItemChanged(ValueChangedEvent item)
+ private void onSelectedItemChanged(ValueChangedEvent item)
{
beatmapLookupCancellation?.Cancel();
beatmapText.Clear();
- if (Type.Value == MatchType.Playlists)
+ if (room.Type == MatchType.Playlists)
{
statusText.Text = "Ready to play";
return;
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs
index 60e05285d9..5bcc974c26 100644
--- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs
+++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs
@@ -1,13 +1,10 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
-using System.Collections.Specialized;
-using System.Diagnostics;
+using System.Collections.Generic;
+using System.ComponentModel;
using System.Linq;
using osu.Framework.Allocation;
-using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
@@ -16,31 +13,33 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.API.Requests.Responses;
+using osu.Game.Online.Rooms;
using osu.Game.Overlays;
using osu.Game.Users.Drawables;
using osuTK;
+using Container = osu.Framework.Graphics.Containers.Container;
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
- public partial class DrawableRoomParticipantsList : OnlinePlayComposite
+ public partial class DrawableRoomParticipantsList : CompositeDrawable
{
public const float SHEAR_WIDTH = 12f;
-
private const float avatar_size = 36;
-
private const float height = 60f;
-
private static readonly Vector2 shear = new Vector2(SHEAR_WIDTH / height, 0);
- private FillFlowContainer avatarFlow;
+ private readonly Room room;
- private CircularAvatar hostAvatar;
- private LinkFlowContainer hostText;
- private HiddenUserCount hiddenUsers;
- private OsuSpriteText totalCount;
+ private FillFlowContainer avatarFlow = null!;
+ private CircularAvatar hostAvatar = null!;
+ private LinkFlowContainer hostText = null!;
+ private HiddenUserCount hiddenUsers = null!;
+ private OsuSpriteText totalCount = null!;
- public DrawableRoomParticipantsList()
+ public DrawableRoomParticipantsList(Room room)
{
+ this.room = room;
+
AutoSizeAxes = Axes.X;
Height = height;
}
@@ -165,14 +164,11 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
base.LoadComplete();
- RecentParticipants.BindCollectionChanged(onParticipantsChanged, true);
- ParticipantCount.BindValueChanged(_ =>
- {
- updateHiddenUsers();
- totalCount.Text = ParticipantCount.Value.ToString();
- }, true);
+ room.PropertyChanged += onRoomPropertyChanged;
- Host.BindValueChanged(onHostChanged, true);
+ updateRoomHost();
+ updateRoomParticipantCount();
+ updateRoomParticipants();
}
private int numberOfCircles = 4;
@@ -192,43 +188,38 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
// Reinitialising the list looks janky, but this is unlikely to be used in a setting where it's visible.
clearUsers();
- foreach (var u in RecentParticipants)
+ foreach (var u in room.RecentParticipants)
addUser(u);
updateHiddenUsers();
}
}
- private void onParticipantsChanged(object sender, NotifyCollectionChangedEventArgs e)
+ private void updateRoomParticipants()
{
- switch (e.Action)
+ HashSet newUsers = room.RecentParticipants.ToHashSet();
+
+ avatarFlow.RemoveAll(a =>
{
- case NotifyCollectionChangedAction.Add:
- Debug.Assert(e.NewItems != null);
+ // Avatar with no user. Really shouldn't ever be the case but asserting it correctly is difficult.
+ if (a.User == null)
+ return false;
- foreach (var added in e.NewItems.OfType())
- addUser(added);
- break;
+ // User was previously and still is a participant. Keep them around but remove them from the new set.
+ // This will be useful when we add all remaining users (now just the new participants) to the flow.
+ if (newUsers.Contains(a.User))
+ {
+ newUsers.Remove(a.User);
+ return false;
+ }
- case NotifyCollectionChangedAction.Remove:
- Debug.Assert(e.OldItems != null);
+ // User is no longer a participant. Remove them from the flow.
+ return true;
+ }, true);
- foreach (var removed in e.OldItems.OfType())
- removeUser(removed);
- break;
-
- case NotifyCollectionChangedAction.Reset:
- clearUsers();
- break;
-
- case NotifyCollectionChangedAction.Replace:
- case NotifyCollectionChangedAction.Move:
- // Easiest is to just reinitialise the whole list. These are unlikely to ever be use cases.
- clearUsers();
- foreach (var u in RecentParticipants)
- addUser(u);
- break;
- }
+ // Add all remaining users to the flow.
+ foreach (var u in newUsers)
+ addUser(u);
updateHiddenUsers();
}
@@ -241,11 +232,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
avatarFlow.Add(new CircularAvatar { User = user });
}
- private void removeUser(APIUser user)
- {
- avatarFlow.RemoveAll(a => a.User == user, true);
- }
-
private void clearUsers()
{
avatarFlow.Clear();
@@ -255,8 +241,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
private void updateHiddenUsers()
{
int hiddenCount = 0;
- if (RecentParticipants.Count > NumberOfCircles)
- hiddenCount = ParticipantCount.Value - NumberOfCircles + 1;
+ if (room.RecentParticipants.Count > NumberOfCircles)
+ hiddenCount = room.ParticipantCount - NumberOfCircles + 1;
hiddenUsers.Count = hiddenCount;
@@ -264,26 +250,56 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
avatarFlow.Remove(avatarFlow.Last(), true);
else if (displayedCircles < NumberOfCircles)
{
- var nextUser = RecentParticipants.FirstOrDefault(u => avatarFlow.All(a => a.User != u));
+ var nextUser = room.RecentParticipants.FirstOrDefault(u => avatarFlow.All(a => a.User != u));
if (nextUser != null) addUser(nextUser);
}
}
- private void onHostChanged(ValueChangedEvent host)
+ private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
- hostAvatar.User = host.NewValue;
+ switch (e.PropertyName)
+ {
+ case nameof(Room.Host):
+ updateRoomHost();
+ break;
+
+ case nameof(Room.ParticipantCount):
+ updateRoomParticipantCount();
+ break;
+
+ case nameof(Room.RecentParticipants):
+ updateRoomParticipants();
+ break;
+ }
+ }
+
+ private void updateRoomHost()
+ {
+ hostAvatar.User = room.Host;
hostText.Clear();
- if (host.NewValue != null)
+ if (room.Host != null)
{
hostText.AddText("hosted by ");
- hostText.AddUserLink(host.NewValue);
+ hostText.AddUserLink(room.Host);
}
}
+ private void updateRoomParticipantCount()
+ {
+ updateHiddenUsers();
+ totalCount.Text = room.ParticipantCount.ToString();
+ }
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+ room.PropertyChanged -= onRoomPropertyChanged;
+ }
+
private partial class CircularAvatar : CompositeDrawable
{
- public APIUser User
+ public APIUser? User
{
get => avatar.User;
set => avatar.User = value;
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/EndDateInfo.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/EndDateInfo.cs
index 844991095e..3b03ce61f1 100644
--- a/osu.Game/Screens/OnlinePlay/Lounge/Components/EndDateInfo.cs
+++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/EndDateInfo.cs
@@ -2,49 +2,69 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.ComponentModel;
using osu.Framework.Allocation;
-using osu.Framework.Bindables;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
+using osu.Game.Online.Rooms;
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
- public partial class EndDateInfo : OnlinePlayComposite
+ public partial class EndDateInfo : CompositeDrawable
{
- public EndDateInfo()
+ private readonly Room room;
+
+ public EndDateInfo(Room room)
{
+ this.room = room;
AutoSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load()
{
- InternalChild = new EndDatePart
+ InternalChild = new EndDatePart(room)
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
- Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 12),
- EndDate = { BindTarget = EndDate }
+ Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 12)
};
}
private partial class EndDatePart : DrawableDate
{
- public readonly IBindable EndDate = new Bindable();
+ private readonly Room room;
- public EndDatePart()
+ public EndDatePart(Room room)
: base(DateTimeOffset.UtcNow)
{
- EndDate.BindValueChanged(date =>
- {
- // If null, set a very large future date to prevent unnecessary schedules.
- Date = date.NewValue ?? DateTimeOffset.Now.AddYears(1);
- }, true);
+ this.room = room;
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ room.PropertyChanged += onRoomPropertyChanged;
+ updateEndDate();
+ }
+
+ private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == nameof(Room.EndDate))
+ updateEndDate();
+ }
+
+ private void updateEndDate()
+ {
+ // If null, set a very large future date to prevent unnecessary schedules.
+ Date = room.EndDate ?? DateTimeOffset.Now.AddYears(1);
}
protected override string Format()
{
- if (EndDate.Value == null)
+ if (room.EndDate == null)
return string.Empty;
var diffToNow = Date.Subtract(DateTimeOffset.Now);
@@ -60,6 +80,12 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
return $"Closing {base.Format()}";
}
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+ room.PropertyChanged -= onRoomPropertyChanged;
+ }
}
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs
index 3a687ad351..0f63718355 100644
--- a/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs
+++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs
@@ -1,18 +1,16 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Game.Rulesets;
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
public class FilterCriteria
{
- public string SearchString;
+ public string SearchString = string.Empty;
public RoomStatusFilter Status;
- public string Category;
- public RulesetInfo Ruleset;
+ public string Category = string.Empty;
+ public RulesetInfo? Ruleset;
public RoomPermissionsFilter Permissions;
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/MatchTypePill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/MatchTypePill.cs
index e30d673b26..d5405c2d0e 100644
--- a/osu.Game/Screens/OnlinePlay/Lounge/Components/MatchTypePill.cs
+++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/MatchTypePill.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 osu.Framework.Bindables;
+using System.ComponentModel;
using osu.Framework.Extensions;
using osu.Game.Online.Rooms;
@@ -9,16 +9,36 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
public partial class MatchTypePill : OnlinePlayPill
{
+ private readonly Room room;
+
+ public MatchTypePill(Room room)
+ {
+ this.room = room;
+ }
+
protected override void LoadComplete()
{
base.LoadComplete();
- Type.BindValueChanged(onMatchTypeChanged, true);
+ room.PropertyChanged += onRoomPropertyChanged;
+ updateRoomType();
}
- private void onMatchTypeChanged(ValueChangedEvent type)
+ private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
- TextFlow.Text = type.NewValue.GetLocalisableDescription();
+ if (e.PropertyName == nameof(Room.Type))
+ updateRoomType();
+ }
+
+ private void updateRoomType()
+ {
+ TextFlow.Text = room.Type.GetLocalisableDescription();
+ }
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+ room.PropertyChanged -= onRoomPropertyChanged;
}
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/OnlinePlayPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/OnlinePlayPill.cs
index 3e6d7a2e54..c65a5e2469 100644
--- a/osu.Game/Screens/OnlinePlay/Lounge/Components/OnlinePlayPill.cs
+++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/OnlinePlayPill.cs
@@ -3,13 +3,14 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
- public abstract partial class OnlinePlayPill : OnlinePlayComposite
+ public abstract partial class OnlinePlayPill : CompositeDrawable
{
protected PillContainer Pill { get; private set; } = null!;
protected OsuTextFlowContainer TextFlow { get; private set; } = null!;
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs
index fe5ccb4f09..70ddf15abf 100644
--- a/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs
+++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs
@@ -1,10 +1,12 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.ComponentModel;
using System.Linq;
using Humanizer;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Game.Graphics;
+using osu.Game.Online.Rooms;
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
@@ -13,26 +15,50 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
///
public partial class PlaylistCountPill : OnlinePlayPill
{
+ private readonly Room room;
+
+ public PlaylistCountPill(Room room)
+ {
+ this.room = room;
+ }
+
protected override void LoadComplete()
{
base.LoadComplete();
- PlaylistItemStats.BindValueChanged(_ => updateCount());
- Playlist.BindCollectionChanged((_, _) => updateCount(), true);
+ room.PropertyChanged += onRoomPropertyChanged;
+ updateCount();
+ }
+
+ private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
+ {
+ switch (e.PropertyName)
+ {
+ case nameof(Room.Playlist):
+ case nameof(Room.PlaylistItemStats):
+ updateCount();
+ break;
+ }
}
private void updateCount()
{
- int activeItems = Playlist.Count > 0 || PlaylistItemStats.Value == null
+ int activeItems = room.Playlist.Count > 0 || room.PlaylistItemStats == 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;
+ ? room.Playlist.Count(i => !i.Expired)
+ : room.PlaylistItemStats.CountActive;
TextFlow.Clear();
TextFlow.AddText(activeItems.ToLocalisableString(), s => s.Font = s.Font.With(weight: FontWeight.Bold));
TextFlow.AddText(" ");
TextFlow.AddText("Beatmap".ToQuantity(activeItems, ShowQuantityAs.None));
}
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+ room.PropertyChanged -= onRoomPropertyChanged;
+ }
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/QueueModePill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/QueueModePill.cs
index 23f4ecf8db..c7d7876644 100644
--- a/osu.Game/Screens/OnlinePlay/Lounge/Components/QueueModePill.cs
+++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/QueueModePill.cs
@@ -1,24 +1,42 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Framework.Bindables;
+using System.ComponentModel;
using osu.Framework.Extensions;
-using osu.Game.Online.Multiplayer;
+using osu.Game.Online.Rooms;
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
public partial class QueueModePill : OnlinePlayPill
{
+ private readonly Room room;
+
+ public QueueModePill(Room room)
+ {
+ this.room = room;
+ }
+
protected override void LoadComplete()
{
base.LoadComplete();
- QueueMode.BindValueChanged(onQueueModeChanged, true);
+ room.PropertyChanged += onRoomPropertyChanged;
+ updateRoomQueueMode();
}
- private void onQueueModeChanged(ValueChangedEvent mode)
+ private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
- TextFlow.Text = mode.NewValue.GetLocalisableDescription();
+ if (e.PropertyName == nameof(Room.QueueMode))
+ updateRoomQueueMode();
+ }
+
+ private void updateRoomQueueMode()
+ => TextFlow.Text = room.QueueMode.GetLocalisableDescription();
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+ room.PropertyChanged -= onRoomPropertyChanged;
}
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomSpecialCategoryPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomSpecialCategoryPill.cs
index 9b8954bb33..9bb3a59d0c 100644
--- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomSpecialCategoryPill.cs
+++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomSpecialCategoryPill.cs
@@ -1,21 +1,30 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.ComponentModel;
using osu.Framework.Allocation;
using osu.Framework.Extensions;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
+using osu.Game.Online.Rooms;
using osuTK.Graphics;
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
public partial class RoomSpecialCategoryPill : OnlinePlayPill
{
+ private readonly Room room;
+
[Resolved]
private OsuColour colours { get; set; } = null!;
protected override FontUsage Font => base.Font.With(weight: FontWeight.SemiBold);
+ public RoomSpecialCategoryPill(Room room)
+ {
+ this.room = room;
+ }
+
protected override void LoadComplete()
{
base.LoadComplete();
@@ -23,11 +32,26 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
Pill.Background.Alpha = 1;
TextFlow.Colour = Color4.Black;
- Category.BindValueChanged(c =>
- {
- TextFlow.Text = c.NewValue.GetLocalisableDescription();
- Pill.Background.Colour = colours.ForRoomCategory(c.NewValue) ?? colours.Pink;
- }, true);
+ room.PropertyChanged += onRoomPropertyChanged;
+ updateRoomCategory();
+ }
+
+ private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == nameof(Room.Category))
+ updateRoomCategory();
+ }
+
+ private void updateRoomCategory()
+ {
+ TextFlow.Text = room.Category.GetLocalisableDescription();
+ Pill.Background.Colour = colours.ForRoomCategory(room.Category) ?? colours.Pink;
+ }
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+ room.PropertyChanged -= onRoomPropertyChanged;
}
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs
index 96d698a184..b3dc617fd6 100644
--- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs
+++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs
@@ -1,6 +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.ComponentModel;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
@@ -19,25 +20,47 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
protected override FontUsage Font => base.Font.With(weight: FontWeight.SemiBold);
+ private readonly Room room;
+
+ public RoomStatusPill(Room room)
+ {
+ this.room = room;
+ }
+
protected override void LoadComplete()
{
base.LoadComplete();
- EndDate.BindValueChanged(_ => updateDisplay());
- Status.BindValueChanged(_ => updateDisplay(), true);
-
- FinishTransforms(true);
-
TextFlow.Colour = Colour4.Black;
Pill.Background.Alpha = 1;
+
+ room.PropertyChanged += onRoomPropertyChanged;
+ updateDisplay();
+
+ FinishTransforms(true);
+ }
+
+ private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
+ {
+ switch (e.PropertyName)
+ {
+ case nameof(Room.Status):
+ case nameof(Room.EndDate):
+ updateDisplay();
+ break;
+ }
}
private void updateDisplay()
{
- RoomStatus status = Status.Value;
+ Pill.Background.FadeColour(room.Status.GetAppropriateColour(colours), 100);
+ TextFlow.Text = room.Status.Message;
+ }
- Pill.Background.FadeColour(status.GetAppropriateColour(colours), 100);
- TextFlow.Text = status.Message;
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+ room.PropertyChanged -= onRoomPropertyChanged;
}
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs
index e842f8c436..17aed021b2 100644
--- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs
+++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
@@ -11,6 +9,7 @@ using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions;
+using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Bindings;
@@ -24,8 +23,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
public partial class RoomsContainer : CompositeDrawable, IKeyBindingHandler
{
- public readonly Bindable SelectedRoom = new Bindable();
- public readonly Bindable Filter = new Bindable();
+ public readonly Bindable SelectedRoom = new Bindable();
+ public readonly Bindable Filter = new Bindable();
public IReadOnlyList Rooms => roomFlow.FlowingChildren.Cast().ToArray();
@@ -33,7 +32,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
private readonly FillFlowContainer roomFlow;
[Resolved]
- private IRoomManager roomManager { get; set; }
+ private IRoomManager roomManager { get; set; } = null!;
// handle deselection
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
@@ -67,10 +66,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
rooms.BindTo(roomManager.Rooms);
- Filter?.BindValueChanged(criteria => applyFilterCriteria(criteria.NewValue), true);
+ Filter.BindValueChanged(criteria => applyFilterCriteria(criteria.NewValue), true);
}
- private void applyFilterCriteria(FilterCriteria criteria)
+ private void applyFilterCriteria(FilterCriteria? criteria)
{
roomFlow.Children.ForEach(r =>
{
@@ -80,7 +79,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
bool matchingFilter = true;
- matchingFilter &= criteria.Ruleset == null || r.Room.PlaylistItemStats.Value?.RulesetIDs.Any(id => id == criteria.Ruleset.OnlineID) != false;
+ matchingFilter &= criteria.Ruleset == null || r.Room.PlaylistItemStats?.RulesetIDs.Any(id => id == criteria.Ruleset.OnlineID) != false;
if (!string.IsNullOrEmpty(criteria.SearchString))
{
@@ -102,10 +101,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
return true;
case RoomPermissionsFilter.Public:
- return !room.Room.HasPassword.Value;
+ return !room.Room.HasPassword;
case RoomPermissionsFilter.Private:
- return room.Room.HasPassword.Value;
+ return room.Room.HasPassword;
default:
throw new ArgumentOutOfRangeException(nameof(accessType), accessType, $"Unsupported {nameof(RoomPermissionsFilter)} in filter");
@@ -113,7 +112,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
}
}
- private void roomsChanged(object sender, NotifyCollectionChangedEventArgs args)
+ private void roomsChanged(object? sender, NotifyCollectionChangedEventArgs args)
{
switch (args.Action)
{
@@ -140,9 +139,9 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
private void addRooms(IEnumerable rooms)
{
foreach (var room in rooms)
- roomFlow.Add(new DrawableLoungeRoom(room) { SelectedRoom = { BindTarget = SelectedRoom } });
+ roomFlow.Add(new DrawableLoungeRoom(room) { SelectedRoom = SelectedRoom });
- applyFilterCriteria(Filter?.Value);
+ applyFilterCriteria(Filter.Value);
}
private void removeRooms(IEnumerable rooms)
@@ -170,10 +169,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
foreach (var room in roomFlow)
{
- roomFlow.SetLayoutPosition(room, room.Room.Category.Value > RoomCategory.Normal
+ roomFlow.SetLayoutPosition(room, room.Room.Category > RoomCategory.Normal
// Always show spotlight playlists at the top of the listing.
? float.MinValue
- : -(room.Room.RoomID.Value ?? 0));
+ : -(room.Room.RoomID ?? 0));
}
}
@@ -213,7 +212,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
var visibleRooms = Rooms.AsEnumerable().Where(r => r.IsPresent);
- Room room;
+ Room? room;
if (SelectedRoom.Value == null)
room = visibleRooms.FirstOrDefault()?.Room;
@@ -236,7 +235,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
base.Dispose(isDisposing);
- if (roomManager != null)
+ if (roomManager.IsNotNull())
roomManager.RoomsUpdated -= updateSorting;
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs
index fed47e847a..d396d18b4f 100644
--- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs
+++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs
@@ -1,9 +1,8 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System.Collections.Generic;
+using System.ComponentModel;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
@@ -28,6 +27,7 @@ using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.OnlinePlay.Lounge.Components;
using osuTK;
using osuTK.Graphics;
+using Container = osu.Framework.Graphics.Containers.Container;
namespace osu.Game.Screens.OnlinePlay.Lounge
{
@@ -39,14 +39,19 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
private const float transition_duration = 60;
private const float selection_border_width = 4;
- public readonly Bindable SelectedRoom = new Bindable();
+ public required Bindable SelectedRoom
+ {
+ get => selectedRoom;
+ set => selectedRoom.Current = value;
+ }
[Resolved(canBeNull: true)]
- private LoungeSubScreen lounge { get; set; }
+ private LoungeSubScreen? lounge { get; set; }
- private Sample sampleSelect;
- private Sample sampleJoin;
- private Drawable selectionBox;
+ private readonly BindableWithCurrent selectedRoom = new BindableWithCurrent();
+ private Sample? sampleSelect;
+ private Sample? sampleJoin;
+ private Drawable selectionBox = null!;
public DrawableLoungeRoom(Room room)
: base(room)
@@ -61,7 +66,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
AddRangeInternal(new Drawable[]
{
- new StatusColouredContainer(transition_duration)
+ new StatusColouredContainer(Room, transition_duration)
{
RelativeSizeAxes = Axes.Both,
Child = selectionBox = new Container
@@ -89,12 +94,24 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
base.LoadComplete();
Alpha = matchingFilter ? 1 : 0;
- selectionBox.Alpha = SelectedRoom.Value == Room ? 1 : 0;
+ selectionBox.Alpha = selectedRoom.Value == Room ? 1 : 0;
- SelectedRoom.BindValueChanged(updateSelectedRoom);
+ selectedRoom.BindValueChanged(updateSelectedRoom);
+
+ Room.PropertyChanged += onRoomPropertyChanged;
+ updateSelectedItem();
}
- private void updateSelectedRoom(ValueChangedEvent selected)
+ private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == nameof(Room.CurrentPlaylistItem))
+ updateSelectedItem();
+ }
+
+ private void updateSelectedItem()
+ => SelectedItem.Value = Room.CurrentPlaylistItem;
+
+ private void updateSelectedRoom(ValueChangedEvent selected)
{
if (selected.NewValue == Room)
selectionBox.FadeIn(transition_duration);
@@ -104,7 +121,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
public bool FilteringActive { get; set; }
- public IEnumerable FilterTerms => new LocalisableString[] { Room.Name.Value };
+ public IEnumerable FilterTerms => new LocalisableString[] { Room.Name };
private bool matchingFilter = true;
@@ -140,7 +157,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
if (e.Repeat)
return false;
- if (SelectedRoom.Value != Room)
+ if (selectedRoom.Value != Room)
return false;
switch (e.Action)
@@ -157,18 +174,18 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
{
}
- protected override bool ShouldBeConsideredForInput(Drawable child) => SelectedRoom.Value == Room || child is HoverSounds;
+ protected override bool ShouldBeConsideredForInput(Drawable child) => selectedRoom.Value == Room || child is HoverSounds;
protected override bool OnClick(ClickEvent e)
{
- if (Room != SelectedRoom.Value)
+ if (Room != selectedRoom.Value)
{
sampleSelect?.Play();
- SelectedRoom.Value = Room;
+ selectedRoom.Value = Room;
return true;
}
- if (Room.HasPassword.Value)
+ if (Room.HasPassword)
{
this.ShowPopover();
return true;
@@ -179,12 +196,18 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
return true;
}
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+ Room.PropertyChanged -= onRoomPropertyChanged;
+ }
+
public partial class PasswordEntryPopover : OsuPopover
{
private readonly Room room;
[Resolved(canBeNull: true)]
- private LoungeSubScreen lounge { get; set; }
+ private LoungeSubScreen? lounge { get; set; }
public override bool HandleNonPositionalInput => true;
@@ -195,10 +218,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
this.room = room;
}
- private OsuPasswordTextBox passwordTextBox;
- private RoundedButton joinButton;
- private OsuSpriteText errorText;
- private Sample sampleJoinFail;
+ private OsuPasswordTextBox passwordTextBox = null!;
+ private RoundedButton joinButton = null!;
+ private OsuSpriteText errorText = null!;
+ private Sample? sampleJoinFail;
[BackgroundDependencyLoader]
private void load(OsuColour colours, AudioManager audio)
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeBackgroundScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeBackgroundScreen.cs
index b31c351b82..4a3985c386 100644
--- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeBackgroundScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeBackgroundScreen.cs
@@ -1,6 +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.ComponentModel;
using osu.Framework.Bindables;
using osu.Framework.Screens;
using osu.Game.Online.Rooms;
@@ -19,21 +20,44 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
playlist.BindCollectionChanged((_, _) => PlaylistItem = playlist.GetCurrentItem());
}
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+ SelectedRoom.BindValueChanged(onSelectedRoomChanged, true);
+ }
+
private void onSelectedRoomChanged(ValueChangedEvent room)
{
if (room.OldValue != null)
- playlist.UnbindFrom(room.OldValue.Playlist);
+ room.OldValue.PropertyChanged -= onRoomPropertyChanged;
if (room.NewValue != null)
- playlist.BindTo(room.NewValue.Playlist);
- else
- playlist.Clear();
+ room.NewValue.PropertyChanged += onRoomPropertyChanged;
+
+ updateCurrentItem();
}
+ private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == nameof(Room.Playlist))
+ updateCurrentItem();
+ }
+
+ private void updateCurrentItem()
+ => PlaylistItem = SelectedRoom.Value?.Playlist.GetCurrentItem();
+
public override bool OnExiting(ScreenExitEvent e)
{
// This screen never exits.
return true;
}
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+
+ if (SelectedRoom.Value != null)
+ SelectedRoom.Value.PropertyChanged -= onRoomPropertyChanged;
+ }
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs
index 3792a67896..ac8caa6b88 100644
--- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs
@@ -1,13 +1,10 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
-using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
@@ -54,42 +51,42 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
AutoSizeAxes = Axes.Both
};
- protected ListingPollingComponent ListingPollingComponent { get; private set; }
+ protected ListingPollingComponent ListingPollingComponent { get; private set; } = null!;
- protected readonly Bindable SelectedRoom = new Bindable();
+ protected readonly Bindable SelectedRoom = new Bindable();
[Resolved]
- private MusicController music { get; set; }
+ private MusicController music { get; set; } = null!;
[Resolved(CanBeNull = true)]
- private OngoingOperationTracker ongoingOperationTracker { get; set; }
+ private OngoingOperationTracker? ongoingOperationTracker { get; set; }
[Resolved]
- private IBindable ruleset { get; set; }
+ private IBindable ruleset { get; set; } = null!;
[Resolved]
- private IAPIProvider api { get; set; }
+ private IAPIProvider api { get; set; } = null!;
- [CanBeNull]
- private IDisposable joiningRoomOperation { get; set; }
-
- [CanBeNull]
- private LeasedBindable selectionLease;
+ [Resolved(CanBeNull = true)]
+ private IdleTracker? idleTracker { get; set; }
[Resolved]
- protected OsuConfigManager Config { get; private set; }
+ protected OsuConfigManager Config { get; private set; } = null!;
- private readonly Bindable filter = new Bindable(new FilterCriteria());
+ private IDisposable? joiningRoomOperation { get; set; }
+ private LeasedBindable? selectionLease;
+
+ private readonly Bindable filter = new Bindable();
private readonly IBindable operationInProgress = new Bindable();
private readonly IBindable isIdle = new BindableBool();
- private PopoverContainer popoverContainer;
- private LoadingLayer loadingLayer;
- private RoomsContainer roomsContainer;
- private SearchTextBox searchTextBox;
- private Dropdown