1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-26 16:12:54 +08:00

Merge branch 'master' into tourney-switching-ui

This commit is contained in:
Shivam 2021-01-10 17:33:52 +01:00
commit e5c670f843
112 changed files with 1836 additions and 1059 deletions

View File

@ -15,7 +15,7 @@
]
},
"jetbrains.resharper.globaltools": {
"version": "2020.2.4",
"version": "2020.3.2",
"commands": [
"jb"
]

View File

@ -16,7 +16,7 @@
<EmbeddedResource Include="Resources\**\*.*" />
</ItemGroup>
<ItemGroup Label="Code Analysis">
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.1" PrivateAssets="All" />
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.2" PrivateAssets="All" />
<AdditionalFiles Include="$(MSBuildThisFileDirectory)CodeAnalysis\BannedSymbols.txt" />
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="3.3.1" PrivateAssets="All" />
</ItemGroup>

View File

@ -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)

View File

@ -52,6 +52,6 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.1202.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.1222.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.1229.0" />
</ItemGroup>
</Project>

View File

@ -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
{

View File

@ -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<UserStatus> status = new Bindable<UserStatus>();
private readonly IBindable<UserActivity> activity = new Bindable<UserActivity>();
private readonly Bindable<DiscordRichPresenceMode> privacyMode = new Bindable<DiscordRichPresenceMode>();
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;

View File

@ -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()

View File

@ -2,7 +2,7 @@
<Import Project="..\osu.TestProject.props" />
<ItemGroup Label="Package References">
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />

View File

@ -2,7 +2,7 @@
<Import Project="..\osu.TestProject.props" />
<ItemGroup Label="Package References">
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />

View File

@ -2,7 +2,7 @@
<Import Project="..\osu.TestProject.props" />
<ItemGroup Label="Package References">
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />

View File

@ -20,17 +20,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
private double lastTrailTime;
private IBindable<float> 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.

View File

@ -2,7 +2,7 @@
<Import Project="..\osu.TestProject.props" />
<ItemGroup Label="Package References">
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />

View File

@ -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()
{

View File

@ -0,0 +1,57 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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);
}
});
}
}

View File

@ -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

View File

@ -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));
}
/// <summary>
@ -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));
}
/// <summary>
@ -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;
/// <summary>
/// Make sure every time a screen gets pushed, the background doesn't get replaced
/// </summary>
/// <returns>Whether or not the original background (The one created in DummySongSelect) is still the current background</returns>
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<bool>(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

View File

@ -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();

View File

@ -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;
}

View File

@ -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
{

View File

@ -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<int> PlayingUsers => (BindableList<int>)base.PlayingUsers;

View File

@ -0,0 +1,145 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<BeatmapInfo> 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<BeatmapInfo>();
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;
}
}
}

View File

@ -43,6 +43,16 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddAssert("two unique panels", () => this.ChildrenOfType<ParticipantPanel>().Select(p => p.User).Distinct().Count() == 2);
}
[Test]
public void TestAddNullUser()
{
AddAssert("one unique panel", () => this.ChildrenOfType<ParticipantPanel>().Select(p => p.User).Distinct().Count() == 1);
AddStep("add non-resolvable user", () => Client.AddNullUser(-3));
AddUntilStep("two unique panels", () => this.ChildrenOfType<ParticipantPanel>().Select(p => p.User).Distinct().Count() == 2);
}
[Test]
public void TestRemoveUser()
{

View File

@ -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<OsuButton>().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()
{

View File

@ -143,7 +143,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
RoomManager =
{
TimeBetweenListingPolls = { Value = 1 },
TimeBetweenSelectionPolls = { Value = 1 }
}
};

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<User> 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);
}
}
}

View File

@ -1,8 +1,13 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<string, APIUpdateStream> streams;
private readonly Dictionary<string, APIChangelogBuild> 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<APIChangelogEntry>()
}).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<APIChangelogEntry>
{
Version = "2018.712.0",
DisplayVersion = "2018.712.0",
UpdateStream = new APIUpdateStream { Id = 5, Name = OsuGameBase.CLIENT_STREAM_NAME },
ChangelogEntries = new List<APIChangelogEntry>
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<APIChangelogEntry>
{
new APIChangelogEntry
{
Name = "Test",
DisplayName = "Test"
},
ChangelogEntries = new List<APIChangelogEntry>
{
new APIChangelogEntry
Category = "Testing HTML strings unescaping",
Title = "Ensuring HTML strings are being unescaped",
MessageHtml = "&quot;&quot;&quot;This text should appear triple-quoted&quot;&quot;&quot; &gt;_&lt;",
GithubUser = new APIChangelogUser
{
Category = "Testing HTML strings unescaping",
Title = "Ensuring HTML strings are being unescaped",
MessageHtml = "&quot;&quot;&quot;This text should appear triple-quoted&quot;&quot;&quot; &gt;_&lt;",
GithubUser = new APIChangelogUser
{
DisplayName = "Dummy",
OsuUsername = "Dummy",
}
},
}
});
DisplayName = "Dummy",
OsuUsername = "Dummy",
}
},
}
});
}
private void showBuild(Func<APIChangelogBuild> build)
{
AddStep("set up build", () => requestedBuild = build.Invoke());
AddStep("show build", () => changelog.ShowBuild(requestedBuild));
}
private class TestChangelogOverlay : ChangelogOverlay
{
public new List<APIUpdateStream> Streams => base.Streams;

View File

@ -90,11 +90,17 @@ namespace osu.Game.Tests.Visual.Online
};
protected override Task<User> ComputeValueAsync(int lookup, CancellationToken token = default)
=> Task.FromResult(new User
{
// tests against failed lookups
if (lookup == 13)
return Task.FromResult<User>(null);
return Task.FromResult(new User
{
Id = lookup,
Username = usernames[lookup % usernames.Length],
});
}
}
}
}

View File

@ -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)
{
}
}
}
}

View File

@ -3,7 +3,7 @@
<ItemGroup Label="Package References">
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="DeepEqual" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />

View File

@ -5,7 +5,7 @@
</PropertyGroup>
<ItemGroup Label="Package References">
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
</ItemGroup>

View File

@ -0,0 +1,17 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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
}
}

View File

@ -1,4 +1,4 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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,
}
}

View File

@ -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; }
/// <summary>
/// Perform an API lookup on the specified user, populating a <see cref="User"/> model.
/// </summary>
/// <param name="userId">The user to lookup.</param>
/// <param name="token">An optional cancellation token.</param>
/// <returns>The populated user, or null if the user does not exist or the request could not be satisfied.</returns>
[ItemCanBeNull]
public Task<User> GetUserAsync(int userId, CancellationToken token = default) => GetAsync(userId, token);
protected override async Task<User> 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<User> 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);
}
}
}

