diff --git a/osu.Android.props b/osu.Android.props
index e30416bc1c..183ac61c90 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -52,6 +52,6 @@
-
+
diff --git a/osu.Android/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs
index ad929bbac3..d087c6218d 100644
--- a/osu.Android/OsuGameActivity.cs
+++ b/osu.Android/OsuGameActivity.cs
@@ -17,7 +17,7 @@ using osu.Game.Database;
namespace osu.Android
{
- [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false, LaunchMode = LaunchMode.SingleInstance)]
+ [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false, LaunchMode = LaunchMode.SingleInstance, Exported = true)]
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osz", DataHost = "*", DataMimeType = "*/*")]
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osk", DataHost = "*", DataMimeType = "*/*")]
[IntentFilter(new[] { Intent.ActionSend, Intent.ActionSendMultiple }, Categories = new[] { Intent.CategoryDefault }, DataMimeTypes = new[] { "application/zip", "application/octet-stream", "application/download", "application/x-zip", "application/x-zip-compressed" })]
diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs
index 63b12fb84b..832d26b0ef 100644
--- a/osu.Desktop/DiscordRichPresence.cs
+++ b/osu.Desktop/DiscordRichPresence.cs
@@ -105,7 +105,7 @@ namespace osu.Desktop
if (privacyMode.Value == DiscordRichPresenceMode.Limited)
presence.Assets.LargeImageText = string.Empty;
else
- presence.Assets.LargeImageText = $"{user.Value.Username}" + (user.Value.Statistics?.Ranks.Global > 0 ? $" (rank #{user.Value.Statistics.Ranks.Global:N0})" : string.Empty);
+ presence.Assets.LargeImageText = $"{user.Value.Username}" + (user.Value.Statistics?.GlobalRank > 0 ? $" (rank #{user.Value.Statistics.GlobalRank:N0})" : string.Empty);
// update ruleset
presence.Assets.SmallImageKey = ruleset.Value.ID <= 3 ? $"mode_{ruleset.Value.ID}" : "mode_custom";
diff --git a/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs
index ee416e5a38..f4ee3f5a42 100644
--- a/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs
+++ b/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs
@@ -4,6 +4,7 @@
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Difficulty;
+using osu.Game.Rulesets.Catch.Mods;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Tests.Beatmaps;
@@ -17,6 +18,10 @@ namespace osu.Game.Rulesets.Catch.Tests
public void Test(double expected, string name)
=> base.Test(expected, name);
+ [TestCase(5.0565038923984691d, "diffcalc-test")]
+ public void TestClockRateAdjusted(double expected, string name)
+ => Test(expected, name, new CatchModDoubleTime());
+
protected override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchDifficultyCalculator(new CatchRuleset(), beatmap);
protected override Ruleset CreateRuleset() => new CatchRuleset();
diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs
index a25551f854..09ca04be8a 100644
--- a/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs
+++ b/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs
@@ -5,6 +5,7 @@ using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mania.Difficulty;
+using osu.Game.Rulesets.Mania.Mods;
using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Mania.Tests
@@ -17,6 +18,10 @@ namespace osu.Game.Rulesets.Mania.Tests
public void Test(double expected, string name)
=> base.Test(expected, name);
+ [TestCase(2.7646128945056723d, "diffcalc-test")]
+ public void TestClockRateAdjusted(double expected, string name)
+ => Test(expected, name, new ManiaModDoubleTime());
+
protected override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new ManiaDifficultyCalculator(new ManiaRuleset(), beatmap);
protected override Ruleset CreateRuleset() => new ManiaRuleset();
diff --git a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs
index 85a41137d4..a365ea10d4 100644
--- a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs
+++ b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs
@@ -5,6 +5,7 @@ using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Osu.Difficulty;
+using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Osu.Tests
@@ -19,6 +20,11 @@ namespace osu.Game.Rulesets.Osu.Tests
public void Test(double expected, string name)
=> base.Test(expected, name);
+ [TestCase(8.6228371119393064d, "diffcalc-test")]
+ [TestCase(1.2864585434597433d, "zero-length-sliders")]
+ public void TestClockRateAdjusted(double expected, string name)
+ => Test(expected, name, new OsuModDoubleTime());
+
protected override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new OsuDifficultyCalculator(new OsuRuleset(), beatmap);
protected override Ruleset CreateRuleset() => new OsuRuleset();
diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs
index 71b3c23b50..eb21c02d5f 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs
@@ -5,6 +5,7 @@ using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Taiko.Difficulty;
+using osu.Game.Rulesets.Taiko.Mods;
using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Taiko.Tests
@@ -18,6 +19,11 @@ namespace osu.Game.Rulesets.Taiko.Tests
public void Test(double expected, string name)
=> base.Test(expected, name);
+ [TestCase(3.1473940254109078d, "diffcalc-test")]
+ [TestCase(3.1473940254109078d, "diffcalc-test-strong")]
+ public void TestClockRateAdjusted(double expected, string name)
+ => Test(expected, name, new TaikoModDoubleTime());
+
protected override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new TaikoDifficultyCalculator(new TaikoRuleset(), beatmap);
protected override Ruleset CreateRuleset() => new TaikoRuleset();
diff --git a/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs b/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs
index a2f2c5e41f..f421a30283 100644
--- a/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs
+++ b/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs
@@ -119,9 +119,11 @@ namespace osu.Game.Tests.Rulesets
public BindableNumber Tempo => throw new NotImplementedException();
public void BindAdjustments(IAggregateAudioAdjustment component) => throw new NotImplementedException();
+
public void UnbindAdjustments(IAggregateAudioAdjustment component) => throw new NotImplementedException();
public void AddAdjustment(AdjustableProperty type, IBindable adjustBindable) => throw new NotImplementedException();
+
public void RemoveAdjustment(AdjustableProperty type, IBindable adjustBindable) => throw new NotImplementedException();
public void RemoveAllAdjustments(AdjustableProperty type) => throw new NotImplementedException();
diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs
index 46dd91710a..bddc7ab731 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs
@@ -56,9 +56,7 @@ namespace osu.Game.Tests.Visual.Gameplay
pauseAndConfirm();
resume();
- confirmClockRunning(false);
- confirmPauseOverlayShown(false);
-
+ confirmPausedWithNoOverlay();
AddStep("click to resume", () => InputManager.Click(MouseButton.Left));
confirmClockRunning(true);
@@ -71,15 +69,14 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("wait for hitobjects", () => Player.HealthProcessor.Health.Value < 1);
pauseAndConfirm();
-
resume();
- confirmClockRunning(false);
- confirmPauseOverlayShown(false);
+ confirmPausedWithNoOverlay();
pauseAndConfirm();
AddUntilStep("resume overlay is not active", () => Player.DrawableRuleset.ResumeOverlay.State.Value == Visibility.Hidden);
confirmPaused();
+ confirmNotExited();
}
[Test]
@@ -94,33 +91,54 @@ namespace osu.Game.Tests.Visual.Gameplay
}
[Test]
- public void TestPauseTooSoon()
+ public void TestUserPauseWhenPauseNotAllowed()
+ {
+ AddStep("disable pause support", () => Player.Configuration.AllowPause = false);
+
+ pauseFromUserExitKey();
+ confirmExited();
+ }
+
+ [Test]
+ public void TestUserPauseDuringCooldownTooSoon()
{
AddStep("move cursor outside", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.TopLeft - new Vector2(10)));
pauseAndConfirm();
resume();
- pause();
+ pauseFromUserExitKey();
- confirmClockRunning(true);
- confirmPauseOverlayShown(false);
+ confirmResumed();
+ confirmNotExited();
}
[Test]
- public void TestExitTooSoon()
+ public void TestQuickExitDuringCooldownTooSoon()
+ {
+ AddStep("move cursor outside", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.TopLeft - new Vector2(10)));
+
+ pauseAndConfirm();
+
+ resume();
+ AddStep("pause via exit key", () => Player.ExitViaQuickExit());
+
+ confirmResumed();
+ AddAssert("exited", () => !Player.IsCurrentScreen());
+ }
+
+ [Test]
+ public void TestExitSoonAfterResumeSucceeds()
{
AddStep("seek before gameplay", () => Player.GameplayClockContainer.Seek(-5000));
pauseAndConfirm();
resume();
- AddStep("exit too soon", () => Player.Exit());
+ AddStep("exit quick", () => Player.Exit());
- confirmClockRunning(true);
- confirmPauseOverlayShown(false);
-
- AddAssert("not exited", () => Player.IsCurrentScreen());
+ confirmResumed();
+ AddAssert("exited", () => !Player.IsCurrentScreen());
}
[Test]
@@ -131,22 +149,37 @@ namespace osu.Game.Tests.Visual.Gameplay
confirmClockRunning(false);
- pause();
-
- confirmClockRunning(false);
- confirmPauseOverlayShown(false);
+ AddStep("pause via forced pause", () => Player.Pause());
+ confirmPausedWithNoOverlay();
AddAssert("fail overlay still shown", () => Player.FailOverlayVisible);
exitAndConfirm();
}
[Test]
- public void TestExitFromFailedGameplay()
+ public void TestExitFromFailedGameplayAfterFailAnimation()
{
AddUntilStep("wait for fail", () => Player.HasFailed);
- AddStep("exit", () => Player.Exit());
+ AddUntilStep("wait for fail overlay shown", () => Player.FailOverlayVisible);
+ confirmClockRunning(false);
+
+ AddStep("exit via user pause", () => Player.ExitViaPause());
+ confirmExited();
+ }
+
+ [Test]
+ public void TestExitFromFailedGameplayDuringFailAnimation()
+ {
+ AddUntilStep("wait for fail", () => Player.HasFailed);
+
+ // will finish the fail animation and show the fail/pause screen.
+ AddStep("attempt exit via pause key", () => Player.ExitViaPause());
+ AddAssert("fail overlay shown", () => Player.FailOverlayVisible);
+
+ // will actually exit.
+ AddStep("exit via pause key", () => Player.ExitViaPause());
confirmExited();
}
@@ -245,7 +278,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private void pauseAndConfirm()
{
- pause();
+ pauseFromUserExitKey();
confirmPaused();
}
@@ -257,7 +290,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private void exitAndConfirm()
{
- AddUntilStep("player not exited", () => Player.IsCurrentScreen());
+ confirmNotExited();
AddStep("exit", () => Player.Exit());
confirmExited();
confirmNoTrackAdjustments();
@@ -266,7 +299,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private void confirmPaused()
{
confirmClockRunning(false);
- AddAssert("player not exited", () => Player.IsCurrentScreen());
+ confirmNotExited();
AddAssert("player not failed", () => !Player.HasFailed);
AddAssert("pause overlay shown", () => Player.PauseOverlayVisible);
}
@@ -277,18 +310,22 @@ namespace osu.Game.Tests.Visual.Gameplay
confirmPauseOverlayShown(false);
}
- private void confirmExited()
+ private void confirmPausedWithNoOverlay()
{
- AddUntilStep("player exited", () => !Player.IsCurrentScreen());
+ confirmClockRunning(false);
+ confirmPauseOverlayShown(false);
}
+ private void confirmExited() => AddUntilStep("player exited", () => !Player.IsCurrentScreen());
+ private void confirmNotExited() => AddAssert("player not exited", () => Player.IsCurrentScreen());
+
private void confirmNoTrackAdjustments()
{
AddAssert("track has no adjustments", () => Beatmap.Value.Track.AggregateFrequency.Value == 1);
}
private void restart() => AddStep("restart", () => Player.Restart());
- private void pause() => AddStep("pause", () => Player.Pause());
+ private void pauseFromUserExitKey() => AddStep("user pause", () => Player.ExitViaPause());
private void resume() => AddStep("resume", () => Player.Resume());
private void confirmPauseOverlayShown(bool isShown) =>
@@ -307,6 +344,10 @@ namespace osu.Game.Tests.Visual.Gameplay
public bool PauseOverlayVisible => PauseOverlay.State.Value == Visibility.Visible;
+ public void ExitViaPause() => PerformExit(true);
+
+ public void ExitViaQuickExit() => PerformExit(false);
+
public override void OnEntering(IScreen last)
{
base.OnEntering(last);
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs
index aab69d687a..1ee848b902 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs
@@ -13,6 +13,7 @@ using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Database;
using osu.Game.Online;
+using osu.Game.Online.API;
using osu.Game.Online.Spectator;
using osu.Game.Replays.Legacy;
using osu.Game.Rulesets.Osu.Scoring;
@@ -50,6 +51,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
[SetUpSteps]
public override void SetUpSteps()
{
+ AddStep("set local user", () => ((DummyAPIAccess)API).LocalUser.Value = lookupCache.GetUserAsync(1).Result);
+
AddStep("create leaderboard", () =>
{
leaderboard?.Expire();
@@ -85,6 +88,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
public void TestScoreUpdates()
{
AddRepeatStep("update state", () => streamingClient.RandomlyUpdateState(), 100);
+ AddToggleStep("switch compact mode", expanded => leaderboard.Expanded.Value = expanded);
}
[Test]
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs
index 0f7a9b442d..e713cff233 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.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.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
@@ -155,7 +156,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
Id = i,
Username = $"User {i}",
- CurrentModeRank = RNG.Next(1, 100000),
+ RulesetsStatistics = new Dictionary
+ {
+ {
+ Ruleset.Value.ShortName,
+ new UserStatistics { GlobalRank = RNG.Next(1, 100000), }
+ }
+ },
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
});
@@ -193,7 +200,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
Id = 0,
Username = "User 0",
- CurrentModeRank = RNG.Next(1, 100000),
+ RulesetsStatistics = new Dictionary
+ {
+ {
+ Ruleset.Value.ShortName,
+ new UserStatistics { GlobalRank = RNG.Next(1, 100000), }
+ }
+ },
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
});
diff --git a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs
index c5038068ec..96393cc4c3 100644
--- a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs
+++ b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs
@@ -17,6 +17,7 @@ using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API;
using osu.Game.Overlays;
using osu.Game.Rulesets;
+using osu.Game.Rulesets.Mods;
using osu.Game.Scoring;
using osu.Game.Screens;
using osu.Game.Screens.Menu;
@@ -115,6 +116,8 @@ namespace osu.Game.Tests.Visual.Navigation
public new Bindable Ruleset => base.Ruleset;
+ public new Bindable> SelectedMods => base.SelectedMods;
+
// if we don't do this, when running under nUnit the version that gets populated is that of nUnit.
public override string Version => "test game";
diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs
index 8480e6eaaa..d8380b2dd3 100644
--- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs
+++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs
@@ -11,7 +11,9 @@ using osu.Game.Beatmaps;
using osu.Game.Overlays;
using osu.Game.Overlays.Mods;
using osu.Game.Overlays.Toolbar;
+using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Screens.Play;
+using osu.Game.Screens.Ranking;
using osu.Game.Screens.Select;
using osu.Game.Screens.Select.Options;
using osu.Game.Tests.Beatmaps.IO;
@@ -41,6 +43,30 @@ namespace osu.Game.Tests.Visual.Navigation
exitViaEscapeAndConfirm();
}
+ [Test]
+ public void TestRetryFromResults()
+ {
+ Player player = null;
+ ResultsScreen results = null;
+
+ WorkingBeatmap beatmap() => Game.Beatmap.Value;
+
+ PushAndConfirm(() => new TestSongSelect());
+
+ AddStep("import beatmap", () => ImportBeatmapTest.LoadOszIntoOsu(Game, virtualTrack: true).Wait());
+
+ AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
+
+ AddStep("set autoplay", () => Game.SelectedMods.Value = new[] { new OsuModAutoplay() });
+
+ AddStep("press enter", () => InputManager.Key(Key.Enter));
+ AddUntilStep("wait for player", () => (player = Game.ScreenStack.CurrentScreen as Player) != null);
+ AddStep("seek to end", () => beatmap().Track.Seek(beatmap().Track.Length));
+ AddUntilStep("wait for pass", () => (results = Game.ScreenStack.CurrentScreen as ResultsScreen) != null && results.IsLoaded);
+ AddStep("attempt to retry", () => results.ChildrenOfType().First().Action());
+ AddUntilStep("wait for player", () => Game.ScreenStack.CurrentScreen != player && Game.ScreenStack.CurrentScreen is Player);
+ }
+
[TestCase(true)]
[TestCase(false)]
public void TestSongContinuesAfterExitPlayer(bool withUserPause)
diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankGraph.cs b/osu.Game.Tests/Visual/Online/TestSceneRankGraph.cs
index 3b31192259..5bf9e31309 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneRankGraph.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneRankGraph.cs
@@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual.Online
{
graph.Statistics.Value = new UserStatistics
{
- Ranks = new UserStatistics.UserRanks { Global = 123456 },
+ GlobalRank = 123456,
PP = 12345,
};
});
@@ -79,7 +79,7 @@ namespace osu.Game.Tests.Visual.Online
{
graph.Statistics.Value = new UserStatistics
{
- Ranks = new UserStatistics.UserRanks { Global = 89000 },
+ GlobalRank = 89000,
PP = 12345,
RankHistory = new User.RankHistoryData
{
@@ -92,7 +92,7 @@ namespace osu.Game.Tests.Visual.Online
{
graph.Statistics.Value = new UserStatistics
{
- Ranks = new UserStatistics.UserRanks { Global = 89000 },
+ GlobalRank = 89000,
PP = 12345,
RankHistory = new User.RankHistoryData
{
@@ -105,7 +105,7 @@ namespace osu.Game.Tests.Visual.Online
{
graph.Statistics.Value = new UserStatistics
{
- Ranks = new UserStatistics.UserRanks { Global = 12000 },
+ GlobalRank = 12000,
PP = 12345,
RankHistory = new User.RankHistoryData
{
@@ -118,7 +118,7 @@ namespace osu.Game.Tests.Visual.Online
{
graph.Statistics.Value = new UserStatistics
{
- Ranks = new UserStatistics.UserRanks { Global = 12000 },
+ GlobalRank = 12000,
PP = 12345,
RankHistory = new User.RankHistoryData
{
diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs
index 7ade24f4de..03d079261d 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs
@@ -33,7 +33,8 @@ namespace osu.Game.Tests.Visual.Online
ProfileOrder = new[] { "me" },
Statistics = new UserStatistics
{
- Ranks = new UserStatistics.UserRanks { Global = 2148, Country = 1 },
+ GlobalRank = 2148,
+ CountryRank = 1,
PP = 4567.89m,
Level = new UserStatistics.LevelInfo
{
diff --git a/osu.Game.Tournament.Tests/TournamentTestScene.cs b/osu.Game.Tournament.Tests/TournamentTestScene.cs
index 47d2160561..cdfd19c157 100644
--- a/osu.Game.Tournament.Tests/TournamentTestScene.cs
+++ b/osu.Game.Tournament.Tests/TournamentTestScene.cs
@@ -113,11 +113,11 @@ namespace osu.Game.Tournament.Tests
},
Players =
{
- new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 12 } } },
- new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 16 } } },
- new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 20 } } },
- new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 24 } } },
- new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 30 } } },
+ new User { Username = "Hello", Statistics = new UserStatistics { GlobalRank = 12 } },
+ new User { Username = "Hello", Statistics = new UserStatistics { GlobalRank = 16 } },
+ new User { Username = "Hello", Statistics = new UserStatistics { GlobalRank = 20 } },
+ new User { Username = "Hello", Statistics = new UserStatistics { GlobalRank = 24 } },
+ new User { Username = "Hello", Statistics = new UserStatistics { GlobalRank = 30 } },
}
}
},
diff --git a/osu.Game.Tournament/Models/TournamentTeam.cs b/osu.Game.Tournament/Models/TournamentTeam.cs
index 7fca75cea4..7074ae413c 100644
--- a/osu.Game.Tournament/Models/TournamentTeam.cs
+++ b/osu.Game.Tournament/Models/TournamentTeam.cs
@@ -36,7 +36,7 @@ namespace osu.Game.Tournament.Models
{
get
{
- var ranks = Players.Select(p => p.Statistics?.Ranks.Global)
+ var ranks = Players.Select(p => p.Statistics?.GlobalRank)
.Where(i => i.HasValue)
.Select(i => i.Value)
.ToArray();
diff --git a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs
index 55fc80dba2..4f66d89b7f 100644
--- a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs
+++ b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs
@@ -250,7 +250,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro
};
foreach (var p in team.Players)
- fill.Add(new RowDisplay(p.Username, p.Statistics?.Ranks.Global?.ToString("\\##,0") ?? "-"));
+ fill.Add(new RowDisplay(p.Username, p.Statistics?.GlobalRank?.ToString("\\##,0") ?? "-"));
}
internal class RowDisplay : CompositeDrawable
diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs
index 4224da4bbe..d506724017 100644
--- a/osu.Game.Tournament/TournamentGameBase.cs
+++ b/osu.Game.Tournament/TournamentGameBase.cs
@@ -150,9 +150,9 @@ namespace osu.Game.Tournament
{
foreach (var p in t.Players)
{
- if (string.IsNullOrEmpty(p.Username) || p.Statistics == null)
+ if (string.IsNullOrEmpty(p.Username) || p.Statistics?.GlobalRank == null)
{
- PopulateUser(p);
+ PopulateUser(p, immediate: true);
addedInfo = true;
}
}
@@ -211,12 +211,14 @@ namespace osu.Game.Tournament
return addedInfo;
}
- public void PopulateUser(User user, Action success = null, Action failure = null)
+ public void PopulateUser(User user, Action success = null, Action failure = null, bool immediate = false)
{
var req = new GetUserRequest(user.Id, Ruleset.Value);
req.Success += res =>
{
+ user.Id = res.Id;
+
user.Username = res.Username;
user.Statistics = res.Statistics;
user.Country = res.Country;
@@ -231,7 +233,10 @@ namespace osu.Game.Tournament
failure?.Invoke();
};
- API.Queue(req);
+ if (immediate)
+ API.Perform(req);
+ else
+ API.Queue(req);
}
protected override void LoadComplete()
diff --git a/osu.Game/Collections/CollectionFilterDropdown.cs b/osu.Game/Collections/CollectionFilterDropdown.cs
index ec0e9d5a89..bb743d4ccc 100644
--- a/osu.Game/Collections/CollectionFilterDropdown.cs
+++ b/osu.Game/Collections/CollectionFilterDropdown.cs
@@ -29,6 +29,14 @@ namespace osu.Game.Collections
///
protected virtual bool ShowManageCollectionsItem => true;
+ private readonly BindableWithCurrent current = new BindableWithCurrent();
+
+ public new Bindable Current
+ {
+ get => current.Current;
+ set => current.Current = value;
+ }
+
private readonly IBindableList collections = new BindableList();
private readonly IBindableList beatmaps = new BindableList();
private readonly BindableList filters = new BindableList();
@@ -36,25 +44,28 @@ namespace osu.Game.Collections
[Resolved(CanBeNull = true)]
private ManageCollectionsDialog manageCollectionsDialog { get; set; }
+ [Resolved(CanBeNull = true)]
+ private CollectionManager collectionManager { get; set; }
+
public CollectionFilterDropdown()
{
ItemSource = filters;
- }
-
- [BackgroundDependencyLoader(permitNulls: true)]
- private void load([CanBeNull] CollectionManager collectionManager)
- {
- if (collectionManager != null)
- collections.BindTo(collectionManager.Collections);
-
- collections.CollectionChanged += (_, __) => collectionsChanged();
- collectionsChanged();
+ Current.Value = new AllBeatmapsCollectionFilterMenuItem();
}
protected override void LoadComplete()
{
base.LoadComplete();
+ if (collectionManager != null)
+ collections.BindTo(collectionManager.Collections);
+
+ // Dropdown has logic which triggers a change on the bindable with every change to the contained items.
+ // This is not desirable here, as it leads to multiple filter operations running even though nothing has changed.
+ // An extra bindable is enough to subvert this behaviour.
+ base.Current = Current;
+
+ collections.BindCollectionChanged((_, __) => collectionsChanged(), true);
Current.BindValueChanged(filterChanged, true);
}
diff --git a/osu.Game/Collections/CollectionFilterMenuItem.cs b/osu.Game/Collections/CollectionFilterMenuItem.cs
index 4a489d2945..fe79358223 100644
--- a/osu.Game/Collections/CollectionFilterMenuItem.cs
+++ b/osu.Game/Collections/CollectionFilterMenuItem.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;
using JetBrains.Annotations;
using osu.Framework.Bindables;
@@ -9,7 +10,7 @@ namespace osu.Game.Collections
///
/// A filter.
///
- public class CollectionFilterMenuItem
+ public class CollectionFilterMenuItem : IEquatable
{
///
/// The collection to filter beatmaps from.
@@ -33,6 +34,11 @@ namespace osu.Game.Collections
Collection = collection;
CollectionName = Collection?.Name.GetBoundCopy() ?? new Bindable("All beatmaps");
}
+
+ public bool Equals(CollectionFilterMenuItem other)
+ => other != null && CollectionName.Value == other.CollectionName.Value;
+
+ public override int GetHashCode() => CollectionName.Value.GetHashCode();
}
public class AllBeatmapsCollectionFilterMenuItem : CollectionFilterMenuItem
diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs
index 569ac749a4..a65d9a415d 100644
--- a/osu.Game/Collections/CollectionManager.cs
+++ b/osu.Game/Collections/CollectionManager.cs
@@ -139,35 +139,43 @@ namespace osu.Game.Collections
PostNotification?.Invoke(notification);
var collection = readCollections(stream, notification);
- bool importCompleted = false;
-
- Schedule(() =>
- {
- importCollections(collection);
- importCompleted = true;
- });
-
- while (!IsDisposed && !importCompleted)
- await Task.Delay(10);
+ await importCollections(collection);
notification.CompletionText = $"Imported {collection.Count} collections";
notification.State = ProgressNotificationState.Completed;
}
- private void importCollections(List newCollections)
+ private Task importCollections(List newCollections)
{
- foreach (var newCol in newCollections)
- {
- var existing = Collections.FirstOrDefault(c => c.Name == newCol.Name);
- if (existing == null)
- Collections.Add(existing = new BeatmapCollection { Name = { Value = newCol.Name.Value } });
+ var tcs = new TaskCompletionSource();
- foreach (var newBeatmap in newCol.Beatmaps)
+ Schedule(() =>
+ {
+ try
{
- if (!existing.Beatmaps.Contains(newBeatmap))
- existing.Beatmaps.Add(newBeatmap);
+ foreach (var newCol in newCollections)
+ {
+ var existing = Collections.FirstOrDefault(c => c.Name == newCol.Name);
+ if (existing == null)
+ Collections.Add(existing = new BeatmapCollection { Name = { Value = newCol.Name.Value } });
+
+ foreach (var newBeatmap in newCol.Beatmaps)
+ {
+ if (!existing.Beatmaps.Contains(newBeatmap))
+ existing.Beatmaps.Add(newBeatmap);
+ }
+ }
+
+ tcs.SetResult(true);
}
- }
+ catch (Exception e)
+ {
+ Logger.Error(e, "Failed to import collection.");
+ tcs.SetException(e);
+ }
+ });
+
+ return tcs.Task;
}
private List readCollections(Stream stream, ProgressNotification notification = null)
diff --git a/osu.Game/Configuration/ScoreMeterType.cs b/osu.Game/Configuration/ScoreMeterType.cs
index b9499c758e..ddbd2327c2 100644
--- a/osu.Game/Configuration/ScoreMeterType.cs
+++ b/osu.Game/Configuration/ScoreMeterType.cs
@@ -16,12 +16,12 @@ namespace osu.Game.Configuration
[Description("Hit Error (right)")]
HitErrorRight,
- [Description("Hit Error (bottom)")]
- HitErrorBottom,
-
[Description("Hit Error (left+right)")]
HitErrorBoth,
+ [Description("Hit Error (bottom)")]
+ HitErrorBottom,
+
[Description("Colour (left)")]
ColourLeft,
diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs
index 4fb9d724b5..7d6c76bc2f 100644
--- a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs
+++ b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs
@@ -36,18 +36,23 @@ namespace osu.Game.Online.Multiplayer
[Key(5)]
public IEnumerable AllowedMods { get; set; } = Enumerable.Empty();
+ [Key(6)]
+ public long PlaylistItemId { get; set; }
+
public bool Equals(MultiplayerRoomSettings other)
=> BeatmapID == other.BeatmapID
&& BeatmapChecksum == other.BeatmapChecksum
&& RequiredMods.SequenceEqual(other.RequiredMods)
&& AllowedMods.SequenceEqual(other.AllowedMods)
&& RulesetID == other.RulesetID
- && Name.Equals(other.Name, StringComparison.Ordinal);
+ && Name.Equals(other.Name, StringComparison.Ordinal)
+ && PlaylistItemId == other.PlaylistItemId;
public override string ToString() => $"Name:{Name}"
+ $" Beatmap:{BeatmapID} ({BeatmapChecksum})"
+ $" RequiredMods:{string.Join(',', RequiredMods)}"
+ $" AllowedMods:{string.Join(',', AllowedMods)}"
- + $" Ruleset:{RulesetID}";
+ + $" Ruleset:{RulesetID}"
+ + $" Item:{PlaylistItemId}";
}
}
diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs
index 639dce9230..bfd505fb19 100644
--- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs
+++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs
@@ -66,6 +66,8 @@ namespace osu.Game.Online.Multiplayer
///
public readonly BindableList CurrentMatchPlayingUserIds = new BindableList();
+ public readonly Bindable CurrentMatchPlayingItem = new Bindable();
+
///
/// The corresponding to the local player, if available.
///
@@ -92,10 +94,11 @@ namespace osu.Game.Online.Multiplayer
[Resolved]
private RulesetStore rulesets { get; set; } = null!;
- private Room? apiRoom;
+ // Only exists for compatibility with old osu-server-spectator build.
+ // Todo: Can be removed on 2021/02/26.
+ private long defaultPlaylistItemId;
- // Todo: This is temporary, until the multiplayer server returns the item id on match start or otherwise.
- private long playlistItemId;
+ private Room? apiRoom;
[BackgroundDependencyLoader]
private void load()
@@ -142,7 +145,7 @@ namespace osu.Game.Online.Multiplayer
{
Room = joinedRoom;
apiRoom = room;
- playlistItemId = room.Playlist.SingleOrDefault()?.ID ?? 0;
+ defaultPlaylistItemId = apiRoom.Playlist.FirstOrDefault()?.ID ?? 0;
}, cancellationSource.Token);
// Update room settings.
@@ -218,7 +221,7 @@ namespace osu.Game.Online.Multiplayer
BeatmapChecksum = item.GetOr(existingPlaylistItem).Beatmap.Value.MD5Hash,
RulesetID = item.GetOr(existingPlaylistItem).RulesetID,
RequiredMods = item.HasValue ? item.Value.AsNonNull().RequiredMods.Select(m => new APIMod(m)).ToList() : Room.Settings.RequiredMods,
- AllowedMods = item.HasValue ? item.Value.AsNonNull().AllowedMods.Select(m => new APIMod(m)).ToList() : Room.Settings.AllowedMods
+ AllowedMods = item.HasValue ? item.Value.AsNonNull().AllowedMods.Select(m => new APIMod(m)).ToList() : Room.Settings.AllowedMods,
});
}
@@ -506,14 +509,13 @@ namespace osu.Game.Online.Multiplayer
Room.Settings = settings;
apiRoom.Name.Value = Room.Settings.Name;
- // The playlist update is delayed until an online beatmap lookup (below) succeeds.
- // In-order for the client to not display an outdated beatmap, the playlist is forcefully cleared here.
- apiRoom.Playlist.Clear();
+ // The current item update is delayed until an online beatmap lookup (below) succeeds.
+ // In-order for the client to not display an outdated beatmap, the current item is forcefully cleared here.
+ CurrentMatchPlayingItem.Value = null;
RoomUpdated?.Invoke();
var req = new GetBeatmapSetRequest(settings.BeatmapID, BeatmapSetLookupType.BeatmapId);
-
req.Success += res =>
{
if (cancellationToken.IsCancellationRequested)
@@ -540,18 +542,30 @@ namespace osu.Game.Online.Multiplayer
var mods = settings.RequiredMods.Select(m => m.ToMod(ruleset));
var allowedMods = settings.AllowedMods.Select(m => m.ToMod(ruleset));
- PlaylistItem playlistItem = new PlaylistItem
+ // Try to retrieve the existing playlist item from the API room.
+ var playlistItem = apiRoom.Playlist.FirstOrDefault(i => i.ID == settings.PlaylistItemId);
+
+ if (playlistItem != null)
+ updateItem(playlistItem);
+ else
{
- ID = playlistItemId,
- Beatmap = { Value = beatmap },
- Ruleset = { Value = ruleset.RulesetInfo },
- };
+ // An existing playlist item does not exist, so append a new one.
+ updateItem(playlistItem = new PlaylistItem());
+ apiRoom.Playlist.Add(playlistItem);
+ }
- playlistItem.RequiredMods.AddRange(mods);
- playlistItem.AllowedMods.AddRange(allowedMods);
+ CurrentMatchPlayingItem.Value = playlistItem;
- apiRoom.Playlist.Clear(); // Clearing should be unnecessary, but here for sanity.
- apiRoom.Playlist.Add(playlistItem);
+ void updateItem(PlaylistItem item)
+ {
+ item.ID = settings.PlaylistItemId == 0 ? defaultPlaylistItemId : settings.PlaylistItemId;
+ item.Beatmap.Value = beatmap;
+ item.Ruleset.Value = ruleset.RulesetInfo;
+ item.RequiredMods.Clear();
+ item.RequiredMods.AddRange(mods);
+ item.AllowedMods.Clear();
+ item.AllowedMods.AddRange(allowedMods);
+ }
}
///
diff --git a/osu.Game/Online/OnlineViewContainer.cs b/osu.Game/Online/OnlineViewContainer.cs
index c9fb70f0cc..8868f90524 100644
--- a/osu.Game/Online/OnlineViewContainer.cs
+++ b/osu.Game/Online/OnlineViewContainer.cs
@@ -15,7 +15,7 @@ namespace osu.Game.Online
/// A for displaying online content which require a local user to be logged in.
/// Shows its children only when the local user is logged in and supports displaying a placeholder if not.
///
- public abstract class OnlineViewContainer : Container
+ public class OnlineViewContainer : Container
{
protected LoadingSpinner LoadingSpinner { get; private set; }
@@ -30,7 +30,7 @@ namespace osu.Game.Online
[Resolved]
protected IAPIProvider API { get; private set; }
- protected OnlineViewContainer(string placeholderMessage)
+ public OnlineViewContainer(string placeholderMessage)
{
this.placeholderMessage = placeholderMessage;
}
diff --git a/osu.Game/Online/Rooms/PlaylistItem.cs b/osu.Game/Online/Rooms/PlaylistItem.cs
index 61982101c1..1d409d4b56 100644
--- a/osu.Game/Online/Rooms/PlaylistItem.cs
+++ b/osu.Game/Online/Rooms/PlaylistItem.cs
@@ -23,6 +23,12 @@ namespace osu.Game.Online.Rooms
[JsonProperty("ruleset_id")]
public int RulesetID { get; set; }
+ ///
+ /// Whether this is still a valid selection for the .
+ ///
+ [JsonProperty("expired")]
+ public bool Expired { get; set; }
+
[JsonIgnore]
public readonly Bindable Beatmap = new Bindable();
diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs
index 1774eb4aca..b28680ffef 100644
--- a/osu.Game/Online/Rooms/Room.cs
+++ b/osu.Game/Online/Rooms/Room.cs
@@ -153,6 +153,12 @@ namespace osu.Game.Online.Rooms
if (EndDate.Value != null && DateTimeOffset.Now >= EndDate.Value)
Status.Value = new RoomStatusEnded();
+ // 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))
+ other.Playlist.RemoveAll(i => i.Expired);
+
if (!Playlist.SequenceEqual(other.Playlist))
{
Playlist.Clear();
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index 15785ea6bd..771bcd2310 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -383,7 +383,7 @@ namespace osu.Game
Ruleset.Value = selection.Ruleset;
Beatmap.Value = BeatmapManager.GetWorkingBeatmap(selection);
- }, validScreens: new[] { typeof(PlaySongSelect) });
+ }, validScreens: new[] { typeof(SongSelect) });
}
///
diff --git a/osu.Game/Overlays/ChangelogOverlay.cs b/osu.Game/Overlays/ChangelogOverlay.cs
index 593f59555a..537dd00727 100644
--- a/osu.Game/Overlays/ChangelogOverlay.cs
+++ b/osu.Game/Overlays/ChangelogOverlay.cs
@@ -30,7 +30,7 @@ namespace osu.Game.Overlays
protected List Streams;
public ChangelogOverlay()
- : base(OverlayColourScheme.Purple)
+ : base(OverlayColourScheme.Purple, false)
{
}
diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs
index 8bc7e21047..28f2287514 100644
--- a/osu.Game/Overlays/ChatOverlay.cs
+++ b/osu.Game/Overlays/ChatOverlay.cs
@@ -24,6 +24,7 @@ using osu.Game.Overlays.Chat.Tabs;
using osuTK.Input;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
+using osu.Game.Online;
namespace osu.Game.Overlays
{
@@ -118,40 +119,47 @@ namespace osu.Game.Overlays
{
RelativeSizeAxes = Axes.Both,
},
- currentChannelContainer = new Container
+ new OnlineViewContainer("Sign in to chat")
{
RelativeSizeAxes = Axes.Both,
- Padding = new MarginPadding
- {
- Bottom = textbox_height
- },
- },
- new Container
- {
- Anchor = Anchor.BottomLeft,
- Origin = Anchor.BottomLeft,
- RelativeSizeAxes = Axes.X,
- Height = textbox_height,
- Padding = new MarginPadding
- {
- Top = padding * 2,
- Bottom = padding * 2,
- Left = ChatLine.LEFT_PADDING + padding * 2,
- Right = padding * 2,
- },
Children = new Drawable[]
{
- textbox = new FocusedTextBox
+ currentChannelContainer = new Container
{
RelativeSizeAxes = Axes.Both,
- Height = 1,
- PlaceholderText = "type your message",
- ReleaseFocusOnCommit = false,
- HoldFocus = true,
- }
- }
- },
- loading = new LoadingSpinner(),
+ Padding = new MarginPadding
+ {
+ Bottom = textbox_height
+ },
+ },
+ new Container
+ {
+ Anchor = Anchor.BottomLeft,
+ Origin = Anchor.BottomLeft,
+ RelativeSizeAxes = Axes.X,
+ Height = textbox_height,
+ Padding = new MarginPadding
+ {
+ Top = padding * 2,
+ Bottom = padding * 2,
+ Left = ChatLine.LEFT_PADDING + padding * 2,
+ Right = padding * 2,
+ },
+ Children = new Drawable[]
+ {
+ textbox = new FocusedTextBox
+ {
+ RelativeSizeAxes = Axes.Both,
+ Height = 1,
+ PlaceholderText = "type your message",
+ ReleaseFocusOnCommit = false,
+ HoldFocus = true,
+ }
+ }
+ },
+ loading = new LoadingSpinner(),
+ },
+ }
}
},
tabsArea = new TabsArea
diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs
index 08e8331dd3..5beb285216 100644
--- a/osu.Game/Overlays/NewsOverlay.cs
+++ b/osu.Game/Overlays/NewsOverlay.cs
@@ -14,7 +14,7 @@ namespace osu.Game.Overlays
private readonly Bindable article = new Bindable(null);
public NewsOverlay()
- : base(OverlayColourScheme.Purple)
+ : base(OverlayColourScheme.Purple, false)
{
}
diff --git a/osu.Game/Overlays/OnlineOverlay.cs b/osu.Game/Overlays/OnlineOverlay.cs
index 7c9f751d3b..de33e4a1bc 100644
--- a/osu.Game/Overlays/OnlineOverlay.cs
+++ b/osu.Game/Overlays/OnlineOverlay.cs
@@ -4,6 +4,7 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Online;
namespace osu.Game.Overlays
{
@@ -16,10 +17,16 @@ namespace osu.Game.Overlays
protected readonly LoadingLayer Loading;
private readonly Container content;
- protected OnlineOverlay(OverlayColourScheme colourScheme)
+ protected OnlineOverlay(OverlayColourScheme colourScheme, bool requiresSignIn = true)
: base(colourScheme)
{
- base.Content.AddRange(new Drawable[]
+ var mainContent = requiresSignIn
+ ? new OnlineViewContainer($"Sign in to view the {Header.Title.Title}")
+ : new Container();
+
+ mainContent.RelativeSizeAxes = Axes.Both;
+
+ mainContent.AddRange(new Drawable[]
{
ScrollFlow = new OverlayScrollContainer
{
@@ -43,6 +50,8 @@ namespace osu.Game.Overlays
},
Loading = new LoadingLayer(true)
});
+
+ base.Content.Add(mainContent);
}
}
}
diff --git a/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs
index 04a1040e06..62ebee7677 100644
--- a/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs
+++ b/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs
@@ -144,8 +144,8 @@ namespace osu.Game.Overlays.Profile.Header
private void updateDisplay(User user)
{
- hiddenDetailGlobal.Content = user?.Statistics?.Ranks.Global?.ToString("\\##,##0") ?? "-";
- hiddenDetailCountry.Content = user?.Statistics?.Ranks.Country?.ToString("\\##,##0") ?? "-";
+ hiddenDetailGlobal.Content = user?.Statistics?.GlobalRank?.ToString("\\##,##0") ?? "-";
+ hiddenDetailCountry.Content = user?.Statistics?.CountryRank?.ToString("\\##,##0") ?? "-";
}
}
}
diff --git a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs
index cf6ae1a3fc..574aef02fd 100644
--- a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs
+++ b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs
@@ -176,8 +176,8 @@ namespace osu.Game.Overlays.Profile.Header
foreach (var scoreRankInfo in scoreRankInfos)
scoreRankInfo.Value.RankCount = user?.Statistics?.GradesCount[scoreRankInfo.Key] ?? 0;
- detailGlobalRank.Content = user?.Statistics?.Ranks.Global?.ToString("\\##,##0") ?? "-";
- detailCountryRank.Content = user?.Statistics?.Ranks.Country?.ToString("\\##,##0") ?? "-";
+ detailGlobalRank.Content = user?.Statistics?.GlobalRank?.ToString("\\##,##0") ?? "-";
+ detailCountryRank.Content = user?.Statistics?.CountryRank?.ToString("\\##,##0") ?? "-";
rankGraph.Statistics.Value = user?.Statistics;
}
diff --git a/osu.Game/Overlays/TabbableOnlineOverlay.cs b/osu.Game/Overlays/TabbableOnlineOverlay.cs
index 8172e99c1b..9ceab12d3d 100644
--- a/osu.Game/Overlays/TabbableOnlineOverlay.cs
+++ b/osu.Game/Overlays/TabbableOnlineOverlay.cs
@@ -61,8 +61,7 @@ namespace osu.Game.Overlays
LoadComponentAsync(display, loaded =>
{
- if (API.IsLoggedIn)
- Loading.Hide();
+ Loading.Hide();
Child = loaded;
}, (cancellationToken = new CancellationTokenSource()).Token);
diff --git a/osu.Game/PerformFromMenuRunner.cs b/osu.Game/PerformFromMenuRunner.cs
index 7999023998..fe75a3a607 100644
--- a/osu.Game/PerformFromMenuRunner.cs
+++ b/osu.Game/PerformFromMenuRunner.cs
@@ -13,6 +13,7 @@ using osu.Game.Beatmaps;
using osu.Game.Overlays;
using osu.Game.Overlays.Dialog;
using osu.Game.Overlays.Notifications;
+using osu.Game.Screens;
using osu.Game.Screens.Menu;
namespace osu.Game
@@ -81,24 +82,45 @@ namespace osu.Game
game?.CloseAllOverlays(false);
- // we may already be at the target screen type.
- if (validScreens.Contains(current.GetType()) && !beatmap.Disabled)
+ findValidTarget(current);
+ }
+
+ private bool findValidTarget(IScreen current)
+ {
+ var type = current.GetType();
+
+ // check if we are already at a valid target screen.
+ if (validScreens.Any(t => t.IsAssignableFrom(type)) && !beatmap.Disabled)
{
finalAction(current);
Cancel();
- return;
+ return true;
}
while (current != null)
{
- if (validScreens.Contains(current.GetType()))
+ // if this has a sub stack, recursively check the screens within it.
+ if (current is IHasSubScreenStack currentSubScreen)
+ {
+ if (findValidTarget(currentSubScreen.SubScreenStack.CurrentScreen))
+ {
+ // should be correct in theory, but currently untested/unused in existing implementations.
+ current.MakeCurrent();
+ return true;
+ }
+ }
+
+ if (validScreens.Any(t => t.IsAssignableFrom(type)))
{
current.MakeCurrent();
- break;
+ return true;
}
current = current.GetParentScreen();
+ type = current?.GetType();
}
+
+ return false;
}
///
diff --git a/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs
index 6c31f05337..1d87614b63 100644
--- a/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs
+++ b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs
@@ -110,17 +110,16 @@ namespace osu.Game.Rulesets.UI
public IEnumerable GetAvailableResources() => throw new NotSupportedException();
- public void AddAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => throw new NotSupportedException();
+ public void AddAdjustment(AdjustableProperty type, IBindable adjustBindable) => throw new NotSupportedException();
- public void RemoveAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => throw new NotSupportedException();
-
- public void RemoveAdjustment(AdjustableProperty type, IBindable adjustBindable)
- {
- throw new NotImplementedException();
- }
+ public void RemoveAdjustment(AdjustableProperty type, IBindable adjustBindable) => throw new NotSupportedException();
public void RemoveAllAdjustments(AdjustableProperty type) => throw new NotSupportedException();
+ public void BindAdjustments(IAggregateAudioAdjustment component) => throw new NotImplementedException();
+
+ public void UnbindAdjustments(IAggregateAudioAdjustment component) => throw new NotImplementedException();
+
public BindableNumber Volume => throw new NotSupportedException();
public BindableNumber Balance => throw new NotSupportedException();
@@ -129,14 +128,6 @@ namespace osu.Game.Rulesets.UI
public BindableNumber Tempo => throw new NotSupportedException();
- public void BindAdjustments(IAggregateAudioAdjustment component) => throw new NotImplementedException();
-
- public void UnbindAdjustments(IAggregateAudioAdjustment component) => throw new NotImplementedException();
-
- public void AddAdjustment(AdjustableProperty type, IBindable adjustBindable) => throw new NotImplementedException();
-
- public IBindable GetAggregate(AdjustableProperty type) => throw new NotSupportedException();
-
public IBindable AggregateVolume => throw new NotSupportedException();
public IBindable AggregateBalance => throw new NotSupportedException();
diff --git a/osu.Game/Screens/IHasSubScreenStack.cs b/osu.Game/Screens/IHasSubScreenStack.cs
new file mode 100644
index 0000000000..c5e2015109
--- /dev/null
+++ b/osu.Game/Screens/IHasSubScreenStack.cs
@@ -0,0 +1,15 @@
+// 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.Screens;
+
+namespace osu.Game.Screens
+{
+ ///
+ /// A screen which manages a nested stack of screens within itself.
+ ///
+ public interface IHasSubScreenStack
+ {
+ ScreenStack SubScreenStack { get; }
+ }
+}
diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs
index 86422085a1..4a689314db 100644
--- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs
@@ -8,11 +8,14 @@ using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
using osu.Framework.Screens;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Online.Rooms;
using osu.Game.Overlays;
+using osu.Game.Overlays.Mods;
using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Play;
@@ -21,14 +24,20 @@ namespace osu.Game.Screens.OnlinePlay.Match
[Cached(typeof(IPreviewTrackOwner))]
public abstract class RoomSubScreen : OnlinePlaySubScreen, IPreviewTrackOwner
{
+ [Cached(typeof(IBindable))]
protected readonly Bindable SelectedItem = new Bindable();
public override bool DisallowExternalBeatmapRulesetChanges => true;
- private Sample sampleStart;
+ private readonly ModSelectOverlay userModsSelectOverlay;
- [Resolved(typeof(Room), nameof(Room.Playlist))]
- protected BindableList Playlist { get; private set; }
+ ///
+ /// A container that provides controls for selection of user mods.
+ /// This will be shown/hidden automatically when applicable.
+ ///
+ protected Drawable UserModsSection;
+
+ private Sample sampleStart;
///
/// Any mods applied by/to the local user.
@@ -53,9 +62,26 @@ namespace osu.Game.Screens.OnlinePlay.Match
protected RoomSubScreen()
{
- AddInternal(BeatmapAvailablilityTracker = new OnlinePlayBeatmapAvailablilityTracker
+ AddRangeInternal(new Drawable[]
{
- SelectedItem = { BindTarget = SelectedItem }
+ BeatmapAvailablilityTracker = new OnlinePlayBeatmapAvailablilityTracker
+ {
+ SelectedItem = { BindTarget = SelectedItem }
+ },
+ new Container
+ {
+ Anchor = Anchor.BottomLeft,
+ Origin = Anchor.BottomLeft,
+ Depth = float.MinValue,
+ RelativeSizeAxes = Axes.Both,
+ Height = 0.5f,
+ Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING },
+ Child = userModsSelectOverlay = new UserModSelectOverlay
+ {
+ SelectedMods = { BindTarget = UserMods },
+ IsValidMod = _ => false
+ }
+ },
});
}
@@ -73,7 +99,6 @@ namespace osu.Game.Screens.OnlinePlay.Match
base.LoadComplete();
SelectedItem.BindValueChanged(_ => Scheduler.AddOnce(selectedItemChanged));
- SelectedItem.Value = Playlist.FirstOrDefault();
managerUpdated = beatmapManager.ItemUpdated.GetBoundCopy();
managerUpdated.BindValueChanged(beatmapUpdated);
@@ -81,6 +106,19 @@ namespace osu.Game.Screens.OnlinePlay.Match
UserMods.BindValueChanged(_ => Scheduler.AddOnce(UpdateMods));
}
+ public override bool OnBackButton()
+ {
+ if (userModsSelectOverlay.State.Value == Visibility.Visible)
+ {
+ userModsSelectOverlay.Hide();
+ return true;
+ }
+
+ return base.OnBackButton();
+ }
+
+ protected void ShowUserModSelect() => userModsSelectOverlay.Show();
+
public override void OnEntering(IScreen last)
{
base.OnEntering(last);
@@ -120,17 +158,31 @@ namespace osu.Game.Screens.OnlinePlay.Match
{
updateWorkingBeatmap();
- if (SelectedItem.Value == null)
+ var selected = SelectedItem.Value;
+
+ if (selected == null)
return;
// Remove any user mods that are no longer allowed.
UserMods.Value = UserMods.Value
- .Where(m => SelectedItem.Value.AllowedMods.Any(a => m.GetType() == a.GetType()))
+ .Where(m => selected.AllowedMods.Any(a => m.GetType() == a.GetType()))
.ToList();
UpdateMods();
- Ruleset.Value = SelectedItem.Value.Ruleset.Value;
+ Ruleset.Value = selected.Ruleset.Value;
+
+ if (!selected.AllowedMods.Any())
+ {
+ UserModsSection?.Hide();
+ userModsSelectOverlay.Hide();
+ userModsSelectOverlay.IsValidMod = _ => false;
+ }
+ else
+ {
+ UserModsSection?.Show();
+ userModsSelectOverlay.IsValidMod = m => selected.AllowedMods.Any(a => a.GetType() == m.GetType());
+ }
}
private void beatmapUpdated(ValueChangedEvent> weakSet) => Schedule(updateWorkingBeatmap);
@@ -185,5 +237,9 @@ namespace osu.Game.Screens.OnlinePlay.Match
if (track != null)
track.Looping = false;
}
+
+ private class UserModSelectOverlay : LocalPlayerModSelectOverlay
+ {
+ }
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs
index f17e04d4d4..ebe63e26d6 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.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.
-using System.Collections.Specialized;
-using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -13,7 +11,7 @@ using osu.Game.Screens.OnlinePlay.Match.Components;
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
{
- public class BeatmapSelectionControl : OnlinePlayComposite
+ public class BeatmapSelectionControl : RoomSubScreenComposite
{
[Resolved]
private MultiplayerMatchSubScreen matchSubScreen { get; set; }
@@ -60,7 +58,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
{
base.LoadComplete();
- Playlist.BindCollectionChanged(onPlaylistChanged, true);
+ SelectedItem.BindValueChanged(_ => updateBeatmap(), true);
Host.BindValueChanged(host =>
{
if (RoomID.Value == null || host.NewValue?.Equals(api.LocalUser.Value) == true)
@@ -70,12 +68,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
}, true);
}
- private void onPlaylistChanged(object sender, NotifyCollectionChangedEventArgs e)
+ private void updateBeatmap()
{
- if (Playlist.Any())
- beatmapPanelContainer.Child = new DrawableRoomPlaylistItem(Playlist.Single(), false, false);
- else
+ if (SelectedItem.Value == null)
beatmapPanelContainer.Clear();
+ else
+ beatmapPanelContainer.Child = new DrawableRoomPlaylistItem(SelectedItem.Value, false, false);
}
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs
index 3f3fee1b79..4fbea4e3be 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs
@@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
-using System.Collections.Specialized;
using System.Diagnostics;
using System.Linq;
using JetBrains.Annotations;
@@ -16,7 +15,6 @@ using osu.Framework.Threading;
using osu.Game.Configuration;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms;
-using osu.Game.Overlays.Mods;
using osu.Game.Rulesets.Mods;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.OnlinePlay.Match;
@@ -43,11 +41,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
[Resolved]
private OngoingOperationTracker ongoingOperationTracker { get; set; }
- private ModSelectOverlay userModsSelectOverlay;
private MultiplayerMatchSettingsOverlay settingsOverlay;
- private Drawable userModsSection;
- private IBindable isConnected;
+ private readonly IBindable isConnected = new Bindable();
[CanBeNull]
private IDisposable readyClickOperation;
@@ -155,7 +151,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
new BeatmapSelectionControl { RelativeSizeAxes = Axes.X }
}
},
- userModsSection = new FillFlowContainer
+ UserModsSection = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
@@ -176,7 +172,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
Origin = Anchor.CentreLeft,
Width = 90,
Text = "Select",
- Action = () => userModsSelectOverlay.Show()
+ Action = ShowUserModSelect,
},
new ModDisplay
{
@@ -231,19 +227,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
new Dimension(GridSizeMode.AutoSize),
}
},
- new Container
- {
- Anchor = Anchor.BottomLeft,
- Origin = Anchor.BottomLeft,
- RelativeSizeAxes = Axes.Both,
- Height = 0.5f,
- Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING },
- Child = userModsSelectOverlay = new UserModSelectOverlay
- {
- SelectedMods = { BindTarget = UserMods },
- IsValidMod = _ => false
- }
- },
settingsOverlay = new MultiplayerMatchSettingsOverlay
{
RelativeSizeAxes = Axes.Both,
@@ -269,14 +252,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
{
base.LoadComplete();
- Playlist.BindCollectionChanged(onPlaylistChanged, true);
+ SelectedItem.BindTo(client.CurrentMatchPlayingItem);
+
BeatmapAvailability.BindValueChanged(updateBeatmapAvailability, true);
UserMods.BindValueChanged(onUserModsChanged);
client.LoadRequested += onLoadRequested;
client.RoomUpdated += onRoomUpdated;
- isConnected = client.IsConnected.GetBoundCopy();
+ isConnected.BindTo(client.IsConnected);
isConnected.BindValueChanged(connected =>
{
if (!connected.NewValue)
@@ -303,32 +287,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
return true;
}
- if (userModsSelectOverlay.State.Value == Visibility.Visible)
- {
- userModsSelectOverlay.Hide();
- return true;
- }
-
return base.OnBackButton();
}
- private void onPlaylistChanged(object sender, NotifyCollectionChangedEventArgs e)
- {
- SelectedItem.Value = Playlist.FirstOrDefault();
-
- if (SelectedItem.Value?.AllowedMods.Any() != true)
- {
- userModsSection.Hide();
- userModsSelectOverlay.Hide();
- userModsSelectOverlay.IsValidMod = _ => false;
- }
- else
- {
- userModsSection.Show();
- userModsSelectOverlay.IsValidMod = m => SelectedItem.Value.AllowedMods.Any(a => a.GetType() == m.GetType());
- }
- }
-
private ModSettingChangeTracker modSettingChangeTracker;
private ScheduledDelegate debouncedModSettingsUpdate;
@@ -433,9 +394,5 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
modSettingChangeTracker?.Dispose();
}
-
- private class UserModSelectOverlay : LocalPlayerModSelectOverlay
- {
- }
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs
index 04d9e0a72a..ffcf248575 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs
@@ -89,6 +89,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
Debug.Assert(client.Room != null);
}
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ ((IBindable)leaderboard.Expanded).BindTo(IsBreakTime);
+ }
+
protected override void StartGameplay()
{
// block base call, but let the server know we are ready to start.
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs
index ae32295676..5bef934e6a 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.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.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
@@ -35,9 +36,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
[Resolved]
private RulesetStore rulesets { get; set; }
+ private SpriteIcon crown;
+ private OsuSpriteText userRankText;
private ModDisplay userModsDisplay;
private StateDisplay userStateDisplay;
- private SpriteIcon crown;
public ParticipantPanel(MultiplayerRoomUser user)
{
@@ -119,12 +121,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 18),
Text = user?.Username
},
- new OsuSpriteText
+ userRankText = new OsuSpriteText
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Font = OsuFont.GetFont(size: 14),
- Text = user?.CurrentModeRank != null ? $"#{user.CurrentModeRank}" : string.Empty
}
}
},
@@ -162,6 +163,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
const double fade_time = 50;
+ var ruleset = rulesets.GetRuleset(Room.Settings.RulesetID).CreateInstance();
+
+ var currentModeRank = User.User?.RulesetsStatistics?.GetValueOrDefault(ruleset.ShortName)?.GlobalRank;
+ userRankText.Text = currentModeRank != null ? $"#{currentModeRank.Value:N0}" : string.Empty;
+
userStateDisplay.UpdateStatus(User.State, User.BeatmapAvailability);
if (Room.Host?.Equals(User) == true)
@@ -171,11 +177,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
// If the mods are updated at the end of the frame, the flow container will skip a reflow cycle: https://github.com/ppy/osu-framework/issues/4187
// This looks particularly jarring here, so re-schedule the update to that start of our frame as a fix.
- Schedule(() =>
- {
- var ruleset = rulesets.GetRuleset(Room.Settings.RulesetID).CreateInstance();
- userModsDisplay.Current.Value = User.Mods.Select(m => m.ToMod(ruleset)).ToList();
- });
+ Schedule(() => userModsDisplay.Current.Value = User.Mods.Select(m => m.ToMod(ruleset)).ToList());
}
public MenuItem[] ContextMenuItems
diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs
index edae4aeec5..eb0b23f13f 100644
--- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs
+++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs
@@ -2,14 +2,19 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers;
using osu.Game.Online.Rooms;
+using osu.Game.Screens.OnlinePlay.Match;
using osu.Game.Users;
namespace osu.Game.Screens.OnlinePlay
{
+ ///
+ /// A that exposes bindables for properties.
+ ///
public class OnlinePlayComposite : CompositeDrawable
{
[Resolved(typeof(Room))]
@@ -53,5 +58,23 @@ namespace osu.Game.Screens.OnlinePlay
[Resolved(typeof(Room))]
protected Bindable Duration { get; private set; }
+
+ ///
+ /// The currently selected item in the , or the first item from
+ /// if this is not within a .
+ ///
+ protected readonly Bindable SelectedItem = new Bindable();
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ Playlist.BindCollectionChanged((_, __) => UpdateSelectedItem(), true);
+ }
+
+ protected virtual void UpdateSelectedItem()
+ {
+ SelectedItem.Value = Playlist.FirstOrDefault();
+ }
}
}
diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs
index 71fd0d5c76..90e499c67f 100644
--- a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs
@@ -28,7 +28,7 @@ using osuTK;
namespace osu.Game.Screens.OnlinePlay
{
[Cached]
- public abstract class OnlinePlayScreen : OsuScreen
+ public abstract class OnlinePlayScreen : OsuScreen, IHasSubScreenStack
{
public override bool CursorVisible => (screenStack.CurrentScreen as IOnlinePlaySubScreen)?.CursorVisible ?? true;
@@ -355,5 +355,7 @@ namespace osu.Game.Screens.OnlinePlay
protected override double TransformDuration => 200;
}
}
+
+ ScreenStack IHasSubScreenStack.SubScreenStack => screenStack;
}
}
diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs
index b201c62b7f..f0c77b79bf 100644
--- a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs
+++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs
@@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Humanizer;
+using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@@ -31,7 +32,12 @@ namespace osu.Game.Screens.OnlinePlay
[Resolved(typeof(Room), nameof(Room.Playlist))]
protected BindableList Playlist { get; private set; }
- private readonly Bindable> freeMods = new Bindable>(Array.Empty());
+ protected readonly Bindable> FreeMods = new Bindable>(Array.Empty());
+
+ [CanBeNull]
+ [Resolved(CanBeNull = true)]
+ private IBindable selectedItem { get; set; }
+
private readonly FreeModSelectOverlay freeModSelectOverlay;
private WorkingBeatmap initialBeatmap;
@@ -45,7 +51,7 @@ namespace osu.Game.Screens.OnlinePlay
freeModSelectOverlay = new FreeModSelectOverlay
{
- SelectedMods = { BindTarget = freeMods },
+ SelectedMods = { BindTarget = FreeMods },
IsValidMod = IsValidFreeMod,
};
}
@@ -66,15 +72,15 @@ namespace osu.Game.Screens.OnlinePlay
// At this point, Mods contains both the required and allowed mods. For selection purposes, it should only contain the required mods.
// Similarly, freeMods is currently empty but should only contain the allowed mods.
- Mods.Value = Playlist.FirstOrDefault()?.RequiredMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty();
- freeMods.Value = Playlist.FirstOrDefault()?.AllowedMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty();
+ Mods.Value = selectedItem?.Value?.RequiredMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty();
+ FreeMods.Value = selectedItem?.Value?.AllowedMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty();
Ruleset.BindValueChanged(onRulesetChanged);
}
private void onRulesetChanged(ValueChangedEvent ruleset)
{
- freeMods.Value = Array.Empty();
+ FreeMods.Value = Array.Empty();
}
protected sealed override bool OnStart()
@@ -90,7 +96,7 @@ namespace osu.Game.Screens.OnlinePlay
item.RequiredMods.AddRange(Mods.Value.Select(m => m.CreateCopy()));
item.AllowedMods.Clear();
- item.AllowedMods.AddRange(freeMods.Value.Select(m => m.CreateCopy()));
+ item.AllowedMods.AddRange(FreeMods.Value.Select(m => m.CreateCopy()));
SelectItem(item);
return true;
@@ -133,7 +139,7 @@ namespace osu.Game.Screens.OnlinePlay
protected override IEnumerable<(FooterButton, OverlayContainer)> CreateFooterButtons()
{
var buttons = base.CreateFooterButtons().ToList();
- buttons.Insert(buttons.FindIndex(b => b.Item1 is FooterButtonMods) + 1, (new FooterButtonFreeMods { Current = freeMods }, freeModSelectOverlay));
+ buttons.Insert(buttons.FindIndex(b => b.Item1 is FooterButtonMods) + 1, (new FooterButtonFreeMods { Current = FreeMods }, freeModSelectOverlay));
return buttons;
}
diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs
index 9ccf4775d0..6542d01e64 100644
--- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs
@@ -13,7 +13,9 @@ using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.OnlinePlay.Match;
using osu.Game.Screens.OnlinePlay.Match.Components;
+using osu.Game.Screens.Play.HUD;
using osu.Game.Users;
+using osuTK;
using Footer = osu.Game.Screens.OnlinePlay.Match.Components.Footer;
namespace osu.Game.Screens.OnlinePlay.Playlists
@@ -27,6 +29,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
[Resolved(typeof(Room), nameof(Room.RoomID))]
private Bindable roomId { get; set; }
+ [Resolved(typeof(Room), nameof(Room.Playlist))]
+ private BindableList playlist { get; set; }
+
private MatchSettingsOverlay settingsOverlay;
private MatchLeaderboard leaderboard;
@@ -117,7 +122,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
new DrawableRoomPlaylistWithResults
{
RelativeSizeAxes = Axes.Both,
- Items = { BindTarget = Playlist },
+ Items = { BindTarget = playlist },
SelectedItem = { BindTarget = SelectedItem },
RequestShowResults = item =>
{
@@ -140,13 +145,55 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
RelativeSizeAxes = Axes.Both,
Content = new[]
{
- new Drawable[] { new OverlinedHeader("Leaderboard"), },
+ new[]
+ {
+ UserModsSection = new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Margin = new MarginPadding { Bottom = 10 },
+ Children = new Drawable[]
+ {
+ new OverlinedHeader("Extra mods"),
+ new FillFlowContainer
+ {
+ AutoSizeAxes = Axes.Both,
+ Direction = FillDirection.Horizontal,
+ Spacing = new Vector2(10, 0),
+ Children = new Drawable[]
+ {
+ new PurpleTriangleButton
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ Width = 90,
+ Text = "Select",
+ Action = ShowUserModSelect,
+ },
+ new ModDisplay
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ DisplayUnrankedText = false,
+ Current = UserMods,
+ Scale = new Vector2(0.8f),
+ },
+ }
+ }
+ }
+ },
+ },
+ new Drawable[]
+ {
+ new OverlinedHeader("Leaderboard")
+ },
new Drawable[] { leaderboard = new MatchLeaderboard { RelativeSizeAxes = Axes.Both }, },
new Drawable[] { new OverlinedHeader("Chat"), },
new Drawable[] { new MatchChatDisplay { RelativeSizeAxes = Axes.Both } }
},
RowDimensions = new[]
{
+ new Dimension(GridSizeMode.AutoSize),
new Dimension(GridSizeMode.AutoSize),
new Dimension(),
new Dimension(GridSizeMode.AutoSize),
@@ -222,7 +269,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
// Set the first playlist item.
// This is scheduled since updating the room and playlist may happen in an arbitrary order (via Room.CopyFrom()).
- Schedule(() => SelectedItem.Value = Playlist.FirstOrDefault());
+ Schedule(() => SelectedItem.Value = playlist.FirstOrDefault());
}
}, true);
}
diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs
index 0e8db6dfe5..21335fc90c 100644
--- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs
+++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs
@@ -56,6 +56,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
item.RequiredMods.Clear();
item.RequiredMods.AddRange(Mods.Value.Select(m => m.CreateCopy()));
+
+ item.AllowedMods.Clear();
+ item.AllowedMods.AddRange(FreeMods.Value.Select(m => m.CreateCopy()));
}
}
}
diff --git a/osu.Game/Screens/OnlinePlay/RoomSubScreenComposite.cs b/osu.Game/Screens/OnlinePlay/RoomSubScreenComposite.cs
new file mode 100644
index 0000000000..4cfd881aa3
--- /dev/null
+++ b/osu.Game/Screens/OnlinePlay/RoomSubScreenComposite.cs
@@ -0,0 +1,38 @@
+// 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 osu.Framework.Bindables;
+using osu.Game.Online.Rooms;
+using osu.Game.Screens.OnlinePlay.Match;
+
+namespace osu.Game.Screens.OnlinePlay
+{
+ ///
+ /// An with additional logic tracking the currently-selected inside a .
+ ///
+ public class RoomSubScreenComposite : OnlinePlayComposite
+ {
+ [Resolved]
+ private IBindable subScreenSelectedItem { get; set; }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ subScreenSelectedItem.BindValueChanged(_ => UpdateSelectedItem(), true);
+ }
+
+ protected override void UpdateSelectedItem()
+ {
+ if (RoomID.Value == null)
+ {
+ // If the room hasn't been created yet, fall-back to the base logic.
+ base.UpdateSelectedItem();
+ return;
+ }
+
+ SelectedItem.Value = subScreenSelectedItem.Value;
+ }
+ }
+}
diff --git a/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs
index 7b94bf19ec..34efeab54c 100644
--- a/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs
+++ b/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs
@@ -4,6 +4,7 @@
using System;
using System.Linq;
using JetBrains.Annotations;
+using osu.Framework.Bindables;
using osu.Framework.Caching;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -16,6 +17,8 @@ namespace osu.Game.Screens.Play.HUD
{
private readonly Cached sorting = new Cached();
+ public Bindable Expanded = new Bindable();
+
public GameplayLeaderboard()
{
Width = GameplayLeaderboardScore.EXTENDED_WIDTH + GameplayLeaderboardScore.SHEAR_WIDTH;
@@ -47,8 +50,7 @@ namespace osu.Game.Screens.Play.HUD
{
var drawable = new GameplayLeaderboardScore(user, isTracked)
{
- Anchor = Anchor.TopRight,
- Origin = Anchor.TopRight,
+ Expanded = { BindTarget = Expanded },
};
base.Add(drawable);
diff --git a/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs b/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs
index cb20deb272..10476e5565 100644
--- a/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs
+++ b/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs
@@ -20,16 +20,33 @@ namespace osu.Game.Screens.Play.HUD
{
public class GameplayLeaderboardScore : CompositeDrawable, ILeaderboardScore
{
- public const float EXTENDED_WIDTH = 255f;
+ public const float EXTENDED_WIDTH = regular_width + top_player_left_width_extension;
private const float regular_width = 235f;
+ // a bit hand-wavy, but there's a lot of hard-coded paddings in each of the grid's internals.
+ private const float compact_width = 77.5f;
+
+ private const float top_player_left_width_extension = 20f;
+
public const float PANEL_HEIGHT = 35f;
public const float SHEAR_WIDTH = PANEL_HEIGHT * panel_shear;
private const float panel_shear = 0.15f;
+ private const float rank_text_width = 35f;
+
+ private const float score_components_width = 85f;
+
+ private const float avatar_size = 25f;
+
+ private const double panel_transition_duration = 500;
+
+ private const double text_transition_duration = 200;
+
+ public Bindable Expanded = new Bindable();
+
private OsuSpriteText positionText, scoreText, accuracyText, comboText, usernameText;
public BindableDouble TotalScore { get; } = new BindableDouble();
@@ -63,8 +80,15 @@ namespace osu.Game.Screens.Play.HUD
private readonly bool trackedPlayer;
private Container mainFillContainer;
+
private Box centralFill;
+ private Container backgroundPaddingAdjustContainer;
+
+ private GridContainer gridContainer;
+
+ private Container scoreComponents;
+
///
/// Creates a new .
///
@@ -75,7 +99,8 @@ namespace osu.Game.Screens.Play.HUD
User = user;
this.trackedPlayer = trackedPlayer;
- Size = new Vector2(EXTENDED_WIDTH, PANEL_HEIGHT);
+ AutoSizeAxes = Axes.X;
+ Height = PANEL_HEIGHT;
}
[BackgroundDependencyLoader]
@@ -85,147 +110,167 @@ namespace osu.Game.Screens.Play.HUD
InternalChildren = new Drawable[]
{
- mainFillContainer = new Container
+ new Container
{
- Width = regular_width,
+ AutoSizeAxes = Axes.X,
RelativeSizeAxes = Axes.Y,
- Anchor = Anchor.TopRight,
- Origin = Anchor.TopRight,
- Masking = true,
- CornerRadius = 5f,
- Shear = new Vector2(panel_shear, 0f),
- Child = new Box
+ Margin = new MarginPadding { Left = top_player_left_width_extension },
+ Children = new Drawable[]
{
- Alpha = 0.5f,
- RelativeSizeAxes = Axes.Both,
- }
- },
- new GridContainer
- {
- Width = regular_width,
- RelativeSizeAxes = Axes.Y,
- Anchor = Anchor.TopRight,
- Origin = Anchor.TopRight,
- ColumnDimensions = new[]
- {
- new Dimension(GridSizeMode.Absolute, 35f),
- new Dimension(),
- new Dimension(GridSizeMode.Absolute, 85f),
- },
- Content = new[]
- {
- new Drawable[]
+ backgroundPaddingAdjustContainer = new Container
{
- positionText = new OsuSpriteText
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
{
- Padding = new MarginPadding { Right = SHEAR_WIDTH / 2 },
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Colour = Color4.White,
- Font = OsuFont.Torus.With(size: 14, weight: FontWeight.Bold),
- Shadow = false,
- },
- new Container
- {
- Padding = new MarginPadding { Horizontal = SHEAR_WIDTH / 3 },
- RelativeSizeAxes = Axes.Both,
- Children = new Drawable[]
+ mainFillContainer = new Container
{
- new Container
+ Anchor = Anchor.TopRight,
+ Origin = Anchor.TopRight,
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ CornerRadius = 5f,
+ Shear = new Vector2(panel_shear, 0f),
+ Children = new Drawable[]
{
- Masking = true,
- CornerRadius = 5f,
- Shear = new Vector2(panel_shear, 0f),
- RelativeSizeAxes = Axes.Both,
- Children = new[]
+ new Box
{
- centralFill = new Box
- {
- Alpha = 0.5f,
- RelativeSizeAxes = Axes.Both,
- Colour = Color4Extensions.FromHex("3399cc"),
- },
- }
- },
- new FillFlowContainer
- {
- Padding = new MarginPadding { Left = SHEAR_WIDTH },
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.CentreLeft,
- RelativeSizeAxes = Axes.Both,
- Direction = FillDirection.Horizontal,
- Spacing = new Vector2(4f, 0f),
- Children = new Drawable[]
- {
- avatarContainer = new CircularContainer
- {
- Masking = true,
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.CentreLeft,
- Size = new Vector2(25f),
- Children = new Drawable[]
- {
- new Box
- {
- Name = "Placeholder while avatar loads",
- Alpha = 0.3f,
- RelativeSizeAxes = Axes.Both,
- Colour = colours.Gray4,
- }
- }
- },
- usernameText = new OsuSpriteText
- {
- RelativeSizeAxes = Axes.X,
- Width = 0.6f,
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.CentreLeft,
- Colour = Color4.White,
- Font = OsuFont.Torus.With(size: 14, weight: FontWeight.SemiBold),
- Text = User?.Username,
- Truncate = true,
- Shadow = false,
- }
- }
- },
- }
- },
- new Container
- {
- Padding = new MarginPadding { Top = 2f, Right = 17.5f, Bottom = 5f },
- RelativeSizeAxes = Axes.Both,
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.CentreLeft,
- Colour = Color4.White,
- Children = new Drawable[]
- {
- scoreText = new OsuSpriteText
- {
- Spacing = new Vector2(-1f, 0f),
- Font = OsuFont.Torus.With(size: 16, weight: FontWeight.SemiBold, fixedWidth: true),
- Shadow = false,
- },
- accuracyText = new OsuSpriteText
- {
- Anchor = Anchor.BottomLeft,
- Origin = Anchor.BottomLeft,
- Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold, fixedWidth: true),
- Spacing = new Vector2(-1f, 0f),
- Shadow = false,
- },
- comboText = new OsuSpriteText
- {
- Anchor = Anchor.BottomRight,
- Origin = Anchor.BottomRight,
- Spacing = new Vector2(-1f, 0f),
- Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold, fixedWidth: true),
- Shadow = false,
+ Alpha = 0.5f,
+ RelativeSizeAxes = Axes.Both,
+ },
},
},
}
+ },
+ gridContainer = new GridContainer
+ {
+ RelativeSizeAxes = Axes.Y,
+ Width = compact_width, // will be updated by expanded state.
+ Anchor = Anchor.TopRight,
+ Origin = Anchor.TopRight,
+ ColumnDimensions = new[]
+ {
+ new Dimension(GridSizeMode.Absolute, rank_text_width),
+ new Dimension(),
+ new Dimension(GridSizeMode.AutoSize, maxSize: score_components_width),
+ },
+ Content = new[]
+ {
+ new Drawable[]
+ {
+ positionText = new OsuSpriteText
+ {
+ Padding = new MarginPadding { Right = SHEAR_WIDTH / 2 },
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Colour = Color4.White,
+ Font = OsuFont.Torus.With(size: 14, weight: FontWeight.Bold),
+ Shadow = false,
+ },
+ new Container
+ {
+ Padding = new MarginPadding { Horizontal = SHEAR_WIDTH / 3 },
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ new Container
+ {
+ Masking = true,
+ CornerRadius = 5f,
+ Shear = new Vector2(panel_shear, 0f),
+ RelativeSizeAxes = Axes.Both,
+ Children = new[]
+ {
+ centralFill = new Box
+ {
+ Alpha = 0.5f,
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4Extensions.FromHex("3399cc"),
+ },
+ }
+ },
+ new FillFlowContainer
+ {
+ Padding = new MarginPadding { Left = SHEAR_WIDTH },
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ RelativeSizeAxes = Axes.Both,
+ Direction = FillDirection.Horizontal,
+ Spacing = new Vector2(4f, 0f),
+ Children = new Drawable[]
+ {
+ avatarContainer = new CircularContainer
+ {
+ Masking = true,
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ Size = new Vector2(avatar_size),
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ Name = "Placeholder while avatar loads",
+ Alpha = 0.3f,
+ RelativeSizeAxes = Axes.Both,
+ Colour = colours.Gray4,
+ }
+ }
+ },
+ usernameText = new OsuSpriteText
+ {
+ RelativeSizeAxes = Axes.X,
+ Width = 0.6f,
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ Colour = Color4.White,
+ Font = OsuFont.Torus.With(size: 14, weight: FontWeight.SemiBold),
+ Text = User?.Username,
+ Truncate = true,
+ Shadow = false,
+ }
+ }
+ },
+ }
+ },
+ scoreComponents = new Container
+ {
+ Padding = new MarginPadding { Top = 2f, Right = 17.5f, Bottom = 5f },
+ AlwaysPresent = true, // required to smoothly animate autosize after hidden early.
+ Masking = true,
+ RelativeSizeAxes = Axes.Y,
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ Colour = Color4.White,
+ Children = new Drawable[]
+ {
+ scoreText = new OsuSpriteText
+ {
+ Spacing = new Vector2(-1f, 0f),
+ Font = OsuFont.Torus.With(size: 16, weight: FontWeight.SemiBold, fixedWidth: true),
+ Shadow = false,
+ },
+ accuracyText = new OsuSpriteText
+ {
+ Anchor = Anchor.BottomLeft,
+ Origin = Anchor.BottomLeft,
+ Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold, fixedWidth: true),
+ Spacing = new Vector2(-1f, 0f),
+ Shadow = false,
+ },
+ comboText = new OsuSpriteText
+ {
+ Anchor = Anchor.BottomRight,
+ Origin = Anchor.BottomRight,
+ Spacing = new Vector2(-1f, 0f),
+ Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold, fixedWidth: true),
+ Shadow = false,
+ },
+ },
+ }
+ }
+ }
}
}
- }
+ },
};
LoadComponentAsync(new DrawableAvatar(User), avatarContainer.Add);
@@ -241,18 +286,43 @@ namespace osu.Game.Screens.Play.HUD
base.LoadComplete();
updateState();
+ Expanded.BindValueChanged(changeExpandedState, true);
+
FinishTransforms(true);
}
- private const double panel_transition_duration = 500;
+ private void changeExpandedState(ValueChangedEvent expanded)
+ {
+ scoreComponents.ClearTransforms();
+
+ if (expanded.NewValue)
+ {
+ gridContainer.ResizeWidthTo(regular_width, panel_transition_duration, Easing.OutQuint);
+
+ scoreComponents.ResizeWidthTo(score_components_width, panel_transition_duration, Easing.OutQuint);
+ scoreComponents.FadeIn(panel_transition_duration, Easing.OutQuint);
+
+ usernameText.FadeIn(panel_transition_duration, Easing.OutQuint);
+ }
+ else
+ {
+ gridContainer.ResizeWidthTo(compact_width, panel_transition_duration, Easing.OutQuint);
+
+ scoreComponents.ResizeWidthTo(0, panel_transition_duration, Easing.OutQuint);
+ scoreComponents.FadeOut(text_transition_duration, Easing.OutQuint);
+
+ usernameText.FadeOut(text_transition_duration, Easing.OutQuint);
+ }
+ }
private void updateState()
{
+ bool widthExtension = false;
+
if (HasQuit.Value)
{
// we will probably want to display this in a better way once we have a design.
// and also show states other than quit.
- mainFillContainer.ResizeWidthTo(regular_width, panel_transition_duration, Easing.OutElastic);
panelColour = Color4.Gray;
textColour = Color4.White;
return;
@@ -260,22 +330,29 @@ namespace osu.Game.Screens.Play.HUD
if (scorePosition == 1)
{
- mainFillContainer.ResizeWidthTo(EXTENDED_WIDTH, panel_transition_duration, Easing.OutElastic);
+ widthExtension = true;
panelColour = Color4Extensions.FromHex("7fcc33");
textColour = Color4.White;
}
else if (trackedPlayer)
{
- mainFillContainer.ResizeWidthTo(EXTENDED_WIDTH, panel_transition_duration, Easing.OutElastic);
+ widthExtension = true;
panelColour = Color4Extensions.FromHex("ffd966");
textColour = Color4Extensions.FromHex("2e576b");
}
else
{
- mainFillContainer.ResizeWidthTo(regular_width, panel_transition_duration, Easing.OutElastic);
panelColour = Color4Extensions.FromHex("3399cc");
textColour = Color4.White;
}
+
+ this.TransformTo(nameof(SizeContainerLeftPadding), widthExtension ? -top_player_left_width_extension : 0, panel_transition_duration, Easing.OutElastic);
+ }
+
+ public float SizeContainerLeftPadding
+ {
+ get => backgroundPaddingAdjustContainer.Padding.Left;
+ set => backgroundPaddingAdjustContainer.Padding = new MarginPadding { Left = value };
}
private Color4 panelColour
@@ -287,8 +364,6 @@ namespace osu.Game.Screens.Play.HUD
}
}
- private const double text_transition_duration = 200;
-
private Color4 textColour
{
set
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index 74059da21a..1e130b7f88 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -339,7 +339,7 @@ namespace osu.Game.Screens.Play
{
HoldToQuit =
{
- Action = performUserRequestedExit,
+ Action = () => PerformExit(true),
IsPaused = { BindTarget = GameplayClockContainer.IsPaused }
},
PlayerSettingsOverlay = { PlaybackSettings = { UserPlaybackRate = { BindTarget = GameplayClockContainer.UserPlaybackRate } } },
@@ -363,14 +363,14 @@ namespace osu.Game.Screens.Play
FailOverlay = new FailOverlay
{
OnRetry = Restart,
- OnQuit = performUserRequestedExit,
+ OnQuit = () => PerformExit(true),
},
PauseOverlay = new PauseOverlay
{
OnResume = Resume,
Retries = RestartCount,
OnRetry = Restart,
- OnQuit = performUserRequestedExit,
+ OnQuit = () => PerformExit(true),
},
new HotkeyExitOverlay
{
@@ -379,7 +379,7 @@ namespace osu.Game.Screens.Play
if (!this.IsCurrentScreen()) return;
fadeOut(true);
- PerformExit(true);
+ PerformExit(false);
},
},
failAnimation = new FailAnimation(DrawableRuleset) { OnComplete = onFailComplete, },
@@ -478,23 +478,47 @@ namespace osu.Game.Screens.Play
///
/// Exits the .
///
- ///
- /// Whether the exit is requested by the user, or a higher-level game component.
- /// Pausing is allowed only in the former case.
+ ///
+ /// Whether the pause or fail dialog should be shown before performing an exit.
+ /// If true and a dialog is not yet displayed, the exit will be blocked the the relevant dialog will display instead.
///
- protected void PerformExit(bool userRequested)
+ protected void PerformExit(bool showDialogFirst)
{
// if a restart has been requested, cancel any pending completion (user has shown intent to restart).
completionProgressDelegate?.Cancel();
- ValidForResume = false;
+ // there is a chance that the exit was performed after the transition to results has started.
+ // we want to give the user what they want, so forcefully return to this screen (to proceed with the upwards exit process).
+ if (!this.IsCurrentScreen())
+ {
+ ValidForResume = false;
+ this.MakeCurrent();
+ return;
+ }
- if (!this.IsCurrentScreen()) return;
+ bool pauseOrFailDialogVisible =
+ PauseOverlay.State.Value == Visibility.Visible || FailOverlay.State.Value == Visibility.Visible;
- if (userRequested)
- performUserRequestedExit();
- else
- this.Exit();
+ if (showDialogFirst && !pauseOrFailDialogVisible)
+ {
+ // if the fail animation is currently in progress, accelerate it (it will show the pause dialog on completion).
+ if (ValidForResume && HasFailed)
+ {
+ failAnimation.FinishTransforms(true);
+ return;
+ }
+
+ // there's a chance the pausing is not supported in the current state, at which point immediate exit should be preferred.
+ if (pausingSupportedByCurrentState)
+ {
+ // in the case a dialog needs to be shown, attempt to pause and show it.
+ // this may fail (see internal checks in Pause()) but the fail cases are temporary, so don't fall through to Exit().
+ Pause();
+ return;
+ }
+ }
+
+ this.Exit();
}
private void performUserRequestedSkip()
@@ -508,20 +532,6 @@ namespace osu.Game.Screens.Play
updateSampleDisabledState();
}
- private void performUserRequestedExit()
- {
- if (ValidForResume && HasFailed && !FailOverlay.IsPresent)
- {
- failAnimation.FinishTransforms(true);
- return;
- }
-
- if (canPause)
- Pause();
- else
- this.Exit();
- }
-
///
/// Restart gameplay via a parent .
/// This can be called from a child screen in order to trigger the restart process.
@@ -538,10 +548,7 @@ namespace osu.Game.Screens.Play
sampleRestart?.Play();
RestartRequested?.Invoke();
- if (this.IsCurrentScreen())
- PerformExit(true);
- else
- this.MakeCurrent();
+ PerformExit(false);
}
private ScheduledDelegate completionProgressDelegate;
@@ -667,15 +674,17 @@ namespace osu.Game.Screens.Play
private double? lastPauseActionTime;
- private bool canPause =>
+ ///
+ /// A set of conditionals which defines whether the current game state and configuration allows for
+ /// pausing to be attempted via . If false, the game should generally exit if a user pause
+ /// is attempted.
+ ///
+ private bool pausingSupportedByCurrentState =>
// must pass basic screen conditions (beatmap loaded, instance allows pause)
LoadedBeatmapSuccessfully && Configuration.AllowPause && ValidForResume
// replays cannot be paused and exit immediately
&& !DrawableRuleset.HasReplayLoaded.Value
- // cannot pause if we are already in a fail state
- && !HasFailed
- // cannot pause if already paused (or in a cooldown state) unless we are in a resuming state.
- && (IsResuming || (GameplayClockContainer.IsPaused.Value == false && !pauseCooldownActive));
+ && !HasFailed;
private bool pauseCooldownActive =>
lastPauseActionTime.HasValue && GameplayClockContainer.GameplayClock.CurrentTime < lastPauseActionTime + pause_cooldown;
@@ -690,7 +699,10 @@ namespace osu.Game.Screens.Play
public void Pause()
{
- if (!canPause) return;
+ if (!pausingSupportedByCurrentState) return;
+
+ if (!IsResuming && pauseCooldownActive)
+ return;
if (IsResuming)
{
@@ -809,14 +821,6 @@ namespace osu.Game.Screens.Play
return true;
}
- // ValidForResume is false when restarting
- if (ValidForResume)
- {
- if (pauseCooldownActive && !GameplayClockContainer.IsPaused.Value)
- // still want to block if we are within the cooldown period and not already paused.
- return true;
- }
-
// GameplayClockContainer performs seeks / start / stop operations on the beatmap's track.
// as we are no longer the current screen, we cannot guarantee the track is still usable.
GameplayClockContainer?.StopUsingBeatmapClock();
diff --git a/osu.Game/Screens/Select/Carousel/CarouselHeader.cs b/osu.Game/Screens/Select/Carousel/CarouselHeader.cs
index 947334c747..2fbf64de29 100644
--- a/osu.Game/Screens/Select/Carousel/CarouselHeader.cs
+++ b/osu.Game/Screens/Select/Carousel/CarouselHeader.cs
@@ -25,8 +25,13 @@ namespace osu.Game.Screens.Select.Carousel
public readonly Bindable State = new Bindable(CarouselItemState.NotSelected);
+ private readonly HoverLayer hoverLayer;
+
protected override Container Content { get; } = new Container { RelativeSizeAxes = Axes.Both };
+ private const float corner_radius = 10;
+ private const float border_thickness = 2.5f;
+
public CarouselHeader()
{
RelativeSizeAxes = Axes.X;
@@ -36,12 +41,12 @@ namespace osu.Game.Screens.Select.Carousel
{
RelativeSizeAxes = Axes.Both,
Masking = true,
- CornerRadius = 10,
+ CornerRadius = corner_radius,
BorderColour = new Color4(221, 255, 255, 255),
Children = new Drawable[]
{
Content,
- new HoverLayer()
+ hoverLayer = new HoverLayer()
}
};
}
@@ -59,6 +64,8 @@ namespace osu.Game.Screens.Select.Carousel
{
case CarouselItemState.Collapsed:
case CarouselItemState.NotSelected:
+ hoverLayer.InsetForBorder = false;
+
BorderContainer.BorderThickness = 0;
BorderContainer.EdgeEffect = new EdgeEffectParameters
{
@@ -70,7 +77,9 @@ namespace osu.Game.Screens.Select.Carousel
break;
case CarouselItemState.Selected:
- BorderContainer.BorderThickness = 2.5f;
+ hoverLayer.InsetForBorder = true;
+
+ BorderContainer.BorderThickness = border_thickness;
BorderContainer.EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
@@ -107,6 +116,26 @@ namespace osu.Game.Screens.Select.Carousel
sampleHover = audio.Samples.Get("SongSelect/song-ping");
}
+ public bool InsetForBorder
+ {
+ set
+ {
+ if (value)
+ {
+ // apply same border as above to avoid applying additive overlay to it (and blowing out the colour).
+ Masking = true;
+ CornerRadius = corner_radius;
+ BorderThickness = border_thickness;
+ }
+ else
+ {
+ BorderThickness = 0;
+ CornerRadius = 0;
+ Masking = false;
+ }
+ }
+ }
+
protected override bool OnHover(HoverEvent e)
{
box.FadeIn(100, Easing.OutQuint);
diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs
index 952a5d1eaa..eafd8a87d1 100644
--- a/osu.Game/Screens/Select/FilterControl.cs
+++ b/osu.Game/Screens/Select/FilterControl.cs
@@ -44,7 +44,7 @@ namespace osu.Game.Screens.Select
Sort = sortMode.Value,
AllowConvertedBeatmaps = showConverted.Value,
Ruleset = ruleset.Value,
- Collection = collectionDropdown?.Current.Value.Collection
+ Collection = collectionDropdown?.Current.Value?.Collection
};
if (!minimumStars.IsDefault)
diff --git a/osu.Game/Skinning/PoolableSkinnableSample.cs b/osu.Game/Skinning/PoolableSkinnableSample.cs
index b12fbf90f3..9103a6a960 100644
--- a/osu.Game/Skinning/PoolableSkinnableSample.cs
+++ b/osu.Game/Skinning/PoolableSkinnableSample.cs
@@ -17,7 +17,7 @@ namespace osu.Game.Skinning
///
/// A sample corresponding to an that supports being pooled and responding to skin changes.
///
- public class PoolableSkinnableSample : SkinReloadableDrawable, IAggregateAudioAdjustment, IAdjustableAudioComponent
+ public class PoolableSkinnableSample : SkinReloadableDrawable, IAdjustableAudioComponent
{
///
/// The currently-loaded .
@@ -173,10 +173,6 @@ namespace osu.Game.Skinning
public void RemoveAdjustment(AdjustableProperty type, IBindable adjustBindable) => sampleContainer.RemoveAdjustment(type, adjustBindable);
- public void AddAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => sampleContainer.AddAdjustment(type, adjustBindable);
-
- public void RemoveAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => sampleContainer.RemoveAdjustment(type, adjustBindable);
-
public void RemoveAllAdjustments(AdjustableProperty type) => sampleContainer.RemoveAllAdjustments(type);
public IBindable AggregateVolume => sampleContainer.AggregateVolume;
diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs
index c971517c7f..edd3a2cdd3 100644
--- a/osu.Game/Skinning/SkinnableSound.cs
+++ b/osu.Game/Skinning/SkinnableSound.cs
@@ -184,10 +184,6 @@ namespace osu.Game.Skinning
public void RemoveAdjustment(AdjustableProperty type, IBindable adjustBindable) => samplesContainer.RemoveAdjustment(type, adjustBindable);
- public void AddAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => samplesContainer.AddAdjustment(type, adjustBindable);
-
- public void RemoveAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => samplesContainer.RemoveAdjustment(type, adjustBindable);
-
public void RemoveAllAdjustments(AdjustableProperty type) => samplesContainer.RemoveAllAdjustments(type);
///
@@ -197,6 +193,14 @@ namespace osu.Game.Skinning
public bool IsPlayed => samplesContainer.Any(s => s.Played);
+ public IBindable AggregateVolume => samplesContainer.AggregateVolume;
+
+ public IBindable AggregateBalance => samplesContainer.AggregateBalance;
+
+ public IBindable AggregateFrequency => samplesContainer.AggregateFrequency;
+
+ public IBindable AggregateTempo => samplesContainer.AggregateTempo;
+
#endregion
}
}
diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs
index 518236755d..4a6fd540c7 100644
--- a/osu.Game/Users/User.cs
+++ b/osu.Game/Users/User.cs
@@ -2,10 +2,13 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
+using JetBrains.Annotations;
using Newtonsoft.Json;
using osu.Framework.Bindables;
+using osu.Game.Online.API.Requests;
namespace osu.Game.Users
{
@@ -178,6 +181,10 @@ namespace osu.Game.Users
private UserStatistics statistics;
+ ///
+ /// User statistics for the requested ruleset (in the case of a response).
+ /// Otherwise empty.
+ ///
[JsonProperty(@"statistics")]
public UserStatistics Statistics
{
@@ -228,14 +235,14 @@ namespace osu.Game.Users
[JsonProperty("replays_watched_counts")]
public UserHistoryCount[] ReplaysWatchedCounts;
- public class UserHistoryCount
- {
- [JsonProperty("start_date")]
- public DateTime Date;
-
- [JsonProperty("count")]
- public long Count;
- }
+ ///
+ /// All user statistics per ruleset's short name (in the case of a response).
+ /// Otherwise empty. Can be altered for testing purposes.
+ ///
+ // todo: this should likely be moved to a separate UserCompact class at some point.
+ [JsonProperty("statistics_rulesets")]
+ [CanBeNull]
+ public Dictionary RulesetsStatistics { get; set; }
public override string ToString() => Username;
@@ -249,6 +256,14 @@ namespace osu.Game.Users
Id = 0
};
+ public bool Equals(User other)
+ {
+ if (ReferenceEquals(null, other)) return false;
+ if (ReferenceEquals(this, other)) return true;
+
+ return Id == other.Id;
+ }
+
public enum PlayStyle
{
[Description("Keyboard")]
@@ -264,12 +279,13 @@ namespace osu.Game.Users
Touch,
}
- public bool Equals(User other)
+ public class UserHistoryCount
{
- if (ReferenceEquals(null, other)) return false;
- if (ReferenceEquals(this, other)) return true;
+ [JsonProperty("start_date")]
+ public DateTime Date;
- return Id == other.Id;
+ [JsonProperty("count")]
+ public long Count;
}
}
}
diff --git a/osu.Game/Users/UserStatistics.cs b/osu.Game/Users/UserStatistics.cs
index 8b7699d0ad..78e6f5a05a 100644
--- a/osu.Game/Users/UserStatistics.cs
+++ b/osu.Game/Users/UserStatistics.cs
@@ -26,17 +26,24 @@ namespace osu.Game.Users
public int Progress;
}
- [JsonProperty(@"pp")]
- public decimal? PP;
+ [JsonProperty(@"global_rank")]
+ public int? GlobalRank;
- [JsonProperty(@"pp_rank")] // the API sometimes only returns this value in condensed user responses
- private int? rank
- {
- set => Ranks.Global = value;
- }
+ public int? CountryRank;
[JsonProperty(@"rank")]
- public UserRanks Ranks;
+ private UserRanks ranks
+ {
+ // eventually that will also become an own json property instead of reading from a `rank` object.
+ // see https://github.com/ppy/osu-web/blob/cb79bb72186c8f1a25f6a6f5ef315123decb4231/app/Transformers/UserStatisticsTransformer.php#L53.
+ set => CountryRank = value.Country;
+ }
+
+ // populated via User model, as that's where the data currently lives.
+ public RankHistoryData RankHistory;
+
+ [JsonProperty(@"pp")]
+ public decimal? PP;
[JsonProperty(@"ranked_score")]
public long RankedScore;
@@ -113,15 +120,12 @@ namespace osu.Game.Users
}
}
- public struct UserRanks
+#pragma warning disable 649
+ private struct UserRanks
{
- [JsonProperty(@"global")]
- public int? Global;
-
[JsonProperty(@"country")]
public int? Country;
}
-
- public RankHistoryData RankHistory;
+#pragma warning restore 649
}
}
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 72f680f6f8..37d730bf42 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -29,7 +29,7 @@
-
+
diff --git a/osu.iOS.props b/osu.iOS.props
index 137c96a72d..ca11952cc8 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -70,7 +70,7 @@
-
+
@@ -91,7 +91,7 @@
-
+