diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
index dd53eefd23..58c24181d3 100644
--- a/.config/dotnet-tools.json
+++ b/.config/dotnet-tools.json
@@ -15,7 +15,7 @@
]
},
"jetbrains.resharper.globaltools": {
- "version": "2020.2.4",
+ "version": "2020.3.2",
"commands": [
"jb"
]
diff --git a/Directory.Build.props b/Directory.Build.props
index 551cb75077..9ec442aafa 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -16,7 +16,7 @@
-
+
diff --git a/Gemfile.lock b/Gemfile.lock
index a4b49af7e4..8ac863c9a8 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,27 +1,27 @@
GEM
remote: https://rubygems.org/
specs:
- CFPropertyList (3.0.2)
+ CFPropertyList (3.0.3)
addressable (2.7.0)
public_suffix (>= 2.0.2, < 5.0)
atomos (0.1.3)
aws-eventstream (1.1.0)
- aws-partitions (1.354.0)
- aws-sdk-core (3.104.3)
+ aws-partitions (1.413.0)
+ aws-sdk-core (3.110.0)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.239.0)
aws-sigv4 (~> 1.1)
jmespath (~> 1.0)
- aws-sdk-kms (1.36.0)
- aws-sdk-core (~> 3, >= 3.99.0)
+ aws-sdk-kms (1.40.0)
+ aws-sdk-core (~> 3, >= 3.109.0)
aws-sigv4 (~> 1.1)
- aws-sdk-s3 (1.78.0)
- aws-sdk-core (~> 3, >= 3.104.3)
+ aws-sdk-s3 (1.87.0)
+ aws-sdk-core (~> 3, >= 3.109.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.1)
- aws-sigv4 (1.2.1)
+ aws-sigv4 (1.2.2)
aws-eventstream (~> 1, >= 1.0.2)
- babosa (1.0.3)
+ babosa (1.0.4)
claide (1.0.3)
colored (1.2)
colored2 (3.1.2)
@@ -29,22 +29,23 @@ GEM
highline (~> 1.7.2)
declarative (0.0.20)
declarative-option (0.1.0)
- digest-crc (0.6.1)
- rake (~> 13.0)
+ digest-crc (0.6.3)
+ rake (>= 12.0.0, < 14.0.0)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
dotenv (2.7.6)
- emoji_regex (3.0.0)
- excon (0.76.0)
- faraday (1.0.1)
+ emoji_regex (3.2.1)
+ excon (0.78.1)
+ faraday (1.2.0)
multipart-post (>= 1.2, < 3)
- faraday-cookie_jar (0.0.6)
- faraday (>= 0.7.4)
+ ruby2_keywords
+ faraday-cookie_jar (0.0.7)
+ faraday (>= 0.8.0)
http-cookie (~> 1.0.0)
faraday_middleware (1.0.0)
faraday (~> 1.0)
- fastimage (2.2.0)
- fastlane (2.156.0)
+ fastimage (2.2.1)
+ fastlane (2.170.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.3, < 3.0.0)
aws-sdk-s3 (~> 1.0)
@@ -96,17 +97,17 @@ GEM
google-cloud-core (1.5.0)
google-cloud-env (~> 1.0)
google-cloud-errors (~> 1.0)
- google-cloud-env (1.3.3)
+ google-cloud-env (1.4.0)
faraday (>= 0.17.3, < 2.0)
google-cloud-errors (1.0.1)
- google-cloud-storage (1.27.0)
+ google-cloud-storage (1.29.2)
addressable (~> 2.5)
digest-crc (~> 0.4)
google-api-client (~> 0.33)
google-cloud-core (~> 1.2)
googleauth (~> 0.9)
mini_mime (~> 1.0)
- googleauth (0.13.1)
+ googleauth (0.14.0)
faraday (>= 0.17.3, < 2.0)
jwt (>= 1.4, < 3.0)
memoist (~> 0.16)
@@ -118,10 +119,10 @@ GEM
domain_name (~> 0.5)
httpclient (2.8.3)
jmespath (1.4.0)
- json (2.3.1)
- jwt (2.2.1)
+ json (2.5.1)
+ jwt (2.2.2)
memoist (0.16.2)
- mini_magick (4.10.1)
+ mini_magick (4.11.0)
mini_mime (1.0.2)
mini_portile2 (2.4.0)
multi_json (1.15.0)
@@ -132,14 +133,15 @@ GEM
mini_portile2 (~> 2.4.0)
os (1.1.1)
plist (3.5.0)
- public_suffix (4.0.5)
- rake (13.0.1)
+ public_suffix (4.0.6)
+ rake (13.0.3)
representable (3.0.4)
declarative (< 0.1.0)
declarative-option (< 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
rouge (2.0.7)
+ ruby2_keywords (0.0.2)
rubyzip (2.3.0)
security (0.1.3)
signet (0.14.0)
@@ -168,7 +170,7 @@ GEM
unf_ext (0.0.7.7)
unicode-display_width (1.7.0)
word_wrap (1.0.0)
- xcodeproj (1.18.0)
+ xcodeproj (1.19.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
diff --git a/osu.Android.props b/osu.Android.props
index fc01f9bf1d..611f0d05f4 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 953c06f4e2..9d28ad7c5b 100644
--- a/osu.Android/OsuGameActivity.cs
+++ b/osu.Android/OsuGameActivity.cs
@@ -16,7 +16,9 @@ using osu.Framework.Android;
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)]
- [IntentFilter(new[] { Intent.ActionDefault, Intent.ActionSend }, Categories = new[] { Intent.CategoryDefault }, DataPathPatterns = new[] { ".*\\.osz", ".*\\.osk" }, DataMimeType = "application/*")]
+ [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 }, Categories = new[] { Intent.CategoryDefault }, DataMimeTypes = new[] { "application/zip", "application/octet-stream" })]
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryBrowsable, Intent.CategoryDefault }, DataSchemes = new[] { "osu", "osump" })]
public class OsuGameActivity : AndroidGameActivity
{
diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs
index f1878d967d..63b12fb84b 100644
--- a/osu.Desktop/DiscordRichPresence.cs
+++ b/osu.Desktop/DiscordRichPresence.cs
@@ -9,6 +9,7 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Logging;
+using osu.Game.Configuration;
using osu.Game.Online.API;
using osu.Game.Rulesets;
using osu.Game.Users;
@@ -31,13 +32,15 @@ namespace osu.Desktop
private readonly IBindable status = new Bindable();
private readonly IBindable activity = new Bindable();
+ private readonly Bindable privacyMode = new Bindable();
+
private readonly RichPresence presence = new RichPresence
{
Assets = new Assets { LargeImageKey = "osu_logo_lazer", }
};
[BackgroundDependencyLoader]
- private void load(IAPIProvider provider)
+ private void load(IAPIProvider provider, OsuConfigManager config)
{
client = new DiscordRpcClient(client_id)
{
@@ -51,6 +54,8 @@ namespace osu.Desktop
client.OnError += (_, e) => Logger.Log($"An error occurred with Discord RPC Client: {e.Code} {e.Message}", LoggingTarget.Network);
+ config.BindWith(OsuSetting.DiscordRichPresence, privacyMode);
+
(user = provider.LocalUser.GetBoundCopy()).BindValueChanged(u =>
{
status.UnbindBindings();
@@ -63,6 +68,7 @@ namespace osu.Desktop
ruleset.BindValueChanged(_ => updateStatus());
status.BindValueChanged(_ => updateStatus());
activity.BindValueChanged(_ => updateStatus());
+ privacyMode.BindValueChanged(_ => updateStatus());
client.Initialize();
}
@@ -78,7 +84,7 @@ namespace osu.Desktop
if (!client.IsInitialized)
return;
- if (status.Value is UserStatusOffline)
+ if (status.Value is UserStatusOffline || privacyMode.Value == DiscordRichPresenceMode.Off)
{
client.ClearPresence();
return;
@@ -96,7 +102,10 @@ namespace osu.Desktop
}
// update user information
- presence.Assets.LargeImageText = $"{user.Value.Username}" + (user.Value.Statistics?.Ranks.Global > 0 ? $" (rank #{user.Value.Statistics.Ranks.Global:N0})" : string.Empty);
+ 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);
// update ruleset
presence.Assets.SmallImageKey = ruleset.Value.ID <= 3 ? $"mode_{ruleset.Value.ID}" : "mode_custom";
@@ -137,7 +146,7 @@ namespace osu.Desktop
return edit.Beatmap.ToString();
case UserActivity.InLobby lobby:
- return lobby.Room.Name.Value;
+ return privacyMode.Value == DiscordRichPresenceMode.Limited ? string.Empty : lobby.Room.Name.Value;
}
return string.Empty;
diff --git a/osu.Desktop/Overlays/VersionManager.cs b/osu.Desktop/Overlays/VersionManager.cs
index 8c759f8487..e4a3451651 100644
--- a/osu.Desktop/Overlays/VersionManager.cs
+++ b/osu.Desktop/Overlays/VersionManager.cs
@@ -26,9 +26,11 @@ namespace osu.Desktop.Overlays
Alpha = 0;
+ FillFlowContainer mainFill;
+
Children = new Drawable[]
{
- new FillFlowContainer
+ mainFill = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
@@ -55,23 +57,30 @@ namespace osu.Desktop.Overlays
},
}
},
- new OsuSpriteText
- {
- Anchor = Anchor.TopCentre,
- Origin = Anchor.TopCentre,
- Font = OsuFont.Numeric.With(size: 12),
- Colour = colours.Yellow,
- Text = @"Development Build"
- },
- new Sprite
- {
- Anchor = Anchor.TopCentre,
- Origin = Anchor.TopCentre,
- Texture = textures.Get(@"Menu/dev-build-footer"),
- },
}
}
};
+
+ if (!game.IsDeployedBuild)
+ {
+ mainFill.AddRange(new Drawable[]
+ {
+ new OsuSpriteText
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ Font = OsuFont.Numeric.With(size: 12),
+ Colour = colours.Yellow,
+ Text = @"Development Build"
+ },
+ new Sprite
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ Texture = textures.Get(@"Menu/dev-build-footer"),
+ },
+ });
+ }
}
protected override void PopIn()
diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
index 61ecd79e3d..51d2032795 100644
--- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
+++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
@@ -2,7 +2,7 @@
-
+
diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
index fa7bfd7169..3261f632f2 100644
--- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
+++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
@@ -2,7 +2,7 @@
-
+
diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
index d6a03da807..32243e0bc3 100644
--- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
+++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
@@ -2,7 +2,7 @@
-
+
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorTrail.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorTrail.cs
index f18d3191ca..af9ea99232 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorTrail.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorTrail.cs
@@ -20,17 +20,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
private double lastTrailTime;
private IBindable cursorSize;
- public LegacyCursorTrail()
- {
- Blending = BlendingParameters.Additive;
- }
-
[BackgroundDependencyLoader]
private void load(ISkinSource skin, OsuConfigManager config)
{
Texture = skin.GetTexture("cursortrail");
disjointTrail = skin.GetTexture("cursormiddle") == null;
+ Blending = !disjointTrail ? BlendingParameters.Additive : BlendingParameters.Inherit;
+
if (Texture != null)
{
// stable "magic ratio". see OsuPlayfieldAdjustmentContainer for full explanation.
diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
index a89645d881..210f81d111 100644
--- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
+++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
@@ -2,7 +2,7 @@
-
+
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs
index 9ebedb3c80..7bee580863 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs
@@ -95,6 +95,26 @@ namespace osu.Game.Tests.Beatmaps.Formats
}
}
+ [Test]
+ public void TestOutOfOrderStartTimes()
+ {
+ var decoder = new LegacyStoryboardDecoder();
+
+ using (var resStream = TestResources.OpenResource("out-of-order-starttimes.osb"))
+ using (var stream = new LineBufferedReader(resStream))
+ {
+ var storyboard = decoder.Decode(stream);
+
+ StoryboardLayer background = storyboard.Layers.Single(l => l.Depth == 3);
+ Assert.AreEqual(2, background.Elements.Count);
+
+ Assert.AreEqual(1500, background.Elements[0].StartTime);
+ Assert.AreEqual(1000, background.Elements[1].StartTime);
+
+ Assert.AreEqual(1000, storyboard.EarliestEventTime);
+ }
+ }
+
[Test]
public void TestDecodeVariableWithSuffix()
{
diff --git a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs
new file mode 100644
index 0000000000..a2ad37cf4a
--- /dev/null
+++ b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs
@@ -0,0 +1,57 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using Humanizer;
+using NUnit.Framework;
+using osu.Framework.Testing;
+using osu.Game.Online.Multiplayer;
+using osu.Game.Tests.Visual.Multiplayer;
+using osu.Game.Users;
+
+namespace osu.Game.Tests.NonVisual.Multiplayer
+{
+ [HeadlessTest]
+ public class StatefulMultiplayerClientTest : MultiplayerTestScene
+ {
+ [Test]
+ public void TestPlayingUserTracking()
+ {
+ int id = 2000;
+
+ AddRepeatStep("add some users", () => Client.AddUser(new User { Id = id++ }), 5);
+ checkPlayingUserCount(0);
+
+ changeState(3, MultiplayerUserState.WaitingForLoad);
+ checkPlayingUserCount(3);
+
+ changeState(3, MultiplayerUserState.Playing);
+ checkPlayingUserCount(3);
+
+ changeState(3, MultiplayerUserState.Results);
+ checkPlayingUserCount(0);
+
+ changeState(6, MultiplayerUserState.WaitingForLoad);
+ checkPlayingUserCount(6);
+
+ AddStep("another user left", () => Client.RemoveUser(Client.Room?.Users.Last().User));
+ checkPlayingUserCount(5);
+
+ AddStep("leave room", () => Client.LeaveRoom());
+ checkPlayingUserCount(0);
+ }
+
+ private void checkPlayingUserCount(int expectedCount)
+ => AddAssert($"{"user".ToQuantity(expectedCount)} playing", () => Client.CurrentMatchPlayingUserIds.Count == expectedCount);
+
+ private void changeState(int userCount, MultiplayerUserState state)
+ => AddStep($"{"user".ToQuantity(userCount)} in {state}", () =>
+ {
+ for (int i = 0; i < userCount; ++i)
+ {
+ var userId = Client.Room?.Users[i].UserID ?? throw new AssertionException("Room cannot be null!");
+ Client.ChangeUserState(userId, state);
+ }
+ });
+ }
+}
diff --git a/osu.Game.Tests/Resources/out-of-order-starttimes.osb b/osu.Game.Tests/Resources/out-of-order-starttimes.osb
new file mode 100644
index 0000000000..09988ff64e
--- /dev/null
+++ b/osu.Game.Tests/Resources/out-of-order-starttimes.osb
@@ -0,0 +1,6 @@
+[Events]
+//Storyboard Layer 0 (Background)
+Sprite,Background,TopCentre,"img.jpg",320,240
+ F,0,1500,1600,0,1
+Sprite,Background,TopCentre,"img.jpg",320,240
+ F,0,1000,1100,0,1
diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs
index 5323f58a66..7ade7725d9 100644
--- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs
+++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs
@@ -82,7 +82,7 @@ namespace osu.Game.Tests.Visual.Background
});
AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
AddStep("Stop background preview", () => InputManager.MoveMouseTo(playerLoader.ScreenPos));
- AddUntilStep("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && playerLoader.IsBlurCorrect());
+ AddUntilStep("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.CheckBackgroundBlur(playerLoader.ExpectedBackgroundBlur));
}
///
@@ -106,6 +106,7 @@ namespace osu.Game.Tests.Visual.Background
public void TestStoryboardBackgroundVisibility()
{
performFullSetup();
+ AddAssert("Background retained from song select", () => songSelect.IsBackgroundCurrent());
createFakeStoryboard();
AddStep("Enable Storyboard", () =>
{
@@ -198,8 +199,9 @@ namespace osu.Game.Tests.Visual.Background
})));
AddUntilStep("Wait for results is current", () => results.IsCurrentScreen());
+
AddUntilStep("Screen is undimmed, original background retained", () =>
- songSelect.IsBackgroundUndimmed() && songSelect.IsBackgroundCurrent() && results.IsBlurCorrect());
+ songSelect.IsBackgroundUndimmed() && songSelect.IsBackgroundCurrent() && songSelect.CheckBackgroundBlur(results.ExpectedBackgroundBlur));
}
///
@@ -224,7 +226,7 @@ namespace osu.Game.Tests.Visual.Background
AddStep("Resume PlayerLoader", () => player.Restart());
AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
AddStep("Move mouse to center of screen", () => InputManager.MoveMouseTo(playerLoader.ScreenPos));
- AddUntilStep("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && playerLoader.IsBlurCorrect());
+ AddUntilStep("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.CheckBackgroundBlur(playerLoader.ExpectedBackgroundBlur));
}
private void createFakeStoryboard() => AddStep("Create storyboard", () =>
@@ -274,9 +276,11 @@ namespace osu.Game.Tests.Visual.Background
private class DummySongSelect : PlaySongSelect
{
+ private FadeAccessibleBackground background;
+
protected override BackgroundScreen CreateBackground()
{
- FadeAccessibleBackground background = new FadeAccessibleBackground(Beatmap.Value);
+ background = new FadeAccessibleBackground(Beatmap.Value);
DimEnabled.BindTo(background.EnableUserDim);
return background;
}
@@ -294,25 +298,27 @@ namespace osu.Game.Tests.Visual.Background
config.BindWith(OsuSetting.BlurLevel, BlurLevel);
}
- public bool IsBackgroundDimmed() => ((FadeAccessibleBackground)Background).CurrentColour == OsuColour.Gray(1f - ((FadeAccessibleBackground)Background).CurrentDim);
+ public bool IsBackgroundDimmed() => background.CurrentColour == OsuColour.Gray(1f - background.CurrentDim);
- public bool IsBackgroundUndimmed() => ((FadeAccessibleBackground)Background).CurrentColour == Color4.White;
+ public bool IsBackgroundUndimmed() => background.CurrentColour == Color4.White;
- public bool IsUserBlurApplied() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2((float)BlurLevel.Value * BackgroundScreenBeatmap.USER_BLUR_FACTOR);
+ public bool IsUserBlurApplied() => background.CurrentBlur == new Vector2((float)BlurLevel.Value * BackgroundScreenBeatmap.USER_BLUR_FACTOR);
- public bool IsUserBlurDisabled() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(0);
+ public bool IsUserBlurDisabled() => background.CurrentBlur == new Vector2(0);
- public bool IsBackgroundInvisible() => ((FadeAccessibleBackground)Background).CurrentAlpha == 0;
+ public bool IsBackgroundInvisible() => background.CurrentAlpha == 0;
- public bool IsBackgroundVisible() => ((FadeAccessibleBackground)Background).CurrentAlpha == 1;
+ public bool IsBackgroundVisible() => background.CurrentAlpha == 1;
- public bool IsBlurCorrect() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(BACKGROUND_BLUR);
+ public bool IsBlurCorrect() => background.CurrentBlur == new Vector2(BACKGROUND_BLUR);
+
+ public bool CheckBackgroundBlur(Vector2 expected) => background.CurrentBlur == expected;
///
/// Make sure every time a screen gets pushed, the background doesn't get replaced
///
/// Whether or not the original background (The one created in DummySongSelect) is still the current background
- public bool IsBackgroundCurrent() => ((FadeAccessibleBackground)Background).IsCurrentScreen();
+ public bool IsBackgroundCurrent() => background?.IsCurrentScreen() == true;
}
private class FadeAccessibleResults : ResultsScreen
@@ -324,12 +330,20 @@ namespace osu.Game.Tests.Visual.Background
protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value);
- public bool IsBlurCorrect() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(BACKGROUND_BLUR);
+ public Vector2 ExpectedBackgroundBlur => new Vector2(BACKGROUND_BLUR);
}
private class LoadBlockingTestPlayer : TestPlayer
{
- protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value);
+ protected override BackgroundScreen CreateBackground() =>
+ new FadeAccessibleBackground(Beatmap.Value);
+
+ public override void OnEntering(IScreen last)
+ {
+ base.OnEntering(last);
+
+ ApplyToBackground(b => ReplacesBackground.BindTo(b.StoryboardReplacesBackground));
+ }
public new DimmableStoryboard DimmableStoryboard => base.DimmableStoryboard;
@@ -354,15 +368,16 @@ namespace osu.Game.Tests.Visual.Background
Thread.Sleep(1);
StoryboardEnabled = config.GetBindable(OsuSetting.ShowStoryboard);
- ReplacesBackground.BindTo(Background.StoryboardReplacesBackground);
DrawableRuleset.IsPaused.BindTo(IsPaused);
}
}
private class TestPlayerLoader : PlayerLoader
{
+ private FadeAccessibleBackground background;
+
public VisualSettings VisualSettingsPos => VisualSettings;
- public BackgroundScreen ScreenPos => Background;
+ public BackgroundScreen ScreenPos => background;
public TestPlayerLoader(Player player)
: base(() => player)
@@ -371,9 +386,9 @@ namespace osu.Game.Tests.Visual.Background
public void TriggerOnHover() => OnHover(new HoverEvent(new InputState()));
- public bool IsBlurCorrect() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(BACKGROUND_BLUR);
+ public Vector2 ExpectedBackgroundBlur => new Vector2(BACKGROUND_BLUR);
- protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value);
+ protected override BackgroundScreen CreateBackground() => background = new FadeAccessibleBackground(Beatmap.Value);
}
private class FadeAccessibleBackground : BackgroundScreenBeatmap
diff --git a/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs b/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs
index a3db20ce83..9a999a4931 100644
--- a/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs
+++ b/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs
@@ -8,7 +8,6 @@ using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Containers;
using osu.Game.Audio;
using osu.Game.Beatmaps;
-using static osu.Game.Tests.Visual.Components.TestScenePreviewTrackManager.TestPreviewTrackManager;
namespace osu.Game.Tests.Visual.Components
{
@@ -100,7 +99,7 @@ namespace osu.Game.Tests.Visual.Components
[Test]
public void TestNonPresentTrack()
{
- TestPreviewTrack track = null;
+ TestPreviewTrackManager.TestPreviewTrack track = null;
AddStep("get non-present track", () =>
{
@@ -182,9 +181,9 @@ namespace osu.Game.Tests.Visual.Components
AddAssert("track stopped", () => !track.IsRunning);
}
- private TestPreviewTrack getTrack() => (TestPreviewTrack)trackManager.Get(null);
+ private TestPreviewTrackManager.TestPreviewTrack getTrack() => (TestPreviewTrackManager.TestPreviewTrack)trackManager.Get(null);
- private TestPreviewTrack getOwnedTrack()
+ private TestPreviewTrackManager.TestPreviewTrack getOwnedTrack()
{
var track = getTrack();
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayLeaderboard.cs
index c0a021436e..17fe09f2c6 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayLeaderboard.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayLeaderboard.cs
@@ -95,7 +95,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
public bool CheckPositionByUsername(string username, int? expectedPosition)
{
- var scoreItem = this.FirstOrDefault(i => i.User.Username == username);
+ var scoreItem = this.FirstOrDefault(i => i.User?.Username == username);
return scoreItem != null && scoreItem.ScorePosition == expectedPosition;
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs
index e1b0820662..5bac8582d7 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs
@@ -9,7 +9,7 @@ using osu.Game.Rulesets;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Judgements;
-using osu.Game.Screens.Play;
+using osu.Game.Screens.Play.HUD;
namespace osu.Game.Tests.Visual.Gameplay
{
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs
similarity index 89%
rename from osu.Game.Tests/Visual/Gameplay/TestSceneMultiplayerGameplayLeaderboard.cs
rename to osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs
index 8078c7b994..d016accc25 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneMultiplayerGameplayLeaderboard.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs
@@ -22,12 +22,14 @@ using osu.Game.Scoring;
using osu.Game.Screens.Play.HUD;
using osu.Game.Tests.Visual.Online;
-namespace osu.Game.Tests.Visual.Gameplay
+namespace osu.Game.Tests.Visual.Multiplayer
{
- public class TestSceneMultiplayerGameplayLeaderboard : OsuTestScene
+ public class TestSceneMultiplayerGameplayLeaderboard : MultiplayerTestScene
{
+ private const int users = 16;
+
[Cached(typeof(SpectatorStreamingClient))]
- private TestMultiplayerStreaming streamingClient = new TestMultiplayerStreaming(16);
+ private TestMultiplayerStreaming streamingClient = new TestMultiplayerStreaming(users);
[Cached(typeof(UserLookupCache))]
private UserLookupCache lookupCache = new TestSceneCurrentlyPlayingDisplay.TestUserLookupCache();
@@ -47,10 +49,12 @@ namespace osu.Game.Tests.Visual.Gameplay
}
[SetUpSteps]
- public void SetUpSteps()
+ public override void SetUpSteps()
{
AddStep("create leaderboard", () =>
{
+ leaderboard?.Expire();
+
OsuScoreProcessor scoreProcessor;
Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value);
@@ -58,6 +62,9 @@ namespace osu.Game.Tests.Visual.Gameplay
streamingClient.Start(Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0);
+ Client.CurrentMatchPlayingUserIds.Clear();
+ Client.CurrentMatchPlayingUserIds.AddRange(streamingClient.PlayingUsers);
+
Children = new Drawable[]
{
scoreProcessor = new OsuScoreProcessor(),
@@ -81,6 +88,12 @@ namespace osu.Game.Tests.Visual.Gameplay
AddRepeatStep("update state", () => streamingClient.RandomlyUpdateState(), 100);
}
+ [Test]
+ public void TestUserQuit()
+ {
+ AddRepeatStep("mark user quit", () => Client.CurrentMatchPlayingUserIds.RemoveAt(0), users);
+ }
+
public class TestMultiplayerStreaming : SpectatorStreamingClient
{
public new BindableList PlayingUsers => (BindableList)base.PlayingUsers;
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs
new file mode 100644
index 0000000000..95c333e9f4
--- /dev/null
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs
@@ -0,0 +1,145 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Audio;
+using osu.Framework.Extensions;
+using osu.Framework.Platform;
+using osu.Framework.Screens;
+using osu.Framework.Utils;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Catch;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.Osu.Mods;
+using osu.Game.Rulesets.Taiko;
+using osu.Game.Rulesets.Taiko.Mods;
+using osu.Game.Screens.OnlinePlay.Multiplayer;
+using osu.Game.Screens.Select;
+
+namespace osu.Game.Tests.Visual.Multiplayer
+{
+ public class TestSceneMultiplayerMatchSongSelect : RoomTestScene
+ {
+ private BeatmapManager manager;
+ private RulesetStore rulesets;
+
+ private List beatmaps;
+
+ private TestMultiplayerMatchSongSelect songSelect;
+
+ [BackgroundDependencyLoader]
+ private void load(GameHost host, AudioManager audio)
+ {
+ Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
+ Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default));
+
+ beatmaps = new List();
+
+ for (int i = 0; i < 8; ++i)
+ {
+ int beatmapId = 10 * 10 + i;
+
+ int length = RNG.Next(30000, 200000);
+ double bpm = RNG.NextSingle(80, 200);
+
+ beatmaps.Add(new BeatmapInfo
+ {
+ Ruleset = rulesets.GetRuleset(i % 4),
+ OnlineBeatmapID = beatmapId,
+ Length = length,
+ BPM = bpm,
+ BaseDifficulty = new BeatmapDifficulty()
+ });
+ }
+
+ manager.Import(new BeatmapSetInfo
+ {
+ OnlineBeatmapSetID = 10,
+ Hash = Guid.NewGuid().ToString().ComputeMD5Hash(),
+ Metadata = new BeatmapMetadata
+ {
+ Artist = "Some Artist",
+ Title = "Some Beatmap",
+ AuthorString = "Some Author"
+ },
+ Beatmaps = beatmaps,
+ DateAdded = DateTimeOffset.UtcNow
+ }).Wait();
+ }
+
+ public override void SetUpSteps()
+ {
+ base.SetUpSteps();
+
+ AddStep("reset", () =>
+ {
+ Ruleset.Value = new OsuRuleset().RulesetInfo;
+ Beatmap.SetDefault();
+ SelectedMods.SetDefault();
+ });
+
+ AddStep("create song select", () => LoadScreen(songSelect = new TestMultiplayerMatchSongSelect()));
+ AddUntilStep("wait for present", () => songSelect.IsCurrentScreen());
+ }
+
+ [Test]
+ public void TestBeatmapRevertedOnExitIfNoSelection()
+ {
+ BeatmapInfo selectedBeatmap = null;
+
+ AddStep("select beatmap",
+ () => songSelect.Carousel.SelectBeatmap(selectedBeatmap = beatmaps.Where(beatmap => beatmap.RulesetID == new OsuRuleset().LegacyID).ElementAt(1)));
+ AddUntilStep("wait for selection", () => Beatmap.Value.BeatmapInfo.Equals(selectedBeatmap));
+
+ AddStep("exit song select", () => songSelect.Exit());
+ AddAssert("beatmap reverted", () => Beatmap.IsDefault);
+ }
+
+ [Test]
+ public void TestModsRevertedOnExitIfNoSelection()
+ {
+ AddStep("change mods", () => SelectedMods.Value = new[] { new OsuModDoubleTime() });
+
+ AddStep("exit song select", () => songSelect.Exit());
+ AddAssert("mods reverted", () => SelectedMods.Value.Count == 0);
+ }
+
+ [Test]
+ public void TestRulesetRevertedOnExitIfNoSelection()
+ {
+ AddStep("change ruleset", () => Ruleset.Value = new CatchRuleset().RulesetInfo);
+
+ AddStep("exit song select", () => songSelect.Exit());
+ AddAssert("ruleset reverted", () => Ruleset.Value.Equals(new OsuRuleset().RulesetInfo));
+ }
+
+ [Test]
+ public void TestBeatmapConfirmed()
+ {
+ BeatmapInfo selectedBeatmap = null;
+
+ AddStep("change ruleset", () => Ruleset.Value = new TaikoRuleset().RulesetInfo);
+ AddStep("select beatmap",
+ () => songSelect.Carousel.SelectBeatmap(selectedBeatmap = beatmaps.First(beatmap => beatmap.RulesetID == new TaikoRuleset().LegacyID)));
+ AddUntilStep("wait for selection", () => Beatmap.Value.BeatmapInfo.Equals(selectedBeatmap));
+ AddStep("set mods", () => SelectedMods.Value = new[] { new TaikoModDoubleTime() });
+
+ AddStep("confirm selection", () => songSelect.FinaliseSelection());
+ AddStep("exit song select", () => songSelect.Exit());
+
+ AddAssert("beatmap not changed", () => Beatmap.Value.BeatmapInfo.Equals(selectedBeatmap));
+ AddAssert("ruleset not changed", () => Ruleset.Value.Equals(new TaikoRuleset().RulesetInfo));
+ AddAssert("mods not changed", () => SelectedMods.Value.Single() is TaikoModDoubleTime);
+ }
+
+ private class TestMultiplayerMatchSongSelect : MultiplayerMatchSongSelect
+ {
+ public new BeatmapCarousel Carousel => base.Carousel;
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs
index 9181170bee..968a869532 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs
@@ -43,6 +43,16 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddAssert("two unique panels", () => this.ChildrenOfType().Select(p => p.User).Distinct().Count() == 2);
}
+ [Test]
+ public void TestAddNullUser()
+ {
+ AddAssert("one unique panel", () => this.ChildrenOfType().Select(p => p.User).Distinct().Count() == 1);
+
+ AddStep("add non-resolvable user", () => Client.AddNullUser(-3));
+
+ AddUntilStep("two unique panels", () => this.ChildrenOfType().Select(p => p.User).Distinct().Count() == 2);
+ }
+
[Test]
public void TestRemoveUser()
{
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs
index 6b11613f1c..03ba73d35b 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs
@@ -7,8 +7,10 @@ using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Graphics;
using osu.Framework.Platform;
+using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
+using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets;
@@ -23,6 +25,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
public class TestSceneMultiplayerReadyButton : MultiplayerTestScene
{
private MultiplayerReadyButton button;
+ private BeatmapSetInfo importedSet;
private BeatmapManager beatmaps;
private RulesetStore rulesets;
@@ -38,9 +41,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
[SetUp]
public new void Setup() => Schedule(() =>
{
- var beatmap = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First().Beatmaps.First();
-
- Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap);
+ importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First();
+ Beatmap.Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First());
Child = button = new MultiplayerReadyButton
{
@@ -51,13 +53,30 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
Value = new PlaylistItem
{
- Beatmap = { Value = beatmap },
- Ruleset = { Value = beatmap.Ruleset }
+ Beatmap = { Value = Beatmap.Value.BeatmapInfo },
+ Ruleset = { Value = Beatmap.Value.BeatmapInfo.Ruleset }
}
}
};
});
+ [Test]
+ public void TestDeletedBeatmapDisableReady()
+ {
+ OsuButton readyButton = null;
+
+ AddAssert("ensure ready button enabled", () =>
+ {
+ readyButton = button.ChildrenOfType().Single();
+ return readyButton.Enabled.Value;
+ });
+
+ AddStep("delete beatmap", () => beatmaps.Delete(importedSet));
+ AddAssert("ready button disabled", () => !readyButton.Enabled.Value);
+ AddStep("undelete beatmap", () => beatmaps.Undelete(importedSet));
+ AddAssert("ready button enabled back", () => readyButton.Enabled.Value);
+ }
+
[Test]
public void TestToggleStateWhenNotHost()
{
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs
index 7a3845cbf3..80d1acd145 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs
@@ -143,7 +143,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
RoomManager =
{
TimeBetweenListingPolls = { Value = 1 },
- TimeBetweenSelectionPolls = { Value = 1 }
}
};
diff --git a/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs
index dcfe0432a8..3d65e7e4ba 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.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 NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@@ -13,13 +14,12 @@ namespace osu.Game.Tests.Visual.Online
public class TestSceneAccountCreationOverlay : OsuTestScene
{
private readonly Container userPanelArea;
+ private readonly AccountCreationOverlay accountCreation;
private IBindable localUser;
public TestSceneAccountCreationOverlay()
{
- AccountCreationOverlay accountCreation;
-
Children = new Drawable[]
{
accountCreation = new AccountCreationOverlay(),
@@ -31,8 +31,6 @@ namespace osu.Game.Tests.Visual.Online
Origin = Anchor.TopRight,
},
};
-
- AddStep("show", () => accountCreation.Show());
}
[BackgroundDependencyLoader]
@@ -42,8 +40,19 @@ namespace osu.Game.Tests.Visual.Online
localUser = API.LocalUser.GetBoundCopy();
localUser.BindValueChanged(user => { userPanelArea.Child = new UserGridPanel(user.NewValue) { Width = 200 }; }, true);
+ }
- AddStep("logout", API.Logout);
+ [Test]
+ public void TestOverlayVisibility()
+ {
+ AddStep("start hidden", () => accountCreation.Hide());
+ AddStep("log out", API.Logout);
+
+ AddStep("show manually", () => accountCreation.Show());
+ AddUntilStep("overlay is visible", () => accountCreation.State.Value == Visibility.Visible);
+
+ AddStep("log back in", () => API.Login("dummy", "password"));
+ AddUntilStep("overlay is hidden", () => accountCreation.State.Value == Visibility.Hidden);
}
}
}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs
index 998e42b478..cd2c4e9346 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs
@@ -1,8 +1,13 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using System.Collections.Generic;
+using System.Linq;
+using Humanizer;
using NUnit.Framework;
+using osu.Game.Online.API;
+using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays;
using osu.Game.Overlays.Changelog;
@@ -12,13 +17,61 @@ namespace osu.Game.Tests.Visual.Online
[TestFixture]
public class TestSceneChangelogOverlay : OsuTestScene
{
+ private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;
+
+ private readonly Dictionary streams;
+ private readonly Dictionary builds;
+
+ private APIChangelogBuild requestedBuild;
private TestChangelogOverlay changelog;
- protected override bool UseOnlineAPI => true;
+ public TestSceneChangelogOverlay()
+ {
+ streams = APIUpdateStream.KNOWN_STREAMS.Keys.Select((stream, id) => new APIUpdateStream
+ {
+ Id = id + 1,
+ Name = stream,
+ DisplayName = stream.Humanize(), // not quite there, but good enough.
+ }).ToDictionary(stream => stream.Name);
+
+ string version = DateTimeOffset.Now.ToString("yyyy.Mdd.0");
+ builds = APIUpdateStream.KNOWN_STREAMS.Keys.Select(stream => new APIChangelogBuild
+ {
+ Version = version,
+ DisplayVersion = version,
+ UpdateStream = streams[stream],
+ ChangelogEntries = new List()
+ }).ToDictionary(build => build.UpdateStream.Name);
+
+ foreach (var stream in streams.Values)
+ stream.LatestBuild = builds[stream.Name];
+ }
[SetUp]
public void SetUp() => Schedule(() =>
{
+ requestedBuild = null;
+
+ dummyAPI.HandleRequest = request =>
+ {
+ switch (request)
+ {
+ case GetChangelogRequest changelogRequest:
+ var changelogResponse = new APIChangelogIndex
+ {
+ Streams = streams.Values.ToList(),
+ Builds = builds.Values.ToList()
+ };
+ changelogRequest.TriggerSuccess(changelogResponse);
+ break;
+
+ case GetChangelogBuildRequest buildRequest:
+ if (requestedBuild != null)
+ buildRequest.TriggerSuccess(requestedBuild);
+ break;
+ }
+ };
+
Child = changelog = new TestChangelogOverlay();
});
@@ -41,26 +94,60 @@ namespace osu.Game.Tests.Visual.Online
}
[Test]
- [Ignore("needs to be updated to not be so server dependent")]
public void ShowWithBuild()
{
- AddStep(@"Show with Lazer 2018.712.0", () =>
+ showBuild(() => new APIChangelogBuild
{
- changelog.ShowBuild(new APIChangelogBuild
+ Version = "2018.712.0",
+ DisplayVersion = "2018.712.0",
+ UpdateStream = streams[OsuGameBase.CLIENT_STREAM_NAME],
+ ChangelogEntries = new List
{
- Version = "2018.712.0",
- DisplayVersion = "2018.712.0",
- UpdateStream = new APIUpdateStream { Id = 5, Name = OsuGameBase.CLIENT_STREAM_NAME },
- ChangelogEntries = new List
+ new APIChangelogEntry
{
- new APIChangelogEntry
+ Type = ChangelogEntryType.Fix,
+ Category = "osu!",
+ Title = "Fix thing",
+ MessageHtml = "Additional info goes here.",
+ Repository = "osu",
+ GithubPullRequestId = 11100,
+ GithubUser = new APIChangelogUser
{
- Category = "Test",
- Title = "Title",
- MessageHtml = "Message",
+ OsuUsername = "smoogipoo",
+ UserId = 1040328
}
+ },
+ new APIChangelogEntry
+ {
+ Type = ChangelogEntryType.Add,
+ Category = "osu!",
+ Title = "Add thing",
+ Major = true,
+ Repository = "ppy/osu-framework",
+ GithubPullRequestId = 4444,
+ GithubUser = new APIChangelogUser
+ {
+ DisplayName = "frenzibyte",
+ GithubUrl = "https://github.com/frenzibyte"
+ }
+ },
+ new APIChangelogEntry
+ {
+ Type = ChangelogEntryType.Misc,
+ Category = "Code quality",
+ Title = "Clean up thing",
+ GithubUser = new APIChangelogUser
+ {
+ DisplayName = "some dude"
+ }
+ },
+ new APIChangelogEntry
+ {
+ Type = ChangelogEntryType.Misc,
+ Category = "Code quality",
+ Title = "Clean up another thing"
}
- });
+ }
});
AddUntilStep(@"wait for streams", () => changelog.Streams?.Count > 0);
@@ -71,35 +158,38 @@ namespace osu.Game.Tests.Visual.Online
[Test]
public void TestHTMLUnescaping()
{
- AddStep(@"Ensure HTML string unescaping", () =>
+ showBuild(() => new APIChangelogBuild
{
- changelog.ShowBuild(new APIChangelogBuild
+ Version = "2019.920.0",
+ DisplayVersion = "2019.920.0",
+ UpdateStream = new APIUpdateStream
{
- Version = "2019.920.0",
- DisplayVersion = "2019.920.0",
- UpdateStream = new APIUpdateStream
+ Name = "Test",
+ DisplayName = "Test"
+ },
+ ChangelogEntries = new List
+ {
+ new APIChangelogEntry
{
- Name = "Test",
- DisplayName = "Test"
- },
- ChangelogEntries = new List
- {
- new APIChangelogEntry
+ Category = "Testing HTML strings unescaping",
+ Title = "Ensuring HTML strings are being unescaped",
+ MessageHtml = """"This text should appear triple-quoted""" >_<",
+ GithubUser = new APIChangelogUser
{
- Category = "Testing HTML strings unescaping",
- Title = "Ensuring HTML strings are being unescaped",
- MessageHtml = """"This text should appear triple-quoted""" >_<",
- GithubUser = new APIChangelogUser
- {
- DisplayName = "Dummy",
- OsuUsername = "Dummy",
- }
- },
- }
- });
+ DisplayName = "Dummy",
+ OsuUsername = "Dummy",
+ }
+ },
+ }
});
}
+ private void showBuild(Func build)
+ {
+ AddStep("set up build", () => requestedBuild = build.Invoke());
+ AddStep("show build", () => changelog.ShowBuild(requestedBuild));
+ }
+
private class TestChangelogOverlay : ChangelogOverlay
{
public new List Streams => base.Streams;
diff --git a/osu.Game.Tests/Visual/Online/TestSceneCurrentlyPlayingDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneCurrentlyPlayingDisplay.cs
index 1666c9cde4..1baa07f208 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneCurrentlyPlayingDisplay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneCurrentlyPlayingDisplay.cs
@@ -90,11 +90,17 @@ namespace osu.Game.Tests.Visual.Online
};
protected override Task ComputeValueAsync(int lookup, CancellationToken token = default)
- => Task.FromResult(new User
+ {
+ // tests against failed lookups
+ if (lookup == 13)
+ return Task.FromResult(null);
+
+ return Task.FromResult(new User
{
Id = lookup,
Username = usernames[lookup % usernames.Length],
});
+ }
}
}
}
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLoadingLayer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLoadingLayer.cs
index 1be191fc29..d426723f0b 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneLoadingLayer.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLoadingLayer.cs
@@ -5,6 +5,7 @@ using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
+using osu.Framework.Utils;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osuTK;
@@ -14,8 +15,7 @@ namespace osu.Game.Tests.Visual.UserInterface
{
public class TestSceneLoadingLayer : OsuTestScene
{
- private Drawable dimContent;
- private LoadingLayer overlay;
+ private TestLoadingLayer overlay;
private Container content;
@@ -29,14 +29,14 @@ namespace osu.Game.Tests.Visual.UserInterface
Size = new Vector2(300),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
- Children = new[]
+ Children = new Drawable[]
{
new Box
{
Colour = Color4.SlateGray,
RelativeSizeAxes = Axes.Both,
},
- dimContent = new FillFlowContainer
+ new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -51,7 +51,7 @@ namespace osu.Game.Tests.Visual.UserInterface
new TriangleButton { Text = "puush me", Width = 200, Action = () => { } },
}
},
- overlay = new LoadingLayer(dimContent),
+ overlay = new TestLoadingLayer(true),
}
},
};
@@ -64,25 +64,11 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep("show", () => overlay.Show());
- AddUntilStep("wait for content dim", () => dimContent.Colour != Color4.White);
+ AddUntilStep("wait for content dim", () => overlay.BackgroundDimLayer.Alpha > 0);
AddStep("hide", () => overlay.Hide());
- AddUntilStep("wait for content restore", () => dimContent.Colour == Color4.White);
- }
-
- [Test]
- public void TestContentRestoreOnDispose()
- {
- AddAssert("not visible", () => !overlay.IsPresent);
-
- AddStep("show", () => overlay.Show());
-
- AddUntilStep("wait for content dim", () => dimContent.Colour != Color4.White);
-
- AddStep("expire", () => overlay.Expire());
-
- AddUntilStep("wait for content restore", () => dimContent.Colour == Color4.White);
+ AddUntilStep("wait for content restore", () => Precision.AlmostEquals(overlay.BackgroundDimLayer.Alpha, 0));
}
[Test]
@@ -98,5 +84,15 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep("hide", () => overlay.Hide());
}
+
+ private class TestLoadingLayer : LoadingLayer
+ {
+ public new Box BackgroundDimLayer => base.BackgroundDimLayer;
+
+ public TestLoadingLayer(bool dimBackground = false, bool withBox = true)
+ : base(dimBackground, withBox)
+ {
+ }
+ }
}
}
diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj
index 83d7b4135a..9049b67f90 100644
--- a/osu.Game.Tests/osu.Game.Tests.csproj
+++ b/osu.Game.Tests/osu.Game.Tests.csproj
@@ -3,7 +3,7 @@
-
+
diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj
index bc6b994988..dc4f22788d 100644
--- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj
+++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj
@@ -5,7 +5,7 @@
-
+
diff --git a/osu.Game/Configuration/DiscordRichPresenceMode.cs b/osu.Game/Configuration/DiscordRichPresenceMode.cs
new file mode 100644
index 0000000000..2e58e3554b
--- /dev/null
+++ b/osu.Game/Configuration/DiscordRichPresenceMode.cs
@@ -0,0 +1,17 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.ComponentModel;
+
+namespace osu.Game.Configuration
+{
+ public enum DiscordRichPresenceMode
+ {
+ Off,
+
+ [Description("Hide identifiable information")]
+ Limited,
+
+ Full
+ }
+}
diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs
index a07e446d2e..eb34a0885d 100644
--- a/osu.Game/Configuration/OsuConfigManager.cs
+++ b/osu.Game/Configuration/OsuConfigManager.cs
@@ -1,4 +1,4 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
@@ -138,6 +138,8 @@ namespace osu.Game.Configuration
Set(OsuSetting.MenuBackgroundSource, BackgroundSource.Skin);
Set(OsuSetting.SeasonalBackgroundMode, SeasonalBackgroundMode.Sometimes);
+ Set(OsuSetting.DiscordRichPresence, DiscordRichPresenceMode.Full);
+
Set(OsuSetting.EditorWaveformOpacity, 1f);
}
@@ -266,6 +268,7 @@ namespace osu.Game.Configuration
GameplayDisableWinKey,
SeasonalBackgroundMode,
EditorWaveformOpacity,
+ DiscordRichPresence,
AutomaticallyDownloadWhenSpectating,
}
}
diff --git a/osu.Game/Database/UserLookupCache.cs b/osu.Game/Database/UserLookupCache.cs
index 05d6930992..568726199c 100644
--- a/osu.Game/Database/UserLookupCache.cs
+++ b/osu.Game/Database/UserLookupCache.cs
@@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
@@ -17,6 +18,13 @@ namespace osu.Game.Database
[Resolved]
private IAPIProvider api { get; set; }
+ ///
+ /// Perform an API lookup on the specified user, populating a model.
+ ///
+ /// The user to lookup.
+ /// An optional cancellation token.
+ /// The populated user, or null if the user does not exist or the request could not be satisfied.
+ [ItemCanBeNull]
public Task GetUserAsync(int userId, CancellationToken token = default) => GetAsync(userId, token);
protected override async Task ComputeValueAsync(int lookup, CancellationToken token = default)
@@ -72,6 +80,7 @@ namespace osu.Game.Database
var request = new GetUsersRequest(userTasks.Keys.ToArray());
// rather than queueing, we maintain our own single-threaded request stream.
+ // todo: we probably want retry logic here.
api.Perform(request);
// Create a new request task if there's still more users to query.
@@ -82,14 +91,19 @@ namespace osu.Game.Database
createNewTask();
}
- foreach (var user in request.Result.Users)
- {
- if (userTasks.TryGetValue(user.Id, out var tasks))
- {
- foreach (var task in tasks)
- task.SetResult(user);
+ List foundUsers = request.Result?.Users;
- userTasks.Remove(user.Id);
+ if (foundUsers != null)
+ {
+ foreach (var user in foundUsers)
+ {
+ if (userTasks.TryGetValue(user.Id, out var tasks))
+ {
+ foreach (var task in tasks)
+ task.SetResult(user);
+
+ userTasks.Remove(user.Id);
+ }
}
}
diff --git a/osu.Game/Extensions/TaskExtensions.cs b/osu.Game/Extensions/TaskExtensions.cs
index a1215d786b..4138c2757a 100644
--- a/osu.Game/Extensions/TaskExtensions.cs
+++ b/osu.Game/Extensions/TaskExtensions.cs
@@ -1,7 +1,11 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+#nullable enable
+
+using System;
using System.Threading.Tasks;
+using osu.Framework.Extensions.ExceptionExtensions;
using osu.Framework.Logging;
namespace osu.Game.Extensions
@@ -13,13 +17,19 @@ namespace osu.Game.Extensions
/// Avoids unobserved exceptions from being fired.
///
/// The task.
- /// Whether errors should be logged as important, or silently ignored.
- public static void CatchUnobservedExceptions(this Task task, bool logOnError = false)
+ ///
+ /// Whether errors should be logged as errors visible to users, or as debug messages.
+ /// Logging as debug will essentially silence the errors on non-release builds.
+ ///
+ public static void CatchUnobservedExceptions(this Task task, bool logAsError = false)
{
task.ContinueWith(t =>
{
- if (logOnError)
- Logger.Log($"Error running task: {t.Exception?.Message ?? "unknown"}", LoggingTarget.Runtime, LogLevel.Important);
+ Exception? exception = t.Exception?.AsSingular();
+ if (logAsError)
+ Logger.Error(exception, $"Error running task: {exception?.Message ?? "(unknown)"}", LoggingTarget.Runtime, true);
+ else
+ Logger.Log($"Error running task: {exception}", LoggingTarget.Runtime, LogLevel.Debug);
}, TaskContinuationOptions.NotOnRanToCompletion);
}
}
diff --git a/osu.Game/Graphics/UserInterface/LoadingLayer.cs b/osu.Game/Graphics/UserInterface/LoadingLayer.cs
index c8c4424bee..47ba5fce4d 100644
--- a/osu.Game/Graphics/UserInterface/LoadingLayer.cs
+++ b/osu.Game/Graphics/UserInterface/LoadingLayer.cs
@@ -2,8 +2,9 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using JetBrains.Annotations;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osuTK;
using osuTK.Graphics;
@@ -17,22 +18,32 @@ namespace osu.Game.Graphics.UserInterface
///
public class LoadingLayer : LoadingSpinner
{
- private readonly Drawable dimTarget;
+ [CanBeNull]
+ protected Box BackgroundDimLayer { get; }
///
- /// Constuct a new loading spinner.
+ /// Construct a new loading spinner.
///
- /// An optional target to dim when displayed.
+ /// Whether the full background area should be dimmed while loading.
/// Whether the spinner should have a surrounding black box for visibility.
- public LoadingLayer(Drawable dimTarget = null, bool withBox = true)
+ public LoadingLayer(bool dimBackground = false, bool withBox = true)
: base(withBox)
{
RelativeSizeAxes = Axes.Both;
Size = new Vector2(1);
- this.dimTarget = dimTarget;
-
MainContents.RelativeSizeAxes = Axes.None;
+
+ if (dimBackground)
+ {
+ AddInternal(BackgroundDimLayer = new Box
+ {
+ Depth = float.MaxValue,
+ Colour = Color4.Black,
+ Alpha = 0,
+ RelativeSizeAxes = Axes.Both,
+ });
+ }
}
public override bool HandleNonPositionalInput => false;
@@ -56,31 +67,21 @@ namespace osu.Game.Graphics.UserInterface
protected override void PopIn()
{
- dimTarget?.FadeColour(OsuColour.Gray(0.5f), TRANSITION_DURATION, Easing.OutQuint);
+ BackgroundDimLayer?.FadeTo(0.5f, TRANSITION_DURATION * 2, Easing.OutQuint);
base.PopIn();
}
protected override void PopOut()
{
- dimTarget?.FadeColour(Color4.White, TRANSITION_DURATION, Easing.OutQuint);
+ BackgroundDimLayer?.FadeOut(TRANSITION_DURATION, Easing.OutQuint);
base.PopOut();
}
protected override void Update()
{
base.Update();
+
MainContents.Size = new Vector2(Math.Clamp(Math.Min(DrawWidth, DrawHeight) * 0.25f, 30, 100));
}
-
- protected override void Dispose(bool isDisposing)
- {
- base.Dispose(isDisposing);
-
- if (State.Value == Visibility.Visible)
- {
- // ensure we don't leave the target in a bad state.
- dimTarget?.FadeColour(Color4.White, TRANSITION_DURATION, Easing.OutQuint);
- }
- }
}
}
diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs
index 1270df5374..b8c2fa201f 100644
--- a/osu.Game/Input/Bindings/GlobalActionContainer.cs
+++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs
@@ -34,7 +34,7 @@ namespace osu.Game.Input.Bindings
new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.R }, GlobalAction.ResetInputSettings),
new KeyBinding(new[] { InputKey.Control, InputKey.T }, GlobalAction.ToggleToolbar),
new KeyBinding(new[] { InputKey.Control, InputKey.O }, GlobalAction.ToggleSettings),
- new KeyBinding(new[] { InputKey.Control, InputKey.D }, GlobalAction.ToggleDirect),
+ new KeyBinding(new[] { InputKey.Control, InputKey.D }, GlobalAction.ToggleBeatmapListing),
new KeyBinding(new[] { InputKey.Control, InputKey.N }, GlobalAction.ToggleNotifications),
new KeyBinding(InputKey.Escape, GlobalAction.Back),
@@ -112,8 +112,8 @@ namespace osu.Game.Input.Bindings
[Description("Toggle settings")]
ToggleSettings,
- [Description("Toggle osu!direct")]
- ToggleDirect,
+ [Description("Toggle beatmap listing")]
+ ToggleBeatmapListing,
[Description("Increase volume")]
IncreaseVolume,
diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs
index 133ba22406..2aaea22155 100644
--- a/osu.Game/Online/API/APIAccess.cs
+++ b/osu.Game/Online/API/APIAccess.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Net.Http;
+using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
@@ -293,8 +294,21 @@ namespace osu.Game.Online.API
failureCount = 0;
return true;
}
+ catch (HttpRequestException re)
+ {
+ log.Add($"{nameof(HttpRequestException)} while performing request {req}: {re.Message}");
+ handleFailure();
+ return false;
+ }
+ catch (SocketException se)
+ {
+ log.Add($"{nameof(SocketException)} while performing request {req}: {se.Message}");
+ handleFailure();
+ return false;
+ }
catch (WebException we)
{
+ log.Add($"{nameof(WebException)} while performing request {req}: {we.Message}");
handleWebException(we);
return false;
}
@@ -312,7 +326,7 @@ namespace osu.Game.Online.API
///
public IBindable State => state;
- private bool handleWebException(WebException we)
+ private void handleWebException(WebException we)
{
HttpStatusCode statusCode = (we.Response as HttpWebResponse)?.StatusCode
?? (we.Status == WebExceptionStatus.UnknownError ? HttpStatusCode.NotAcceptable : HttpStatusCode.RequestTimeout);
@@ -330,26 +344,24 @@ namespace osu.Game.Online.API
{
case HttpStatusCode.Unauthorized:
Logout();
- return true;
+ break;
case HttpStatusCode.RequestTimeout:
- failureCount++;
- log.Add($@"API failure count is now {failureCount}");
-
- if (failureCount < 3)
- // we might try again at an api level.
- return false;
-
- if (State.Value == APIState.Online)
- {
- state.Value = APIState.Failing;
- flushQueue();
- }
-
- return true;
+ handleFailure();
+ break;
}
+ }
- return true;
+ private void handleFailure()
+ {
+ failureCount++;
+ log.Add($@"API failure count is now {failureCount}");
+
+ if (failureCount >= 3 && State.Value == APIState.Online)
+ {
+ state.Value = APIState.Failing;
+ flushQueue();
+ }
}
public bool IsLoggedIn => localUser.Value.Id > 1;
diff --git a/osu.Game/Online/API/APIMod.cs b/osu.Game/Online/API/APIMod.cs
index 780e5daa16..c8b76b9685 100644
--- a/osu.Game/Online/API/APIMod.cs
+++ b/osu.Game/Online/API/APIMod.cs
@@ -31,7 +31,12 @@ namespace osu.Game.Online.API
Acronym = mod.Acronym;
foreach (var (_, property) in mod.GetSettingsSourceProperties())
- Settings.Add(property.Name.Underscore(), property.GetValue(mod));
+ {
+ var bindable = (IBindable)property.GetValue(mod);
+
+ if (!bindable.IsDefault)
+ Settings.Add(property.Name.Underscore(), bindable);
+ }
}
public Mod ToMod(Ruleset ruleset)
@@ -46,7 +51,7 @@ namespace osu.Game.Online.API
if (!Settings.TryGetValue(property.Name.Underscore(), out object settingValue))
continue;
- ((IBindable)property.GetValue(resultMod)).Parse(settingValue);
+ resultMod.CopyAdjustedSetting((IBindable)property.GetValue(resultMod), settingValue);
}
return resultMod;
diff --git a/osu.Game/Online/API/Requests/Responses/APIUpdateStream.cs b/osu.Game/Online/API/Requests/Responses/APIUpdateStream.cs
index d9e48373bb..5af7d6a01c 100644
--- a/osu.Game/Online/API/Requests/Responses/APIUpdateStream.cs
+++ b/osu.Game/Online/API/Requests/Responses/APIUpdateStream.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Collections.Generic;
using Newtonsoft.Json;
using osu.Framework.Graphics.Colour;
using osuTK.Graphics;
@@ -27,34 +28,16 @@ namespace osu.Game.Online.API.Requests.Responses
public bool Equals(APIUpdateStream other) => Id == other?.Id;
- public ColourInfo Colour
+ internal static readonly Dictionary KNOWN_STREAMS = new Dictionary
{
- get
- {
- switch (Name)
- {
- case "stable40":
- return new Color4(102, 204, 255, 255);
+ ["stable40"] = new Color4(102, 204, 255, 255),
+ ["stable"] = new Color4(34, 153, 187, 255),
+ ["beta40"] = new Color4(255, 221, 85, 255),
+ ["cuttingedge"] = new Color4(238, 170, 0, 255),
+ [OsuGameBase.CLIENT_STREAM_NAME] = new Color4(237, 18, 33, 255),
+ ["web"] = new Color4(136, 102, 238, 255)
+ };
- case "stable":
- return new Color4(34, 153, 187, 255);
-
- case "beta40":
- return new Color4(255, 221, 85, 255);
-
- case "cuttingedge":
- return new Color4(238, 170, 0, 255);
-
- case OsuGameBase.CLIENT_STREAM_NAME:
- return new Color4(237, 18, 33, 255);
-
- case "web":
- return new Color4(136, 102, 238, 255);
-
- default:
- return new Color4(0, 0, 0, 255);
- }
- }
- }
+ public ColourInfo Colour => KNOWN_STREAMS.TryGetValue(Name, out var colour) ? colour : new Color4(0, 0, 0, 255);
}
}
diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs
index d8207aa8f4..5608002513 100644
--- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs
+++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs
@@ -78,7 +78,7 @@ namespace osu.Game.Online.Leaderboards
statisticsLabels = GetStatistics(score).Select(s => new ScoreComponentLabel(s)).ToList();
- DrawableAvatar innerAvatar;
+ ClickableAvatar innerAvatar;
Children = new Drawable[]
{
@@ -115,7 +115,7 @@ namespace osu.Game.Online.Leaderboards
Children = new[]
{
avatar = new DelayedLoadWrapper(
- innerAvatar = new DrawableAvatar(user)
+ innerAvatar = new ClickableAvatar(user)
{
RelativeSizeAxes = Axes.Both,
CornerRadius = corner_radius,
diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs
index 24ea6abc4a..7cd1ef78f7 100644
--- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs
+++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs
@@ -88,11 +88,12 @@ namespace osu.Game.Online.Multiplayer
{
isConnected.Value = false;
- if (ex != null)
- {
- Logger.Log($"Multiplayer client lost connection: {ex}", LoggingTarget.Network);
+ Logger.Log(ex != null
+ ? $"Multiplayer client lost connection: {ex}"
+ : "Multiplayer client disconnected", LoggingTarget.Network);
+
+ if (connection != null)
await tryUntilConnected();
- }
};
await tryUntilConnected();
diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoom.cs b/osu.Game/Online/Multiplayer/MultiplayerRoom.cs
index 2134e50d72..12fcf25ace 100644
--- a/osu.Game/Online/Multiplayer/MultiplayerRoom.cs
+++ b/osu.Game/Online/Multiplayer/MultiplayerRoom.cs
@@ -5,9 +5,7 @@
using System;
using System.Collections.Generic;
-using System.Threading;
using Newtonsoft.Json;
-using osu.Framework.Allocation;
namespace osu.Game.Online.Multiplayer
{
@@ -42,35 +40,12 @@ namespace osu.Game.Online.Multiplayer
///
public MultiplayerRoomUser? Host { get; set; }
- private object writeLock = new object();
-
[JsonConstructor]
public MultiplayerRoom(in long roomId)
{
RoomID = roomId;
}
- private object updateLock = new object();
-
- private ManualResetEventSlim freeForWrite = new ManualResetEventSlim(true);
-
- ///
- /// Request a lock on this room to perform a thread-safe update.
- ///
- public IDisposable LockForUpdate()
- {
- // ReSharper disable once InconsistentlySynchronizedField
- freeForWrite.Wait();
-
- lock (updateLock)
- {
- freeForWrite.Wait();
- freeForWrite.Reset();
-
- return new ValueInvokeOnDisposal(this, r => freeForWrite.Set());
- }
- }
-
public override string ToString() => $"RoomID:{RoomID} Host:{Host?.UserID} Users:{Users.Count} State:{State} Settings: [{Settings}]";
}
}
diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs
index fcb0977f53..dc80488d39 100644
--- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs
+++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs
@@ -61,9 +61,9 @@ namespace osu.Game.Online.Multiplayer
public MultiplayerRoom? Room { get; private set; }
///
- /// The users currently in gameplay.
+ /// The users in the joined which are participating in the current gameplay loop.
///
- public readonly BindableList PlayingUsers = new BindableList();
+ public readonly BindableList CurrentMatchPlayingUserIds = new BindableList();
[Resolved]
private UserLookupCache userLookupCache { get; set; } = null!;
@@ -84,7 +84,7 @@ namespace osu.Game.Online.Multiplayer
IsConnected.BindValueChanged(connected =>
{
// clean up local room state on server disconnect.
- if (!connected.NewValue)
+ if (!connected.NewValue && Room != null)
{
Logger.Log("Connection to multiplayer server was lost.", LoggingTarget.Runtime, LogLevel.Important);
LeaveRoom().CatchUnobservedExceptions();
@@ -133,6 +133,7 @@ namespace osu.Game.Online.Multiplayer
apiRoom = null;
Room = null;
+ CurrentMatchPlayingUserIds.Clear();
RoomUpdated?.Invoke();
}, false);
@@ -253,7 +254,7 @@ namespace osu.Game.Online.Multiplayer
return;
Room.Users.Remove(user);
- PlayingUsers.Remove(user.UserID);
+ CurrentMatchPlayingUserIds.Remove(user.UserID);
RoomUpdated?.Invoke();
}, false);
@@ -302,8 +303,7 @@ namespace osu.Game.Online.Multiplayer
Room.Users.Single(u => u.UserID == userId).State = state;
- if (state != MultiplayerUserState.Playing)
- PlayingUsers.Remove(userId);
+ updateUserPlayingState(userId, state);
RoomUpdated?.Invoke();
}, false);
@@ -337,8 +337,6 @@ namespace osu.Game.Online.Multiplayer
if (Room == null)
return;
- PlayingUsers.AddRange(Room.Users.Where(u => u.State == MultiplayerUserState.Playing).Select(u => u.UserID));
-
MatchStarted?.Invoke();
}, false);
@@ -454,5 +452,24 @@ namespace osu.Game.Online.Multiplayer
apiRoom.Playlist.Clear(); // Clearing should be unnecessary, but here for sanity.
apiRoom.Playlist.Add(playlistItem);
}
+
+ ///
+ /// For the provided user ID, update whether the user is included in .
+ ///
+ /// The user's ID.
+ /// The new state of the user.
+ private void updateUserPlayingState(int userId, MultiplayerUserState state)
+ {
+ bool wasPlaying = CurrentMatchPlayingUserIds.Contains(userId);
+ bool isPlaying = state >= MultiplayerUserState.WaitingForLoad && state <= MultiplayerUserState.FinishedPlay;
+
+ if (isPlaying == wasPlaying)
+ return;
+
+ if (isPlaying)
+ CurrentMatchPlayingUserIds.Add(userId);
+ else
+ CurrentMatchPlayingUserIds.Remove(userId);
+ }
}
}
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index f19b909559..442e8a9401 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -151,11 +151,11 @@ namespace osu.Game
updateBlockingOverlayFade();
}
- public void RemoveBlockingOverlay(OverlayContainer overlay)
+ public void RemoveBlockingOverlay(OverlayContainer overlay) => Schedule(() =>
{
visibleBlockingOverlays.Remove(overlay);
updateBlockingOverlayFade();
- }
+ });
///
/// Close all game-wide overlays.
diff --git a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs
index a0b1b27ebf..bcb3d4b635 100644
--- a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs
+++ b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs
@@ -48,11 +48,9 @@ namespace osu.Game.Overlays.AccountCreation
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
- FillFlowContainer mainContent;
-
InternalChildren = new Drawable[]
{
- mainContent = new FillFlowContainer
+ new FillFlowContainer
{
RelativeSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
@@ -124,7 +122,7 @@ namespace osu.Game.Overlays.AccountCreation
},
},
},
- loadingLayer = new LoadingLayer(mainContent)
+ loadingLayer = new LoadingLayer(true)
};
textboxes = new[] { usernameTextBox, emailTextBox, passwordTextBox };
diff --git a/osu.Game/Overlays/AccountCreationOverlay.cs b/osu.Game/Overlays/AccountCreationOverlay.cs
index 58ede5502a..3084c7475a 100644
--- a/osu.Game/Overlays/AccountCreationOverlay.cs
+++ b/osu.Game/Overlays/AccountCreationOverlay.cs
@@ -9,6 +9,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Screens;
+using osu.Framework.Threading;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Online.API;
@@ -93,6 +94,11 @@ namespace osu.Game.Overlays
if (welcomeScreen.GetChildScreen() != null)
welcomeScreen.MakeCurrent();
+
+ // there might be a stale scheduled hide from a previous API state change.
+ // cancel it here so that the overlay is not hidden again after one frame.
+ scheduledHide?.Cancel();
+ scheduledHide = null;
}
protected override void PopOut()
@@ -101,7 +107,9 @@ namespace osu.Game.Overlays
this.FadeOut(100);
}
- private void apiStateChanged(ValueChangedEvent state) => Schedule(() =>
+ private ScheduledDelegate scheduledHide;
+
+ private void apiStateChanged(ValueChangedEvent state)
{
switch (state.NewValue)
{
@@ -113,9 +121,10 @@ namespace osu.Game.Overlays
break;
case APIState.Online:
- Hide();
+ scheduledHide?.Cancel();
+ scheduledHide = Schedule(Hide);
break;
}
- });
+ }
}
}
diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs
index 1e29e713af..0c9c995dd6 100644
--- a/osu.Game/Overlays/BeatmapListingOverlay.cs
+++ b/osu.Game/Overlays/BeatmapListingOverlay.cs
@@ -92,14 +92,14 @@ namespace osu.Game.Overlays
{
foundContent = new FillFlowContainer(),
notFoundContent = new NotFoundDrawable(),
- loadingLayer = new LoadingLayer(panelTarget)
}
}
- }
+ },
},
}
- }
- }
+ },
+ },
+ loadingLayer = new LoadingLayer(true)
};
}
diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs
index c983b337b5..7ad6906cea 100644
--- a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs
+++ b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs
@@ -53,7 +53,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons
Size = new Vector2(18),
Shadow = false,
},
- loading = new LoadingLayer(icon, false),
+ loading = new LoadingLayer(true, false),
});
Action = () =>
diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs
index 9a2dcd014a..b598b7d97f 100644
--- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs
+++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs
@@ -157,11 +157,11 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
}
}
},
- loading = new LoadingLayer()
}
}
- }
- }
+ },
+ },
+ loading = new LoadingLayer()
});
}
@@ -228,7 +228,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
{
Scores = null;
notSupporterPlaceholder.Show();
+
loading.Hide();
+ loading.FinishTransforms();
return;
}
@@ -241,6 +243,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
getScoresRequest.Success += scores =>
{
loading.Hide();
+ loading.FinishTransforms();
+
Scores = scores;
if (!scores.Scores.Any())
diff --git a/osu.Game/Overlays/Changelog/ChangelogBuild.cs b/osu.Game/Overlays/Changelog/ChangelogBuild.cs
index 65ff0fef92..2d071b7345 100644
--- a/osu.Game/Overlays/Changelog/ChangelogBuild.cs
+++ b/osu.Game/Overlays/Changelog/ChangelogBuild.cs
@@ -9,14 +9,8 @@ using osu.Game.Graphics.Containers;
using osu.Game.Online.API.Requests.Responses;
using System;
using System.Linq;
-using System.Text.RegularExpressions;
using osu.Game.Graphics.Sprites;
-using osu.Game.Users;
-using osuTK.Graphics;
using osu.Framework.Allocation;
-using System.Net;
-using osuTK;
-using osu.Framework.Extensions.Color4Extensions;
namespace osu.Game.Overlays.Changelog
{
@@ -63,126 +57,7 @@ namespace osu.Game.Overlays.Changelog
Margin = new MarginPadding { Top = 35, Bottom = 15 },
});
- var fontLarge = OsuFont.GetFont(size: 16);
- var fontMedium = OsuFont.GetFont(size: 12);
-
- foreach (var entry in categoryEntries)
- {
- var entryColour = entry.Major ? colours.YellowLight : Color4.White;
-
- LinkFlowContainer title;
-
- var titleContainer = new Container
- {
- AutoSizeAxes = Axes.Y,
- RelativeSizeAxes = Axes.X,
- Margin = new MarginPadding { Vertical = 5 },
- Children = new Drawable[]
- {
- new SpriteIcon
- {
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.CentreRight,
- Size = new Vector2(10),
- Icon = entry.Type == ChangelogEntryType.Fix ? FontAwesome.Solid.Check : FontAwesome.Solid.Plus,
- Colour = entryColour.Opacity(0.5f),
- Margin = new MarginPadding { Right = 5 },
- },
- title = new LinkFlowContainer
- {
- Direction = FillDirection.Full,
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- TextAnchor = Anchor.BottomLeft,
- }
- }
- };
-
- title.AddText(entry.Title, t =>
- {
- t.Font = fontLarge;
- t.Colour = entryColour;
- });
-
- if (!string.IsNullOrEmpty(entry.Repository))
- {
- title.AddText(" (", t =>
- {
- t.Font = fontLarge;
- t.Colour = entryColour;
- });
- title.AddLink($"{entry.Repository.Replace("ppy/", "")}#{entry.GithubPullRequestId}", entry.GithubUrl,
- creationParameters: t =>
- {
- t.Font = fontLarge;
- t.Colour = entryColour;
- });
- title.AddText(")", t =>
- {
- t.Font = fontLarge;
- t.Colour = entryColour;
- });
- }
-
- title.AddText("by ", t =>
- {
- t.Font = fontMedium;
- t.Colour = entryColour;
- t.Padding = new MarginPadding { Left = 10 };
- });
-
- if (entry.GithubUser != null)
- {
- if (entry.GithubUser.UserId != null)
- {
- title.AddUserLink(new User
- {
- Username = entry.GithubUser.OsuUsername,
- Id = entry.GithubUser.UserId.Value
- }, t =>
- {
- t.Font = fontMedium;
- t.Colour = entryColour;
- });
- }
- else if (entry.GithubUser.GithubUrl != null)
- {
- title.AddLink(entry.GithubUser.DisplayName, entry.GithubUser.GithubUrl, t =>
- {
- t.Font = fontMedium;
- t.Colour = entryColour;
- });
- }
- else
- {
- title.AddText(entry.GithubUser.DisplayName, t =>
- {
- t.Font = fontMedium;
- t.Colour = entryColour;
- });
- }
- }
-
- ChangelogEntries.Add(titleContainer);
-
- if (!string.IsNullOrEmpty(entry.MessageHtml))
- {
- var message = new TextFlowContainer
- {
- AutoSizeAxes = Axes.Y,
- RelativeSizeAxes = Axes.X,
- };
-
- // todo: use markdown parsing once API returns markdown
- message.AddText(WebUtility.HtmlDecode(Regex.Replace(entry.MessageHtml, @"<(.|\n)*?>", string.Empty)), t =>
- {
- t.Font = fontMedium;
- t.Colour = colourProvider.Foreground1;
- });
-
- ChangelogEntries.Add(message);
- }
- }
+ ChangelogEntries.AddRange(categoryEntries.Select(entry => new ChangelogEntry(entry)));
}
}
diff --git a/osu.Game/Overlays/Changelog/ChangelogEntry.cs b/osu.Game/Overlays/Changelog/ChangelogEntry.cs
new file mode 100644
index 0000000000..55edb40283
--- /dev/null
+++ b/osu.Game/Overlays/Changelog/ChangelogEntry.cs
@@ -0,0 +1,202 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Net;
+using System.Text.RegularExpressions;
+using osu.Framework.Allocation;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Containers;
+using osu.Game.Online.API.Requests.Responses;
+using osu.Game.Users;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Overlays.Changelog
+{
+ public class ChangelogEntry : FillFlowContainer
+ {
+ private readonly APIChangelogEntry entry;
+
+ [Resolved]
+ private OsuColour colours { get; set; }
+
+ [Resolved]
+ private OverlayColourProvider colourProvider { get; set; }
+
+ private FontUsage fontLarge;
+ private FontUsage fontMedium;
+
+ public ChangelogEntry(APIChangelogEntry entry)
+ {
+ this.entry = entry;
+
+ RelativeSizeAxes = Axes.X;
+ AutoSizeAxes = Axes.Y;
+ Direction = FillDirection.Vertical;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ fontLarge = OsuFont.GetFont(size: 16);
+ fontMedium = OsuFont.GetFont(size: 12);
+
+ Children = new[]
+ {
+ createTitle(),
+ createMessage()
+ };
+ }
+
+ private Drawable createTitle()
+ {
+ var entryColour = entry.Major ? colours.YellowLight : Color4.White;
+
+ LinkFlowContainer title;
+
+ var titleContainer = new Container
+ {
+ AutoSizeAxes = Axes.Y,
+ RelativeSizeAxes = Axes.X,
+ Margin = new MarginPadding { Vertical = 5 },
+ Children = new Drawable[]
+ {
+ new SpriteIcon
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreRight,
+ Size = new Vector2(10),
+ Icon = getIconForChangelogEntry(entry.Type),
+ Colour = entryColour.Opacity(0.5f),
+ Margin = new MarginPadding { Right = 5 },
+ },
+ title = new LinkFlowContainer
+ {
+ Direction = FillDirection.Full,
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ TextAnchor = Anchor.BottomLeft,
+ }
+ }
+ };
+
+ title.AddText(entry.Title, t =>
+ {
+ t.Font = fontLarge;
+ t.Colour = entryColour;
+ });
+
+ if (!string.IsNullOrEmpty(entry.Repository))
+ addRepositoryReference(title, entryColour);
+
+ if (entry.GithubUser != null)
+ addGithubAuthorReference(title, entryColour);
+
+ return titleContainer;
+ }
+
+ private void addRepositoryReference(LinkFlowContainer title, Color4 entryColour)
+ {
+ title.AddText(" (", t =>
+ {
+ t.Font = fontLarge;
+ t.Colour = entryColour;
+ });
+ title.AddLink($"{entry.Repository.Replace("ppy/", "")}#{entry.GithubPullRequestId}", entry.GithubUrl,
+ t =>
+ {
+ t.Font = fontLarge;
+ t.Colour = entryColour;
+ });
+ title.AddText(")", t =>
+ {
+ t.Font = fontLarge;
+ t.Colour = entryColour;
+ });
+ }
+
+ private void addGithubAuthorReference(LinkFlowContainer title, Color4 entryColour)
+ {
+ title.AddText("by ", t =>
+ {
+ t.Font = fontMedium;
+ t.Colour = entryColour;
+ t.Padding = new MarginPadding { Left = 10 };
+ });
+
+ if (entry.GithubUser.UserId != null)
+ {
+ title.AddUserLink(new User
+ {
+ Username = entry.GithubUser.OsuUsername,
+ Id = entry.GithubUser.UserId.Value
+ }, t =>
+ {
+ t.Font = fontMedium;
+ t.Colour = entryColour;
+ });
+ }
+ else if (entry.GithubUser.GithubUrl != null)
+ {
+ title.AddLink(entry.GithubUser.DisplayName, entry.GithubUser.GithubUrl, t =>
+ {
+ t.Font = fontMedium;
+ t.Colour = entryColour;
+ });
+ }
+ else
+ {
+ title.AddText(entry.GithubUser.DisplayName, t =>
+ {
+ t.Font = fontMedium;
+ t.Colour = entryColour;
+ });
+ }
+ }
+
+ private Drawable createMessage()
+ {
+ if (string.IsNullOrEmpty(entry.MessageHtml))
+ return Empty();
+
+ var message = new TextFlowContainer
+ {
+ AutoSizeAxes = Axes.Y,
+ RelativeSizeAxes = Axes.X,
+ };
+
+ // todo: use markdown parsing once API returns markdown
+ message.AddText(WebUtility.HtmlDecode(Regex.Replace(entry.MessageHtml, @"<(.|\n)*?>", string.Empty)), t =>
+ {
+ t.Font = fontMedium;
+ t.Colour = colourProvider.Foreground1;
+ });
+
+ return message;
+ }
+
+ private static IconUsage getIconForChangelogEntry(ChangelogEntryType entryType)
+ {
+ // compare: https://github.com/ppy/osu-web/blob/master/resources/assets/coffee/react/_components/changelog-entry.coffee#L8-L11
+ switch (entryType)
+ {
+ case ChangelogEntryType.Add:
+ return FontAwesome.Solid.Plus;
+
+ case ChangelogEntryType.Fix:
+ return FontAwesome.Solid.Check;
+
+ case ChangelogEntryType.Misc:
+ return FontAwesome.Regular.Circle;
+
+ default:
+ throw new ArgumentOutOfRangeException(nameof(entryType), $"Unrecognised entry type {entryType}");
+ }
+ }
+ }
+}
diff --git a/osu.Game/Overlays/Changelog/ChangelogUpdateStreamControl.cs b/osu.Game/Overlays/Changelog/ChangelogUpdateStreamControl.cs
index 509a6dabae..aa36a5c8fd 100644
--- a/osu.Game/Overlays/Changelog/ChangelogUpdateStreamControl.cs
+++ b/osu.Game/Overlays/Changelog/ChangelogUpdateStreamControl.cs
@@ -7,6 +7,11 @@ namespace osu.Game.Overlays.Changelog
{
public class ChangelogUpdateStreamControl : OverlayStreamControl
{
+ public ChangelogUpdateStreamControl()
+ {
+ SelectFirstTabByDefault = false;
+ }
+
protected override OverlayStreamItem CreateStreamItem(APIUpdateStream value) => new ChangelogUpdateStreamItem(value);
}
}
diff --git a/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs b/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs
index 5b428a3825..00f46b0035 100644
--- a/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs
+++ b/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs
@@ -25,7 +25,7 @@ namespace osu.Game.Overlays.Chat.Tabs
if (value.Type != ChannelType.PM)
throw new ArgumentException("Argument value needs to have the targettype user!");
- DrawableAvatar avatar;
+ ClickableAvatar avatar;
AddRange(new Drawable[]
{
@@ -48,7 +48,7 @@ namespace osu.Game.Overlays.Chat.Tabs
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Masking = true,
- Child = new DelayedLoadWrapper(avatar = new DrawableAvatar(value.Users.First())
+ Child = new DelayedLoadWrapper(avatar = new ClickableAvatar(value.Users.First())
{
RelativeSizeAxes = Axes.Both,
OpenOnClick = { Value = false },
diff --git a/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs b/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs
index cc26a11da1..e6fe6ac749 100644
--- a/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs
+++ b/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs
@@ -128,7 +128,7 @@ namespace osu.Game.Overlays.Dashboard.Friends
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding { Horizontal = 50 }
},
- loading = new LoadingLayer(itemsPlaceholder)
+ loading = new LoadingLayer(true)
}
}
}
diff --git a/osu.Game/Overlays/DashboardOverlay.cs b/osu.Game/Overlays/DashboardOverlay.cs
index 04defce636..03c320debe 100644
--- a/osu.Game/Overlays/DashboardOverlay.cs
+++ b/osu.Game/Overlays/DashboardOverlay.cs
@@ -68,7 +68,7 @@ namespace osu.Game.Overlays
}
}
},
- loading = new LoadingLayer(content),
+ loading = new LoadingLayer(true),
};
}
diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs
index c8c1db012f..5820d405d4 100644
--- a/osu.Game/Overlays/NewsOverlay.cs
+++ b/osu.Game/Overlays/NewsOverlay.cs
@@ -59,7 +59,7 @@ namespace osu.Game.Overlays
},
},
},
- loading = new LoadingLayer(content),
+ loading = new LoadingLayer(true),
};
}
diff --git a/osu.Game/Overlays/Rankings/SpotlightsLayout.cs b/osu.Game/Overlays/Rankings/SpotlightsLayout.cs
index 61339df76f..b16e0a4908 100644
--- a/osu.Game/Overlays/Rankings/SpotlightsLayout.cs
+++ b/osu.Game/Overlays/Rankings/SpotlightsLayout.cs
@@ -45,6 +45,7 @@ namespace osu.Game.Overlays.Rankings
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
+
InternalChild = new ReverseChildIDFillFlowContainer
{
RelativeSizeAxes = Axes.X,
@@ -68,7 +69,7 @@ namespace osu.Game.Overlays.Rankings
AutoSizeAxes = Axes.Y,
Margin = new MarginPadding { Vertical = 10 }
},
- loading = new LoadingLayer(content)
+ loading = new LoadingLayer(true)
}
}
}
diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs
index ae6d49960a..25350e310a 100644
--- a/osu.Game/Overlays/RankingsOverlay.cs
+++ b/osu.Game/Overlays/RankingsOverlay.cs
@@ -42,6 +42,8 @@ namespace osu.Game.Overlays
Depth = -float.MaxValue
})
{
+ loading = new LoadingLayer(true);
+
Children = new Drawable[]
{
background = new Box
@@ -74,12 +76,12 @@ namespace osu.Game.Overlays
RelativeSizeAxes = Axes.X,
Margin = new MarginPadding { Bottom = 10 }
},
- loading = new LoadingLayer(contentContainer),
}
}
}
}
- }
+ },
+ loading
};
}
diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
index 3d3b543d70..7acbf038d8 100644
--- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
@@ -132,6 +132,15 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
}
},
};
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ scalingSettings.ForEach(s => bindPreviewEvent(s.Current));
+
+ windowModeDropdown.Current.ValueChanged += _ => updateResolutionDropdown();
windowModes.BindCollectionChanged((sender, args) =>
{
@@ -141,8 +150,6 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
windowModeDropdown.Hide();
}, true);
- windowModeDropdown.Current.ValueChanged += _ => updateResolutionDropdown();
-
currentDisplay.BindValueChanged(display => Schedule(() =>
{
resolutions.RemoveRange(1, resolutions.Count - 1);
@@ -159,8 +166,6 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
updateResolutionDropdown();
}), true);
- scalingSettings.ForEach(s => bindPreviewEvent(s.Current));
-
scalingMode.BindValueChanged(mode =>
{
scalingSettings.ClearTransforms();
@@ -181,11 +186,6 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
}
}
- ///
- /// Create a delayed bindable which only updates when a condition is met.
- ///
- /// The config bindable.
- /// A bindable which will propagate updates with a delay.
private void bindPreviewEvent(Bindable bindable)
{
bindable.ValueChanged += _ =>
diff --git a/osu.Game/Overlays/Settings/Sections/Online/IntegrationSettings.cs b/osu.Game/Overlays/Settings/Sections/Online/IntegrationSettings.cs
new file mode 100644
index 0000000000..d2867962c0
--- /dev/null
+++ b/osu.Game/Overlays/Settings/Sections/Online/IntegrationSettings.cs
@@ -0,0 +1,27 @@
+// 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.Graphics;
+using osu.Game.Configuration;
+
+namespace osu.Game.Overlays.Settings.Sections.Online
+{
+ public class IntegrationSettings : SettingsSubsection
+ {
+ protected override string Header => "Integrations";
+
+ [BackgroundDependencyLoader]
+ private void load(OsuConfigManager config)
+ {
+ Children = new Drawable[]
+ {
+ new SettingsEnumDropdown
+ {
+ LabelText = "Discord Rich Presence",
+ Current = config.GetBindable(OsuSetting.DiscordRichPresence)
+ }
+ };
+ }
+ }
+}
diff --git a/osu.Game/Overlays/Settings/Sections/OnlineSection.cs b/osu.Game/Overlays/Settings/Sections/OnlineSection.cs
index 150cddb388..7aa4eff29a 100644
--- a/osu.Game/Overlays/Settings/Sections/OnlineSection.cs
+++ b/osu.Game/Overlays/Settings/Sections/OnlineSection.cs
@@ -20,7 +20,8 @@ namespace osu.Game.Overlays.Settings.Sections
{
Children = new Drawable[]
{
- new WebSettings()
+ new WebSettings(),
+ new IntegrationSettings()
};
}
}
diff --git a/osu.Game/Overlays/Toolbar/ToolbarBeatmapListingButton.cs b/osu.Game/Overlays/Toolbar/ToolbarBeatmapListingButton.cs
index 0363873326..bfe36a6a0f 100644
--- a/osu.Game/Overlays/Toolbar/ToolbarBeatmapListingButton.cs
+++ b/osu.Game/Overlays/Toolbar/ToolbarBeatmapListingButton.cs
@@ -2,15 +2,18 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
+using osu.Framework.Graphics;
using osu.Game.Input.Bindings;
namespace osu.Game.Overlays.Toolbar
{
public class ToolbarBeatmapListingButton : ToolbarOverlayToggleButton
{
+ protected override Anchor TooltipAnchor => Anchor.TopRight;
+
public ToolbarBeatmapListingButton()
{
- Hotkey = GlobalAction.ToggleDirect;
+ Hotkey = GlobalAction.ToggleBeatmapListing;
}
[BackgroundDependencyLoader(true)]
diff --git a/osu.Game/Overlays/Toolbar/ToolbarChangelogButton.cs b/osu.Game/Overlays/Toolbar/ToolbarChangelogButton.cs
index 23f8b141b2..86bc73361a 100644
--- a/osu.Game/Overlays/Toolbar/ToolbarChangelogButton.cs
+++ b/osu.Game/Overlays/Toolbar/ToolbarChangelogButton.cs
@@ -2,11 +2,14 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
+using osu.Framework.Graphics;
namespace osu.Game.Overlays.Toolbar
{
public class ToolbarChangelogButton : ToolbarOverlayToggleButton
{
+ protected override Anchor TooltipAnchor => Anchor.TopRight;
+
[BackgroundDependencyLoader(true)]
private void load(ChangelogOverlay changelog)
{
diff --git a/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs b/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs
index f9a66ae7bb..2d3b33e9bc 100644
--- a/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs
+++ b/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs
@@ -2,12 +2,15 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
+using osu.Framework.Graphics;
using osu.Game.Input.Bindings;
namespace osu.Game.Overlays.Toolbar
{
public class ToolbarChatButton : ToolbarOverlayToggleButton
{
+ protected override Anchor TooltipAnchor => Anchor.TopRight;
+
public ToolbarChatButton()
{
Hotkey = GlobalAction.ToggleChat;
diff --git a/osu.Game/Overlays/Toolbar/ToolbarNewsButton.cs b/osu.Game/Overlays/Toolbar/ToolbarNewsButton.cs
index 0ba2935c80..9b2573ad07 100644
--- a/osu.Game/Overlays/Toolbar/ToolbarNewsButton.cs
+++ b/osu.Game/Overlays/Toolbar/ToolbarNewsButton.cs
@@ -2,11 +2,14 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
+using osu.Framework.Graphics;
namespace osu.Game.Overlays.Toolbar
{
public class ToolbarNewsButton : ToolbarOverlayToggleButton
{
+ protected override Anchor TooltipAnchor => Anchor.TopRight;
+
[BackgroundDependencyLoader(true)]
private void load(NewsOverlay news)
{
diff --git a/osu.Game/Overlays/Toolbar/ToolbarRankingsButton.cs b/osu.Game/Overlays/Toolbar/ToolbarRankingsButton.cs
index 22a01bcdb5..312fc41aab 100644
--- a/osu.Game/Overlays/Toolbar/ToolbarRankingsButton.cs
+++ b/osu.Game/Overlays/Toolbar/ToolbarRankingsButton.cs
@@ -2,11 +2,14 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
+using osu.Framework.Graphics;
namespace osu.Game.Overlays.Toolbar
{
public class ToolbarRankingsButton : ToolbarOverlayToggleButton
{
+ protected override Anchor TooltipAnchor => Anchor.TopRight;
+
[BackgroundDependencyLoader(true)]
private void load(RankingsOverlay rankings)
{
diff --git a/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs b/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs
index e62c7bc807..1e00afc5fd 100644
--- a/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs
+++ b/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs
@@ -2,12 +2,15 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
+using osu.Framework.Graphics;
using osu.Game.Input.Bindings;
namespace osu.Game.Overlays.Toolbar
{
public class ToolbarSocialButton : ToolbarOverlayToggleButton
{
+ protected override Anchor TooltipAnchor => Anchor.TopRight;
+
public ToolbarSocialButton()
{
Hotkey = GlobalAction.ToggleSocial;
diff --git a/osu.Game/PerformFromMenuRunner.cs b/osu.Game/PerformFromMenuRunner.cs
index e2d4fc6051..7999023998 100644
--- a/osu.Game/PerformFromMenuRunner.cs
+++ b/osu.Game/PerformFromMenuRunner.cs
@@ -73,15 +73,19 @@ namespace osu.Game
// find closest valid target
IScreen current = getCurrentScreen();
+ if (current == null)
+ return;
+
// a dialog may be blocking the execution for now.
if (checkForDialog(current)) return;
game?.CloseAllOverlays(false);
// we may already be at the target screen type.
- if (validScreens.Contains(getCurrentScreen().GetType()) && !beatmap.Disabled)
+ if (validScreens.Contains(current.GetType()) && !beatmap.Disabled)
{
- complete();
+ finalAction(current);
+ Cancel();
return;
}
@@ -135,11 +139,5 @@ namespace osu.Game
lastEncounteredDialogScreen = current;
return true;
}
-
- private void complete()
- {
- finalAction(getCurrentScreen());
- Cancel();
- }
}
}
diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs
index b8dc7a2661..24d184e531 100644
--- a/osu.Game/Rulesets/Mods/Mod.cs
+++ b/osu.Game/Rulesets/Mods/Mod.cs
@@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics;
using System.Linq;
using System.Reflection;
using Newtonsoft.Json;
@@ -84,12 +83,10 @@ namespace osu.Game.Rulesets.Mods
foreach ((SettingSourceAttribute attr, PropertyInfo property) in this.GetOrderedSettingsSourceProperties())
{
- object bindableObj = property.GetValue(this);
+ var bindable = (IBindable)property.GetValue(this);
- if ((bindableObj as IHasDefaultValue)?.IsDefault == true)
- continue;
-
- tooltipTexts.Add($"{attr.Label} {bindableObj}");
+ if (!bindable.IsDefault)
+ tooltipTexts.Add($"{attr.Label} {bindable}");
}
return string.Join(", ", tooltipTexts.Where(s => !string.IsNullOrEmpty(s)));
@@ -136,19 +133,38 @@ namespace osu.Game.Rulesets.Mods
// Copy bindable values across
foreach (var (_, prop) in this.GetSettingsSourceProperties())
{
- var origBindable = prop.GetValue(this);
- var copyBindable = prop.GetValue(copy);
+ var origBindable = (IBindable)prop.GetValue(this);
+ var copyBindable = (IBindable)prop.GetValue(copy);
- // The bindables themselves are readonly, so the value must be transferred through the Bindable.Value property.
- var valueProperty = origBindable.GetType().GetProperty(nameof(Bindable
/// The score's player.
/// Whether the player is the local user or a replay player.
- public GameplayLeaderboardScore(User user, bool trackedPlayer)
+ public GameplayLeaderboardScore([CanBeNull] User user, bool trackedPlayer)
{
User = user;
this.trackedPlayer = trackedPlayer;
@@ -78,6 +81,8 @@ namespace osu.Game.Screens.Play.HUD
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
+ Container avatarContainer;
+
InternalChildren = new Drawable[]
{
mainFillContainer = new Container
@@ -152,7 +157,7 @@ namespace osu.Game.Screens.Play.HUD
Spacing = new Vector2(4f, 0f),
Children = new Drawable[]
{
- new CircularContainer
+ avatarContainer = new CircularContainer
{
Masking = true,
Anchor = Anchor.CentreLeft,
@@ -166,11 +171,7 @@ namespace osu.Game.Screens.Play.HUD
Alpha = 0.3f,
RelativeSizeAxes = Axes.Both,
Colour = colours.Gray4,
- },
- new UpdateableAvatar(User)
- {
- RelativeSizeAxes = Axes.Both,
- },
+ }
}
},
usernameText = new OsuSpriteText
@@ -181,7 +182,7 @@ namespace osu.Game.Screens.Play.HUD
Origin = Anchor.CentreLeft,
Colour = Color4.White,
Font = OsuFont.Torus.With(size: 14, weight: FontWeight.SemiBold),
- Text = User.Username,
+ Text = User?.Username,
Truncate = true,
Shadow = false,
}
@@ -227,23 +228,36 @@ namespace osu.Game.Screens.Play.HUD
}
};
+ LoadComponentAsync(new DrawableAvatar(User), avatarContainer.Add);
+
TotalScore.BindValueChanged(v => scoreText.Text = v.NewValue.ToString("N0"), true);
Accuracy.BindValueChanged(v => accuracyText.Text = v.NewValue.FormatAccuracy(), true);
Combo.BindValueChanged(v => comboText.Text = $"{v.NewValue}x", true);
+ HasQuit.BindValueChanged(_ => updateState());
}
protected override void LoadComplete()
{
base.LoadComplete();
- updateColour();
+ updateState();
FinishTransforms(true);
}
private const double panel_transition_duration = 500;
- private void updateColour()
+ private void updateState()
{
+ 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;
+ }
+
if (scorePosition == 1)
{
mainFillContainer.ResizeWidthTo(EXTENDED_WIDTH, panel_transition_duration, Easing.OutElastic);
diff --git a/osu.Game/Screens/Play/HUD/ILeaderboardScore.cs b/osu.Game/Screens/Play/HUD/ILeaderboardScore.cs
index bc1a03c5aa..83b6f6621b 100644
--- a/osu.Game/Screens/Play/HUD/ILeaderboardScore.cs
+++ b/osu.Game/Screens/Play/HUD/ILeaderboardScore.cs
@@ -10,5 +10,7 @@ namespace osu.Game.Screens.Play.HUD
BindableDouble TotalScore { get; }
BindableDouble Accuracy { get; }
BindableInt Combo { get; }
+
+ BindableBool HasQuit { get; }
}
}
diff --git a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs
index c10ec9e004..d4ce542a67 100644
--- a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs
+++ b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs
@@ -2,12 +2,15 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Game.Configuration;
using osu.Game.Database;
using osu.Game.Online.API;
+using osu.Game.Online.Multiplayer;
using osu.Game.Online.Spectator;
using osu.Game.Rulesets.Scoring;
@@ -18,10 +21,21 @@ namespace osu.Game.Screens.Play.HUD
{
private readonly ScoreProcessor scoreProcessor;
- private readonly int[] userIds;
-
private readonly Dictionary userScores = new Dictionary();
+ [Resolved]
+ private SpectatorStreamingClient streamingClient { get; set; }
+
+ [Resolved]
+ private StatefulMultiplayerClient multiplayerClient { get; set; }
+
+ [Resolved]
+ private UserLookupCache userLookupCache { get; set; }
+
+ private Bindable scoringMode;
+
+ private readonly BindableList playingUsers;
+
///
/// Construct a new leaderboard.
///
@@ -33,43 +47,68 @@ namespace osu.Game.Screens.Play.HUD
this.scoreProcessor = scoreProcessor;
// todo: this will likely be passed in as User instances.
- this.userIds = userIds;
+ playingUsers = new BindableList(userIds);
}
- [Resolved]
- private SpectatorStreamingClient streamingClient { get; set; }
-
- [Resolved]
- private UserLookupCache userLookupCache { get; set; }
-
- private Bindable scoringMode;
-
[BackgroundDependencyLoader]
private void load(OsuConfigManager config, IAPIProvider api)
{
streamingClient.OnNewFrames += handleIncomingFrames;
- foreach (var user in userIds)
+ foreach (var userId in playingUsers)
{
- streamingClient.WatchUser(user);
+ streamingClient.WatchUser(userId);
// probably won't be required in the final implementation.
- var resolvedUser = userLookupCache.GetUserAsync(user).Result;
+ var resolvedUser = userLookupCache.GetUserAsync(userId).Result;
var trackedUser = new TrackedUserData();
- userScores[user] = trackedUser;
- var leaderboardScore = AddPlayer(resolvedUser, resolvedUser.Id == api.LocalUser.Value.Id);
+ userScores[userId] = trackedUser;
+ var leaderboardScore = AddPlayer(resolvedUser, resolvedUser?.Id == api.LocalUser.Value.Id);
((IBindable)leaderboardScore.Accuracy).BindTo(trackedUser.Accuracy);
((IBindable)leaderboardScore.TotalScore).BindTo(trackedUser.Score);
((IBindable)leaderboardScore.Combo).BindTo(trackedUser.CurrentCombo);
+ ((IBindable)leaderboardScore.HasQuit).BindTo(trackedUser.UserQuit);
}
scoringMode = config.GetBindable(OsuSetting.ScoreDisplayMode);
scoringMode.BindValueChanged(updateAllScores, true);
}
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ // BindableList handles binding in a really bad way (Clear then AddRange) so we need to do this manually..
+ foreach (int userId in playingUsers)
+ {
+ if (!multiplayerClient.CurrentMatchPlayingUserIds.Contains(userId))
+ usersChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new[] { userId }));
+ }
+
+ playingUsers.BindTo(multiplayerClient.CurrentMatchPlayingUserIds);
+ playingUsers.BindCollectionChanged(usersChanged);
+ }
+
+ private void usersChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ switch (e.Action)
+ {
+ case NotifyCollectionChangedAction.Remove:
+ foreach (var userId in e.OldItems.OfType())
+ {
+ streamingClient.StopWatchingUser(userId);
+
+ if (userScores.TryGetValue(userId, out var trackedData))
+ trackedData.MarkUserQuit();
+ }
+
+ break;
+ }
+ }
+
private void updateAllScores(ValueChangedEvent mode)
{
foreach (var trackedData in userScores.Values)
@@ -91,7 +130,7 @@ namespace osu.Game.Screens.Play.HUD
if (streamingClient != null)
{
- foreach (var user in userIds)
+ foreach (var user in playingUsers)
{
streamingClient.StopWatchingUser(user);
}
@@ -114,9 +153,15 @@ namespace osu.Game.Screens.Play.HUD
private readonly BindableInt currentCombo = new BindableInt();
+ public IBindable UserQuit => userQuit;
+
+ private readonly BindableBool userQuit = new BindableBool();
+
[CanBeNull]
public FrameHeader LastHeader;
+ public void MarkUserQuit() => userQuit.Value = true;
+
public void UpdateScore(ScoreProcessor processor, ScoringMode mode)
{
if (LastHeader == null)
diff --git a/osu.Game/Screens/Play/SkinnableHealthDisplay.cs b/osu.Game/Screens/Play/HUD/SkinnableHealthDisplay.cs
similarity index 95%
rename from osu.Game/Screens/Play/SkinnableHealthDisplay.cs
rename to osu.Game/Screens/Play/HUD/SkinnableHealthDisplay.cs
index d35d15d665..1f91f5e50f 100644
--- a/osu.Game/Screens/Play/SkinnableHealthDisplay.cs
+++ b/osu.Game/Screens/Play/HUD/SkinnableHealthDisplay.cs
@@ -5,10 +5,9 @@ using System;
using osu.Framework.Bindables;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
-using osu.Game.Screens.Play.HUD;
using osu.Game.Skinning;
-namespace osu.Game.Screens.Play
+namespace osu.Game.Screens.Play.HUD
{
public class SkinnableHealthDisplay : SkinnableDrawable, IHealthDisplay
{
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index bf2e6f5379..1fcbed7ef7 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -721,15 +721,20 @@ namespace osu.Game.Screens.Play
.Delay(250)
.FadeIn(250);
- Background.EnableUserDim.Value = true;
- Background.BlurAmount.Value = 0;
+ ApplyToBackground(b =>
+ {
+ b.EnableUserDim.Value = true;
+ b.BlurAmount.Value = 0;
+
+ // bind component bindables.
+ b.IsBreakTime.BindTo(breakTracker.IsBreakTime);
+
+ b.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground);
+ });
- // bind component bindables.
- Background.IsBreakTime.BindTo(breakTracker.IsBreakTime);
HUDOverlay.IsBreakTime.BindTo(breakTracker.IsBreakTime);
DimmableStoryboard.IsBreakTime.BindTo(breakTracker.IsBreakTime);
- Background.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground);
DimmableStoryboard.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground);
storyboardReplacesBackground.Value = Beatmap.Value.Storyboard.ReplacesBackground && Beatmap.Value.Storyboard.HasDrawable;
@@ -875,7 +880,7 @@ namespace osu.Game.Screens.Play
float fadeOutDuration = instant ? 0 : 250;
this.FadeOut(fadeOutDuration);
- Background.EnableUserDim.Value = false;
+ ApplyToBackground(b => b.EnableUserDim.Value = false);
storyboardReplacesBackground.Value = false;
}
diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs
index f59b36bc42..5b4bd11216 100644
--- a/osu.Game/Screens/Play/PlayerLoader.cs
+++ b/osu.Game/Screens/Play/PlayerLoader.cs
@@ -67,7 +67,7 @@ namespace osu.Game.Screens.Play
backgroundBrightnessReduction = value;
- Background.FadeColour(OsuColour.Gray(backgroundBrightnessReduction ? 0.8f : 1), 200);
+ ApplyToBackground(b => b.FadeColour(OsuColour.Gray(backgroundBrightnessReduction ? 0.8f : 1), 200));
}
}
@@ -176,12 +176,17 @@ namespace osu.Game.Screens.Play
{
base.OnEntering(last);
- if (epilepsyWarning != null)
- epilepsyWarning.DimmableBackground = Background;
+ ApplyToBackground(b =>
+ {
+ if (epilepsyWarning != null)
+ epilepsyWarning.DimmableBackground = b;
+
+ b?.FadeColour(Color4.White, 800, Easing.OutQuint);
+ });
+
Beatmap.Value.Track.AddAdjustment(AdjustableProperty.Volume, volumeAdjustment);
content.ScaleTo(0.7f);
- Background?.FadeColour(Color4.White, 800, Easing.OutQuint);
contentIn();
@@ -225,7 +230,8 @@ namespace osu.Game.Screens.Play
content.ScaleTo(0.7f, 150, Easing.InQuint);
this.FadeOut(150);
- Background.EnableUserDim.Value = false;
+ ApplyToBackground(b => b.EnableUserDim.Value = false);
+
BackgroundBrightnessReduction = false;
Beatmap.Value.Track.RemoveAdjustment(AdjustableProperty.Volume, volumeAdjustment);
@@ -270,16 +276,22 @@ namespace osu.Game.Screens.Play
if (inputManager.HoveredDrawables.Contains(VisualSettings))
{
// Preview user-defined background dim and blur when hovered on the visual settings panel.
- Background.EnableUserDim.Value = true;
- Background.BlurAmount.Value = 0;
+ ApplyToBackground(b =>
+ {
+ b.EnableUserDim.Value = true;
+ b.BlurAmount.Value = 0;
+ });
BackgroundBrightnessReduction = false;
}
else
{
- // Returns background dim and blur to the values specified by PlayerLoader.
- Background.EnableUserDim.Value = false;
- Background.BlurAmount.Value = BACKGROUND_BLUR;
+ ApplyToBackground(b =>
+ {
+ // Returns background dim and blur to the values specified by PlayerLoader.
+ b.EnableUserDim.Value = false;
+ b.BlurAmount.Value = BACKGROUND_BLUR;
+ });
BackgroundBrightnessReduction = true;
}
diff --git a/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs b/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs
index 8eb253608b..88dab88d42 100644
--- a/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs
+++ b/osu.Game/Screens/Play/ScreenWithBeatmapBackground.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 osu.Game.Screens.Backgrounds;
namespace osu.Game.Screens.Play
@@ -9,6 +10,6 @@ namespace osu.Game.Screens.Play
{
protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(Beatmap.Value);
- public new BackgroundScreenBeatmap Background => (BackgroundScreenBeatmap)base.Background;
+ public void ApplyToBackground(Action action) => base.ApplyToBackground(b => action.Invoke((BackgroundScreenBeatmap)b));
}
}
diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs
index 528a1842af..c1f5d92d17 100644
--- a/osu.Game/Screens/Ranking/ResultsScreen.cs
+++ b/osu.Game/Screens/Ranking/ResultsScreen.cs
@@ -18,14 +18,13 @@ using osu.Game.Input.Bindings;
using osu.Game.Online.API;
using osu.Game.Rulesets.Mods;
using osu.Game.Scoring;
-using osu.Game.Screens.Backgrounds;
using osu.Game.Screens.Play;
using osu.Game.Screens.Ranking.Statistics;
using osuTK;
namespace osu.Game.Screens.Ranking
{
- public abstract class ResultsScreen : OsuScreen, IKeyBindingHandler
+ public abstract class ResultsScreen : ScreenWithBeatmapBackground, IKeyBindingHandler
{
protected const float BACKGROUND_BLUR = 20;
private static readonly float screen_height = 768 - TwoLayerButton.SIZE_EXTENDED.Y;
@@ -35,8 +34,6 @@ namespace osu.Game.Screens.Ranking
// Temporary for now to stop dual transitions. Should respect the current toolbar mode, but there's no way to do so currently.
public override bool HideOverlaysOnEnter => true;
- protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(Beatmap.Value);
-
public readonly Bindable SelectedScore = new Bindable();
public readonly ScoreInfo Score;
@@ -237,15 +234,18 @@ namespace osu.Game.Screens.Ranking
{
base.OnEntering(last);
- ((BackgroundScreenBeatmap)Background).BlurAmount.Value = BACKGROUND_BLUR;
+ ApplyToBackground(b =>
+ {
+ b.BlurAmount.Value = BACKGROUND_BLUR;
+ b.FadeTo(0.5f, 250);
+ });
- Background.FadeTo(0.5f, 250);
bottomPanel.FadeTo(1, 250);
}
public override bool OnExiting(IScreen next)
{
- Background.FadeTo(1, 250);
+ ApplyToBackground(b => b.FadeTo(1, 250));
return base.OnExiting(next);
}
@@ -295,7 +295,7 @@ namespace osu.Game.Screens.Ranking
ScorePanelList.HandleInput = false;
// Dim background.
- Background.FadeTo(0.1f, 150);
+ ApplyToBackground(b => b.FadeTo(0.1f, 150));
detachedPanel = expandedPanel;
}
@@ -319,7 +319,7 @@ namespace osu.Game.Screens.Ranking
ScorePanelList.HandleInput = true;
// Un-dim background.
- Background.FadeTo(0.5f, 150);
+ ApplyToBackground(b => b.FadeTo(0.5f, 150));
detachedPanel = null;
}
diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs
index d76f0abb9e..36f8fbedb3 100644
--- a/osu.Game/Screens/Select/BeatmapCarousel.cs
+++ b/osu.Game/Screens/Select/BeatmapCarousel.cs
@@ -13,6 +13,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Pooling;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
+using osu.Framework.Layout;
using osu.Framework.Threading;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
@@ -124,6 +125,8 @@ namespace osu.Game.Screens.Select
{
BeatmapSetsChanged?.Invoke();
BeatmapSetsLoaded = true;
+
+ itemsCache.Invalidate();
});
}
@@ -567,6 +570,15 @@ namespace osu.Game.Screens.Select
#endregion
+ protected override bool OnInvalidate(Invalidation invalidation, InvalidationSource source)
+ {
+ // handles the vertical size of the carousel changing (ie. on window resize when aspect ratio has changed).
+ if ((invalidation & Invalidation.Layout) > 0)
+ itemsCache.Invalidate();
+
+ return base.OnInvalidate(invalidation, source);
+ }
+
protected override void Update()
{
base.Update();
@@ -777,13 +789,19 @@ namespace osu.Game.Screens.Select
Scroll.ScrollContent.Height = currentY;
- if (BeatmapSetsLoaded && (selectedBeatmapSet == null || selectedBeatmap == null || selectedBeatmapSet.State.Value != CarouselItemState.Selected))
- {
- selectedBeatmapSet = null;
- SelectionChanged?.Invoke(null);
- }
-
itemsCache.Validate();
+
+ // update and let external consumers know about selection loss.
+ if (BeatmapSetsLoaded)
+ {
+ bool selectionLost = selectedBeatmapSet != null && selectedBeatmapSet.State.Value != CarouselItemState.Selected;
+
+ if (selectionLost)
+ {
+ selectedBeatmapSet = null;
+ SelectionChanged?.Invoke(null);
+ }
+ }
}
private bool firstScroll = true;
@@ -806,14 +824,13 @@ namespace osu.Game.Screens.Select
break;
case PendingScrollOperation.Immediate:
+
// in order to simplify animation logic, rather than using the animated version of ScrollTo,
// we take the difference in scroll height and apply to all visible panels.
// this avoids edge cases like when the visible panels is reduced suddenly, causing ScrollContainer
// to enter clamp-special-case mode where it animates completely differently to normal.
float scrollChange = scrollTarget.Value - Scroll.Current;
-
Scroll.ScrollTo(scrollTarget.Value, false);
-
foreach (var i in Scroll.Children)
i.Y += scrollChange;
break;
diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs
index 71f78c5c95..8a1c291fca 100644
--- a/osu.Game/Screens/Select/BeatmapDetails.cs
+++ b/osu.Game/Screens/Select/BeatmapDetails.cs
@@ -63,8 +63,6 @@ namespace osu.Game.Screens.Select
public BeatmapDetails()
{
- Container content;
-
Children = new Drawable[]
{
new Box
@@ -72,7 +70,7 @@ namespace osu.Game.Screens.Select
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black.Opacity(0.5f),
},
- content = new Container
+ new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Horizontal = spacing },
@@ -159,7 +157,7 @@ namespace osu.Game.Screens.Select
},
},
},
- loading = new LoadingLayer(content),
+ loading = new LoadingLayer(true),
};
}
diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs
index b3c5d458d6..d7e901b71e 100644
--- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs
+++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs
@@ -5,6 +5,8 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
+using System.Threading.Tasks;
+using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -36,10 +38,14 @@ namespace osu.Game.Screens.Select.Carousel
public IEnumerable DrawableBeatmaps => beatmapContainer?.Children ?? Enumerable.Empty();
+ [CanBeNull]
private Container beatmapContainer;
private BeatmapSetInfo beatmapSet;
+ [CanBeNull]
+ private Task beatmapsLoadTask;
+
[Resolved]
private BeatmapManager manager { get; set; }
@@ -85,7 +91,9 @@ namespace osu.Game.Screens.Select.Carousel
base.UpdateItem();
Content.Clear();
+
beatmapContainer = null;
+ beatmapsLoadTask = null;
if (Item == null)
return;
@@ -122,11 +130,7 @@ namespace osu.Game.Screens.Select.Carousel
MovementContainer.MoveToX(0, 500, Easing.OutExpo);
- if (beatmapContainer != null)
- {
- foreach (var beatmap in beatmapContainer)
- beatmap.MoveToY(0, 800, Easing.OutQuint);
- }
+ updateBeatmapYPositions();
}
protected override void Selected()
@@ -163,7 +167,7 @@ namespace osu.Game.Screens.Select.Carousel
ChildrenEnumerable = visibleBeatmaps.Select(c => c.CreateDrawableRepresentation())
};
- LoadComponentAsync(beatmapContainer, loaded =>
+ beatmapsLoadTask = LoadComponentAsync(beatmapContainer, loaded =>
{
// make sure the pooled target hasn't changed.
if (beatmapContainer != loaded)
@@ -173,16 +177,29 @@ namespace osu.Game.Screens.Select.Carousel
updateBeatmapYPositions();
});
}
+ }
- void updateBeatmapYPositions()
+ private void updateBeatmapYPositions()
+ {
+ if (beatmapContainer == null)
+ return;
+
+ if (beatmapsLoadTask == null || !beatmapsLoadTask.IsCompleted)
+ return;
+
+ float yPos = DrawableCarouselBeatmap.CAROUSEL_BEATMAP_SPACING;
+
+ bool isSelected = Item.State.Value == CarouselItemState.Selected;
+
+ foreach (var panel in beatmapContainer.Children)
{
- float yPos = DrawableCarouselBeatmap.CAROUSEL_BEATMAP_SPACING;
-
- foreach (var panel in beatmapContainer.Children)
+ if (isSelected)
{
panel.MoveToY(yPos, 800, Easing.OutQuint);
yPos += panel.Item.TotalHeight;
}
+ else
+ panel.MoveToY(0, 800, Easing.OutQuint);
}
}
diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs
index a5252fdc96..40db04ae71 100644
--- a/osu.Game/Screens/Select/SongSelect.cs
+++ b/osu.Game/Screens/Select/SongSelect.cs
@@ -19,7 +19,6 @@ using osu.Game.Overlays;
using osu.Game.Overlays.Mods;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
-using osu.Game.Screens.Backgrounds;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Menu;
using osu.Game.Screens.Select.Options;
@@ -38,10 +37,11 @@ using osu.Game.Collections;
using osu.Game.Graphics.UserInterface;
using osu.Game.Scoring;
using System.Diagnostics;
+using osu.Game.Screens.Play;
namespace osu.Game.Screens.Select
{
- public abstract class SongSelect : OsuScreen, IKeyBindingHandler
+ public abstract class SongSelect : ScreenWithBeatmapBackground, IKeyBindingHandler
{
public static readonly float WEDGE_HEIGHT = 245;
@@ -76,8 +76,6 @@ namespace osu.Game.Screens.Select
[Resolved]
private Bindable> selectedMods { get; set; }
- protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(Beatmap.Value);
-
protected BeatmapCarousel Carousel { get; private set; }
private BeatmapInfoWedge beatmapInfoWedge;
@@ -428,16 +426,21 @@ namespace osu.Game.Screens.Select
private void updateSelectedBeatmap(BeatmapInfo beatmap)
{
+ if (beatmap == null && beatmapNoDebounce == null)
+ return;
+
if (beatmap?.Equals(beatmapNoDebounce) == true)
return;
beatmapNoDebounce = beatmap;
-
performUpdateSelected();
}
private void updateSelectedRuleset(RulesetInfo ruleset)
{
+ if (ruleset == null && rulesetNoDebounce == null)
+ return;
+
if (ruleset?.Equals(rulesetNoDebounce) == true)
return;
@@ -684,12 +687,12 @@ namespace osu.Game.Screens.Select
/// The working beatmap.
private void updateComponentFromBeatmap(WorkingBeatmap beatmap)
{
- if (Background is BackgroundScreenBeatmap backgroundModeBeatmap)
+ ApplyToBackground(backgroundModeBeatmap =>
{
backgroundModeBeatmap.Beatmap = beatmap;
backgroundModeBeatmap.BlurAmount.Value = BACKGROUND_BLUR;
backgroundModeBeatmap.FadeColour(Color4.White, 250);
- }
+ });
beatmapInfoWedge.Beatmap = beatmap;
diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs
index e0d18eab00..1ba25cc11e 100644
--- a/osu.Game/Storyboards/Storyboard.cs
+++ b/osu.Game/Storyboards/Storyboard.cs
@@ -27,7 +27,14 @@ namespace osu.Game.Storyboards
public bool HasDrawable => Layers.Any(l => l.Elements.Any(e => e.IsDrawable));
- public double FirstEventTime => Layers.Min(l => l.Elements.FirstOrDefault()?.StartTime ?? 0);
+ ///
+ /// Across all layers, find the earliest point in time that a storyboard element exists at.
+ /// Will return null if there are no elements.
+ ///
+ ///
+ /// This iterates all elements and as such should be used sparingly or stored locally.
+ ///
+ public double? EarliestEventTime => Layers.SelectMany(l => l.Elements).OrderBy(e => e.StartTime).FirstOrDefault()?.StartTime;
///
/// Depth of the currently front-most storyboard layer, excluding the overlay layer.
diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs
index 9a839c8d22..2ce5211757 100644
--- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs
+++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs
@@ -28,6 +28,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
public void AddUser(User user) => ((IMultiplayerClient)this).UserJoined(new MultiplayerRoomUser(user.Id) { User = user });
+ public void AddNullUser(int userId) => ((IMultiplayerClient)this).UserJoined(new MultiplayerRoomUser(userId));
+
public void RemoveUser(User user)
{
Debug.Assert(Room != null);
diff --git a/osu.Game/Users/Drawables/ClickableAvatar.cs b/osu.Game/Users/Drawables/ClickableAvatar.cs
new file mode 100644
index 0000000000..0fca9c7c9b
--- /dev/null
+++ b/osu.Game/Users/Drawables/ClickableAvatar.cs
@@ -0,0 +1,73 @@
+// 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.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Textures;
+using osu.Framework.Input.Events;
+using osu.Game.Graphics.Containers;
+
+namespace osu.Game.Users.Drawables
+{
+ public class ClickableAvatar : Container
+ {
+ ///
+ /// Whether to open the user's profile when clicked.
+ ///
+ public readonly BindableBool OpenOnClick = new BindableBool(true);
+
+ private readonly User user;
+
+ [Resolved(CanBeNull = true)]
+ private OsuGame game { get; set; }
+
+ ///
+ /// A clickable avatar for the specified user, with UI sounds included.
+ /// If is true, clicking will open the user's profile.
+ ///
+ /// The user. A null value will get a placeholder avatar.
+ public ClickableAvatar(User user = null)
+ {
+ this.user = user;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(LargeTextureStore textures)
+ {
+ ClickableArea clickableArea;
+ Add(clickableArea = new ClickableArea
+ {
+ RelativeSizeAxes = Axes.Both,
+ Action = openProfile
+ });
+
+ LoadComponentAsync(new DrawableAvatar(user), clickableArea.Add);
+
+ clickableArea.Enabled.BindTo(OpenOnClick);
+ }
+
+ private void openProfile()
+ {
+ if (!OpenOnClick.Value)
+ return;
+
+ if (user?.Id > 1)
+ game?.ShowUser(user.Id);
+ }
+
+ private class ClickableArea : OsuClickableContainer
+ {
+ public override string TooltipText => Enabled.Value ? @"view profile" : null;
+
+ protected override bool OnClick(ClickEvent e)
+ {
+ if (!Enabled.Value)
+ return false;
+
+ return base.OnClick(e);
+ }
+ }
+ }
+}
diff --git a/osu.Game/Users/Drawables/DrawableAvatar.cs b/osu.Game/Users/Drawables/DrawableAvatar.cs
index 42d2dbb1c6..3dae3afe3f 100644
--- a/osu.Game/Users/Drawables/DrawableAvatar.cs
+++ b/osu.Game/Users/Drawables/DrawableAvatar.cs
@@ -1,88 +1,45 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// 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 osu.Framework.Allocation;
-using osu.Framework.Bindables;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
-using osu.Framework.Input.Events;
-using osu.Game.Graphics.Containers;
namespace osu.Game.Users.Drawables
{
[LongRunningLoad]
- public class DrawableAvatar : Container
+ public class DrawableAvatar : Sprite
{
- ///
- /// Whether to open the user's profile when clicked.
- ///
- public readonly BindableBool OpenOnClick = new BindableBool(true);
-
private readonly User user;
- [Resolved(CanBeNull = true)]
- private OsuGame game { get; set; }
-
///
- /// An avatar for specified user.
+ /// A simple, non-interactable avatar sprite for the specified user.
///
/// The user. A null value will get a placeholder avatar.
public DrawableAvatar(User user = null)
{
this.user = user;
+
+ RelativeSizeAxes = Axes.Both;
+ FillMode = FillMode.Fit;
+ Anchor = Anchor.Centre;
+ Origin = Anchor.Centre;
}
[BackgroundDependencyLoader]
private void load(LargeTextureStore textures)
{
- if (textures == null)
- throw new ArgumentNullException(nameof(textures));
+ if (user != null && user.Id > 1)
+ Texture = textures.Get($@"https://a.ppy.sh/{user.Id}");
- Texture texture = null;
- if (user != null && user.Id > 1) texture = textures.Get($@"https://a.ppy.sh/{user.Id}");
- texture ??= textures.Get(@"Online/avatar-guest");
-
- ClickableArea clickableArea;
- Add(clickableArea = new ClickableArea
- {
- RelativeSizeAxes = Axes.Both,
- Child = new Sprite
- {
- RelativeSizeAxes = Axes.Both,
- Texture = texture,
- FillMode = FillMode.Fit,
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre
- },
- Action = openProfile
- });
-
- clickableArea.Enabled.BindTo(OpenOnClick);
+ Texture ??= textures.Get(@"Online/avatar-guest");
}
- private void openProfile()
+ protected override void LoadComplete()
{
- if (!OpenOnClick.Value)
- return;
-
- if (user?.Id > 1)
- game?.ShowUser(user.Id);
- }
-
- private class ClickableArea : OsuClickableContainer
- {
- public override string TooltipText => Enabled.Value ? @"view profile" : null;
-
- protected override bool OnClick(ClickEvent e)
- {
- if (!Enabled.Value)
- return false;
-
- return base.OnClick(e);
- }
+ base.LoadComplete();
+ this.FadeInFromZero(300, Easing.OutQuint);
}
}
}
diff --git a/osu.Game/Users/Drawables/UpdateableAvatar.cs b/osu.Game/Users/Drawables/UpdateableAvatar.cs
index 171462f3fc..927e48cb56 100644
--- a/osu.Game/Users/Drawables/UpdateableAvatar.cs
+++ b/osu.Game/Users/Drawables/UpdateableAvatar.cs
@@ -65,12 +65,11 @@ namespace osu.Game.Users.Drawables
if (user == null && !ShowGuestOnNull)
return null;
- var avatar = new DrawableAvatar(user)
+ var avatar = new ClickableAvatar(user)
{
RelativeSizeAxes = Axes.Both,
};
- avatar.OnLoadComplete += d => d.FadeInFromZero(300, Easing.OutQuint);
avatar.OpenOnClick.BindTo(OpenOnClick);
return avatar;
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index cbf9f6f1bd..6c220a5c21 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -21,12 +21,12 @@
-
-
+
+
-
+
diff --git a/osu.iOS.props b/osu.iOS.props
index adbcc0ef1c..5445adb3fb 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -70,7 +70,7 @@
-
+
@@ -88,7 +88,7 @@
-
+
diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings
index 22ea73858e..aa8f8739c1 100644
--- a/osu.sln.DotSettings
+++ b/osu.sln.DotSettings
@@ -106,6 +106,7 @@
HINT
WARNING
WARNING
+ DO_NOT_SHOW
WARNING
WARNING
WARNING