View File

@ -1,7 +1,11 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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.
/// </summary>
/// <param name="task">The task.</param>
/// <param name="logOnError">Whether errors should be logged as important, or silently ignored.</param>
public static void CatchUnobservedExceptions(this Task task, bool logOnError = false)
/// <param name="logAsError">
/// 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.
/// </param>
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);
}
}

View File

@ -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
/// </summary>
public class LoadingLayer : LoadingSpinner
{
private readonly Drawable dimTarget;
[CanBeNull]
protected Box BackgroundDimLayer { get; }
/// <summary>
/// Constuct a new loading spinner.
/// Construct a new loading spinner.
/// </summary>
/// <param name="dimTarget">An optional target to dim when displayed.</param>
/// <param name="dimBackground">Whether the full background area should be dimmed while loading.</param>
/// <param name="withBox">Whether the spinner should have a surrounding black box for visibility.</param>
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);
}
}
}
}

View File

@ -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,

View File

@ -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
/// </summary>
public IBindable<APIState> 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;

View File

@ -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;

View File

@ -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<string, Color4> KNOWN_STREAMS = new Dictionary<string, Color4>
{
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);
}
}

View File

@ -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,

View File

@ -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();

View File

@ -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
/// </summary>
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);
/// <summary>
/// Request a lock on this room to perform a thread-safe update.
/// </summary>
public IDisposable LockForUpdate()
{
// ReSharper disable once InconsistentlySynchronizedField
freeForWrite.Wait();
lock (updateLock)
{
freeForWrite.Wait();
freeForWrite.Reset();
return new ValueInvokeOnDisposal<MultiplayerRoom>(this, r => freeForWrite.Set());
}
}
public override string ToString() => $"RoomID:{RoomID} Host:{Host?.UserID} Users:{Users.Count} State:{State} Settings: [{Settings}]";
}
}

View File

@ -61,9 +61,9 @@ namespace osu.Game.Online.Multiplayer
public MultiplayerRoom? Room { get; private set; }
/// <summary>
/// The users currently in gameplay.
/// The users in the joined <see cref="Room"/> which are participating in the current gameplay loop.
/// </summary>
public readonly BindableList<int> PlayingUsers = new BindableList<int>();
public readonly BindableList<int> CurrentMatchPlayingUserIds = new BindableList<int>();
[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);
}
/// <summary>
/// For the provided user ID, update whether the user is included in <see cref="CurrentMatchPlayingUserIds"/>.
/// </summary>
/// <param name="userId">The user's ID.</param>
/// <param name="state">The new state of the user.</param>
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);
}
}
}

View File

@ -151,11 +151,11 @@ namespace osu.Game
updateBlockingOverlayFade();
}
public void RemoveBlockingOverlay(OverlayContainer overlay)
public void RemoveBlockingOverlay(OverlayContainer overlay) => Schedule(() =>
{
visibleBlockingOverlays.Remove(overlay);
updateBlockingOverlayFade();
}
});
/// <summary>
/// Close all game-wide overlays.

View File

@ -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 };

View File

@ -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<APIState> state) => Schedule(() =>
private ScheduledDelegate scheduledHide;
private void apiStateChanged(ValueChangedEvent<APIState> state)
{
switch (state.NewValue)
{
@ -113,9 +121,10 @@ namespace osu.Game.Overlays
break;
case APIState.Online:
Hide();
scheduledHide?.Cancel();
scheduledHide = Schedule(Hide);
break;
}
});
}
}
}

View File

@ -92,14 +92,14 @@ namespace osu.Game.Overlays
{
foundContent = new FillFlowContainer<BeatmapPanel>(),
notFoundContent = new NotFoundDrawable(),
loadingLayer = new LoadingLayer(panelTarget)
}
}
}
},
},
}
}
}
},
},
loadingLayer = new LoadingLayer(true)
};
}

View File

@ -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 = () =>

View File

@ -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())

View File

@ -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)));
}
}

View File

@ -0,0 +1,202 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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}");
}
}
}
}

View File

@ -7,6 +7,11 @@ namespace osu.Game.Overlays.Changelog
{
public class ChangelogUpdateStreamControl : OverlayStreamControl<APIUpdateStream>
{
public ChangelogUpdateStreamControl()
{
SelectFirstTabByDefault = false;
}
protected override OverlayStreamItem<APIUpdateStream> CreateStreamItem(APIUpdateStream value) => new ChangelogUpdateStreamItem(value);
}
}

View File

@ -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 },

View File

@ -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)
}
}
}

View File

@ -68,7 +68,7 @@ namespace osu.Game.Overlays
}
}
},
loading = new LoadingLayer(content),
loading = new LoadingLayer(true),
};
}

View File

@ -59,7 +59,7 @@ namespace osu.Game.Overlays
},
},
},
loading = new LoadingLayer(content),
loading = new LoadingLayer(true),
};
}

View File

@ -45,6 +45,7 @@ namespace osu.Game.Overlays.Rankings
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
InternalChild = new ReverseChildIDFillFlowContainer<Drawable>
{
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)
}
}
}

View File

@ -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
};
}

View File

@ -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
}
}
/// <summary>
/// Create a delayed bindable which only updates when a condition is met.
/// </summary>
/// <param name="bindable">The config bindable.</param>
/// <returns>A bindable which will propagate updates with a delay.</returns>
private void bindPreviewEvent(Bindable<float> bindable)
{
bindable.ValueChanged += _ =>

View File

@ -0,0 +1,27 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<DiscordRichPresenceMode>
{
LabelText = "Discord Rich Presence",
Current = config.GetBindable<DiscordRichPresenceMode>(OsuSetting.DiscordRichPresence)
}
};
}
}
}

View File

@ -20,7 +20,8 @@ namespace osu.Game.Overlays.Settings.Sections
{
Children = new Drawable[]
{
new WebSettings()
new WebSettings(),
new IntegrationSettings()
};
}
}

View File

@ -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)]

View File

@ -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)
{

View File

@ -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;

View File

@ -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)
{

View File

@ -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)
{

View File

@ -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;

View File

@ -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();
}
}
}

View File

