diff --git a/Directory.Build.props b/Directory.Build.props
index b55eff9df9..2e1873a9ed 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -48,7 +48,7 @@
https://github.com/ppy/osu
Automated release.
ppy Pty Ltd
- Copyright (c) 2020 ppy Pty Ltd
+ Copyright (c) 2021 ppy Pty Ltd
osu game
diff --git a/LICENCE b/LICENCE
index 2435c23545..b5962ad3b2 100644
--- a/LICENCE
+++ b/LICENCE
@@ -1,4 +1,4 @@
-Copyright (c) 2020 ppy Pty Ltd .
+Copyright (c) 2021 ppy Pty Ltd .
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/osu.Android.props b/osu.Android.props
index 611f0d05f4..492c88c7e4 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -52,6 +52,6 @@
-
+
diff --git a/osu.Android/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs
index 953c06f4e2..9d28ad7c5b 100644
--- a/osu.Android/OsuGameActivity.cs
+++ b/osu.Android/OsuGameActivity.cs
@@ -16,7 +16,9 @@ using osu.Framework.Android;
namespace osu.Android
{
[Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false, LaunchMode = LaunchMode.SingleInstance)]
- [IntentFilter(new[] { Intent.ActionDefault, Intent.ActionSend }, Categories = new[] { Intent.CategoryDefault }, DataPathPatterns = new[] { ".*\\.osz", ".*\\.osk" }, DataMimeType = "application/*")]
+ [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osz", DataHost = "*", DataMimeType = "*/*")]
+ [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osk", DataHost = "*", DataMimeType = "*/*")]
+ [IntentFilter(new[] { Intent.ActionSend }, Categories = new[] { Intent.CategoryDefault }, DataMimeTypes = new[] { "application/zip", "application/octet-stream" })]
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryBrowsable, Intent.CategoryDefault }, DataSchemes = new[] { "osu", "osump" })]
public class OsuGameActivity : AndroidGameActivity
{
diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs
index f1878d967d..63b12fb84b 100644
--- a/osu.Desktop/DiscordRichPresence.cs
+++ b/osu.Desktop/DiscordRichPresence.cs
@@ -9,6 +9,7 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Logging;
+using osu.Game.Configuration;
using osu.Game.Online.API;
using osu.Game.Rulesets;
using osu.Game.Users;
@@ -31,13 +32,15 @@ namespace osu.Desktop
private readonly IBindable status = new Bindable();
private readonly IBindable activity = new Bindable();
+ private readonly Bindable privacyMode = new Bindable();
+
private readonly RichPresence presence = new RichPresence
{
Assets = new Assets { LargeImageKey = "osu_logo_lazer", }
};
[BackgroundDependencyLoader]
- private void load(IAPIProvider provider)
+ private void load(IAPIProvider provider, OsuConfigManager config)
{
client = new DiscordRpcClient(client_id)
{
@@ -51,6 +54,8 @@ namespace osu.Desktop
client.OnError += (_, e) => Logger.Log($"An error occurred with Discord RPC Client: {e.Code} {e.Message}", LoggingTarget.Network);
+ config.BindWith(OsuSetting.DiscordRichPresence, privacyMode);
+
(user = provider.LocalUser.GetBoundCopy()).BindValueChanged(u =>
{
status.UnbindBindings();
@@ -63,6 +68,7 @@ namespace osu.Desktop
ruleset.BindValueChanged(_ => updateStatus());
status.BindValueChanged(_ => updateStatus());
activity.BindValueChanged(_ => updateStatus());
+ privacyMode.BindValueChanged(_ => updateStatus());
client.Initialize();
}
@@ -78,7 +84,7 @@ namespace osu.Desktop
if (!client.IsInitialized)
return;
- if (status.Value is UserStatusOffline)
+ if (status.Value is UserStatusOffline || privacyMode.Value == DiscordRichPresenceMode.Off)
{
client.ClearPresence();
return;
@@ -96,7 +102,10 @@ namespace osu.Desktop
}
// update user information
- presence.Assets.LargeImageText = $"{user.Value.Username}" + (user.Value.Statistics?.Ranks.Global > 0 ? $" (rank #{user.Value.Statistics.Ranks.Global:N0})" : string.Empty);
+ if (privacyMode.Value == DiscordRichPresenceMode.Limited)
+ presence.Assets.LargeImageText = string.Empty;
+ else
+ presence.Assets.LargeImageText = $"{user.Value.Username}" + (user.Value.Statistics?.Ranks.Global > 0 ? $" (rank #{user.Value.Statistics.Ranks.Global:N0})" : string.Empty);
// update ruleset
presence.Assets.SmallImageKey = ruleset.Value.ID <= 3 ? $"mode_{ruleset.Value.ID}" : "mode_custom";
@@ -137,7 +146,7 @@ namespace osu.Desktop
return edit.Beatmap.ToString();
case UserActivity.InLobby lobby:
- return lobby.Room.Name.Value;
+ return privacyMode.Value == DiscordRichPresenceMode.Limited ? string.Empty : lobby.Room.Name.Value;
}
return string.Empty;
diff --git a/osu.Desktop/Overlays/VersionManager.cs b/osu.Desktop/Overlays/VersionManager.cs
index 8c759f8487..e4a3451651 100644
--- a/osu.Desktop/Overlays/VersionManager.cs
+++ b/osu.Desktop/Overlays/VersionManager.cs
@@ -26,9 +26,11 @@ namespace osu.Desktop.Overlays
Alpha = 0;
+ FillFlowContainer mainFill;
+
Children = new Drawable[]
{
- new FillFlowContainer
+ mainFill = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
@@ -55,23 +57,30 @@ namespace osu.Desktop.Overlays
},
}
},
- new OsuSpriteText
- {
- Anchor = Anchor.TopCentre,
- Origin = Anchor.TopCentre,
- Font = OsuFont.Numeric.With(size: 12),
- Colour = colours.Yellow,
- Text = @"Development Build"
- },
- new Sprite
- {
- Anchor = Anchor.TopCentre,
- Origin = Anchor.TopCentre,
- Texture = textures.Get(@"Menu/dev-build-footer"),
- },
}
}
};
+
+ if (!game.IsDeployedBuild)
+ {
+ mainFill.AddRange(new Drawable[]
+ {
+ new OsuSpriteText
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ Font = OsuFont.Numeric.With(size: 12),
+ Colour = colours.Yellow,
+ Text = @"Development Build"
+ },
+ new Sprite
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ Texture = textures.Get(@"Menu/dev-build-footer"),
+ },
+ });
+ }
}
protected override void PopIn()
diff --git a/osu.Desktop/osu.nuspec b/osu.Desktop/osu.nuspec
index 2fc6009183..fa182f8e70 100644
--- a/osu.Desktop/osu.nuspec
+++ b/osu.Desktop/osu.nuspec
@@ -11,7 +11,7 @@
false
A free-to-win rhythm game. Rhythm is just a *click* away!
testing
- Copyright (c) 2020 ppy Pty Ltd
+ Copyright (c) 2021 ppy Pty Ltd
en-AU
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs
index acdd0a420c..438d17dbc5 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs
@@ -59,8 +59,8 @@ namespace osu.Game.Rulesets.Catch.Mods
{
base.ApplySettings(difficulty);
- difficulty.CircleSize = CircleSize.Value;
- difficulty.ApproachRate = ApproachRate.Value;
+ ApplySetting(CircleSize, cs => difficulty.CircleSize = cs);
+ ApplySetting(ApproachRate, ar => difficulty.ApproachRate = ar);
}
}
}
diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs
index 49c1fe8540..db8546c71b 100644
--- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs
@@ -1,13 +1,17 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Framework.Utils;
+using osu.Game.Beatmaps;
using osu.Game.Graphics.Containers;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Mods;
+using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
namespace osu.Game.Rulesets.Osu.Tests.Mods
@@ -18,8 +22,23 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
public void TestNoAdjustment() => CreateModTest(new ModTestData
{
Mod = new OsuModDifficultyAdjust(),
+ Beatmap = new Beatmap
+ {
+ BeatmapInfo = new BeatmapInfo
+ {
+ BaseDifficulty = new BeatmapDifficulty
+ {
+ CircleSize = 8
+ }
+ },
+ HitObjects = new List
+ {
+ new HitCircle { StartTime = 1000 },
+ new HitCircle { StartTime = 2000 }
+ }
+ },
Autoplay = true,
- PassCondition = checkSomeHit
+ PassCondition = () => checkSomeHit() && checkObjectsScale(0.29f)
});
[Test]
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs
index ff995e38ce..a638234dbd 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs
@@ -59,8 +59,8 @@ namespace osu.Game.Rulesets.Osu.Mods
{
base.ApplySettings(difficulty);
- difficulty.CircleSize = CircleSize.Value;
- difficulty.ApproachRate = ApproachRate.Value;
+ ApplySetting(CircleSize, cs => difficulty.CircleSize = cs);
+ ApplySetting(ApproachRate, ar => difficulty.ApproachRate = ar);
}
}
}
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs
index 9ebedb3c80..7bee580863 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs
@@ -95,6 +95,26 @@ namespace osu.Game.Tests.Beatmaps.Formats
}
}
+ [Test]
+ public void TestOutOfOrderStartTimes()
+ {
+ var decoder = new LegacyStoryboardDecoder();
+
+ using (var resStream = TestResources.OpenResource("out-of-order-starttimes.osb"))
+ using (var stream = new LineBufferedReader(resStream))
+ {
+ var storyboard = decoder.Decode(stream);
+
+ StoryboardLayer background = storyboard.Layers.Single(l => l.Depth == 3);
+ Assert.AreEqual(2, background.Elements.Count);
+
+ Assert.AreEqual(1500, background.Elements[0].StartTime);
+ Assert.AreEqual(1000, background.Elements[1].StartTime);
+
+ Assert.AreEqual(1000, storyboard.EarliestEventTime);
+ }
+ }
+
[Test]
public void TestDecodeVariableWithSuffix()
{
diff --git a/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs b/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs
index 90a487c0ac..b27c257795 100644
--- a/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs
+++ b/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs
@@ -246,5 +246,32 @@ namespace osu.Game.Tests.NonVisual
Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(0));
Assert.That(cpi.AllControlPoints.Count, Is.EqualTo(0));
}
+
+ [Test]
+ public void TestCreateCopyIsDeepClone()
+ {
+ var cpi = new ControlPointInfo();
+
+ cpi.Add(1000, new TimingControlPoint { BeatLength = 500 });
+
+ var cpiCopy = cpi.CreateCopy();
+
+ cpiCopy.Add(2000, new TimingControlPoint { BeatLength = 500 });
+
+ Assert.That(cpi.Groups.Count, Is.EqualTo(1));
+ Assert.That(cpiCopy.Groups.Count, Is.EqualTo(2));
+
+ Assert.That(cpi.TimingPoints.Count, Is.EqualTo(1));
+ Assert.That(cpiCopy.TimingPoints.Count, Is.EqualTo(2));
+
+ Assert.That(cpi.TimingPoints[0], Is.Not.SameAs(cpiCopy.TimingPoints[0]));
+ Assert.That(cpi.TimingPoints[0].BeatLengthBindable, Is.Not.SameAs(cpiCopy.TimingPoints[0].BeatLengthBindable));
+
+ Assert.That(cpi.TimingPoints[0].BeatLength, Is.EqualTo(cpiCopy.TimingPoints[0].BeatLength));
+
+ cpi.TimingPoints[0].BeatLength = 800;
+
+ Assert.That(cpi.TimingPoints[0].BeatLength, Is.Not.EqualTo(cpiCopy.TimingPoints[0].BeatLength));
+ }
}
}
diff --git a/osu.Game.Tests/Resources/out-of-order-starttimes.osb b/osu.Game.Tests/Resources/out-of-order-starttimes.osb
new file mode 100644
index 0000000000..09988ff64e
--- /dev/null
+++ b/osu.Game.Tests/Resources/out-of-order-starttimes.osb
@@ -0,0 +1,6 @@
+[Events]
+//Storyboard Layer 0 (Background)
+Sprite,Background,TopCentre,"img.jpg",320,240
+ F,0,1500,1600,0,1
+Sprite,Background,TopCentre,"img.jpg",320,240
+ F,0,1000,1100,0,1
diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs
index 5323f58a66..7ade7725d9 100644
--- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs
+++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs
@@ -82,7 +82,7 @@ namespace osu.Game.Tests.Visual.Background
});
AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
AddStep("Stop background preview", () => InputManager.MoveMouseTo(playerLoader.ScreenPos));
- AddUntilStep("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && playerLoader.IsBlurCorrect());
+ AddUntilStep("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.CheckBackgroundBlur(playerLoader.ExpectedBackgroundBlur));
}
///
@@ -106,6 +106,7 @@ namespace osu.Game.Tests.Visual.Background
public void TestStoryboardBackgroundVisibility()
{
performFullSetup();
+ AddAssert("Background retained from song select", () => songSelect.IsBackgroundCurrent());
createFakeStoryboard();
AddStep("Enable Storyboard", () =>
{
@@ -198,8 +199,9 @@ namespace osu.Game.Tests.Visual.Background
})));
AddUntilStep("Wait for results is current", () => results.IsCurrentScreen());
+
AddUntilStep("Screen is undimmed, original background retained", () =>
- songSelect.IsBackgroundUndimmed() && songSelect.IsBackgroundCurrent() && results.IsBlurCorrect());
+ songSelect.IsBackgroundUndimmed() && songSelect.IsBackgroundCurrent() && songSelect.CheckBackgroundBlur(results.ExpectedBackgroundBlur));
}
///
@@ -224,7 +226,7 @@ namespace osu.Game.Tests.Visual.Background
AddStep("Resume PlayerLoader", () => player.Restart());
AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
AddStep("Move mouse to center of screen", () => InputManager.MoveMouseTo(playerLoader.ScreenPos));
- AddUntilStep("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && playerLoader.IsBlurCorrect());
+ AddUntilStep("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.CheckBackgroundBlur(playerLoader.ExpectedBackgroundBlur));
}
private void createFakeStoryboard() => AddStep("Create storyboard", () =>
@@ -274,9 +276,11 @@ namespace osu.Game.Tests.Visual.Background
private class DummySongSelect : PlaySongSelect
{
+ private FadeAccessibleBackground background;
+
protected override BackgroundScreen CreateBackground()
{
- FadeAccessibleBackground background = new FadeAccessibleBackground(Beatmap.Value);
+ background = new FadeAccessibleBackground(Beatmap.Value);
DimEnabled.BindTo(background.EnableUserDim);
return background;
}
@@ -294,25 +298,27 @@ namespace osu.Game.Tests.Visual.Background
config.BindWith(OsuSetting.BlurLevel, BlurLevel);
}
- public bool IsBackgroundDimmed() => ((FadeAccessibleBackground)Background).CurrentColour == OsuColour.Gray(1f - ((FadeAccessibleBackground)Background).CurrentDim);
+ public bool IsBackgroundDimmed() => background.CurrentColour == OsuColour.Gray(1f - background.CurrentDim);
- public bool IsBackgroundUndimmed() => ((FadeAccessibleBackground)Background).CurrentColour == Color4.White;
+ public bool IsBackgroundUndimmed() => background.CurrentColour == Color4.White;
- public bool IsUserBlurApplied() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2((float)BlurLevel.Value * BackgroundScreenBeatmap.USER_BLUR_FACTOR);
+ public bool IsUserBlurApplied() => background.CurrentBlur == new Vector2((float)BlurLevel.Value * BackgroundScreenBeatmap.USER_BLUR_FACTOR);
- public bool IsUserBlurDisabled() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(0);
+ public bool IsUserBlurDisabled() => background.CurrentBlur == new Vector2(0);
- public bool IsBackgroundInvisible() => ((FadeAccessibleBackground)Background).CurrentAlpha == 0;
+ public bool IsBackgroundInvisible() => background.CurrentAlpha == 0;
- public bool IsBackgroundVisible() => ((FadeAccessibleBackground)Background).CurrentAlpha == 1;
+ public bool IsBackgroundVisible() => background.CurrentAlpha == 1;
- public bool IsBlurCorrect() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(BACKGROUND_BLUR);
+ public bool IsBlurCorrect() => background.CurrentBlur == new Vector2(BACKGROUND_BLUR);
+
+ public bool CheckBackgroundBlur(Vector2 expected) => background.CurrentBlur == expected;
///
/// Make sure every time a screen gets pushed, the background doesn't get replaced
///
/// Whether or not the original background (The one created in DummySongSelect) is still the current background
- public bool IsBackgroundCurrent() => ((FadeAccessibleBackground)Background).IsCurrentScreen();
+ public bool IsBackgroundCurrent() => background?.IsCurrentScreen() == true;
}
private class FadeAccessibleResults : ResultsScreen
@@ -324,12 +330,20 @@ namespace osu.Game.Tests.Visual.Background
protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value);
- public bool IsBlurCorrect() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(BACKGROUND_BLUR);
+ public Vector2 ExpectedBackgroundBlur => new Vector2(BACKGROUND_BLUR);
}
private class LoadBlockingTestPlayer : TestPlayer
{
- protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value);
+ protected override BackgroundScreen CreateBackground() =>
+ new FadeAccessibleBackground(Beatmap.Value);
+
+ public override void OnEntering(IScreen last)
+ {
+ base.OnEntering(last);
+
+ ApplyToBackground(b => ReplacesBackground.BindTo(b.StoryboardReplacesBackground));
+ }
public new DimmableStoryboard DimmableStoryboard => base.DimmableStoryboard;
@@ -354,15 +368,16 @@ namespace osu.Game.Tests.Visual.Background
Thread.Sleep(1);
StoryboardEnabled = config.GetBindable(OsuSetting.ShowStoryboard);
- ReplacesBackground.BindTo(Background.StoryboardReplacesBackground);
DrawableRuleset.IsPaused.BindTo(IsPaused);
}
}
private class TestPlayerLoader : PlayerLoader
{
+ private FadeAccessibleBackground background;
+
public VisualSettings VisualSettingsPos => VisualSettings;
- public BackgroundScreen ScreenPos => Background;
+ public BackgroundScreen ScreenPos => background;
public TestPlayerLoader(Player player)
: base(() => player)
@@ -371,9 +386,9 @@ namespace osu.Game.Tests.Visual.Background
public void TriggerOnHover() => OnHover(new HoverEvent(new InputState()));
- public bool IsBlurCorrect() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(BACKGROUND_BLUR);
+ public Vector2 ExpectedBackgroundBlur => new Vector2(BACKGROUND_BLUR);
- protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value);
+ protected override BackgroundScreen CreateBackground() => background = new FadeAccessibleBackground(Beatmap.Value);
}
private class FadeAccessibleBackground : BackgroundScreenBeatmap
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs
index 6b11613f1c..03ba73d35b 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs
@@ -7,8 +7,10 @@ using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Graphics;
using osu.Framework.Platform;
+using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
+using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets;
@@ -23,6 +25,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
public class TestSceneMultiplayerReadyButton : MultiplayerTestScene
{
private MultiplayerReadyButton button;
+ private BeatmapSetInfo importedSet;
private BeatmapManager beatmaps;
private RulesetStore rulesets;
@@ -38,9 +41,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
[SetUp]
public new void Setup() => Schedule(() =>
{
- var beatmap = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First().Beatmaps.First();
-
- Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap);
+ importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First();
+ Beatmap.Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First());
Child = button = new MultiplayerReadyButton
{
@@ -51,13 +53,30 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
Value = new PlaylistItem
{
- Beatmap = { Value = beatmap },
- Ruleset = { Value = beatmap.Ruleset }
+ Beatmap = { Value = Beatmap.Value.BeatmapInfo },
+ Ruleset = { Value = Beatmap.Value.BeatmapInfo.Ruleset }
}
}
};
});
+ [Test]
+ public void TestDeletedBeatmapDisableReady()
+ {
+ OsuButton readyButton = null;
+
+ AddAssert("ensure ready button enabled", () =>
+ {
+ readyButton = button.ChildrenOfType().Single();
+ return readyButton.Enabled.Value;
+ });
+
+ AddStep("delete beatmap", () => beatmaps.Delete(importedSet));
+ AddAssert("ready button disabled", () => !readyButton.Enabled.Value);
+ AddStep("undelete beatmap", () => beatmaps.Undelete(importedSet));
+ AddAssert("ready button enabled back", () => readyButton.Enabled.Value);
+ }
+
[Test]
public void TestToggleStateWhenNotHost()
{
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs
index 3d3517ada4..40b2f66d74 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs
@@ -11,6 +11,7 @@ using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Screens.Select.Details;
using osuTK.Graphics;
@@ -141,16 +142,12 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("select changed Difficulty Adjust mod", () =>
{
var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance();
- var difficultyAdjustMod = ruleset.GetAllMods().OfType().Single();
+ var difficultyAdjustMod = ruleset.GetAllMods().OfType().Single();
var originalDifficulty = advancedStats.Beatmap.BaseDifficulty;
- var adjustedDifficulty = new BeatmapDifficulty
- {
- CircleSize = originalDifficulty.CircleSize,
- DrainRate = originalDifficulty.DrainRate - 0.5f,
- OverallDifficulty = originalDifficulty.OverallDifficulty,
- ApproachRate = originalDifficulty.ApproachRate + 2.2f,
- };
- difficultyAdjustMod.ReadFromDifficulty(adjustedDifficulty);
+
+ difficultyAdjustMod.ReadFromDifficulty(originalDifficulty);
+ difficultyAdjustMod.DrainRate.Value = originalDifficulty.DrainRate - 0.5f;
+ difficultyAdjustMod.ApproachRate.Value = originalDifficulty.ApproachRate + 2.2f;
SelectedMods.Value = new[] { difficultyAdjustMod };
});
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLoadingLayer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLoadingLayer.cs
index 1be191fc29..d426723f0b 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneLoadingLayer.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLoadingLayer.cs
@@ -5,6 +5,7 @@ using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
+using osu.Framework.Utils;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osuTK;
@@ -14,8 +15,7 @@ namespace osu.Game.Tests.Visual.UserInterface
{
public class TestSceneLoadingLayer : OsuTestScene
{
- private Drawable dimContent;
- private LoadingLayer overlay;
+ private TestLoadingLayer overlay;
private Container content;
@@ -29,14 +29,14 @@ namespace osu.Game.Tests.Visual.UserInterface
Size = new Vector2(300),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
- Children = new[]
+ Children = new Drawable[]
{
new Box
{
Colour = Color4.SlateGray,
RelativeSizeAxes = Axes.Both,
},
- dimContent = new FillFlowContainer
+ new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -51,7 +51,7 @@ namespace osu.Game.Tests.Visual.UserInterface
new TriangleButton { Text = "puush me", Width = 200, Action = () => { } },
}
},
- overlay = new LoadingLayer(dimContent),
+ overlay = new TestLoadingLayer(true),
}
},
};
@@ -64,25 +64,11 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep("show", () => overlay.Show());
- AddUntilStep("wait for content dim", () => dimContent.Colour != Color4.White);
+ AddUntilStep("wait for content dim", () => overlay.BackgroundDimLayer.Alpha > 0);
AddStep("hide", () => overlay.Hide());
- AddUntilStep("wait for content restore", () => dimContent.Colour == Color4.White);
- }
-
- [Test]
- public void TestContentRestoreOnDispose()
- {
- AddAssert("not visible", () => !overlay.IsPresent);
-
- AddStep("show", () => overlay.Show());
-
- AddUntilStep("wait for content dim", () => dimContent.Colour != Color4.White);
-
- AddStep("expire", () => overlay.Expire());
-
- AddUntilStep("wait for content restore", () => dimContent.Colour == Color4.White);
+ AddUntilStep("wait for content restore", () => Precision.AlmostEquals(overlay.BackgroundDimLayer.Alpha, 0));
}
[Test]
@@ -98,5 +84,15 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep("hide", () => overlay.Hide());
}
+
+ private class TestLoadingLayer : LoadingLayer
+ {
+ public new Box BackgroundDimLayer => base.BackgroundDimLayer;
+
+ public TestLoadingLayer(bool dimBackground = false, bool withBox = true)
+ : base(dimBackground, withBox)
+ {
+ }
+ }
}
}
diff --git a/osu.Game.Tournament/JsonPointConverter.cs b/osu.Game.Tournament/JsonPointConverter.cs
new file mode 100644
index 0000000000..9c82f8ac06
--- /dev/null
+++ b/osu.Game.Tournament/JsonPointConverter.cs
@@ -0,0 +1,65 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Diagnostics;
+using System.Drawing;
+using Newtonsoft.Json;
+
+namespace osu.Game.Tournament
+{
+ ///
+ /// We made a change from using SixLabors.ImageSharp.Point to System.Drawing.Point at some stage.
+ /// This handles converting to a standardised format on json serialize/deserialize operations.
+ ///
+ internal class JsonPointConverter : JsonConverter
+ {
+ public override void WriteJson(JsonWriter writer, Point value, JsonSerializer serializer)
+ {
+ // use the format of LaborSharp's Point since it is nicer.
+ serializer.Serialize(writer, new { value.X, value.Y });
+ }
+
+ public override Point ReadJson(JsonReader reader, Type objectType, Point existingValue, bool hasExistingValue, JsonSerializer serializer)
+ {
+ if (reader.TokenType != JsonToken.StartObject)
+ {
+ // if there's no object present then this is using string representation (System.Drawing.Point serializes to "x,y")
+ string str = (string)reader.Value;
+
+ Debug.Assert(str != null);
+
+ return new PointConverter().ConvertFromString(str) as Point? ?? new Point();
+ }
+
+ var point = new Point();
+
+ while (reader.Read())
+ {
+ if (reader.TokenType == JsonToken.EndObject) break;
+
+ if (reader.TokenType == JsonToken.PropertyName)
+ {
+ var name = reader.Value?.ToString();
+ int? val = reader.ReadAsInt32();
+
+ if (val == null)
+ continue;
+
+ switch (name)
+ {
+ case "X":
+ point.X = val.Value;
+ break;
+
+ case "Y":
+ point.Y = val.Value;
+ break;
+ }
+ }
+ }
+
+ return point;
+ }
+ }
+}
diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs
index dbda6aa023..bc36f27e5b 100644
--- a/osu.Game.Tournament/TournamentGameBase.cs
+++ b/osu.Game.Tournament/TournamentGameBase.cs
@@ -8,12 +8,12 @@ using Newtonsoft.Json;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Textures;
using osu.Framework.Input;
-using osu.Framework.Platform;
using osu.Framework.IO.Stores;
+using osu.Framework.Platform;
using osu.Game.Beatmaps;
using osu.Game.Online.API.Requests;
-using osu.Game.Tournament.IPC;
using osu.Game.Tournament.IO;
+using osu.Game.Tournament.IPC;
using osu.Game.Tournament.Models;
using osu.Game.Users;
using osuTK.Input;
@@ -60,7 +60,7 @@ namespace osu.Game.Tournament
{
using (Stream stream = storage.GetStream(bracket_filename, FileAccess.Read, FileMode.Open))
using (var sr = new StreamReader(stream))
- ladder = JsonConvert.DeserializeObject(sr.ReadToEnd());
+ ladder = JsonConvert.DeserializeObject(sr.ReadToEnd(), new JsonPointConverter());
}
ladder ??= new LadderInfo();
@@ -251,6 +251,7 @@ namespace osu.Game.Tournament
Formatting = Formatting.Indented,
NullValueHandling = NullValueHandling.Ignore,
DefaultValueHandling = DefaultValueHandling.Ignore,
+ Converters = new JsonConverter[] { new JsonPointConverter() }
}));
}
}
diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs
index 5435e86dfd..be2006e67a 100644
--- a/osu.Game/Beatmaps/Beatmap.cs
+++ b/osu.Game/Beatmaps/Beatmap.cs
@@ -50,7 +50,15 @@ namespace osu.Game.Beatmaps
IBeatmap IBeatmap.Clone() => Clone();
- public Beatmap Clone() => (Beatmap)MemberwiseClone();
+ public Beatmap Clone()
+ {
+ var clone = (Beatmap)MemberwiseClone();
+
+ clone.ControlPointInfo = ControlPointInfo.CreateCopy();
+ // todo: deep clone other elements as required.
+
+ return clone;
+ }
}
public class Beatmap : Beatmap
diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs
index c6649f6af1..e8dc623ddb 100644
--- a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs
@@ -28,5 +28,21 @@ namespace osu.Game.Beatmaps.ControlPoints
/// An existing control point to compare with.
/// Whether this is redundant when placed alongside .
public abstract bool IsRedundant(ControlPoint existing);
+
+ ///
+ /// Create an unbound copy of this control point.
+ ///
+ public ControlPoint CreateCopy()
+ {
+ var copy = (ControlPoint)Activator.CreateInstance(GetType());
+
+ copy.CopyFrom(this);
+
+ return copy;
+ }
+
+ public virtual void CopyFrom(ControlPoint other)
+ {
+ }
}
}
diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
index b843aad950..e8a91e4001 100644
--- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
+++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
@@ -297,5 +297,15 @@ namespace osu.Game.Beatmaps.ControlPoints
break;
}
}
+
+ public ControlPointInfo CreateCopy()
+ {
+ var controlPointInfo = new ControlPointInfo();
+
+ foreach (var point in AllControlPoints)
+ controlPointInfo.Add(point.Time, point.CreateCopy());
+
+ return controlPointInfo;
+ }
}
}
diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs
index 283bf76572..0bc5605051 100644
--- a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs
@@ -39,5 +39,12 @@ namespace osu.Game.Beatmaps.ControlPoints
public override bool IsRedundant(ControlPoint existing)
=> existing is DifficultyControlPoint existingDifficulty
&& SpeedMultiplier == existingDifficulty.SpeedMultiplier;
+
+ public override void CopyFrom(ControlPoint other)
+ {
+ SpeedMultiplier = ((DifficultyControlPoint)other).SpeedMultiplier;
+
+ base.CopyFrom(other);
+ }
}
}
diff --git a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs
index ea28fca170..79bc88e773 100644
--- a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs
@@ -50,5 +50,13 @@ namespace osu.Game.Beatmaps.ControlPoints
&& existing is EffectControlPoint existingEffect
&& KiaiMode == existingEffect.KiaiMode
&& OmitFirstBarLine == existingEffect.OmitFirstBarLine;
+
+ public override void CopyFrom(ControlPoint other)
+ {
+ KiaiMode = ((EffectControlPoint)other).KiaiMode;
+ OmitFirstBarLine = ((EffectControlPoint)other).OmitFirstBarLine;
+
+ base.CopyFrom(other);
+ }
}
}
diff --git a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs
index fd0b496335..4aa6a3d6e9 100644
--- a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs
@@ -72,5 +72,13 @@ namespace osu.Game.Beatmaps.ControlPoints
=> existing is SampleControlPoint existingSample
&& SampleBank == existingSample.SampleBank
&& SampleVolume == existingSample.SampleVolume;
+
+ public override void CopyFrom(ControlPoint other)
+ {
+ SampleVolume = ((SampleControlPoint)other).SampleVolume;
+ SampleBank = ((SampleControlPoint)other).SampleBank;
+
+ base.CopyFrom(other);
+ }
}
}
diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs
index d9378bca4a..580642f593 100644
--- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs
@@ -69,5 +69,13 @@ namespace osu.Game.Beatmaps.ControlPoints
// Timing points are never redundant as they can change the time signature.
public override bool IsRedundant(ControlPoint existing) => false;
+
+ public override void CopyFrom(ControlPoint other)
+ {
+ TimeSignature = ((TimingControlPoint)other).TimeSignature;
+ BeatLength = ((TimingControlPoint)other).BeatLength;
+
+ base.CopyFrom(other);
+ }
}
}
diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
index c9d139bdd0..069a25b83d 100644
--- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
@@ -164,13 +164,24 @@ namespace osu.Game.Beatmaps.Formats
/// Legacy BPM multiplier that introduces floating-point errors for rulesets that depend on it.
/// DO NOT USE THIS UNLESS 100% SURE.
///
- public readonly float BpmMultiplier;
+ public float BpmMultiplier { get; private set; }
public LegacyDifficultyControlPoint(double beatLength)
+ : this()
+ {
+ BpmMultiplier = beatLength < 0 ? Math.Clamp((float)-beatLength, 10, 10000) / 100f : 1;
+ }
+
+ public LegacyDifficultyControlPoint()
{
SpeedMultiplierBindable.Precision = double.Epsilon;
+ }
- BpmMultiplier = beatLength < 0 ? Math.Clamp((float)-beatLength, 10, 10000) / 100f : 1;
+ public override void CopyFrom(ControlPoint other)
+ {
+ base.CopyFrom(other);
+
+ BpmMultiplier = ((LegacyDifficultyControlPoint)other).BpmMultiplier;
}
}
@@ -192,6 +203,13 @@ namespace osu.Game.Beatmaps.Formats
=> base.IsRedundant(existing)
&& existing is LegacySampleControlPoint existingSample
&& CustomSampleBank == existingSample.CustomSampleBank;
+
+ public override void CopyFrom(ControlPoint other)
+ {
+ base.CopyFrom(other);
+
+ CustomSampleBank = ((LegacySampleControlPoint)other).CustomSampleBank;
+ }
}
}
}
diff --git a/osu.Game/Configuration/DiscordRichPresenceMode.cs b/osu.Game/Configuration/DiscordRichPresenceMode.cs
new file mode 100644
index 0000000000..2e58e3554b
--- /dev/null
+++ b/osu.Game/Configuration/DiscordRichPresenceMode.cs
@@ -0,0 +1,17 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.ComponentModel;
+
+namespace osu.Game.Configuration
+{
+ public enum DiscordRichPresenceMode
+ {
+ Off,
+
+ [Description("Hide identifiable information")]
+ Limited,
+
+ Full
+ }
+}
diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs
index a07e446d2e..eb34a0885d 100644
--- a/osu.Game/Configuration/OsuConfigManager.cs
+++ b/osu.Game/Configuration/OsuConfigManager.cs
@@ -1,4 +1,4 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
@@ -138,6 +138,8 @@ namespace osu.Game.Configuration
Set(OsuSetting.MenuBackgroundSource, BackgroundSource.Skin);
Set(OsuSetting.SeasonalBackgroundMode, SeasonalBackgroundMode.Sometimes);
+ Set(OsuSetting.DiscordRichPresence, DiscordRichPresenceMode.Full);
+
Set(OsuSetting.EditorWaveformOpacity, 1f);
}
@@ -266,6 +268,7 @@ namespace osu.Game.Configuration
GameplayDisableWinKey,
SeasonalBackgroundMode,
EditorWaveformOpacity,
+ DiscordRichPresence,
AutomaticallyDownloadWhenSpectating,
}
}
diff --git a/osu.Game/Configuration/SessionStatics.cs b/osu.Game/Configuration/SessionStatics.cs
index 03bc434aac..fd401119ff 100644
--- a/osu.Game/Configuration/SessionStatics.cs
+++ b/osu.Game/Configuration/SessionStatics.cs
@@ -1,7 +1,9 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API.Requests.Responses;
+using osu.Game.Overlays;
namespace osu.Game.Configuration
{
@@ -14,6 +16,7 @@ namespace osu.Game.Configuration
{
Set(Static.LoginOverlayDisplayed, false);
Set(Static.MutedAudioNotificationShownOnce, false);
+ Set(Static.LastHoverSoundPlaybackTime, (double?)null);
Set(Static.SeasonalBackgrounds, null);
}
}
@@ -28,5 +31,11 @@ namespace osu.Game.Configuration
/// Value under this lookup can be null if there are no backgrounds available (or API is not reachable).
///
SeasonalBackgrounds,
+
+ ///
+ /// The last playback time in milliseconds of a hover sample (from ).
+ /// Used to debounce hover sounds game-wide to avoid volume saturation, especially in scrolling views with many UI controls like .
+ ///
+ LastHoverSoundPlaybackTime
}
}
diff --git a/osu.Game/Extensions/TaskExtensions.cs b/osu.Game/Extensions/TaskExtensions.cs
index a1215d786b..4138c2757a 100644
--- a/osu.Game/Extensions/TaskExtensions.cs
+++ b/osu.Game/Extensions/TaskExtensions.cs
@@ -1,7 +1,11 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+#nullable enable
+
+using System;
using System.Threading.Tasks;
+using osu.Framework.Extensions.ExceptionExtensions;
using osu.Framework.Logging;
namespace osu.Game.Extensions
@@ -13,13 +17,19 @@ namespace osu.Game.Extensions
/// Avoids unobserved exceptions from being fired.
///
/// The task.
- /// Whether errors should be logged as important, or silently ignored.
- public static void CatchUnobservedExceptions(this Task task, bool logOnError = false)
+ ///
+ /// Whether errors should be logged as errors visible to users, or as debug messages.
+ /// Logging as debug will essentially silence the errors on non-release builds.
+ ///
+ public static void CatchUnobservedExceptions(this Task task, bool logAsError = false)
{
task.ContinueWith(t =>
{
- if (logOnError)
- Logger.Log($"Error running task: {t.Exception?.Message ?? "unknown"}", LoggingTarget.Runtime, LogLevel.Important);
+ Exception? exception = t.Exception?.AsSingular();
+ if (logAsError)
+ Logger.Error(exception, $"Error running task: {exception?.Message ?? "(unknown)"}", LoggingTarget.Runtime, true);
+ else
+ Logger.Log($"Error running task: {exception}", LoggingTarget.Runtime, LogLevel.Debug);
}, TaskContinuationOptions.NotOnRanToCompletion);
}
}
diff --git a/osu.Game/Graphics/UserInterface/HoverSounds.cs b/osu.Game/Graphics/UserInterface/HoverSounds.cs
index 40899e7e95..a1d06711db 100644
--- a/osu.Game/Graphics/UserInterface/HoverSounds.cs
+++ b/osu.Game/Graphics/UserInterface/HoverSounds.cs
@@ -5,11 +5,12 @@ using System.ComponentModel;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
+using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events;
-using osu.Framework.Threading;
+using osu.Game.Configuration;
namespace osu.Game.Graphics.UserInterface
{
@@ -22,37 +23,40 @@ namespace osu.Game.Graphics.UserInterface
private SampleChannel sampleHover;
///
- /// Length of debounce for hover sound playback, in milliseconds. Default is 50ms.
+ /// Length of debounce for hover sound playback, in milliseconds.
///
- public double HoverDebounceTime { get; } = 50;
+ public double HoverDebounceTime { get; } = 20;
protected readonly HoverSampleSet SampleSet;
+ private Bindable lastPlaybackTime;
+
public HoverSounds(HoverSampleSet sampleSet = HoverSampleSet.Normal)
{
SampleSet = sampleSet;
RelativeSizeAxes = Axes.Both;
}
- private ScheduledDelegate playDelegate;
+ [BackgroundDependencyLoader]
+ private void load(AudioManager audio, SessionStatics statics)
+ {
+ lastPlaybackTime = statics.GetBindable(Static.LastHoverSoundPlaybackTime);
+
+ sampleHover = audio.Samples.Get($@"UI/generic-hover{SampleSet.GetDescription()}");
+ }
protected override bool OnHover(HoverEvent e)
{
- playDelegate?.Cancel();
+ bool enoughTimePassedSinceLastPlayback = !lastPlaybackTime.Value.HasValue || Time.Current - lastPlaybackTime.Value >= HoverDebounceTime;
- if (HoverDebounceTime <= 0)
+ if (enoughTimePassedSinceLastPlayback)
+ {
sampleHover?.Play();
- else
- playDelegate = Scheduler.AddDelayed(() => sampleHover?.Play(), HoverDebounceTime);
+ lastPlaybackTime.Value = Time.Current;
+ }
return base.OnHover(e);
}
-
- [BackgroundDependencyLoader]
- private void load(AudioManager audio)
- {
- sampleHover = audio.Samples.Get($@"UI/generic-hover{SampleSet.GetDescription()}");
- }
}
public enum HoverSampleSet
diff --git a/osu.Game/Graphics/UserInterface/LoadingLayer.cs b/osu.Game/Graphics/UserInterface/LoadingLayer.cs
index c8c4424bee..47ba5fce4d 100644
--- a/osu.Game/Graphics/UserInterface/LoadingLayer.cs
+++ b/osu.Game/Graphics/UserInterface/LoadingLayer.cs
@@ -2,8 +2,9 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using JetBrains.Annotations;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osuTK;
using osuTK.Graphics;
@@ -17,22 +18,32 @@ namespace osu.Game.Graphics.UserInterface
///
public class LoadingLayer : LoadingSpinner
{
- private readonly Drawable dimTarget;
+ [CanBeNull]
+ protected Box BackgroundDimLayer { get; }
///
- /// Constuct a new loading spinner.
+ /// Construct a new loading spinner.
///
- /// An optional target to dim when displayed.
+ /// Whether the full background area should be dimmed while loading.
/// Whether the spinner should have a surrounding black box for visibility.
- public LoadingLayer(Drawable dimTarget = null, bool withBox = true)
+ public LoadingLayer(bool dimBackground = false, bool withBox = true)
: base(withBox)
{
RelativeSizeAxes = Axes.Both;
Size = new Vector2(1);
- this.dimTarget = dimTarget;
-
MainContents.RelativeSizeAxes = Axes.None;
+
+ if (dimBackground)
+ {
+ AddInternal(BackgroundDimLayer = new Box
+ {
+ Depth = float.MaxValue,
+ Colour = Color4.Black,
+ Alpha = 0,
+ RelativeSizeAxes = Axes.Both,
+ });
+ }
}
public override bool HandleNonPositionalInput => false;
@@ -56,31 +67,21 @@ namespace osu.Game.Graphics.UserInterface
protected override void PopIn()
{
- dimTarget?.FadeColour(OsuColour.Gray(0.5f), TRANSITION_DURATION, Easing.OutQuint);
+ BackgroundDimLayer?.FadeTo(0.5f, TRANSITION_DURATION * 2, Easing.OutQuint);
base.PopIn();
}
protected override void PopOut()
{
- dimTarget?.FadeColour(Color4.White, TRANSITION_DURATION, Easing.OutQuint);
+ BackgroundDimLayer?.FadeOut(TRANSITION_DURATION, Easing.OutQuint);
base.PopOut();
}
protected override void Update()
{
base.Update();
+
MainContents.Size = new Vector2(Math.Clamp(Math.Min(DrawWidth, DrawHeight) * 0.25f, 30, 100));
}
-
- protected override void Dispose(bool isDisposing)
- {
- base.Dispose(isDisposing);
-
- if (State.Value == Visibility.Visible)
- {
- // ensure we don't leave the target in a bad state.
- dimTarget?.FadeColour(Color4.White, TRANSITION_DURATION, Easing.OutQuint);
- }
- }
}
}
diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs
index 1270df5374..b8c2fa201f 100644
--- a/osu.Game/Input/Bindings/GlobalActionContainer.cs
+++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs
@@ -34,7 +34,7 @@ namespace osu.Game.Input.Bindings
new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.R }, GlobalAction.ResetInputSettings),
new KeyBinding(new[] { InputKey.Control, InputKey.T }, GlobalAction.ToggleToolbar),
new KeyBinding(new[] { InputKey.Control, InputKey.O }, GlobalAction.ToggleSettings),
- new KeyBinding(new[] { InputKey.Control, InputKey.D }, GlobalAction.ToggleDirect),
+ new KeyBinding(new[] { InputKey.Control, InputKey.D }, GlobalAction.ToggleBeatmapListing),
new KeyBinding(new[] { InputKey.Control, InputKey.N }, GlobalAction.ToggleNotifications),
new KeyBinding(InputKey.Escape, GlobalAction.Back),
@@ -112,8 +112,8 @@ namespace osu.Game.Input.Bindings
[Description("Toggle settings")]
ToggleSettings,
- [Description("Toggle osu!direct")]
- ToggleDirect,
+ [Description("Toggle beatmap listing")]
+ ToggleBeatmapListing,
[Description("Increase volume")]
IncreaseVolume,
diff --git a/osu.Game/Online/API/APIMod.cs b/osu.Game/Online/API/APIMod.cs
index 780e5daa16..c8b76b9685 100644
--- a/osu.Game/Online/API/APIMod.cs
+++ b/osu.Game/Online/API/APIMod.cs
@@ -31,7 +31,12 @@ namespace osu.Game.Online.API
Acronym = mod.Acronym;
foreach (var (_, property) in mod.GetSettingsSourceProperties())
- Settings.Add(property.Name.Underscore(), property.GetValue(mod));
+ {
+ var bindable = (IBindable)property.GetValue(mod);
+
+ if (!bindable.IsDefault)
+ Settings.Add(property.Name.Underscore(), bindable);
+ }
}
public Mod ToMod(Ruleset ruleset)
@@ -46,7 +51,7 @@ namespace osu.Game.Online.API
if (!Settings.TryGetValue(property.Name.Underscore(), out object settingValue))
continue;
- ((IBindable)property.GetValue(resultMod)).Parse(settingValue);
+ resultMod.CopyAdjustedSetting((IBindable)property.GetValue(resultMod), settingValue);
}
return resultMod;
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index 17831ed26b..36e3078653 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -151,11 +151,11 @@ namespace osu.Game
updateBlockingOverlayFade();
}
- public void RemoveBlockingOverlay(OverlayContainer overlay)
+ public void RemoveBlockingOverlay(OverlayContainer overlay) => Schedule(() =>
{
visibleBlockingOverlays.Remove(overlay);
updateBlockingOverlayFade();
- }
+ });
///
/// Close all game-wide overlays.
diff --git a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs
index a0b1b27ebf..bcb3d4b635 100644
--- a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs
+++ b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs
@@ -48,11 +48,9 @@ namespace osu.Game.Overlays.AccountCreation
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
- FillFlowContainer mainContent;
-
InternalChildren = new Drawable[]
{
- mainContent = new FillFlowContainer
+ new FillFlowContainer
{
RelativeSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
@@ -124,7 +122,7 @@ namespace osu.Game.Overlays.AccountCreation
},
},
},
- loadingLayer = new LoadingLayer(mainContent)
+ loadingLayer = new LoadingLayer(true)
};
textboxes = new[] { usernameTextBox, emailTextBox, passwordTextBox };
diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs
index 1e29e713af..0c9c995dd6 100644
--- a/osu.Game/Overlays/BeatmapListingOverlay.cs
+++ b/osu.Game/Overlays/BeatmapListingOverlay.cs
@@ -92,14 +92,14 @@ namespace osu.Game.Overlays
{
foundContent = new FillFlowContainer(),
notFoundContent = new NotFoundDrawable(),
- loadingLayer = new LoadingLayer(panelTarget)
}
}
- }
+ },
},
}
- }
- }
+ },
+ },
+ loadingLayer = new LoadingLayer(true)
};
}
diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs
index c983b337b5..7ad6906cea 100644
--- a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs
+++ b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs
@@ -53,7 +53,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons
Size = new Vector2(18),
Shadow = false,
},
- loading = new LoadingLayer(icon, false),
+ loading = new LoadingLayer(true, false),
});
Action = () =>
diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs
index 9a2dcd014a..b598b7d97f 100644
--- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs
+++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs
@@ -157,11 +157,11 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
}
}
},
- loading = new LoadingLayer()
}
}
- }
- }
+ },
+ },
+ loading = new LoadingLayer()
});
}
@@ -228,7 +228,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
{
Scores = null;
notSupporterPlaceholder.Show();
+
loading.Hide();
+ loading.FinishTransforms();
return;
}
@@ -241,6 +243,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
getScoresRequest.Success += scores =>
{
loading.Hide();
+ loading.FinishTransforms();
+
Scores = scores;
if (!scores.Scores.Any())
diff --git a/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs b/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs
index cc26a11da1..e6fe6ac749 100644
--- a/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs
+++ b/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs
@@ -128,7 +128,7 @@ namespace osu.Game.Overlays.Dashboard.Friends
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding { Horizontal = 50 }
},
- loading = new LoadingLayer(itemsPlaceholder)
+ loading = new LoadingLayer(true)
}
}
}
diff --git a/osu.Game/Overlays/DashboardOverlay.cs b/osu.Game/Overlays/DashboardOverlay.cs
index 04defce636..03c320debe 100644
--- a/osu.Game/Overlays/DashboardOverlay.cs
+++ b/osu.Game/Overlays/DashboardOverlay.cs
@@ -68,7 +68,7 @@ namespace osu.Game.Overlays
}
}
},
- loading = new LoadingLayer(content),
+ loading = new LoadingLayer(true),
};
}
diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs
index e574828cd2..ab8efdabcc 100644
--- a/osu.Game/Overlays/Mods/ModButton.cs
+++ b/osu.Game/Overlays/Mods/ModButton.cs
@@ -52,9 +52,10 @@ namespace osu.Game.Overlays.Mods
if (newIndex == selectedIndex) return false;
int direction = newIndex < selectedIndex ? -1 : 1;
+
bool beforeSelected = Selected;
- Mod modBefore = SelectedMod ?? Mods[0];
+ Mod previousSelection = SelectedMod ?? Mods[0];
if (newIndex >= Mods.Length)
newIndex = -1;
@@ -65,40 +66,45 @@ namespace osu.Game.Overlays.Mods
return false;
selectedIndex = newIndex;
- Mod modAfter = SelectedMod ?? Mods[0];
- if (beforeSelected != Selected)
+ Mod newSelection = SelectedMod ?? Mods[0];
+
+ Schedule(() =>
{
- iconsContainer.RotateTo(Selected ? 5f : 0f, 300, Easing.OutElastic);
- iconsContainer.ScaleTo(Selected ? 1.1f : 1f, 300, Easing.OutElastic);
- }
-
- if (modBefore != modAfter)
- {
- const float rotate_angle = 16;
-
- foregroundIcon.RotateTo(rotate_angle * direction, mod_switch_duration, mod_switch_easing);
- backgroundIcon.RotateTo(-rotate_angle * direction, mod_switch_duration, mod_switch_easing);
-
- backgroundIcon.Mod = modAfter;
-
- using (BeginDelayedSequence(mod_switch_duration, true))
+ if (beforeSelected != Selected)
{
- foregroundIcon
- .RotateTo(-rotate_angle * direction)
- .RotateTo(0f, mod_switch_duration, mod_switch_easing);
-
- backgroundIcon
- .RotateTo(rotate_angle * direction)
- .RotateTo(0f, mod_switch_duration, mod_switch_easing);
-
- Schedule(() => displayMod(modAfter));
+ iconsContainer.RotateTo(Selected ? 5f : 0f, 300, Easing.OutElastic);
+ iconsContainer.ScaleTo(Selected ? 1.1f : 1f, 300, Easing.OutElastic);
}
- }
- foregroundIcon.Selected.Value = Selected;
+ if (previousSelection != newSelection)
+ {
+ const float rotate_angle = 16;
+
+ foregroundIcon.RotateTo(rotate_angle * direction, mod_switch_duration, mod_switch_easing);
+ backgroundIcon.RotateTo(-rotate_angle * direction, mod_switch_duration, mod_switch_easing);
+
+ backgroundIcon.Mod = newSelection;
+
+ using (BeginDelayedSequence(mod_switch_duration, true))
+ {
+ foregroundIcon
+ .RotateTo(-rotate_angle * direction)
+ .RotateTo(0f, mod_switch_duration, mod_switch_easing);
+
+ backgroundIcon
+ .RotateTo(rotate_angle * direction)
+ .RotateTo(0f, mod_switch_duration, mod_switch_easing);
+
+ Schedule(() => displayMod(newSelection));
+ }
+ }
+
+ foregroundIcon.Selected.Value = Selected;
+ });
SelectionChanged?.Invoke(SelectedMod);
+
return true;
}
diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs
index 34f5c70adb..491052fa2c 100644
--- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs
+++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs
@@ -249,7 +249,7 @@ namespace osu.Game.Overlays.Mods
{
Width = 180,
Text = "Deselect All",
- Action = DeselectAll,
+ Action = deselectAll,
Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft,
},
@@ -318,7 +318,7 @@ namespace osu.Game.Overlays.Mods
sampleOff = audio.Samples.Get(@"UI/check-off");
}
- public void DeselectAll()
+ private void deselectAll()
{
foreach (var section in ModSectionsContainer.Children)
section.DeselectAll();
@@ -331,7 +331,7 @@ namespace osu.Game.Overlays.Mods
///
/// The types of s which should be deselected.
/// Set to true to bypass animations and update selections immediately.
- public void DeselectTypes(Type[] modTypes, bool immediate = false)
+ private void deselectTypes(Type[] modTypes, bool immediate = false)
{
if (modTypes.Length == 0) return;
@@ -438,7 +438,7 @@ namespace osu.Game.Overlays.Mods
{
if (State.Value == Visibility.Visible) sampleOn?.Play();
- DeselectTypes(selectedMod.IncompatibleMods, true);
+ deselectTypes(selectedMod.IncompatibleMods, true);
if (selectedMod.RequiresConfiguration) ModSettingsContainer.Show();
}
diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs
index c8c1db012f..5820d405d4 100644
--- a/osu.Game/Overlays/NewsOverlay.cs
+++ b/osu.Game/Overlays/NewsOverlay.cs
@@ -59,7 +59,7 @@ namespace osu.Game.Overlays
},
},
},
- loading = new LoadingLayer(content),
+ loading = new LoadingLayer(true),
};
}
diff --git a/osu.Game/Overlays/Rankings/SpotlightsLayout.cs b/osu.Game/Overlays/Rankings/SpotlightsLayout.cs
index 61339df76f..b16e0a4908 100644
--- a/osu.Game/Overlays/Rankings/SpotlightsLayout.cs
+++ b/osu.Game/Overlays/Rankings/SpotlightsLayout.cs
@@ -45,6 +45,7 @@ namespace osu.Game.Overlays.Rankings
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
+
InternalChild = new ReverseChildIDFillFlowContainer
{
RelativeSizeAxes = Axes.X,
@@ -68,7 +69,7 @@ namespace osu.Game.Overlays.Rankings
AutoSizeAxes = Axes.Y,
Margin = new MarginPadding { Vertical = 10 }
},
- loading = new LoadingLayer(content)
+ loading = new LoadingLayer(true)
}
}
}
diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs
index ae6d49960a..25350e310a 100644
--- a/osu.Game/Overlays/RankingsOverlay.cs
+++ b/osu.Game/Overlays/RankingsOverlay.cs
@@ -42,6 +42,8 @@ namespace osu.Game.Overlays
Depth = -float.MaxValue
})
{
+ loading = new LoadingLayer(true);
+
Children = new Drawable[]
{
background = new Box
@@ -74,12 +76,12 @@ namespace osu.Game.Overlays
RelativeSizeAxes = Axes.X,
Margin = new MarginPadding { Bottom = 10 }
},
- loading = new LoadingLayer(contentContainer),
}
}
}
}
- }
+ },
+ loading
};
}
diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
index 3d3b543d70..7acbf038d8 100644
--- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
@@ -132,6 +132,15 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
}
},
};
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ scalingSettings.ForEach(s => bindPreviewEvent(s.Current));
+
+ windowModeDropdown.Current.ValueChanged += _ => updateResolutionDropdown();
windowModes.BindCollectionChanged((sender, args) =>
{
@@ -141,8 +150,6 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
windowModeDropdown.Hide();
}, true);
- windowModeDropdown.Current.ValueChanged += _ => updateResolutionDropdown();
-
currentDisplay.BindValueChanged(display => Schedule(() =>
{
resolutions.RemoveRange(1, resolutions.Count - 1);
@@ -159,8 +166,6 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
updateResolutionDropdown();
}), true);
- scalingSettings.ForEach(s => bindPreviewEvent(s.Current));
-
scalingMode.BindValueChanged(mode =>
{
scalingSettings.ClearTransforms();
@@ -181,11 +186,6 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
}
}
- ///
- /// Create a delayed bindable which only updates when a condition is met.
- ///
- /// The config bindable.
- /// A bindable which will propagate updates with a delay.
private void bindPreviewEvent(Bindable bindable)
{
bindable.ValueChanged += _ =>
diff --git a/osu.Game/Overlays/Settings/Sections/Online/IntegrationSettings.cs b/osu.Game/Overlays/Settings/Sections/Online/IntegrationSettings.cs
new file mode 100644
index 0000000000..d2867962c0
--- /dev/null
+++ b/osu.Game/Overlays/Settings/Sections/Online/IntegrationSettings.cs
@@ -0,0 +1,27 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Game.Configuration;
+
+namespace osu.Game.Overlays.Settings.Sections.Online
+{
+ public class IntegrationSettings : SettingsSubsection
+ {
+ protected override string Header => "Integrations";
+
+ [BackgroundDependencyLoader]
+ private void load(OsuConfigManager config)
+ {
+ Children = new Drawable[]
+ {
+ new SettingsEnumDropdown
+ {
+ LabelText = "Discord Rich Presence",
+ Current = config.GetBindable(OsuSetting.DiscordRichPresence)
+ }
+ };
+ }
+ }
+}
diff --git a/osu.Game/Overlays/Settings/Sections/OnlineSection.cs b/osu.Game/Overlays/Settings/Sections/OnlineSection.cs
index 150cddb388..7aa4eff29a 100644
--- a/osu.Game/Overlays/Settings/Sections/OnlineSection.cs
+++ b/osu.Game/Overlays/Settings/Sections/OnlineSection.cs
@@ -20,7 +20,8 @@ namespace osu.Game.Overlays.Settings.Sections
{
Children = new Drawable[]
{
- new WebSettings()
+ new WebSettings(),
+ new IntegrationSettings()
};
}
}
diff --git a/osu.Game/Overlays/Toolbar/ToolbarBeatmapListingButton.cs b/osu.Game/Overlays/Toolbar/ToolbarBeatmapListingButton.cs
index 0363873326..bfe36a6a0f 100644
--- a/osu.Game/Overlays/Toolbar/ToolbarBeatmapListingButton.cs
+++ b/osu.Game/Overlays/Toolbar/ToolbarBeatmapListingButton.cs
@@ -2,15 +2,18 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
+using osu.Framework.Graphics;
using osu.Game.Input.Bindings;
namespace osu.Game.Overlays.Toolbar
{
public class ToolbarBeatmapListingButton : ToolbarOverlayToggleButton
{
+ protected override Anchor TooltipAnchor => Anchor.TopRight;
+
public ToolbarBeatmapListingButton()
{
- Hotkey = GlobalAction.ToggleDirect;
+ Hotkey = GlobalAction.ToggleBeatmapListing;
}
[BackgroundDependencyLoader(true)]
diff --git a/osu.Game/Overlays/Toolbar/ToolbarChangelogButton.cs b/osu.Game/Overlays/Toolbar/ToolbarChangelogButton.cs
index 23f8b141b2..86bc73361a 100644
--- a/osu.Game/Overlays/Toolbar/ToolbarChangelogButton.cs
+++ b/osu.Game/Overlays/Toolbar/ToolbarChangelogButton.cs
@@ -2,11 +2,14 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
+using osu.Framework.Graphics;
namespace osu.Game.Overlays.Toolbar
{
public class ToolbarChangelogButton : ToolbarOverlayToggleButton
{
+ protected override Anchor TooltipAnchor => Anchor.TopRight;
+
[BackgroundDependencyLoader(true)]
private void load(ChangelogOverlay changelog)
{
diff --git a/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs b/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs
index f9a66ae7bb..2d3b33e9bc 100644
--- a/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs
+++ b/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs
@@ -2,12 +2,15 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
+using osu.Framework.Graphics;
using osu.Game.Input.Bindings;
namespace osu.Game.Overlays.Toolbar
{
public class ToolbarChatButton : ToolbarOverlayToggleButton
{
+ protected override Anchor TooltipAnchor => Anchor.TopRight;
+
public ToolbarChatButton()
{
Hotkey = GlobalAction.ToggleChat;
diff --git a/osu.Game/Overlays/Toolbar/ToolbarNewsButton.cs b/osu.Game/Overlays/Toolbar/ToolbarNewsButton.cs
index 0ba2935c80..9b2573ad07 100644
--- a/osu.Game/Overlays/Toolbar/ToolbarNewsButton.cs
+++ b/osu.Game/Overlays/Toolbar/ToolbarNewsButton.cs
@@ -2,11 +2,14 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
+using osu.Framework.Graphics;
namespace osu.Game.Overlays.Toolbar
{
public class ToolbarNewsButton : ToolbarOverlayToggleButton
{
+ protected override Anchor TooltipAnchor => Anchor.TopRight;
+
[BackgroundDependencyLoader(true)]
private void load(NewsOverlay news)
{
diff --git a/osu.Game/Overlays/Toolbar/ToolbarRankingsButton.cs b/osu.Game/Overlays/Toolbar/ToolbarRankingsButton.cs
index 22a01bcdb5..312fc41aab 100644
--- a/osu.Game/Overlays/Toolbar/ToolbarRankingsButton.cs
+++ b/osu.Game/Overlays/Toolbar/ToolbarRankingsButton.cs
@@ -2,11 +2,14 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
+using osu.Framework.Graphics;
namespace osu.Game.Overlays.Toolbar
{
public class ToolbarRankingsButton : ToolbarOverlayToggleButton
{
+ protected override Anchor TooltipAnchor => Anchor.TopRight;
+
[BackgroundDependencyLoader(true)]
private void load(RankingsOverlay rankings)
{
diff --git a/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs b/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs
index e62c7bc807..1e00afc5fd 100644
--- a/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs
+++ b/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs
@@ -2,12 +2,15 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
+using osu.Framework.Graphics;
using osu.Game.Input.Bindings;
namespace osu.Game.Overlays.Toolbar
{
public class ToolbarSocialButton : ToolbarOverlayToggleButton
{
+ protected override Anchor TooltipAnchor => Anchor.TopRight;
+
public ToolbarSocialButton()
{
Hotkey = GlobalAction.ToggleSocial;
diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs
index b8dc7a2661..24d184e531 100644
--- a/osu.Game/Rulesets/Mods/Mod.cs
+++ b/osu.Game/Rulesets/Mods/Mod.cs
@@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics;
using System.Linq;
using System.Reflection;
using Newtonsoft.Json;
@@ -84,12 +83,10 @@ namespace osu.Game.Rulesets.Mods
foreach ((SettingSourceAttribute attr, PropertyInfo property) in this.GetOrderedSettingsSourceProperties())
{
- object bindableObj = property.GetValue(this);
+ var bindable = (IBindable)property.GetValue(this);
- if ((bindableObj as IHasDefaultValue)?.IsDefault == true)
- continue;
-
- tooltipTexts.Add($"{attr.Label} {bindableObj}");
+ if (!bindable.IsDefault)
+ tooltipTexts.Add($"{attr.Label} {bindable}");
}
return string.Join(", ", tooltipTexts.Where(s => !string.IsNullOrEmpty(s)));
@@ -136,19 +133,38 @@ namespace osu.Game.Rulesets.Mods
// Copy bindable values across
foreach (var (_, prop) in this.GetSettingsSourceProperties())
{
- var origBindable = prop.GetValue(this);
- var copyBindable = prop.GetValue(copy);
+ var origBindable = (IBindable)prop.GetValue(this);
+ var copyBindable = (IBindable)prop.GetValue(copy);
- // The bindables themselves are readonly, so the value must be transferred through the Bindable.Value property.
- var valueProperty = origBindable.GetType().GetProperty(nameof(Bindable