@ -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<T>.Value property.
var valueProperty = origBindable.GetType().GetProperty(nameof(Bindable<object>.Value), BindingFlags.Public | BindingFlags.Instance);
Debug.Assert(valueProperty != null);
valueProperty.SetValue(copyBindable, valueProperty.GetValue(origBindable));
// we only care about changes that have been made away from defaults.
if (!origBindable.IsDefault)
copy.CopyAdjustedSetting(copyBindable, origBindable);
}
return copy;
}
/// <summary>
/// When creating copies or clones of a Mod, this method will be called
/// to copy explicitly adjusted user settings from <paramref name="target"/>.
/// The base implementation will transfer the value via <see cref="Bindable{T}.Parse"/>
/// or by binding and unbinding (if <paramref name="source"/> is an <see cref="IBindable"/>)
/// and should be called unless replaced with custom logic.
/// </summary>
/// <param name="target">The target bindable to apply the adjustment to.</param>
/// <param name="source">The adjustment to apply.</param>
internal virtual void CopyAdjustedSetting(IBindable target, object source)
{
if (source is IBindable sourceBindable)
{
// copy including transfer of default values.
target.BindTo(sourceBindable);
target.UnbindFrom(sourceBindable);
}
else
target.Parse(source);
}
public bool Equals(IMod other) => GetType() == other?.GetType();
}
}

View File

@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Mods
public void ApplyToPlayer(Player player)
{
player.Background.EnableUserDim.Value = false;
player.ApplyToBackground(b => b.EnableUserDim.Value = false);
player.DimmableStoryboard.IgnoreUserSettings.Value = true;

View File

@ -114,6 +114,12 @@ namespace osu.Game.Rulesets.Mods
bindable.ValueChanged += _ => userChangedSettings[bindable] = !bindable.IsDefault;
}
internal override void CopyAdjustedSetting(IBindable target, object source)
{
userChangedSettings[target] = true;
base.CopyAdjustedSetting(target, source);
}
/// <summary>
/// Apply all custom settings to the provided beatmap.
/// </summary>

View File

@ -34,6 +34,12 @@ namespace osu.Game.Screens
return false;
}
/// <summary>
/// Apply arbitrary changes to this background in a thread safe manner.
/// </summary>
/// <param name="action">The operation to perform.</param>
public void ApplyToBackground(Action<BackgroundScreen> action) => Schedule(() => action.Invoke(this));
protected override void Update()
{
base.Update();

View File

@ -444,11 +444,14 @@ namespace osu.Game.Screens.Edit
{
base.OnEntering(last);
// todo: temporary. we want to be applying dim using the UserDimContainer eventually.
Background.FadeColour(Color4.DarkGray, 500);
ApplyToBackground(b =>
{
// todo: temporary. we want to be applying dim using the UserDimContainer eventually.
b.FadeColour(Color4.DarkGray, 500);
Background.EnableUserDim.Value = false;
Background.BlurAmount.Value = 0;
b.EnableUserDim.Value = false;
b.BlurAmount.Value = 0;
});
resetTrack(true);
}
@ -480,7 +483,7 @@ namespace osu.Game.Screens.Edit
}
}
Background.FadeColour(Color4.White, 500);
ApplyToBackground(b => b.FadeColour(Color4.White, 500));
resetTrack();
return base.OnExiting(next);

View File

@ -129,8 +129,8 @@ namespace osu.Game.Screens.Menu
buttonsPlay.ForEach(b => b.VisibleState = ButtonSystemState.Play);
buttonsTopLevel.Add(new Button(@"play", @"button-play-select", OsuIcon.Logo, new Color4(102, 68, 204, 255), () => State = ButtonSystemState.Play, WEDGE_WIDTH, Key.P));
buttonsTopLevel.Add(new Button(@"osu!editor", @"button-generic-select", OsuIcon.EditCircle, new Color4(238, 170, 0, 255), () => OnEdit?.Invoke(), 0, Key.E));
buttonsTopLevel.Add(new Button(@"osu!direct", @"button-direct-select", OsuIcon.ChevronDownCircle, new Color4(165, 204, 0, 255), () => OnBeatmapListing?.Invoke(), 0, Key.D));
buttonsTopLevel.Add(new Button(@"edit", @"button-generic-select", OsuIcon.EditCircle, new Color4(238, 170, 0, 255), () => OnEdit?.Invoke(), 0, Key.E));
buttonsTopLevel.Add(new Button(@"browse", @"button-direct-select", OsuIcon.ChevronDownCircle, new Color4(165, 204, 0, 255), () => OnBeatmapListing?.Invoke(), 0, Key.D));
if (host.CanExit)
buttonsTopLevel.Add(new Button(@"exit", string.Empty, OsuIcon.CrossCircle, new Color4(238, 51, 153, 255), () => OnExit?.Invoke(), 0, Key.Q));
@ -156,11 +156,11 @@ namespace osu.Game.Screens.Menu
private void onMultiplayer()
{
if (!api.IsLoggedIn)
if (api.State.Value != APIState.Online)
{
notifications?.Post(new SimpleNotification
{
Text = "You gotta be logged in to multi 'yo!",
Text = "You gotta be online to multi 'yo!",
Icon = FontAwesome.Solid.Globe,
Activated = () =>
{
@ -177,11 +177,11 @@ namespace osu.Game.Screens.Menu
private void onPlaylists()
{
if (!api.IsLoggedIn)
if (api.State.Value != APIState.Online)
{
notifications?.Post(new SimpleNotification
{
Text = "You gotta be logged in to multi 'yo!",
Text = "You gotta be online to view playlists 'yo!",
Icon = FontAwesome.Solid.Globe,
Activated = () =>
{

View File

@ -201,7 +201,7 @@ namespace osu.Game.Screens.Menu
"New features are coming online every update. Make sure to stay up-to-date!",
"If you find the UI too large or small, try adjusting UI scale in settings!",
"Try adjusting the \"Screen Scaling\" mode to change your gameplay or UI area, even in fullscreen!",
"For now, osu!direct is available to all users on lazer. You can access it anywhere using Ctrl-D!",
"For now, what used to be \"osu!direct\" is available to all users on lazer. You can access it anywhere using Ctrl-D!",
"Seeking in replays is available by dragging on the difficulty bar at the bottom of the screen!",
"Multithreading support means that even with low \"FPS\" your input and judgements will be accurate!",
"Try scrolling down in the mod select panel to find a bunch of new fun mods!",

View File

@ -127,11 +127,11 @@ namespace osu.Game.Screens.Menu
{
case ButtonSystemState.Initial:
case ButtonSystemState.Exit:
Background.FadeColour(Color4.White, 500, Easing.OutSine);
ApplyToBackground(b => b.FadeColour(Color4.White, 500, Easing.OutSine));
break;
default:
Background.FadeColour(OsuColour.Gray(0.8f), 500, Easing.OutSine);
ApplyToBackground(b => b.FadeColour(OsuColour.Gray(0.8f), 500, Easing.OutSine));
break;
}
};
@ -256,7 +256,7 @@ namespace osu.Game.Screens.Menu
{
base.OnResuming(last);
(Background as BackgroundScreenDefault)?.Next();
ApplyToBackground(b => (b as BackgroundScreenDefault)?.Next());
// we may have consumed our preloaded instance, so let's make another.
preloadSongSelect();

View File

@ -2,8 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using System.Linq.Expressions;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Game.Beatmaps;
@ -41,38 +39,21 @@ namespace osu.Game.Screens.OnlinePlay.Components
SelectedItem.BindValueChanged(item => updateSelectedItem(item.NewValue), true);
}
private void updateSelectedItem(PlaylistItem item)
{
hasBeatmap = findBeatmap(expr => beatmaps.QueryBeatmap(expr));
}
private void updateSelectedItem(PlaylistItem _) => Scheduler.AddOnce(updateBeatmapState);
private void beatmapUpdated(ValueChangedEvent<WeakReference<BeatmapSetInfo>> _) => Scheduler.AddOnce(updateBeatmapState);
private void beatmapRemoved(ValueChangedEvent<WeakReference<BeatmapSetInfo>> _) => Scheduler.AddOnce(updateBeatmapState);
private void beatmapUpdated(ValueChangedEvent<WeakReference<BeatmapSetInfo>> weakSet)
{
if (weakSet.NewValue.TryGetTarget(out var set))
{
if (findBeatmap(expr => set.Beatmaps.AsQueryable().FirstOrDefault(expr)))
Schedule(() => hasBeatmap = true);
}
}
private void beatmapRemoved(ValueChangedEvent<WeakReference<BeatmapSetInfo>> weakSet)
{
if (weakSet.NewValue.TryGetTarget(out var set))
{
if (findBeatmap(expr => set.Beatmaps.AsQueryable().FirstOrDefault(expr)))
Schedule(() => hasBeatmap = false);
}
}
private bool findBeatmap(Func<Expression<Func<BeatmapInfo, bool>>, BeatmapInfo> expression)
private void updateBeatmapState()
{
int? beatmapId = SelectedItem.Value?.Beatmap.Value?.OnlineBeatmapID;
string checksum = SelectedItem.Value?.Beatmap.Value?.MD5Hash;
if (beatmapId == null || checksum == null)
return false;
return;
return expression(b => b.OnlineBeatmapID == beatmapId && b.MD5Hash == checksum) != null;
var databasedBeatmap = beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == beatmapId && b.MD5Hash == checksum);
hasBeatmap = databasedBeatmap?.BeatmapSet?.DeletePending == false;
}
protected override void Update()

View File

@ -65,7 +65,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
Padding = new MarginPadding(10),
Child = roomsContainer = new RoomsContainer { JoinRequested = joinRequested }
},
loadingLayer = new LoadingLayer(roomsContainer),
loadingLayer = new LoadingLayer(true),
}
},
new RoomInspector

View File

@ -71,201 +71,192 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
Container dimContent;
InternalChildren = new Drawable[]
{
dimContent = new Container
new Box
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
Colour = Color4Extensions.FromHex(@"28242d"),
},
new GridContainer
{
RelativeSizeAxes = Axes.Both,
RowDimensions = new[]
{
new Box
new Dimension(GridSizeMode.Distributed),
new Dimension(GridSizeMode.AutoSize),
},
Content = new[]
{
new Drawable[]
{
RelativeSizeAxes = Axes.Both,
Colour = Color4Extensions.FromHex(@"28242d"),
},
new GridContainer
{
RelativeSizeAxes = Axes.Both,
RowDimensions = new[]
new OsuScrollContainer
{
new Dimension(GridSizeMode.Distributed),
new Dimension(GridSizeMode.AutoSize),
},
Content = new[]
{
new Drawable[]
Padding = new MarginPadding
{
new OsuScrollContainer
Horizontal = OsuScreen.HORIZONTAL_OVERFLOW_PADDING,
Vertical = 10
},
RelativeSizeAxes = Axes.Both,
Children = new[]
{
new FillFlowContainer
{
Padding = new MarginPadding
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 10),
Children = new Drawable[]
{
Horizontal = OsuScreen.HORIZONTAL_OVERFLOW_PADDING,
Vertical = 10
},
RelativeSizeAxes = Axes.Both,
Children = new[]
{
new FillFlowContainer
new Container
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Padding = new MarginPadding { Horizontal = WaveOverlayContainer.WIDTH_PADDING },
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 10),
Children = new Drawable[]
{
new Container
new SectionContainer
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Padding = new MarginPadding { Horizontal = WaveOverlayContainer.WIDTH_PADDING },
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
Padding = new MarginPadding { Right = FIELD_PADDING / 2 },
Children = new[]
{
new SectionContainer
new Section("Room name")
{
Padding = new MarginPadding { Right = FIELD_PADDING / 2 },
Children = new[]
Child = NameField = new SettingsTextBox
{
new Section("Room name")
RelativeSizeAxes = Axes.X,
TabbableContentContainer = this,
},
},
new Section("Room visibility")
{
Alpha = disabled_alpha,
Child = AvailabilityPicker = new RoomAvailabilityPicker
{
Enabled = { Value = false }
},
},
new Section("Game type")
{
Alpha = disabled_alpha,
Child = new FillFlowContainer
{
AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X,
Direction = FillDirection.Vertical,
Spacing = new Vector2(7),
Children = new Drawable[]
{
Child = NameField = new SettingsTextBox
TypePicker = new GameTypePicker
{
RelativeSizeAxes = Axes.X,
TabbableContentContainer = this,
},
},
new Section("Room visibility")
{
Alpha = disabled_alpha,
Child = AvailabilityPicker = new RoomAvailabilityPicker
{
Enabled = { Value = false }
},
},
new Section("Game type")
{
Alpha = disabled_alpha,
Child = new FillFlowContainer
typeLabel = new OsuSpriteText
{
AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X,
Direction = FillDirection.Vertical,
Spacing = new Vector2(7),
Children = new Drawable[]
{
TypePicker = new GameTypePicker
{
RelativeSizeAxes = Axes.X,
Enabled = { Value = false }
},
typeLabel = new OsuSpriteText
{
Font = OsuFont.GetFont(size: 14),
Colour = colours.Yellow
},
},
Font = OsuFont.GetFont(size: 14),
Colour = colours.Yellow
},
},
},
},
new SectionContainer
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
Padding = new MarginPadding { Left = FIELD_PADDING / 2 },
Children = new[]
{
new Section("Max participants")
{
Alpha = disabled_alpha,
Child = MaxParticipantsField = new SettingsNumberTextBox
{
RelativeSizeAxes = Axes.X,
TabbableContentContainer = this,
ReadOnly = true,
},
},
new Section("Password (optional)")
{
Alpha = disabled_alpha,
Child = new SettingsPasswordTextBox
{
RelativeSizeAxes = Axes.X,
TabbableContentContainer = this,
ReadOnly = true,
},
},
}
}
},
},
initialBeatmapControl = new BeatmapSelectionControl
new SectionContainer
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.X,
Width = 0.5f
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
Padding = new MarginPadding { Left = FIELD_PADDING / 2 },
Children = new[]
{
new Section("Max participants")
{
Alpha = disabled_alpha,
Child = MaxParticipantsField = new SettingsNumberTextBox
{
RelativeSizeAxes = Axes.X,
TabbableContentContainer = this,
ReadOnly = true,
},
},
new Section("Password (optional)")
{
Alpha = disabled_alpha,
Child = new SettingsPasswordTextBox
{
RelativeSizeAxes = Axes.X,
TabbableContentContainer = this,
ReadOnly = true,
},
},
}
}
}
},
},
initialBeatmapControl = new BeatmapSelectionControl
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.X,
Width = 0.5f
}
},
},
}
}
},
new Drawable[]
},
},
new Drawable[]
{
new Container
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Y = 2,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
{
new Container
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4Extensions.FromHex(@"28242d").Darken(0.5f).Opacity(1f),
},
new FillFlowContainer
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Y = 2,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 20),
Margin = new MarginPadding { Vertical = 20 },
Padding = new MarginPadding { Horizontal = OsuScreen.HORIZONTAL_OVERFLOW_PADDING },
Children = new Drawable[]
{
new Box
ApplyButton = new CreateOrUpdateButton
{
RelativeSizeAxes = Axes.Both,
Colour = Color4Extensions.FromHex(@"28242d").Darken(0.5f).Opacity(1f),
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
Size = new Vector2(230, 55),
Enabled = { Value = false },
Action = apply,
},
new FillFlowContainer
ErrorText = new OsuSpriteText
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 20),
Margin = new MarginPadding { Vertical = 20 },
Padding = new MarginPadding { Horizontal = OsuScreen.HORIZONTAL_OVERFLOW_PADDING },
Children = new Drawable[]
{
ApplyButton = new CreateOrUpdateButton
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
Size = new Vector2(230, 55),
Enabled = { Value = false },
Action = apply,
},
ErrorText = new OsuSpriteText
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
Alpha = 0,
Depth = 1,
Colour = colours.RedDark
}
}
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
Alpha = 0,
Depth = 1,
Colour = colours.RedDark
}
}
}
}
}
},
}
}
},
loadingLayer = new LoadingLayer(dimContent)
loadingLayer = new LoadingLayer(true)
};
TypePicker.Current.BindValueChanged(type => typeLabel.Text = type.NewValue?.Name ?? string.Empty, true);

View File

@ -33,7 +33,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
if (!this.IsCurrentScreen())
{
multiplayerRoomManager.TimeBetweenListingPolls.Value = 0;
multiplayerRoomManager.TimeBetweenSelectionPolls.Value = 0;
}
else
{
@ -41,18 +40,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
{
case LoungeSubScreen _:
multiplayerRoomManager.TimeBetweenListingPolls.Value = isIdle ? 120000 : 15000;
multiplayerRoomManager.TimeBetweenSelectionPolls.Value = isIdle ? 120000 : 15000;
break;
// Don't poll inside the match or anywhere else.
default:
multiplayerRoomManager.TimeBetweenListingPolls.Value = 0;
multiplayerRoomManager.TimeBetweenSelectionPolls.Value = 0;
break;
}
}
Logger.Log($"Polling adjusted (listing: {multiplayerRoomManager.TimeBetweenListingPolls.Value}, selection: {multiplayerRoomManager.TimeBetweenSelectionPolls.Value})");
Logger.Log($"Polling adjusted (listing: {multiplayerRoomManager.TimeBetweenListingPolls.Value})");
}
protected override Room CreateNewRoom()

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Linq;
using Humanizer;
using osu.Framework.Allocation;
@ -8,9 +9,12 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Logging;
using osu.Framework.Screens;
using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Select;
namespace osu.Game.Screens.OnlinePlay.Multiplayer
@ -29,6 +33,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
private LoadingLayer loadingLayer;
private WorkingBeatmap initialBeatmap;
private RulesetInfo initialRuleset;
private IReadOnlyList<Mod> initialMods;
private bool itemSelected;
public MultiplayerMatchSongSelect()
{
Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING };
@ -37,11 +47,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
[BackgroundDependencyLoader]
private void load()
{
AddInternal(loadingLayer = new LoadingLayer(Carousel));
AddInternal(loadingLayer = new LoadingLayer(true));
initialBeatmap = Beatmap.Value;
initialRuleset = Ruleset.Value;
initialMods = Mods.Value.ToList();
}
protected override bool OnStart()
{
itemSelected = true;
var item = new PlaylistItem();
item.Beatmap.Value = Beatmap.Value.BeatmapInfo;
@ -82,6 +96,18 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
return true;
}
public override bool OnExiting(IScreen next)
{
if (!itemSelected)
{
Beatmap.Value = initialBeatmap;
Ruleset.Value = initialRuleset;
Mods.Value = initialMods;
}
return base.OnExiting(next);
}
protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea();
}
}

View File

@ -200,7 +200,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
{
Debug.Assert(client.Room != null);
int[] userIds = client.Room.Users.Where(u => u.State >= MultiplayerUserState.WaitingForLoad).Select(u => u.UserID).ToArray();
int[] userIds = client.CurrentMatchPlayingUserIds.ToArray();
StartPlay(() => new MultiplayerPlayer(SelectedItem.Value, userIds));
}

View File

@ -62,7 +62,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
// todo: this should be implemented via a custom HUD implementation, and correctly masked to the main content area.
LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(ScoreProcessor, userIds), HUDOverlay.Add);
HUDOverlay.Add(loadingDisplay = new LoadingLayer(DrawableRuleset) { Depth = float.MaxValue });
HUDOverlay.Add(loadingDisplay = new LoadingLayer(true) { Depth = float.MaxValue });
if (Token == null)
return; // Todo: Somehow handle token retrieval failure.

View File

@ -23,7 +23,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
private StatefulMultiplayerClient multiplayerClient { get; set; }
public readonly Bindable<double> TimeBetweenListingPolls = new Bindable<double>();
public readonly Bindable<double> TimeBetweenSelectionPolls = new Bindable<double>();
private readonly IBindable<bool> isConnected = new Bindable<bool>();
private readonly Bindable<bool> allowPolling = new Bindable<bool>();
@ -119,11 +119,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
TimeBetweenPolls = { BindTarget = TimeBetweenListingPolls },
AllowPolling = { BindTarget = allowPolling }
},
new MultiplayerSelectionPollingComponent
{
TimeBetweenPolls = { BindTarget = TimeBetweenSelectionPolls },
AllowPolling = { BindTarget = allowPolling }
}
};
private class MultiplayerListingPollingComponent : ListingPollingComponent
@ -146,26 +141,5 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
protected override Task Poll() => !AllowPolling.Value ? Task.CompletedTask : base.Poll();
}
private class MultiplayerSelectionPollingComponent : SelectionPollingComponent
{
public readonly IBindable<bool> AllowPolling = new Bindable<bool>();
protected override void LoadComplete()
{
base.LoadComplete();
AllowPolling.BindValueChanged(allowPolling =>
{
if (!allowPolling.NewValue)
return;
if (IsLoaded)
PollImmediately();
});
}
protected override Task Poll() => !AllowPolling.Value ? Task.CompletedTask : base.Poll();
}
}
}

View File

@ -1,7 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Diagnostics;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
@ -45,7 +44,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
[BackgroundDependencyLoader]
private void load()
{
Debug.Assert(User.User != null);
var user = User.User;
var backgroundColour = Color4Extensions.FromHex("#33413C");
@ -82,7 +81,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
Origin = Anchor.CentreRight,
RelativeSizeAxes = Axes.Both,
Width = 0.75f,
User = User.User,
User = user,
Colour = ColourInfo.GradientHorizontal(Color4.White.Opacity(0), Color4.White.Opacity(0.25f))
},
new FillFlowContainer
@ -98,28 +97,28 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
Origin = Anchor.CentreLeft,
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fit,
User = User.User
User = user
},
new UpdateableFlag
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Size = new Vector2(30, 20),
Country = User.User.Country
Country = user?.Country
},
new OsuSpriteText
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 18),
Text = User.User.Username
Text = user?.Username
},
new OsuSpriteText
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Font = OsuFont.GetFont(size: 14),
Text = User.User.CurrentModeRank != null ? $"#{User.User.CurrentModeRank}" : string.Empty
Text = user?.CurrentModeRank != null ? $"#{user.CurrentModeRank}" : string.Empty
}
}
},

View File

@ -8,6 +8,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Logging;
using osu.Framework.Screens;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics.Containers;
@ -165,7 +166,10 @@ namespace osu.Game.Screens.OnlinePlay
private void onlineStateChanged(ValueChangedEvent<APIState> state) => Schedule(() =>
{
if (state.NewValue != APIState.Online)
{
Logger.Log("API connection was lost, can't continue with online play", LoggingTarget.Network, LogLevel.Important);
Schedule(forcefullyExit);
}
});
protected override void LoadComplete()

View File

@ -64,243 +64,234 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
Container dimContent;
InternalChildren = new Drawable[]
{
dimContent = new Container
new Box
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
Colour = Color4Extensions.FromHex(@"28242d"),
},
new GridContainer
{
RelativeSizeAxes = Axes.Both,
RowDimensions = new[]
{
new Box
new Dimension(GridSizeMode.Distributed),
new Dimension(GridSizeMode.AutoSize),
},
Content = new[]
{
new Drawable[]
{
RelativeSizeAxes = Axes.Both,
Colour = Color4Extensions.FromHex(@"28242d"),
},
new GridContainer
{
RelativeSizeAxes = Axes.Both,
RowDimensions = new[]
new OsuScrollContainer
{
new Dimension(GridSizeMode.Distributed),
new Dimension(GridSizeMode.AutoSize),
},
Content = new[]
{
new Drawable[]
Padding = new MarginPadding
{
new OsuScrollContainer
{
Padding = new MarginPadding
{
Horizontal = OsuScreen.HORIZONTAL_OVERFLOW_PADDING,
Vertical = 10
},
RelativeSizeAxes = Axes.Both,
Children = new[]
{
new Container
{
Padding = new MarginPadding { Horizontal = WaveOverlayContainer.WIDTH_PADDING },
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
{
new SectionContainer
{
Padding = new MarginPadding { Right = FIELD_PADDING / 2 },
Children = new[]
{
new Section("Room name")
{
Child = NameField = new SettingsTextBox
{
RelativeSizeAxes = Axes.X,
TabbableContentContainer = this,
LengthLimit = 100
},
},
new Section("Duration")
{
Child = DurationField = new DurationDropdown
{
RelativeSizeAxes = Axes.X,
Items = new[]
{
TimeSpan.FromMinutes(30),
TimeSpan.FromHours(1),
TimeSpan.FromHours(2),
TimeSpan.FromHours(4),
TimeSpan.FromHours(8),
TimeSpan.FromHours(12),
//TimeSpan.FromHours(16),
TimeSpan.FromHours(24),
TimeSpan.FromDays(3),
TimeSpan.FromDays(7)
}
}
},
new Section("Room visibility")
{
Alpha = disabled_alpha,
Child = AvailabilityPicker = new RoomAvailabilityPicker
{
Enabled = { Value = false }
},
},
new Section("Game type")
{
Alpha = disabled_alpha,
Child = new FillFlowContainer
{
AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X,
Direction = FillDirection.Vertical,
Spacing = new Vector2(7),
Children = new Drawable[]
{
TypePicker = new GameTypePicker
{
RelativeSizeAxes = Axes.X,
Enabled = { Value = false }
},
typeLabel = new OsuSpriteText
{
Font = OsuFont.GetFont(size: 14),
Colour = colours.Yellow
},
},
},
},
new Section("Max participants")
{
Alpha = disabled_alpha,
Child = MaxParticipantsField = new SettingsNumberTextBox
{
RelativeSizeAxes = Axes.X,
TabbableContentContainer = this,
ReadOnly = true,
},
},
new Section("Password (optional)")
{
Alpha = disabled_alpha,
Child = new SettingsPasswordTextBox
{
RelativeSizeAxes = Axes.X,
TabbableContentContainer = this,
ReadOnly = true,
},
},
},
},
new SectionContainer
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
Padding = new MarginPadding { Left = FIELD_PADDING / 2 },
Children = new[]
{
new Section("Playlist")
{
Child = new GridContainer
{
RelativeSizeAxes = Axes.X,
Height = 300,
Content = new[]
{
new Drawable[]
{
playlist = new DrawableRoomPlaylist(true, true) { RelativeSizeAxes = Axes.Both }
},
new Drawable[]
{
playlistLength = new OsuSpriteText
{
Margin = new MarginPadding { Vertical = 5 },
Colour = colours.Yellow,
Font = OsuFont.GetFont(size: 12),
}
},
new Drawable[]
{
new PurpleTriangleButton
{
RelativeSizeAxes = Axes.X,
Height = 40,
Text = "Edit playlist",
Action = () => EditPlaylist?.Invoke()
}
}
},
RowDimensions = new[]
{
new Dimension(),
new Dimension(GridSizeMode.AutoSize),
new Dimension(GridSizeMode.AutoSize),
}
}
},
},
},
},
}
},
},
Horizontal = OsuScreen.HORIZONTAL_OVERFLOW_PADDING,
Vertical = 10
},
new Drawable[]
RelativeSizeAxes = Axes.Both,
Children = new[]
{
new Container
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Y = 2,
Padding = new MarginPadding { Horizontal = WaveOverlayContainer.WIDTH_PADDING },
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
{
new Box
new SectionContainer
{
RelativeSizeAxes = Axes.Both,
Colour = Color4Extensions.FromHex(@"28242d").Darken(0.5f).Opacity(1f),
},
new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 20),
Margin = new MarginPadding { Vertical = 20 },
Padding = new MarginPadding { Horizontal = OsuScreen.HORIZONTAL_OVERFLOW_PADDING },
Children = new Drawable[]
Padding = new MarginPadding { Right = FIELD_PADDING / 2 },
Children = new[]
{
ApplyButton = new CreateRoomButton
new Section("Room name")
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
Size = new Vector2(230, 55),
Enabled = { Value = false },
Action = apply,
Child = NameField = new SettingsTextBox
{
RelativeSizeAxes = Axes.X,
TabbableContentContainer = this,
LengthLimit = 100
},
},
ErrorText = new OsuSpriteText
new Section("Duration")
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
Alpha = 0,
Depth = 1,
Colour = colours.RedDark
}
}
Child = DurationField = new DurationDropdown
{
RelativeSizeAxes = Axes.X,
Items = new[]
{
TimeSpan.FromMinutes(30),
TimeSpan.FromHours(1),
TimeSpan.FromHours(2),
TimeSpan.FromHours(4),
TimeSpan.FromHours(8),
TimeSpan.FromHours(12),
//TimeSpan.FromHours(16),
TimeSpan.FromHours(24),
TimeSpan.FromDays(3),
TimeSpan.FromDays(7)
}
}
},
new Section("Room visibility")
{
Alpha = disabled_alpha,
Child = AvailabilityPicker = new RoomAvailabilityPicker
{
Enabled = { Value = false }
},
},
new Section("Game type")
{
Alpha = disabled_alpha,
Child = new FillFlowContainer
{
AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X,
Direction = FillDirection.Vertical,
Spacing = new Vector2(7),
Children = new Drawable[]
{
TypePicker = new GameTypePicker
{
RelativeSizeAxes = Axes.X,
Enabled = { Value = false }
},
typeLabel = new OsuSpriteText
{
Font = OsuFont.GetFont(size: 14),
Colour = colours.Yellow
},
},
},
},
new Section("Max participants")
{
Alpha = disabled_alpha,
Child = MaxParticipantsField = new SettingsNumberTextBox
{
RelativeSizeAxes = Axes.X,
TabbableContentContainer = this,
ReadOnly = true,
},
},
new Section("Password (optional)")
{
Alpha = disabled_alpha,
Child = new SettingsPasswordTextBox
{
RelativeSizeAxes = Axes.X,
TabbableContentContainer = this,
ReadOnly = true,
},
},
},
},
new SectionContainer
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
Padding = new MarginPadding { Left = FIELD_PADDING / 2 },
Children = new[]
{
new Section("Playlist")
{
Child = new GridContainer
{
RelativeSizeAxes = Axes.X,
Height = 300,
Content = new[]
{
new Drawable[]
{
playlist = new DrawableRoomPlaylist(true, true) { RelativeSizeAxes = Axes.Both }
},
new Drawable[]
{
playlistLength = new OsuSpriteText
{
Margin = new MarginPadding { Vertical = 5 },
Colour = colours.Yellow,
Font = OsuFont.GetFont(size: 12),
}
},
new Drawable[]
{
new PurpleTriangleButton
{
RelativeSizeAxes = Axes.X,
Height = 40,
Text = "Edit playlist",
Action = () => EditPlaylist?.Invoke()
}
}
},
RowDimensions = new[]
{
new Dimension(),
new Dimension(GridSizeMode.AutoSize),
new Dimension(GridSizeMode.AutoSize),
}
}
},
},
},
},
}
},
},
},
new Drawable[]
{
new Container
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Y = 2,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4Extensions.FromHex(@"28242d").Darken(0.5f).Opacity(1f),
},
new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 20),
Margin = new MarginPadding { Vertical = 20 },
Padding = new MarginPadding { Horizontal = OsuScreen.HORIZONTAL_OVERFLOW_PADDING },
Children = new Drawable[]
{
ApplyButton = new CreateRoomButton
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
Size = new Vector2(230, 55),
Enabled = { Value = false },
Action = apply,
},
ErrorText = new OsuSpriteText
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
Alpha = 0,
Depth = 1,
Colour = colours.RedDark
}
}
}
}
}
},
}
}
},
loadingLayer = new LoadingLayer(dimContent)
loadingLayer = new LoadingLayer(true)
};
TypePicker.Current.BindValueChanged(type => typeLabel.Text = type.NewValue?.Name ?? string.Empty, true);

View File

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
@ -114,11 +115,17 @@ namespace osu.Game.Screens
Mods = screenDependencies.Mods;
}
protected BackgroundScreen Background => backgroundStack?.CurrentScreen as BackgroundScreen;
/// <summary>
/// The background created and owned by this screen. May be null if the background didn't change.
/// </summary>
[CanBeNull]
private BackgroundScreen ownedBackground;
private BackgroundScreen localBackground;
[CanBeNull]
private BackgroundScreen background;
[Resolved(canBeNull: true)]
[CanBeNull]
private BackgroundScreenStack backgroundStack { get; set; }
[Resolved(canBeNull: true)]
@ -140,6 +147,21 @@ namespace osu.Game.Screens
Activity.Value ??= InitialActivity;
}
/// <summary>
/// Apply arbitrary changes to the current background screen in a thread safe manner.
/// </summary>
/// <param name="action">The operation to perform.</param>
public void ApplyToBackground(Action<BackgroundScreen> action)
{
if (backgroundStack == null)
throw new InvalidOperationException("Attempted to apply to background without a background stack being available.");
if (background == null)
throw new InvalidOperationException("Attempted to apply to background before screen is pushed.");
background.ApplyToBackground(action);
}
public override void OnResuming(IScreen last)
{
if (PlayResumeSound)
@ -160,7 +182,16 @@ namespace osu.Game.Screens
{
applyArrivingDefaults(false);
backgroundStack?.Push(localBackground = CreateBackground());
backgroundStack?.Push(ownedBackground = CreateBackground());
background = backgroundStack?.CurrentScreen as BackgroundScreen;
if (background != ownedBackground)
{
// background may have not been replaced, at which point we don't want to track the background lifetime.
ownedBackground?.Dispose();
ownedBackground = null;
}
base.OnEntering(last);
}
@ -173,7 +204,7 @@ namespace osu.Game.Screens
if (base.OnExiting(next))
return true;
if (localBackground != null && backgroundStack?.CurrentScreen == localBackground)
if (ownedBackground != null && backgroundStack?.CurrentScreen == ownedBackground)
backgroundStack?.Exit();
return false;

View File

@ -53,7 +53,6 @@ namespace osu.Game.Screens.Play
private readonly Bindable<IReadOnlyList<Mod>> mods;
private readonly Drawable facade;
private LoadingSpinner loading;
private Sprite backgroundSprite;
public IBindable<IReadOnlyList<Mod>> Mods => mods;
@ -123,7 +122,7 @@ namespace osu.Game.Screens.Play
Masking = true,
Children = new Drawable[]
{
backgroundSprite = new Sprite
new Sprite
{
RelativeSizeAxes = Axes.Both,
Texture = beatmap?.Background,
@ -131,7 +130,7 @@ namespace osu.Game.Screens.Play
Anchor = Anchor.Centre,
FillMode = FillMode.Fill,
},
loading = new LoadingLayer(backgroundSprite)
loading = new LoadingLayer(true)
}
},
new OsuSpriteText

View File

@ -24,7 +24,19 @@ namespace osu.Game.Screens.Play
Alpha = 0f;
}
public BackgroundScreenBeatmap DimmableBackground { get; set; }
private BackgroundScreenBeatmap dimmableBackground;
public BackgroundScreenBeatmap DimmableBackground
{
get => dimmableBackground;
set
{
dimmableBackground = value;
if (IsLoaded)
updateBackgroundFade();
}
}
[BackgroundDependencyLoader]
private void load(OsuColour colours, IBindable<WorkingBeatmap> beatmap)
@ -75,11 +87,16 @@ namespace osu.Game.Screens.Play
protected override void PopIn()
{
DimmableBackground?.FadeColour(OsuColour.Gray(0.5f), FADE_DURATION, Easing.OutQuint);
updateBackgroundFade();
this.FadeIn(FADE_DURATION, Easing.OutQuint);
}
private void updateBackgroundFade()
{
DimmableBackground?.FadeColour(OsuColour.Gray(0.5f), FADE_DURATION, Easing.OutQuint);
}
protected override void PopOut() => this.FadeOut(FADE_DURATION);
}
}

View File

@ -131,7 +131,9 @@ namespace osu.Game.Screens.Play
// if a storyboard is present, it may dictate the appropriate start time by having events in negative time space.
// this is commonly used to display an intro before the audio track start.
startTime = Math.Min(startTime, beatmap.Storyboard.FirstEventTime);
double? firstStoryboardEvent = beatmap.Storyboard.EarliestEventTime;
if (firstStoryboardEvent != null)
startTime = Math.Min(startTime, firstStoryboardEvent.Value);
// some beatmaps specify a current lead-in time which should be used instead of the ruleset-provided value when available.
// this is not available as an option in the live editor but can still be applied via .osu editing.

View File

@ -3,6 +3,7 @@
using System;
using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Caching;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@ -42,7 +43,7 @@ namespace osu.Game.Screens.Play.HUD
/// Whether the player should be tracked on the leaderboard.
/// Set to <c>true</c> for the local player or a player whose replay is currently being played.
/// </param>
public ILeaderboardScore AddPlayer(User user, bool isTracked)
public ILeaderboardScore AddPlayer([CanBeNull] User user, bool isTracked)
{
var drawable = new GameplayLeaderboardScore(user, isTracked)
{

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
@ -34,6 +35,7 @@ namespace osu.Game.Screens.Play.HUD
public BindableDouble TotalScore { get; } = new BindableDouble();
public BindableDouble Accuracy { get; } = new BindableDouble(1);
public BindableInt Combo { get; } = new BindableInt();
public BindableBool HasQuit { get; } = new BindableBool();
private int? scorePosition;
@ -51,10 +53,11 @@ namespace osu.Game.Screens.Play.HUD
positionText.Text = $"#{scorePosition.Value.FormatRank()}";
positionText.FadeTo(scorePosition.HasValue ? 1 : 0);
updateColour();
updateState();
}
}
[CanBeNull]
public User User { get; }
private readonly bool trackedPlayer;
@ -67,7 +70,7 @@ namespace osu.Game.Screens.Play.HUD
/// </summary>
/// <param name="user">The score's player.</param>
/// <param name="trackedPlayer">Whether the player is the local user or a replay player.</param>
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);

View File

@ -10,5 +10,7 @@ namespace osu.Game.Screens.Play.HUD
BindableDouble TotalScore { get; }
BindableDouble Accuracy { get; }
BindableInt Combo { get; }
BindableBool HasQuit { get; }
}
}

View File

@ -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<int, TrackedUserData> userScores = new Dictionary<int, TrackedUserData>();
[Resolved]
private SpectatorStreamingClient streamingClient { get; set; }
[Resolved]
private StatefulMultiplayerClient multiplayerClient { get; set; }
[Resolved]
private UserLookupCache userLookupCache { get; set; }
private Bindable<ScoringMode> scoringMode;
private readonly BindableList<int> playingUsers;
/// <summary>
/// Construct a new leaderboard.
/// </summary>
@ -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<int>(userIds);
}
[Resolved]
private SpectatorStreamingClient streamingClient { get; set; }
[Resolved]
private UserLookupCache userLookupCache { get; set; }
private Bindable<ScoringMode> 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<double>)leaderboardScore.Accuracy).BindTo(trackedUser.Accuracy);
((IBindable<double>)leaderboardScore.TotalScore).BindTo(trackedUser.Score);
((IBindable<int>)leaderboardScore.Combo).BindTo(trackedUser.CurrentCombo);
((IBindable<bool>)leaderboardScore.HasQuit).BindTo(trackedUser.UserQuit);
}
scoringMode = config.GetBindable<ScoringMode>(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<int>())
{
streamingClient.StopWatchingUser(userId);
if (userScores.TryGetValue(userId, out var trackedData))
trackedData.MarkUserQuit();
}
break;
}
}
private void updateAllScores(ValueChangedEvent<ScoringMode> 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<bool> 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)

View File

@ -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
{

View File

@ -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;
}

View File

@ -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;
}

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<BackgroundScreenBeatmap> action) => base.ApplyToBackground(b => action.Invoke((BackgroundScreenBeatmap)b));
}
}

View File

@ -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<GlobalAction>
public abstract class ResultsScreen : ScreenWithBeatmapBackground, IKeyBindingHandler<GlobalAction>
{
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<ScoreInfo> SelectedScore = new Bindable<ScoreInfo>();
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;
}

Some files were not shown because too many files have changed in this diff Show More