From 1d314a1f4b86ce5fd6ea95804acb8d411d95413c Mon Sep 17 00:00:00 2001 From: voidedWarranties Date: Wed, 11 Mar 2020 22:40:08 -0700 Subject: [PATCH 001/390] Prevent playback from going beyond song end --- .../Edit/Components/PlaybackControl.cs | 3 +++ osu.Game/Screens/Edit/EditorClock.cs | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/osu.Game/Screens/Edit/Components/PlaybackControl.cs b/osu.Game/Screens/Edit/Components/PlaybackControl.cs index 897c6ec531..ff650a7ad7 100644 --- a/osu.Game/Screens/Edit/Components/PlaybackControl.cs +++ b/osu.Game/Screens/Edit/Components/PlaybackControl.cs @@ -87,6 +87,9 @@ namespace osu.Game.Screens.Edit.Components private void togglePause() { + if ((adjustableClock as EditorClock)?.PlaybackFinished == true) + adjustableClock.Seek(0); + if (adjustableClock.IsRunning) adjustableClock.Stop(); else diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index e5e47507f3..0e5b42fe69 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -21,6 +21,8 @@ namespace osu.Game.Screens.Edit private readonly BindableBeatDivisor beatDivisor; + public bool PlaybackFinished { get; private set; } + public EditorClock(WorkingBeatmap beatmap, BindableBeatDivisor beatDivisor) { this.beatDivisor = beatDivisor; @@ -37,6 +39,23 @@ namespace osu.Game.Screens.Edit TrackLength = trackLength; } + public override void ProcessFrame() + { + base.ProcessFrame(); + + if (CurrentTime >= TrackLength) + { + if (!PlaybackFinished) + { + PlaybackFinished = true; + Stop(); + Seek(TrackLength); + } + } + else + PlaybackFinished = false; + } + /// /// Seek to the closest snappable beat from a time. /// From 7e4f58c2d3adc15ccb14a69d99094de5efcd7c13 Mon Sep 17 00:00:00 2001 From: voidedWarranties Date: Fri, 13 Mar 2020 16:42:05 -0700 Subject: [PATCH 002/390] Internalize both looping and stopping --- .../Screens/Edit/Components/PlaybackControl.cs | 3 --- osu.Game/Screens/Edit/EditorClock.cs | 14 ++++++++------ 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/PlaybackControl.cs b/osu.Game/Screens/Edit/Components/PlaybackControl.cs index ff650a7ad7..897c6ec531 100644 --- a/osu.Game/Screens/Edit/Components/PlaybackControl.cs +++ b/osu.Game/Screens/Edit/Components/PlaybackControl.cs @@ -87,9 +87,6 @@ namespace osu.Game.Screens.Edit.Components private void togglePause() { - if ((adjustableClock as EditorClock)?.PlaybackFinished == true) - adjustableClock.Seek(0); - if (adjustableClock.IsRunning) adjustableClock.Stop(); else diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index 0e5b42fe69..aef304bd6e 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -21,7 +21,7 @@ namespace osu.Game.Screens.Edit private readonly BindableBeatDivisor beatDivisor; - public bool PlaybackFinished { get; private set; } + private bool playbackFinished; public EditorClock(WorkingBeatmap beatmap, BindableBeatDivisor beatDivisor) { @@ -43,17 +43,19 @@ namespace osu.Game.Screens.Edit { base.ProcessFrame(); - if (CurrentTime >= TrackLength) + var playbackAlreadyStopped = playbackFinished; + playbackFinished = CurrentTime >= TrackLength; + + if (playbackFinished && IsRunning) { - if (!PlaybackFinished) + if (!playbackAlreadyStopped) { - PlaybackFinished = true; Stop(); Seek(TrackLength); } + else + Seek(0); } - else - PlaybackFinished = false; } /// From a38c912c6d4060a85f5172adf1bcc89e0ed9dc0e Mon Sep 17 00:00:00 2001 From: voidedWarranties Date: Sat, 21 Mar 2020 12:15:20 -0700 Subject: [PATCH 003/390] Test stopping behavior --- .../Visual/Editor/TestSceneEditorClock.cs | 51 +++++++++++++++++++ .../Visual/Editor/TimelineTestScene.cs | 16 +++--- 2 files changed, 58 insertions(+), 9 deletions(-) create mode 100644 osu.Game.Tests/Visual/Editor/TestSceneEditorClock.cs diff --git a/osu.Game.Tests/Visual/Editor/TestSceneEditorClock.cs b/osu.Game.Tests/Visual/Editor/TestSceneEditorClock.cs new file mode 100644 index 0000000000..0b128a974c --- /dev/null +++ b/osu.Game.Tests/Visual/Editor/TestSceneEditorClock.cs @@ -0,0 +1,51 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Screens.Edit.Components; +using osuTK; + +namespace osu.Game.Tests.Visual.Editor +{ + [TestFixture] + public class TestSceneEditorClock : EditorClockTestScene + { + public TestSceneEditorClock() + { + Add(new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new TimeInfoContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(200, 100) + }, + new PlaybackControl + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(200, 100) + } + } + }); + } + + [Test] + public void TestStopAtTrackEnd() + { + AddStep("Reset clock", () => Clock.Seek(0)); + AddStep("Start clock", Clock.Start); + AddAssert("Clock running", () => Clock.IsRunning); + AddStep("Seek near end", () => Clock.Seek(Clock.TrackLength - 250)); + AddUntilStep("Clock stops", () => !Clock.IsRunning); + AddAssert("Clock stopped at end", () => Clock.CurrentTime == Clock.TrackLength); + AddStep("Start clock again", Clock.Start); + AddAssert("Clock looped", () => Clock.IsRunning && Clock.CurrentTime < Clock.TrackLength); + } + } +} diff --git a/osu.Game.Tests/Visual/Editor/TimelineTestScene.cs b/osu.Game.Tests/Visual/Editor/TimelineTestScene.cs index 7081eb3af5..83a0455b46 100644 --- a/osu.Game.Tests/Visual/Editor/TimelineTestScene.cs +++ b/osu.Game.Tests/Visual/Editor/TimelineTestScene.cs @@ -113,7 +113,6 @@ namespace osu.Game.Tests.Visual.Editor private class StartStopButton : OsuButton { private IAdjustableClock adjustableClock; - private bool started; public StartStopButton() { @@ -132,18 +131,17 @@ namespace osu.Game.Tests.Visual.Editor private void onClick() { - if (started) - { + if (adjustableClock.IsRunning) adjustableClock.Stop(); - Text = "Start"; - } else - { adjustableClock.Start(); - Text = "Stop"; - } + } - started = !started; + protected override void Update() + { + base.Update(); + + Text = adjustableClock.IsRunning ? "Stop" : "Start"; } } } From b41f3f1cad7312708d864269cfb1c587ed2353ca Mon Sep 17 00:00:00 2001 From: voidedWarranties Date: Mon, 23 Mar 2020 22:37:53 -0700 Subject: [PATCH 004/390] Fix seeking back to beginning too early --- .../Compose/Components/Timeline/Timeline.cs | 4 +++- osu.Game/Screens/Edit/EditorClock.cs | 21 +++++++++++-------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index ddca5e42c2..590abf20b4 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -130,7 +130,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (!track.IsLoaded) return; - adjustableClock.Seek(Current / Content.DrawWidth * track.Length); + double target = Current / Content.DrawWidth * track.Length; + + adjustableClock.Seek(Math.Min(track.Length, target)); } private void scrollToTrackTime() diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index aef304bd6e..b4ab867774 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -43,18 +43,21 @@ namespace osu.Game.Screens.Edit { base.ProcessFrame(); - var playbackAlreadyStopped = playbackFinished; - playbackFinished = CurrentTime >= TrackLength; - - if (playbackFinished && IsRunning) + if (IsRunning) { - if (!playbackAlreadyStopped) + var playbackAlreadyStopped = playbackFinished; + playbackFinished = CurrentTime >= TrackLength; + + if (playbackFinished) { - Stop(); - Seek(TrackLength); + if (!playbackAlreadyStopped) + { + Stop(); + Seek(TrackLength); + } + else + Seek(0); } - else - Seek(0); } } From d42c872f8f19923dd56388cd1ba632cc7298292a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Mar 2020 16:02:20 +0900 Subject: [PATCH 005/390] Better ensure track restarted --- osu.Game.Tests/Visual/Editor/TestSceneEditorClock.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editor/TestSceneEditorClock.cs b/osu.Game.Tests/Visual/Editor/TestSceneEditorClock.cs index 0b128a974c..a824696022 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneEditorClock.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneEditorClock.cs @@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.Editor AddUntilStep("Clock stops", () => !Clock.IsRunning); AddAssert("Clock stopped at end", () => Clock.CurrentTime == Clock.TrackLength); AddStep("Start clock again", Clock.Start); - AddAssert("Clock looped", () => Clock.IsRunning && Clock.CurrentTime < Clock.TrackLength); + AddAssert("Clock looped to start", () => Clock.IsRunning && Clock.CurrentTime < 500); } } } From 00574a528868d1ef31907484e71623969c0cfeaf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Feb 2021 18:32:28 +0900 Subject: [PATCH 006/390] Use ISample everywhere in Skin GetSample lookup path --- .../Skinning/Legacy/ManiaLegacySkinTransformer.cs | 2 +- osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs | 2 +- osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs | 2 +- .../Skinning/Legacy/TaikoLegacySkinTransformer.cs | 2 +- osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs | 2 +- osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs | 4 ++-- .../NonVisual/Skinning/LegacySkinAnimationTest.cs | 2 +- osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs | 2 +- .../Visual/Gameplay/TestSceneSkinnableDrawable.cs | 6 +++--- osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs | 2 +- osu.Game/Skinning/DefaultSkin.cs | 2 +- osu.Game/Skinning/ISkin.cs | 2 +- osu.Game/Skinning/LegacyBeatmapSkin.cs | 2 +- osu.Game/Skinning/LegacySkin.cs | 2 +- osu.Game/Skinning/LegacySkinTransformer.cs | 2 +- osu.Game/Skinning/Skin.cs | 2 +- osu.Game/Skinning/SkinManager.cs | 2 +- osu.Game/Skinning/SkinProvidingContainer.cs | 4 ++-- 18 files changed, 22 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs index cbbbacfe19..24ccae895d 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs @@ -140,7 +140,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy return animation == null ? null : new LegacyManiaJudgementPiece(result, animation); } - public override Sample GetSample(ISampleInfo sampleInfo) + public override ISample GetSample(ISampleInfo sampleInfo) { // layered hit sounds never play in mania if (sampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacySample && legacySample.IsLayered) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs index e2d9f144c0..8fd13c7417 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs @@ -98,7 +98,7 @@ namespace osu.Game.Rulesets.Osu.Tests return null; } - public Sample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); + public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException(); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs index 8dbb48c048..19b6779619 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs @@ -162,7 +162,7 @@ namespace osu.Game.Rulesets.Osu.Tests public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => null; - public Sample GetSample(ISampleInfo sampleInfo) => null; + public ISample GetSample(ISampleInfo sampleInfo) => null; public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => default; public IBindable GetConfig(TLookup lookup) => null; diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs index 9f29675230..d1214d3456 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs @@ -152,7 +152,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy throw new ArgumentOutOfRangeException(nameof(component), $"Invalid component type: {component}"); } - public override Sample GetSample(ISampleInfo sampleInfo) => Source.GetSample(new LegacyTaikoSampleInfo(sampleInfo)); + public override ISample GetSample(ISampleInfo sampleInfo) => Source.GetSample(new LegacyTaikoSampleInfo(sampleInfo)); public override IBindable GetConfig(TLookup lookup) => Source.GetConfig(lookup); diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs index 3ded3009bd..883791c35c 100644 --- a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs +++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs @@ -121,7 +121,7 @@ namespace osu.Game.Tests.Gameplay public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => throw new NotImplementedException(); - public Sample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); + public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); public IBindable GetConfig(TLookup lookup) { diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs index 7a0dd5b719..6fa1839556 100644 --- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs @@ -35,7 +35,7 @@ namespace osu.Game.Tests.Gameplay public void TestRetrieveTopLevelSample() { ISkin skin = null; - Sample channel = null; + ISample channel = null; AddStep("create skin", () => skin = new TestSkin("test-sample", this)); AddStep("retrieve sample", () => channel = skin.GetSample(new SampleInfo("test-sample"))); @@ -47,7 +47,7 @@ namespace osu.Game.Tests.Gameplay public void TestRetrieveSampleInSubFolder() { ISkin skin = null; - Sample channel = null; + ISample channel = null; AddStep("create skin", () => skin = new TestSkin("folder/test-sample", this)); AddStep("retrieve sample", () => channel = skin.GetSample(new SampleInfo("folder/test-sample"))); diff --git a/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs b/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs index da004b9088..b08a228de3 100644 --- a/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs +++ b/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs @@ -59,7 +59,7 @@ namespace osu.Game.Tests.NonVisual.Skinning } public Drawable GetDrawableComponent(ISkinComponent component) => throw new NotSupportedException(); - public Sample GetSample(ISampleInfo sampleInfo) => throw new NotSupportedException(); + public ISample GetSample(ISampleInfo sampleInfo) => throw new NotSupportedException(); public IBindable GetConfig(TLookup lookup) => throw new NotSupportedException(); } diff --git a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs index 414f7d3f88..732a3f3f42 100644 --- a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs +++ b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs @@ -219,7 +219,7 @@ namespace osu.Game.Tests.Skins public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => skin.GetTexture(componentName, wrapModeS, wrapModeT); - public Sample GetSample(ISampleInfo sampleInfo) => skin.GetSample(sampleInfo); + public ISample GetSample(ISampleInfo sampleInfo) => skin.GetSample(sampleInfo); public IBindable GetConfig(TLookup lookup) => skin.GetConfig(lookup); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs index 44142b69d7..7a6e2f54c2 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs @@ -298,7 +298,7 @@ namespace osu.Game.Tests.Visual.Gameplay public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => throw new NotImplementedException(); - public Sample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); + public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException(); } @@ -309,7 +309,7 @@ namespace osu.Game.Tests.Visual.Gameplay public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => throw new NotImplementedException(); - public Sample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); + public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException(); } @@ -321,7 +321,7 @@ namespace osu.Game.Tests.Visual.Gameplay public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => throw new NotImplementedException(); - public Sample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); + public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException(); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs index d688e9cb21..d792405eeb 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs @@ -145,7 +145,7 @@ namespace osu.Game.Tests.Visual.Gameplay public Drawable GetDrawableComponent(ISkinComponent component) => source?.GetDrawableComponent(component); public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => source?.GetTexture(componentName, wrapModeS, wrapModeT); - public Sample GetSample(ISampleInfo sampleInfo) => source?.GetSample(sampleInfo); + public ISample GetSample(ISampleInfo sampleInfo) => source?.GetSample(sampleInfo); public IBindable GetConfig(TLookup lookup) => source?.GetConfig(lookup); public void TriggerSourceChanged() diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs index 346c7b3c65..0b3f5f3cde 100644 --- a/osu.Game/Skinning/DefaultSkin.cs +++ b/osu.Game/Skinning/DefaultSkin.cs @@ -24,7 +24,7 @@ namespace osu.Game.Skinning public override Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => null; - public override Sample GetSample(ISampleInfo sampleInfo) => null; + public override ISample GetSample(ISampleInfo sampleInfo) => null; public override IBindable GetConfig(TLookup lookup) { diff --git a/osu.Game/Skinning/ISkin.cs b/osu.Game/Skinning/ISkin.cs index ef8de01042..73f7cf6d39 100644 --- a/osu.Game/Skinning/ISkin.cs +++ b/osu.Game/Skinning/ISkin.cs @@ -48,7 +48,7 @@ namespace osu.Game.Skinning /// The requested sample. /// A matching sample channel, or null if unavailable. [CanBeNull] - Sample GetSample(ISampleInfo sampleInfo); + ISample GetSample(ISampleInfo sampleInfo); /// /// Retrieve a configuration value. diff --git a/osu.Game/Skinning/LegacyBeatmapSkin.cs b/osu.Game/Skinning/LegacyBeatmapSkin.cs index fb4207b647..3ec205e897 100644 --- a/osu.Game/Skinning/LegacyBeatmapSkin.cs +++ b/osu.Game/Skinning/LegacyBeatmapSkin.cs @@ -39,7 +39,7 @@ namespace osu.Game.Skinning return base.GetConfig(lookup); } - public override Sample GetSample(ISampleInfo sampleInfo) + public override ISample GetSample(ISampleInfo sampleInfo) { if (sampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacy && legacy.CustomSampleBank == 0) { diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index e5d0217671..1ee797098c 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -452,7 +452,7 @@ namespace osu.Game.Skinning return null; } - public override Sample GetSample(ISampleInfo sampleInfo) + public override ISample GetSample(ISampleInfo sampleInfo) { IEnumerable lookupNames; diff --git a/osu.Game/Skinning/LegacySkinTransformer.cs b/osu.Game/Skinning/LegacySkinTransformer.cs index e2f4a82a54..ae8faf1a3b 100644 --- a/osu.Game/Skinning/LegacySkinTransformer.cs +++ b/osu.Game/Skinning/LegacySkinTransformer.cs @@ -34,7 +34,7 @@ namespace osu.Game.Skinning public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => Source.GetTexture(componentName, wrapModeS, wrapModeT); - public virtual Sample GetSample(ISampleInfo sampleInfo) + public virtual ISample GetSample(ISampleInfo sampleInfo) { if (!(sampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacySample)) return Source.GetSample(sampleInfo); diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index e8d84b49f9..13f5385c20 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -19,7 +19,7 @@ namespace osu.Game.Skinning public abstract Drawable GetDrawableComponent(ISkinComponent componentName); - public abstract Sample GetSample(ISampleInfo sampleInfo); + public abstract ISample GetSample(ISampleInfo sampleInfo); public Texture GetTexture(string componentName) => GetTexture(componentName, default, default); diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 2826c826a5..9e730b2ce1 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -171,7 +171,7 @@ namespace osu.Game.Skinning public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => CurrentSkin.Value.GetTexture(componentName, wrapModeS, wrapModeT); - public Sample GetSample(ISampleInfo sampleInfo) => CurrentSkin.Value.GetSample(sampleInfo); + public ISample GetSample(ISampleInfo sampleInfo) => CurrentSkin.Value.GetSample(sampleInfo); public IBindable GetConfig(TLookup lookup) => CurrentSkin.Value.GetConfig(lookup); diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index ba67d0a678..cf22b2e820 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -59,9 +59,9 @@ namespace osu.Game.Skinning return fallbackSource?.GetTexture(componentName, wrapModeS, wrapModeT); } - public Sample GetSample(ISampleInfo sampleInfo) + public ISample GetSample(ISampleInfo sampleInfo) { - Sample sourceChannel; + ISample sourceChannel; if (AllowSampleLookup(sampleInfo) && (sourceChannel = skin?.GetSample(sampleInfo)) != null) return sourceChannel; From 4aff54412a0a23707b4284510705dd2b2c91492d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Feb 2021 18:32:39 +0900 Subject: [PATCH 007/390] Move dispose method to end of file --- osu.Game/Skinning/LegacySkin.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 1ee797098c..12abc4d867 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -100,13 +100,6 @@ namespace osu.Game.Skinning true) != null); } - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - Textures?.Dispose(); - Samples?.Dispose(); - } - public override IBindable GetConfig(TLookup lookup) { switch (lookup) @@ -504,5 +497,12 @@ namespace osu.Game.Skinning string lastPiece = componentName.Split('/').Last(); yield return componentName.StartsWith("Gameplay/taiko/", StringComparison.Ordinal) ? "taiko-" + lastPiece : lastPiece; } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + Textures?.Dispose(); + Samples?.Dispose(); + } } } From 880fe820733d9159c6eb866f85336b01081ea1c2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Feb 2021 18:32:51 +0900 Subject: [PATCH 008/390] Add sample wrapper in LegacySkin to keep a reference and avoid GC death --- osu.Game/Skinning/LegacySkin.cs | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 12abc4d867..5d015ca5ab 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -461,12 +461,43 @@ namespace osu.Game.Skinning var sample = Samples?.Get(lookup); if (sample != null) - return sample; + return new LegacySkinSample(sample, this); } return null; } + /// + /// A sample wrapper which keeps a reference to the contained skin to avoid finalizer garbage collection of the managing SampleStore. + /// + private class LegacySkinSample : ISample + { + private readonly Sample sample; + + [UsedImplicitly] + private readonly LegacySkin skin; + + public LegacySkinSample(Sample sample, LegacySkin skin) + { + this.sample = sample; + this.skin = skin; + } + + public SampleChannel Play() + { + return sample.Play(); + } + + public SampleChannel GetChannel() + { + return sample.GetChannel(); + } + + public double Length => sample.Length; + + public Bindable PlaybackConcurrency => sample.PlaybackConcurrency; + } + private IEnumerable getLegacyLookupNames(HitSampleInfo hitSample) { var lookupNames = hitSample.LookupNames.SelectMany(getFallbackNames); From 487a39eea95a5215d5aec323c923a600144ba51a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Feb 2021 18:52:34 +0900 Subject: [PATCH 009/390] Update interface implementations with framework changes --- .../TestSceneDrawableRulesetDependencies.cs | 6 ++-- .../UI/DrawableRulesetDependencies.cs | 11 +++++++ osu.Game/Skinning/LegacySkin.cs | 33 +++++++++++++++++++ osu.Game/Skinning/PoolableSkinnableSample.cs | 8 +++++ osu.Game/Skinning/SkinnableSound.cs | 17 ++++++---- 5 files changed, 67 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs b/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs index 787f72ba79..a2f2c5e41f 100644 --- a/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs +++ b/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs @@ -118,9 +118,11 @@ namespace osu.Game.Tests.Rulesets public BindableNumber Frequency => throw new NotImplementedException(); public BindableNumber Tempo => throw new NotImplementedException(); - public void AddAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => throw new NotImplementedException(); + public void BindAdjustments(IAggregateAudioAdjustment component) => throw new NotImplementedException(); + public void UnbindAdjustments(IAggregateAudioAdjustment component) => throw new NotImplementedException(); - public void RemoveAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => throw new NotImplementedException(); + public void AddAdjustment(AdjustableProperty type, IBindable adjustBindable) => throw new NotImplementedException(); + public void RemoveAdjustment(AdjustableProperty type, IBindable adjustBindable) => throw new NotImplementedException(); public void RemoveAllAdjustments(AdjustableProperty type) => throw new NotImplementedException(); diff --git a/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs index deec948d14..6c31f05337 100644 --- a/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs +++ b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs @@ -114,6 +114,11 @@ namespace osu.Game.Rulesets.UI public void RemoveAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => throw new NotSupportedException(); + public void RemoveAdjustment(AdjustableProperty type, IBindable adjustBindable) + { + throw new NotImplementedException(); + } + public void RemoveAllAdjustments(AdjustableProperty type) => throw new NotSupportedException(); public BindableNumber Volume => throw new NotSupportedException(); @@ -124,6 +129,12 @@ namespace osu.Game.Rulesets.UI public BindableNumber Tempo => throw new NotSupportedException(); + public void BindAdjustments(IAggregateAudioAdjustment component) => throw new NotImplementedException(); + + public void UnbindAdjustments(IAggregateAudioAdjustment component) => throw new NotImplementedException(); + + public void AddAdjustment(AdjustableProperty type, IBindable adjustBindable) => throw new NotImplementedException(); + public IBindable GetAggregate(AdjustableProperty type) => throw new NotSupportedException(); public IBindable AggregateVolume => throw new NotSupportedException(); diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 5d015ca5ab..2edc36a770 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.IO; using System.Linq; using JetBrains.Annotations; +using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -496,6 +497,38 @@ namespace osu.Game.Skinning public double Length => sample.Length; public Bindable PlaybackConcurrency => sample.PlaybackConcurrency; + public BindableNumber Volume => sample.Volume; + + public BindableNumber Balance => sample.Balance; + + public BindableNumber Frequency => sample.Frequency; + + public BindableNumber Tempo => sample.Tempo; + + public void BindAdjustments(IAggregateAudioAdjustment component) + { + sample.BindAdjustments(component); + } + + public void UnbindAdjustments(IAggregateAudioAdjustment component) + { + sample.UnbindAdjustments(component); + } + + public void AddAdjustment(AdjustableProperty type, IBindable adjustBindable) + { + sample.AddAdjustment(type, adjustBindable); + } + + public void RemoveAdjustment(AdjustableProperty type, IBindable adjustBindable) + { + sample.RemoveAdjustment(type, adjustBindable); + } + + public void RemoveAllAdjustments(AdjustableProperty type) + { + sample.RemoveAllAdjustments(type); + } } private IEnumerable getLegacyLookupNames(HitSampleInfo hitSample) diff --git a/osu.Game/Skinning/PoolableSkinnableSample.cs b/osu.Game/Skinning/PoolableSkinnableSample.cs index 9025fdbd0f..b12fbf90f3 100644 --- a/osu.Game/Skinning/PoolableSkinnableSample.cs +++ b/osu.Game/Skinning/PoolableSkinnableSample.cs @@ -165,6 +165,14 @@ namespace osu.Game.Skinning public BindableNumber Tempo => sampleContainer.Tempo; + public void BindAdjustments(IAggregateAudioAdjustment component) => sampleContainer.BindAdjustments(component); + + public void UnbindAdjustments(IAggregateAudioAdjustment component) => sampleContainer.UnbindAdjustments(component); + + public void AddAdjustment(AdjustableProperty type, IBindable adjustBindable) => sampleContainer.AddAdjustment(type, adjustBindable); + + public void RemoveAdjustment(AdjustableProperty type, IBindable adjustBindable) => sampleContainer.RemoveAdjustment(type, adjustBindable); + public void AddAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => sampleContainer.AddAdjustment(type, adjustBindable); public void RemoveAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => sampleContainer.RemoveAdjustment(type, adjustBindable); diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index b3db2d6558..c971517c7f 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -176,14 +176,19 @@ namespace osu.Game.Skinning public BindableNumber Tempo => samplesContainer.Tempo; - public void AddAdjustment(AdjustableProperty type, BindableNumber adjustBindable) - => samplesContainer.AddAdjustment(type, adjustBindable); + public void BindAdjustments(IAggregateAudioAdjustment component) => samplesContainer.BindAdjustments(component); - public void RemoveAdjustment(AdjustableProperty type, BindableNumber adjustBindable) - => samplesContainer.RemoveAdjustment(type, adjustBindable); + public void UnbindAdjustments(IAggregateAudioAdjustment component) => samplesContainer.UnbindAdjustments(component); - public void RemoveAllAdjustments(AdjustableProperty type) - => samplesContainer.RemoveAllAdjustments(type); + public void AddAdjustment(AdjustableProperty type, IBindable adjustBindable) => samplesContainer.AddAdjustment(type, adjustBindable); + + public void RemoveAdjustment(AdjustableProperty type, IBindable adjustBindable) => samplesContainer.RemoveAdjustment(type, adjustBindable); + + public void AddAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => samplesContainer.AddAdjustment(type, adjustBindable); + + public void RemoveAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => samplesContainer.RemoveAdjustment(type, adjustBindable); + + public void RemoveAllAdjustments(AdjustableProperty type) => samplesContainer.RemoveAllAdjustments(type); /// /// Whether any samples are currently playing. From 772471a6d826a730ca17176f2c542b7e4d2ba44a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 19 Feb 2021 09:34:39 +0300 Subject: [PATCH 010/390] Add failing test case --- .../Gameplay/TestScenePauseWhenInactive.cs | 63 ++++++++++++++++--- osu.Game/Screens/Play/Player.cs | 10 +-- 2 files changed, 58 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs index e43e5ba3ce..15412fea00 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs @@ -1,28 +1,28 @@ // 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.Allocation; using osu.Framework.Bindables; using osu.Framework.Platform; using osu.Framework.Testing; +using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Rulesets; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Storyboards; +using osu.Game.Tests.Beatmaps; +using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay { [HeadlessTest] // we alter unsafe properties on the game host to test inactive window state. public class TestScenePauseWhenInactive : OsuPlayerTestScene { - protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) - { - var beatmap = (Beatmap)base.CreateBeatmap(ruleset); - - beatmap.HitObjects.RemoveAll(h => h.StartTime < 30000); - - return beatmap; - } - [Resolved] private GameHost host { get; set; } @@ -33,10 +33,53 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("resume player", () => Player.GameplayClockContainer.Start()); AddAssert("ensure not paused", () => !Player.GameplayClockContainer.IsPaused.Value); + + AddStep("progress time to gameplay", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.GameplayStartTime)); + AddUntilStep("wait for pause", () => Player.GameplayClockContainer.IsPaused.Value); + } + + /// + /// Tests that if a pause from focus lose is performed while in pause cooldown, + /// the player will still pause after the cooldown is finished. + /// + [Test] + public void TestPauseWhileInCooldown() + { + AddStep("resume player", () => Player.GameplayClockContainer.Start()); + AddStep("skip to gameplay", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.GameplayStartTime)); + + AddStep("set inactive", () => ((Bindable)host.IsActive).Value = false); + AddUntilStep("wait for pause", () => Player.GameplayClockContainer.IsPaused.Value); + + AddStep("set active", () => ((Bindable)host.IsActive).Value = true); + + AddStep("resume player", () => Player.Resume()); + AddStep("click resume overlay", () => + { + InputManager.MoveMouseTo(this.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + + AddAssert("pause cooldown active", () => Player.PauseCooldownActive); + AddStep("set inactive again", () => ((Bindable)host.IsActive).Value = false); AddUntilStep("wait for pause", () => Player.GameplayClockContainer.IsPaused.Value); - AddAssert("time of pause is after gameplay start time", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= Player.DrawableRuleset.GameplayStartTime); } protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(true, true, true); + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) + { + return new Beatmap + { + HitObjects = new List + { + new HitCircle { StartTime = 30000 }, + new HitCircle { StartTime = 35000 }, + }, + }; + } + + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) + => new TestWorkingBeatmap(beatmap, storyboard, Audio); } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 74059da21a..a7acda926b 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -667,6 +667,9 @@ namespace osu.Game.Screens.Play private double? lastPauseActionTime; + public bool PauseCooldownActive => + lastPauseActionTime.HasValue && GameplayClockContainer.GameplayClock.CurrentTime < lastPauseActionTime + pause_cooldown; + private bool canPause => // must pass basic screen conditions (beatmap loaded, instance allows pause) LoadedBeatmapSuccessfully && Configuration.AllowPause && ValidForResume @@ -675,10 +678,7 @@ namespace osu.Game.Screens.Play // cannot pause if we are already in a fail state && !HasFailed // cannot pause if already paused (or in a cooldown state) unless we are in a resuming state. - && (IsResuming || (GameplayClockContainer.IsPaused.Value == false && !pauseCooldownActive)); - - private bool pauseCooldownActive => - lastPauseActionTime.HasValue && GameplayClockContainer.GameplayClock.CurrentTime < lastPauseActionTime + pause_cooldown; + && (IsResuming || (GameplayClockContainer.IsPaused.Value == false && !PauseCooldownActive)); private bool canResume => // cannot resume from a non-paused state @@ -812,7 +812,7 @@ namespace osu.Game.Screens.Play // ValidForResume is false when restarting if (ValidForResume) { - if (pauseCooldownActive && !GameplayClockContainer.IsPaused.Value) + if (PauseCooldownActive && !GameplayClockContainer.IsPaused.Value) // still want to block if we are within the cooldown period and not already paused. return true; } From 4436585aa4dea5bec025e5bf816ed23008b4c87e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 19 Feb 2021 09:35:29 +0300 Subject: [PATCH 011/390] Keep attempting to pause gameplay while window not active --- osu.Game/Screens/Play/Player.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index a7acda926b..72d9a60c91 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -427,11 +427,16 @@ namespace osu.Game.Screens.Play private void updatePauseOnFocusLostState() { - if (!PauseOnFocusLost || breakTracker.IsBreakTime.Value) + if (!PauseOnFocusLost || DrawableRuleset.HasReplayLoaded.Value || breakTracker.IsBreakTime.Value) return; if (gameActive.Value == false) - Pause(); + { + if (canPause) + Pause(); + else + Scheduler.AddDelayed(updatePauseOnFocusLostState, 200); + } } private IBeatmap loadPlayableBeatmap() From ddd1dcff88428460cfcde74c963196a0518924fe Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 19 Feb 2021 11:33:26 +0300 Subject: [PATCH 012/390] Attempt pausing every single frame --- osu.Game/Screens/Play/Player.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 72d9a60c91..fa545859d4 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -435,7 +435,7 @@ namespace osu.Game.Screens.Play if (canPause) Pause(); else - Scheduler.AddDelayed(updatePauseOnFocusLostState, 200); + Scheduler.AddOnce(updatePauseOnFocusLostState); } } From 0771154dd2831f4d7de9d28913965f49df8909ce Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 19 Feb 2021 11:42:30 +0300 Subject: [PATCH 013/390] Make `PauseCooldownActive` protected and expose on test class --- osu.Game/Screens/Play/Player.cs | 2 +- osu.Game/Tests/Visual/TestPlayer.cs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index fa545859d4..8c816e8030 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -672,7 +672,7 @@ namespace osu.Game.Screens.Play private double? lastPauseActionTime; - public bool PauseCooldownActive => + protected bool PauseCooldownActive => lastPauseActionTime.HasValue && GameplayClockContainer.GameplayClock.CurrentTime < lastPauseActionTime + pause_cooldown; private bool canPause => diff --git a/osu.Game/Tests/Visual/TestPlayer.cs b/osu.Game/Tests/Visual/TestPlayer.cs index f47391ce6a..0addc9de75 100644 --- a/osu.Game/Tests/Visual/TestPlayer.cs +++ b/osu.Game/Tests/Visual/TestPlayer.cs @@ -34,6 +34,8 @@ namespace osu.Game.Tests.Visual public new HealthProcessor HealthProcessor => base.HealthProcessor; + public new bool PauseCooldownActive => base.PauseCooldownActive; + public readonly List Results = new List(); public TestPlayer(bool allowPause = true, bool showResults = true, bool pauseOnFocusLost = false) From fe5e45ea8180931f1c4c8d02a162e50b0000f186 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 19 Feb 2021 11:43:33 +0300 Subject: [PATCH 014/390] Move gameplay cursor outside instead and fix potential failure --- .../Gameplay/TestScenePauseWhenInactive.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs index 15412fea00..fa596c4823 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs @@ -2,21 +2,18 @@ // 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.Allocation; using osu.Framework.Bindables; using osu.Framework.Platform; using osu.Framework.Testing; -using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.UI; using osu.Game.Storyboards; using osu.Game.Tests.Beatmaps; -using osuTK.Input; +using osuTK; namespace osu.Game.Tests.Visual.Gameplay { @@ -45,6 +42,8 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestPauseWhileInCooldown() { + AddStep("move cursor outside", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.TopLeft - new Vector2(10))); + AddStep("resume player", () => Player.GameplayClockContainer.Start()); AddStep("skip to gameplay", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.GameplayStartTime)); @@ -54,14 +53,15 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("set active", () => ((Bindable)host.IsActive).Value = true); AddStep("resume player", () => Player.Resume()); - AddStep("click resume overlay", () => - { - InputManager.MoveMouseTo(this.ChildrenOfType().Single()); - InputManager.Click(MouseButton.Left); - }); - AddAssert("pause cooldown active", () => Player.PauseCooldownActive); - AddStep("set inactive again", () => ((Bindable)host.IsActive).Value = false); + bool pauseCooldownActive = false; + + AddStep("set inactive again", () => + { + pauseCooldownActive = Player.PauseCooldownActive; + ((Bindable)host.IsActive).Value = false; + }); + AddAssert("pause cooldown active", () => pauseCooldownActive); AddUntilStep("wait for pause", () => Player.GameplayClockContainer.IsPaused.Value); } From f6c279ab00d6af3231e09332e4d35b4c2ce7e106 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 19 Feb 2021 11:45:45 +0300 Subject: [PATCH 015/390] Add assert ensuring player resumed properly --- osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs index fa596c4823..49c1163c6c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs @@ -53,6 +53,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("set active", () => ((Bindable)host.IsActive).Value = true); AddStep("resume player", () => Player.Resume()); + AddAssert("unpaused", () => !Player.GameplayClockContainer.IsPaused.Value); bool pauseCooldownActive = false; From 362e4802f761980213893e30c2de0c038b1463db Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Feb 2021 17:58:04 +0900 Subject: [PATCH 016/390] Add the ability for PerformFromMenuRunner to inspect nested screen stacks --- osu.Game/PerformFromMenuRunner.cs | 21 ++++++++++++++++--- osu.Game/Screens/IHasSubScreenStack.cs | 15 +++++++++++++ .../Screens/OnlinePlay/OnlinePlayScreen.cs | 4 +++- 3 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 osu.Game/Screens/IHasSubScreenStack.cs diff --git a/osu.Game/PerformFromMenuRunner.cs b/osu.Game/PerformFromMenuRunner.cs index a4179c94da..39889ea7fc 100644 --- a/osu.Game/PerformFromMenuRunner.cs +++ b/osu.Game/PerformFromMenuRunner.cs @@ -13,6 +13,7 @@ using osu.Game.Beatmaps; using osu.Game.Overlays; using osu.Game.Overlays.Dialog; using osu.Game.Overlays.Notifications; +using osu.Game.Screens; using osu.Game.Screens.Menu; namespace osu.Game @@ -81,27 +82,41 @@ namespace osu.Game game?.CloseAllOverlays(false); - // we may already be at the target screen type. + findValidTarget(current); + } + + private bool findValidTarget(IScreen current) + { var type = current.GetType(); + // check if we are already at a valid target screen. if (validScreens.Any(t => t.IsAssignableFrom(type)) && !beatmap.Disabled) { finalAction(current); Cancel(); - return; + return true; } while (current != null) { + // if this has a sub stack, recursively check the screens within it. + if (current is IHasSubScreenStack currentSubScreen) + { + if (findValidTarget(currentSubScreen.SubScreenStack.CurrentScreen)) + return true; + } + if (validScreens.Any(t => t.IsAssignableFrom(type))) { current.MakeCurrent(); - break; + return true; } current = current.GetParentScreen(); type = current?.GetType(); } + + return false; } /// diff --git a/osu.Game/Screens/IHasSubScreenStack.cs b/osu.Game/Screens/IHasSubScreenStack.cs new file mode 100644 index 0000000000..c5e2015109 --- /dev/null +++ b/osu.Game/Screens/IHasSubScreenStack.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Screens; + +namespace osu.Game.Screens +{ + /// + /// A screen which manages a nested stack of screens within itself. + /// + public interface IHasSubScreenStack + { + ScreenStack SubScreenStack { get; } + } +} diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs index 71fd0d5c76..90e499c67f 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs @@ -28,7 +28,7 @@ using osuTK; namespace osu.Game.Screens.OnlinePlay { [Cached] - public abstract class OnlinePlayScreen : OsuScreen + public abstract class OnlinePlayScreen : OsuScreen, IHasSubScreenStack { public override bool CursorVisible => (screenStack.CurrentScreen as IOnlinePlaySubScreen)?.CursorVisible ?? true; @@ -355,5 +355,7 @@ namespace osu.Game.Screens.OnlinePlay protected override double TransformDuration => 200; } } + + ScreenStack IHasSubScreenStack.SubScreenStack => screenStack; } } From 5eee46074cbe5821394539ed4812c3d1cc8af844 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Feb 2021 19:45:29 +0900 Subject: [PATCH 017/390] Ensure the current screen is current when a sub screen is found as the target --- osu.Game/PerformFromMenuRunner.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/PerformFromMenuRunner.cs b/osu.Game/PerformFromMenuRunner.cs index 39889ea7fc..fe75a3a607 100644 --- a/osu.Game/PerformFromMenuRunner.cs +++ b/osu.Game/PerformFromMenuRunner.cs @@ -103,7 +103,11 @@ namespace osu.Game if (current is IHasSubScreenStack currentSubScreen) { if (findValidTarget(currentSubScreen.SubScreenStack.CurrentScreen)) + { + // should be correct in theory, but currently untested/unused in existing implementations. + current.MakeCurrent(); return true; + } } if (validScreens.Any(t => t.IsAssignableFrom(type))) From 32556b1898cfb65e652a9bae2dabe827d663d27b Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Sat, 20 Feb 2021 02:32:44 +0100 Subject: [PATCH 018/390] add `Exported = true` to Activity manifest --- osu.Android/OsuGameActivity.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Android/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs index ad929bbac3..d087c6218d 100644 --- a/osu.Android/OsuGameActivity.cs +++ b/osu.Android/OsuGameActivity.cs @@ -17,7 +17,7 @@ using osu.Game.Database; namespace osu.Android { - [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false, LaunchMode = LaunchMode.SingleInstance)] + [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false, LaunchMode = LaunchMode.SingleInstance, Exported = true)] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osz", DataHost = "*", DataMimeType = "*/*")] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osk", DataHost = "*", DataMimeType = "*/*")] [IntentFilter(new[] { Intent.ActionSend, Intent.ActionSendMultiple }, Categories = new[] { Intent.CategoryDefault }, DataMimeTypes = new[] { "application/zip", "application/octet-stream", "application/download", "application/x-zip", "application/x-zip-compressed" })] From 3b7ebfa2acad63c3a426610450d681eab9947450 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 20 Feb 2021 17:17:31 +0900 Subject: [PATCH 019/390] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index bfdc8f6b3c..1513f6444d 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 4138fc8d6c..9c3d0c2020 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -29,7 +29,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 783b638aa0..99ab88a064 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -91,7 +91,7 @@ - + From 66643a97b0af5b90793435d5b6abefae582ca163 Mon Sep 17 00:00:00 2001 From: Samuel Cattini-Schultz Date: Sat, 6 Feb 2021 15:06:16 +1100 Subject: [PATCH 020/390] Add a list of mods to Skill class Although this isn't necessary for existing official rulesets and calculators, custom calculators can have use cases for accessing mods in difficulty calculation. For example, accounting for the effects of visual mods. --- .../Difficulty/CatchDifficultyCalculator.cs | 4 ++-- .../Difficulty/Skills/Movement.cs | 4 +++- .../Difficulty/ManiaDifficultyCalculator.cs | 4 ++-- osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs | 4 +++- .../Difficulty/OsuDifficultyCalculator.cs | 6 +++--- osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs | 6 ++++++ osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 6 ++++++ osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs | 6 ++++++ osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs | 6 ++++++ .../Difficulty/Skills/Stamina.cs | 5 ++++- .../Difficulty/TaikoDifficultyCalculator.cs | 10 +++++----- .../DifficultyAdjustmentModCombinationsTest.cs | 2 +- .../Rulesets/Difficulty/DifficultyCalculator.cs | 5 +++-- osu.Game/Rulesets/Difficulty/Skills/Skill.cs | 13 +++++++++++++ 14 files changed, 63 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs index a317ef252d..10aae70722 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty } } - protected override Skill[] CreateSkills(IBeatmap beatmap) + protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) { halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.BeatmapInfo.BaseDifficulty) * 0.5f; @@ -78,7 +78,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty return new Skill[] { - new Movement(halfCatcherWidth), + new Movement(mods, halfCatcherWidth), }; } diff --git a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs index e679231638..9ad719be1a 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs @@ -5,6 +5,7 @@ using System; using osu.Game.Rulesets.Catch.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; +using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Catch.Difficulty.Skills { @@ -25,7 +26,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills private float lastDistanceMoved; private double lastStrainTime; - public Movement(float halfCatcherWidth) + public Movement(Mod[] mods, float halfCatcherWidth) + : base(mods) { HalfCatcherWidth = halfCatcherWidth; } diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs index ade830764d..8c0b9ed8b7 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs @@ -68,9 +68,9 @@ namespace osu.Game.Rulesets.Mania.Difficulty // Sorting is done in CreateDifficultyHitObjects, since the full list of hitobjects is required. protected override IEnumerable SortObjects(IEnumerable input) => input; - protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[] + protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) => new Skill[] { - new Strain(((ManiaBeatmap)beatmap).TotalColumns) + new Strain(mods, ((ManiaBeatmap)beatmap).TotalColumns) }; protected override Mod[] DifficultyAdjustmentMods diff --git a/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs b/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs index 7ebc1ff752..d6ea58ee78 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs @@ -6,6 +6,7 @@ using osu.Framework.Utils; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mania.Difficulty.Preprocessing; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Mania.Difficulty.Skills @@ -24,7 +25,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills private double individualStrain; private double overallStrain; - public Strain(int totalColumns) + public Strain(Mod[] mods, int totalColumns) + : base(mods) { holdEndTimes = new double[totalColumns]; individualStrains = new double[totalColumns]; diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 6a7d76151c..75d6786d95 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -79,10 +79,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty } } - protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[] + protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) => new Skill[] { - new Aim(), - new Speed() + new Aim(mods), + new Speed(mods) }; protected override Mod[] DifficultyAdjustmentMods => new Mod[] diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index e74f4933b2..90cba13c7c 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -4,6 +4,7 @@ using System; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Objects; @@ -17,6 +18,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills private const double angle_bonus_begin = Math.PI / 3; private const double timing_threshold = 107; + public Aim(Mod[] mods) + : base(mods) + { + } + protected override double SkillMultiplier => 26.25; protected override double StrainDecayBase => 0.15; diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 01f2fb8dc8..200bc7997d 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -4,6 +4,7 @@ using System; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Objects; @@ -27,6 +28,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills private const double max_speed_bonus = 45; // ~330BPM private const double speed_balancing_factor = 40; + public Speed(Mod[] mods) + : base(mods) + { + } + protected override double StrainValueOf(DifficultyHitObject current) { if (current.BaseObject is Spinner) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs index 32421ee00a..cc0738e252 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs @@ -5,6 +5,7 @@ using System; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Difficulty.Utils; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing; using osu.Game.Rulesets.Taiko.Objects; @@ -39,6 +40,11 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills /// private int currentMonoLength; + public Colour(Mod[] mods) + : base(mods) + { + } + protected override double StrainValueOf(DifficultyHitObject current) { // changing from/to a drum roll or a swell does not constitute a colour change. diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs index 5569b27ad5..f2b8309ac5 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs @@ -5,6 +5,7 @@ using System; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Difficulty.Utils; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing; using osu.Game.Rulesets.Taiko.Objects; @@ -47,6 +48,11 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills /// private int notesSinceRhythmChange; + public Rhythm(Mod[] mods) + : base(mods) + { + } + protected override double StrainValueOf(DifficultyHitObject current) { // drum rolls and swells are exempt. diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs index 0b61eb9930..c34cce0cd6 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs @@ -5,6 +5,7 @@ using System.Linq; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Difficulty.Utils; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing; using osu.Game.Rulesets.Taiko.Objects; @@ -48,8 +49,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills /// /// Creates a skill. /// + /// Mods for use in skill calculations. /// Whether this instance is performing calculations for the right hand. - public Stamina(bool rightHand) + public Stamina(Mod[] mods, bool rightHand) + : base(mods) { hand = rightHand ? 1 : 0; } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index e5485db4df..fc198d2493 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -29,12 +29,12 @@ namespace osu.Game.Rulesets.Taiko.Difficulty { } - protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[] + protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) => new Skill[] { - new Colour(), - new Rhythm(), - new Stamina(true), - new Stamina(false), + new Colour(mods), + new Rhythm(mods), + new Stamina(mods, true), + new Stamina(mods, false), }; protected override Mod[] DifficultyAdjustmentMods => new Mod[] diff --git a/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs b/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs index 5c7adb3f49..1c0bfd56dd 100644 --- a/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs +++ b/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs @@ -212,7 +212,7 @@ namespace osu.Game.Tests.NonVisual throw new NotImplementedException(); } - protected override Skill[] CreateSkills(IBeatmap beatmap) + protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) { throw new NotImplementedException(); } diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index f15e5e1df0..a25dc3e6db 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Difficulty private DifficultyAttributes calculate(IBeatmap beatmap, Mod[] mods, double clockRate) { - var skills = CreateSkills(beatmap); + var skills = CreateSkills(beatmap, mods); if (!beatmap.HitObjects.Any()) return CreateDifficultyAttributes(beatmap, mods, skills, clockRate); @@ -202,7 +202,8 @@ namespace osu.Game.Rulesets.Difficulty /// Creates the s to calculate the difficulty of an . /// /// The whose difficulty will be calculated. + /// Mods to calculate difficulty with. /// The s. - protected abstract Skill[] CreateSkills(IBeatmap beatmap); + protected abstract Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods); } } diff --git a/osu.Game/Rulesets/Difficulty/Skills/Skill.cs b/osu.Game/Rulesets/Difficulty/Skills/Skill.cs index 1063a24b27..95117be073 100644 --- a/osu.Game/Rulesets/Difficulty/Skills/Skill.cs +++ b/osu.Game/Rulesets/Difficulty/Skills/Skill.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Utils; +using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Difficulty.Skills { @@ -46,10 +47,22 @@ namespace osu.Game.Rulesets.Difficulty.Skills /// protected double CurrentStrain { get; private set; } = 1; + /// + /// Mods for use in skill calculations. + /// + protected IReadOnlyList Mods => mods; + private double currentSectionPeak = 1; // We also keep track of the peak strain level in the current section. private readonly List strainPeaks = new List(); + private readonly Mod[] mods; + + protected Skill(Mod[] mods) + { + this.mods = mods; + } + /// /// Process a and update current strain values accordingly. /// From 8d463987dd6c7260f25c90a31804d180c147b5df Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Feb 2021 13:21:50 +0900 Subject: [PATCH 021/390] Fix being able to select incompatible freemods --- .../Screens/OnlinePlay/OnlinePlaySongSelect.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs index f0c77b79bf..3f30ef1176 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs @@ -75,9 +75,18 @@ namespace osu.Game.Screens.OnlinePlay Mods.Value = selectedItem?.Value?.RequiredMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty(); FreeMods.Value = selectedItem?.Value?.AllowedMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty(); + Mods.BindValueChanged(onModsChanged); Ruleset.BindValueChanged(onRulesetChanged); } + private void onModsChanged(ValueChangedEvent> mods) + { + FreeMods.Value = FreeMods.Value.Where(checkCompatibleFreeMod).ToList(); + + // Reset the validity delegate to update the overlay's display. + freeModSelectOverlay.IsValidMod = IsValidFreeMod; + } + private void onRulesetChanged(ValueChangedEvent ruleset) { FreeMods.Value = Array.Empty(); @@ -155,6 +164,10 @@ namespace osu.Game.Screens.OnlinePlay /// /// The to check. /// Whether is a selectable free-mod. - protected virtual bool IsValidFreeMod(Mod mod) => IsValidMod(mod); + protected virtual bool IsValidFreeMod(Mod mod) => IsValidMod(mod) && checkCompatibleFreeMod(mod); + + private bool checkCompatibleFreeMod(Mod mod) + => Mods.Value.All(m => m.Acronym != mod.Acronym) // Mod must not be contained in the required mods. + && ModUtils.CheckCompatibleSet(Mods.Value.Append(mod).ToArray()); // Mod must be compatible with all the required mods. } } From ca92ad715a9a11bd772b2f53d04f3cb5a8e13431 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Feb 2021 13:32:54 +0900 Subject: [PATCH 022/390] Add test --- osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs | 2 +- .../TestSceneMultiplayerMatchSongSelect.cs | 28 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs index df0a41455f..4b0939db16 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs @@ -11,7 +11,7 @@ using osu.Game.Rulesets.Osu.Skinning.Default; namespace osu.Game.Rulesets.Osu.Mods { - internal class OsuModTraceable : ModWithVisibilityAdjustment + public class OsuModTraceable : ModWithVisibilityAdjustment { public override string Name => "Traceable"; public override string Acronym => "TC"; diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index 95c333e9f4..faa5d9e6fc 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -7,17 +7,23 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; +using osu.Framework.Bindables; using osu.Framework.Extensions; +using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Platform; using osu.Framework.Screens; +using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps; +using osu.Game.Overlays.Mods; using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; +using osu.Game.Rulesets.Mods; 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; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.Select; @@ -137,8 +143,30 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("mods not changed", () => SelectedMods.Value.Single() is TaikoModDoubleTime); } + [TestCase(typeof(OsuModHidden), typeof(OsuModHidden))] // Same mod. + [TestCase(typeof(OsuModHidden), typeof(OsuModTraceable))] // Incompatible. + public void TestAllowedModDeselectedWhenRequired(Type allowedMod, Type requiredMod) + { + AddStep($"select {allowedMod.ReadableName()} as allowed", () => songSelect.FreeMods.Value = new[] { (Mod)Activator.CreateInstance(allowedMod) }); + AddStep($"select {requiredMod.ReadableName()} as required", () => songSelect.Mods.Value = new[] { (Mod)Activator.CreateInstance(requiredMod) }); + + AddAssert("freemods empty", () => songSelect.FreeMods.Value.Count == 0); + assertHasFreeModButton(allowedMod, false); + assertHasFreeModButton(requiredMod, false); + } + + private void assertHasFreeModButton(Type type, bool hasButton = true) + { + AddAssert($"{type.ReadableName()} {(hasButton ? "displayed" : "not displayed")} in freemod overlay", + () => songSelect.ChildrenOfType().Single().ChildrenOfType().All(b => b.Mod.GetType() != type)); + } + private class TestMultiplayerMatchSongSelect : MultiplayerMatchSongSelect { + public new Bindable> Mods => base.Mods; + + public new Bindable> FreeMods => base.FreeMods; + public new BeatmapCarousel Carousel => base.Carousel; } } From e2c5dded7f4e5b4ac1a5123e70e54728f251bb2a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Feb 2021 14:14:36 +0900 Subject: [PATCH 023/390] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 1513f6444d..183ac61c90 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 9c3d0c2020..37d730bf42 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -29,7 +29,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 99ab88a064..ca11952cc8 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -91,7 +91,7 @@ - + From 63dd55c92c9f725926f89dfa14b4ba84d65760a3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Feb 2021 14:18:52 +0900 Subject: [PATCH 024/390] Add missing methods from updated audio component interface implementation --- .../TestSceneDrawableRulesetDependencies.cs | 4 ++++ .../Rulesets/UI/DrawableRulesetDependencies.cs | 4 ++++ osu.Game/Skinning/PoolableSkinnableSample.cs | 4 ++++ osu.Game/Skinning/SkinnableSound.cs | 18 ++++++++++++++++++ 4 files changed, 30 insertions(+) diff --git a/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs b/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs index 4aebed0d31..f421a30283 100644 --- a/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs +++ b/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs @@ -118,6 +118,10 @@ namespace osu.Game.Tests.Rulesets public BindableNumber Frequency => throw new NotImplementedException(); public BindableNumber Tempo => throw new NotImplementedException(); + public void BindAdjustments(IAggregateAudioAdjustment component) => throw new NotImplementedException(); + + public void UnbindAdjustments(IAggregateAudioAdjustment component) => throw new NotImplementedException(); + public void AddAdjustment(AdjustableProperty type, IBindable adjustBindable) => throw new NotImplementedException(); public void RemoveAdjustment(AdjustableProperty type, IBindable adjustBindable) => throw new NotImplementedException(); diff --git a/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs index bbaca7c80f..b31884d246 100644 --- a/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs +++ b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs @@ -134,6 +134,10 @@ namespace osu.Game.Rulesets.UI public IBindable AggregateTempo => throw new NotSupportedException(); + public void BindAdjustments(IAggregateAudioAdjustment component) => throw new NotImplementedException(); + + public void UnbindAdjustments(IAggregateAudioAdjustment component) => throw new NotImplementedException(); + public int PlaybackConcurrency { get => throw new NotSupportedException(); diff --git a/osu.Game/Skinning/PoolableSkinnableSample.cs b/osu.Game/Skinning/PoolableSkinnableSample.cs index abff57091b..5a0cf94d6a 100644 --- a/osu.Game/Skinning/PoolableSkinnableSample.cs +++ b/osu.Game/Skinning/PoolableSkinnableSample.cs @@ -165,6 +165,10 @@ namespace osu.Game.Skinning public BindableNumber Tempo => sampleContainer.Tempo; + public void BindAdjustments(IAggregateAudioAdjustment component) => sampleContainer.BindAdjustments(component); + + public void UnbindAdjustments(IAggregateAudioAdjustment component) => sampleContainer.UnbindAdjustments(component); + public void AddAdjustment(AdjustableProperty type, IBindable adjustBindable) => sampleContainer.AddAdjustment(type, adjustBindable); public void RemoveAdjustment(AdjustableProperty type, IBindable adjustBindable) => sampleContainer.RemoveAdjustment(type, adjustBindable); diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index d3dfcb1dc0..57e20a8d31 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -176,6 +176,16 @@ namespace osu.Game.Skinning public BindableNumber Tempo => samplesContainer.Tempo; + public void BindAdjustments(IAggregateAudioAdjustment component) + { + samplesContainer.BindAdjustments(component); + } + + public void UnbindAdjustments(IAggregateAudioAdjustment component) + { + samplesContainer.UnbindAdjustments(component); + } + public void AddAdjustment(AdjustableProperty type, IBindable adjustBindable) => samplesContainer.AddAdjustment(type, adjustBindable); @@ -192,6 +202,14 @@ namespace osu.Game.Skinning public bool IsPlayed => samplesContainer.Any(s => s.Played); + public IBindable AggregateVolume => samplesContainer.AggregateVolume; + + public IBindable AggregateBalance => samplesContainer.AggregateBalance; + + public IBindable AggregateFrequency => samplesContainer.AggregateFrequency; + + public IBindable AggregateTempo => samplesContainer.AggregateTempo; + #endregion } } From 541237ef16c62d259e4e84fa13943c32a7a54be0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Feb 2021 14:48:04 +0900 Subject: [PATCH 025/390] Use a shorter test beatmap for tests which need to run to completion --- .../Beatmaps/IO/ImportBeatmapTest.cs | 15 +++++++++++ ...241526 Soleily - Renatus_virtual_quick.osz | Bin 0 -> 89215 bytes osu.Game.Tests/Resources/TestResources.cs | 25 +++++++++++++++++- .../Navigation/TestSceneScreenNavigation.cs | 2 +- 4 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Tests/Resources/Archives/241526 Soleily - Renatus_virtual_quick.osz diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index c32e359de6..0c35e9471d 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -852,6 +852,21 @@ namespace osu.Game.Tests.Beatmaps.IO } } + public static async Task LoadQuickOszIntoOsu(OsuGameBase osu) + { + var temp = TestResources.GetQuickTestBeatmapForImport(); + + var manager = osu.Dependencies.Get(); + + var importedSet = await manager.Import(new ImportTask(temp)); + + ensureLoaded(osu); + + waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000); + + return manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.ID); + } + public static async Task LoadOszIntoOsu(OsuGameBase osu, string path = null, bool virtualTrack = false) { var temp = path ?? TestResources.GetTestBeatmapForImport(virtualTrack); diff --git a/osu.Game.Tests/Resources/Archives/241526 Soleily - Renatus_virtual_quick.osz b/osu.Game.Tests/Resources/Archives/241526 Soleily - Renatus_virtual_quick.osz new file mode 100644 index 0000000000000000000000000000000000000000..e9f5fb03282e189ffc23d77a0db40b3fa2488850 GIT binary patch literal 89215 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 + /// Retrieve a path to a copy of a shortened (~10 second) beatmap archive with a virtual track. + /// + /// + /// This is intended for use in tests which need to run to completion as soon as possible and don't need to test a full length beatmap. + /// A path to a copy of a beatmap archive (osz). Should be deleted after use. + public static string GetQuickTestBeatmapForImport() + { + var tempPath = Path.GetTempFileName() + ".osz"; + + using (var stream = GetTestBeatmapStream(true, true)) + using (var newFile = File.Create(tempPath)) + stream.CopyTo(newFile); + + Assert.IsTrue(File.Exists(tempPath)); + return tempPath; + } + + /// + /// Retrieve a path to a copy of a full-fledged beatmap archive. + /// + /// Whether the audio track should be virtual. + /// A path to a copy of a beatmap archive (osz). Should be deleted after use. public static string GetTestBeatmapForImport(bool virtualTrack = false) { var tempPath = Path.GetTempFileName() + ".osz"; diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index d8380b2dd3..e5959a3edf 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual.Navigation PushAndConfirm(() => new TestSongSelect()); - AddStep("import beatmap", () => ImportBeatmapTest.LoadOszIntoOsu(Game, virtualTrack: true).Wait()); + AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait()); AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); From cdbf8de29db80994e903c1e92837d6208e78312f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Feb 2021 14:53:32 +0900 Subject: [PATCH 026/390] Update other tests which can benefit from using a shorter beatmap --- .../Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs | 2 +- osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs | 2 +- .../Visual/Collections/TestSceneManageCollectionsDialog.cs | 2 +- .../Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs | 2 +- osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs | 2 +- osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs | 2 +- .../Visual/UserInterface/TestSceneDeleteLocalScore.cs | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index 3ffb512b7f..8c30802ce3 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -52,7 +52,7 @@ namespace osu.Game.Tests.Online { beatmaps.AllowImport = new TaskCompletionSource(); - testBeatmapFile = TestResources.GetTestBeatmapForImport(); + testBeatmapFile = TestResources.GetQuickTestBeatmapForImport(); testBeatmapInfo = getTestBeatmapInfo(testBeatmapFile); testBeatmapSet = testBeatmapInfo.BeatmapSet; diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index 7ade7725d9..ba4d12b19f 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -51,7 +51,7 @@ namespace osu.Game.Tests.Visual.Background Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default)); Dependencies.Cache(new OsuConfigManager(LocalStorage)); - manager.Import(TestResources.GetTestBeatmapForImport()).Wait(); + manager.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); Beatmap.SetDefault(); } diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index fef1605f0c..1655adf811 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -38,7 +38,7 @@ namespace osu.Game.Tests.Visual.Collections Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, host, Beatmap.Default)); - beatmapManager.Import(TestResources.GetTestBeatmapForImport()).Wait(); + beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); base.Content.AddRange(new Drawable[] { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs index 3b3b1bee86..b44e5b1e5b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs @@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default)); - beatmaps.Import(TestResources.GetTestBeatmapForImport(true)).Wait(); + beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); Add(beatmapTracker = new OnlinePlayBeatmapAvailablilityTracker { diff --git a/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs b/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs index 63bda08c88..0c199bfb62 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs @@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Online ensureSoleilyRemoved(); createButtonWithBeatmap(createSoleily()); AddAssert("button state not downloaded", () => downloadButton.DownloadState == DownloadState.NotDownloaded); - AddStep("import soleily", () => beatmaps.Import(TestResources.GetTestBeatmapForImport())); + AddStep("import soleily", () => beatmaps.Import(TestResources.GetQuickTestBeatmapForImport())); AddUntilStep("wait for beatmap import", () => beatmaps.GetAllUsableBeatmapSets().Any(b => b.OnlineBeatmapSetID == 241526)); createButtonWithBeatmap(createSoleily()); AddAssert("button state downloaded", () => downloadButton.DownloadState == DownloadState.LocallyAvailable); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index 5d0fb248df..c13bdf0955 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -38,7 +38,7 @@ namespace osu.Game.Tests.Visual.SongSelect Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, host, Beatmap.Default)); - beatmapManager.Import(TestResources.GetTestBeatmapForImport()).Wait(); + beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); base.Content.AddRange(new Drawable[] { diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index 81862448a8..d615f1f440 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -84,7 +84,7 @@ namespace osu.Game.Tests.Visual.UserInterface dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get(), dependencies.Get(), Beatmap.Default)); dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, null, ContextFactory)); - beatmap = beatmapManager.Import(new ImportTask(TestResources.GetTestBeatmapForImport())).Result.Beatmaps[0]; + beatmap = beatmapManager.Import(new ImportTask(TestResources.GetQuickTestBeatmapForImport())).Result.Beatmaps[0]; for (int i = 0; i < 50; i++) { From fde026d44342534f7d06ddc0873e18f3f24e7070 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Feb 2021 14:54:48 +0900 Subject: [PATCH 027/390] Remove redundant interface specification --- osu.Game/Skinning/PoolableSkinnableSample.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/PoolableSkinnableSample.cs b/osu.Game/Skinning/PoolableSkinnableSample.cs index 5a0cf94d6a..9103a6a960 100644 --- a/osu.Game/Skinning/PoolableSkinnableSample.cs +++ b/osu.Game/Skinning/PoolableSkinnableSample.cs @@ -17,7 +17,7 @@ namespace osu.Game.Skinning /// /// A sample corresponding to an that supports being pooled and responding to skin changes. /// - public class PoolableSkinnableSample : SkinReloadableDrawable, IAggregateAudioAdjustment, IAdjustableAudioComponent + public class PoolableSkinnableSample : SkinReloadableDrawable, IAdjustableAudioComponent { /// /// The currently-loaded . From adf2dc36c9112200699ac8680b81a32bda9b937f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Feb 2021 15:43:58 +0900 Subject: [PATCH 028/390] Fix PlaylistResults tests performing delays in real-time when headless --- .../TestScenePlaylistsResultsScreen.cs | 87 +++++++------------ 1 file changed, 32 insertions(+), 55 deletions(-) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index cdcded8f61..e34da1ef0c 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("bind user score info handler", () => { userScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { OnlineScoreID = currentScoreId++ }; - bindHandler(3000, userScore); + bindHandler(true, userScore); }); createResults(() => userScore); @@ -89,7 +89,7 @@ namespace osu.Game.Tests.Visual.Playlists [Test] public void TestShowNullUserScoreWithDelay() { - AddStep("bind delayed handler", () => bindHandler(3000)); + AddStep("bind delayed handler", () => bindHandler(true)); createResults(); waitForDisplay(); @@ -103,7 +103,7 @@ namespace osu.Game.Tests.Visual.Playlists createResults(); waitForDisplay(); - AddStep("bind delayed handler", () => bindHandler(3000)); + AddStep("bind delayed handler", () => bindHandler(true)); for (int i = 0; i < 2; i++) { @@ -134,7 +134,7 @@ namespace osu.Game.Tests.Visual.Playlists createResults(() => userScore); waitForDisplay(); - AddStep("bind delayed handler", () => bindHandler(3000)); + AddStep("bind delayed handler", () => bindHandler(true)); for (int i = 0; i < 2; i++) { @@ -169,70 +169,47 @@ namespace osu.Game.Tests.Visual.Playlists AddWaitStep("wait for display", 5); } - private void bindHandler(double delay = 0, ScoreInfo userScore = null, bool failRequests = false) => ((DummyAPIAccess)API).HandleRequest = request => + private void bindHandler(bool delayed = false, ScoreInfo userScore = null, bool failRequests = false) => ((DummyAPIAccess)API).HandleRequest = request => { requestComplete = false; - if (failRequests) - { - triggerFail(request, delay); - return; - } + double delay = delayed ? 3000 : 0; - switch (request) + Scheduler.AddDelayed(() => { - case ShowPlaylistUserScoreRequest s: - if (userScore == null) - triggerFail(s, delay); - else - triggerSuccess(s, createUserResponse(userScore), delay); - break; + if (failRequests) + { + triggerFail(request); + return; + } - case IndexPlaylistScoresRequest i: - triggerSuccess(i, createIndexResponse(i), delay); - break; - } + switch (request) + { + case ShowPlaylistUserScoreRequest s: + if (userScore == null) + triggerFail(s); + else + triggerSuccess(s, createUserResponse(userScore)); + break; + + case IndexPlaylistScoresRequest i: + triggerSuccess(i, createIndexResponse(i)); + break; + } + }, delay); }; - private void triggerSuccess(APIRequest req, T result, double delay) + private void triggerSuccess(APIRequest req, T result) where T : class { - if (delay == 0) - success(); - else - { - Task.Run(async () => - { - await Task.Delay(TimeSpan.FromMilliseconds(delay)); - Schedule(success); - }); - } - - void success() - { - requestComplete = true; - req.TriggerSuccess(result); - } + requestComplete = true; + req.TriggerSuccess(result); } - private void triggerFail(APIRequest req, double delay) + private void triggerFail(APIRequest req) { - if (delay == 0) - fail(); - else - { - Task.Run(async () => - { - await Task.Delay(TimeSpan.FromMilliseconds(delay)); - Schedule(fail); - }); - } - - void fail() - { - requestComplete = true; - req.TriggerFailure(new WebException("Failed.")); - } + requestComplete = true; + req.TriggerFailure(new WebException("Failed.")); } private MultiplayerScore createUserResponse([NotNull] ScoreInfo userScore) From ccb83ef3a374f173b18473665c06c5002d68aecb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Feb 2021 15:47:47 +0900 Subject: [PATCH 029/390] Fix checkbox not being updated --- osu.Game/Overlays/Mods/ModSection.cs | 20 +++++++++---------- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 8 ++++++++ .../OnlinePlay/FreeModSelectOverlay.cs | 14 ++++++++++++- 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index c3e56abd05..aa8a5efd39 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -23,13 +23,15 @@ namespace osu.Game.Overlays.Mods public FillFlowContainer ButtonsContainer { get; } + protected IReadOnlyList Buttons { get; private set; } = Array.Empty(); + public Action Action; public Key[] ToggleKeys; public readonly ModType ModType; - public IEnumerable SelectedMods => buttons.Select(b => b.SelectedMod).Where(m => m != null); + public IEnumerable SelectedMods => Buttons.Select(b => b.SelectedMod).Where(m => m != null); private CancellationTokenSource modsLoadCts; @@ -77,7 +79,7 @@ namespace osu.Game.Overlays.Mods ButtonsContainer.ChildrenEnumerable = c; }, (modsLoadCts = new CancellationTokenSource()).Token); - buttons = modContainers.OfType().ToArray(); + Buttons = modContainers.OfType().ToArray(); header.FadeIn(200); this.FadeIn(200); @@ -88,8 +90,6 @@ namespace osu.Game.Overlays.Mods { } - private ModButton[] buttons = Array.Empty(); - protected override bool OnKeyDown(KeyDownEvent e) { if (e.ControlPressed) return false; @@ -97,8 +97,8 @@ namespace osu.Game.Overlays.Mods if (ToggleKeys != null) { var index = Array.IndexOf(ToggleKeys, e.Key); - if (index > -1 && index < buttons.Length) - buttons[index].SelectNext(e.ShiftPressed ? -1 : 1); + if (index > -1 && index < Buttons.Count) + Buttons[index].SelectNext(e.ShiftPressed ? -1 : 1); } return base.OnKeyDown(e); @@ -141,7 +141,7 @@ namespace osu.Game.Overlays.Mods { pendingSelectionOperations.Clear(); - foreach (var button in buttons.Where(b => !b.Selected)) + foreach (var button in Buttons.Where(b => !b.Selected)) pendingSelectionOperations.Enqueue(() => button.SelectAt(0)); } @@ -151,7 +151,7 @@ namespace osu.Game.Overlays.Mods public void DeselectAll() { pendingSelectionOperations.Clear(); - DeselectTypes(buttons.Select(b => b.SelectedMod?.GetType()).Where(t => t != null)); + DeselectTypes(Buttons.Select(b => b.SelectedMod?.GetType()).Where(t => t != null)); } /// @@ -161,7 +161,7 @@ namespace osu.Game.Overlays.Mods /// Whether the deselection should happen immediately. Should only be used when required to ensure correct selection flow. public void DeselectTypes(IEnumerable modTypes, bool immediate = false) { - foreach (var button in buttons) + foreach (var button in Buttons) { if (button.SelectedMod == null) continue; @@ -184,7 +184,7 @@ namespace osu.Game.Overlays.Mods /// The new list of selected mods to select. public void UpdateSelectedButtons(IReadOnlyList newSelectedMods) { - foreach (var button in buttons) + foreach (var button in Buttons) updateButtonSelection(button, newSelectedMods); } diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index eef91deb4c..26b8632d7f 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -456,6 +456,7 @@ namespace osu.Game.Overlays.Mods } updateSelectedButtons(); + OnAvailableModsChanged(); } /// @@ -533,6 +534,13 @@ namespace osu.Game.Overlays.Mods private void playSelectedSound() => sampleOn?.Play(); private void playDeselectedSound() => sampleOff?.Play(); + /// + /// Invoked after has changed. + /// + protected virtual void OnAvailableModsChanged() + { + } + /// /// Invoked when a new has been selected. /// diff --git a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs index ab7be13479..66262e7dc4 100644 --- a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs @@ -75,6 +75,14 @@ namespace osu.Game.Screens.OnlinePlay section.DeselectAll(); } + protected override void OnAvailableModsChanged() + { + base.OnAvailableModsChanged(); + + foreach (var section in ModSectionsContainer.Children) + ((FreeModSection)section).UpdateCheckboxState(); + } + protected override ModSection CreateModSection(ModType type) => new FreeModSection(type); private class FreeModSection : ModSection @@ -108,10 +116,14 @@ namespace osu.Game.Screens.OnlinePlay protected override void ModButtonStateChanged(Mod mod) { base.ModButtonStateChanged(mod); + UpdateCheckboxState(); + } + public void UpdateCheckboxState() + { if (!SelectionAnimationRunning) { - var validButtons = ButtonsContainer.OfType().Where(b => b.Mod.HasImplementation); + var validButtons = Buttons.Where(b => b.Mod.HasImplementation); checkbox.Current.Value = validButtons.All(b => b.Selected); } } From d985b8ab2aafd86c9f4d24fdcd39634de8a0b10c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Feb 2021 17:14:39 +0900 Subject: [PATCH 030/390] Increase beatmapset download timeout --- osu.Game/Online/API/Requests/DownloadBeatmapSetRequest.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Online/API/Requests/DownloadBeatmapSetRequest.cs b/osu.Game/Online/API/Requests/DownloadBeatmapSetRequest.cs index 707c59436d..e8871bef05 100644 --- a/osu.Game/Online/API/Requests/DownloadBeatmapSetRequest.cs +++ b/osu.Game/Online/API/Requests/DownloadBeatmapSetRequest.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.IO.Network; using osu.Game.Beatmaps; namespace osu.Game.Online.API.Requests @@ -15,6 +16,13 @@ namespace osu.Game.Online.API.Requests this.noVideo = noVideo; } + protected override WebRequest CreateWebRequest() + { + var req = base.CreateWebRequest(); + req.Timeout = 60000; + return req; + } + protected override string Target => $@"beatmapsets/{Model.OnlineBeatmapSetID}/download{(noVideo ? "?noVideo=1" : "")}"; } } From 0bda9e4b794f16d108f234563746202bf3b8a160 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Feb 2021 18:31:33 +0900 Subject: [PATCH 031/390] Implement some new methods --- osu.Game/Skinning/LegacySkin.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 2edc36a770..6bdc4575c9 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -529,6 +529,14 @@ namespace osu.Game.Skinning { sample.RemoveAllAdjustments(type); } + + public IBindable AggregateVolume => sample.AggregateVolume; + + public IBindable AggregateBalance => sample.AggregateBalance; + + public IBindable AggregateFrequency => sample.AggregateFrequency; + + public IBindable AggregateTempo => sample.AggregateTempo; } private IEnumerable getLegacyLookupNames(HitSampleInfo hitSample) From f48e017ac901bcd5387a87d53b2a5ab5dc771d06 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Feb 2021 18:34:05 +0900 Subject: [PATCH 032/390] Move nested class to bottom of file --- osu.Game/Skinning/LegacySkin.cs | 78 ++++++++++++++++----------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 6bdc4575c9..571d65e28b 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -468,7 +468,45 @@ namespace osu.Game.Skinning return null; } - /// + private IEnumerable getLegacyLookupNames(HitSampleInfo hitSample) + { + var lookupNames = hitSample.LookupNames.SelectMany(getFallbackNames); + + if (!UseCustomSampleBanks && !string.IsNullOrEmpty(hitSample.Suffix)) + { + // for compatibility with stable, exclude the lookup names with the custom sample bank suffix, if they are not valid for use in this skin. + // using .EndsWith() is intentional as it ensures parity in all edge cases + // (see LegacyTaikoSampleInfo for an example of one - prioritising the taiko prefix should still apply, but the sample bank should not). + lookupNames = lookupNames.Where(name => !name.EndsWith(hitSample.Suffix, StringComparison.Ordinal)); + } + + foreach (var l in lookupNames) + yield return l; + + // also for compatibility, try falling back to non-bank samples (so-called "universal" samples) as the last resort. + // going forward specifying banks shall always be required, even for elements that wouldn't require it on stable, + // which is why this is done locally here. + yield return hitSample.Name; + } + + private IEnumerable getFallbackNames(string componentName) + { + // May be something like "Gameplay/osu/approachcircle" from lazer, or "Arrows/note1" from a user skin. + yield return componentName; + + // Fall back to using the last piece for components coming from lazer (e.g. "Gameplay/osu/approachcircle" -> "approachcircle"). + string lastPiece = componentName.Split('/').Last(); + yield return componentName.StartsWith("Gameplay/taiko/", StringComparison.Ordinal) ? "taiko-" + lastPiece : lastPiece; + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + Textures?.Dispose(); + Samples?.Dispose(); + } + + /// /// A sample wrapper which keeps a reference to the contained skin to avoid finalizer garbage collection of the managing SampleStore. /// private class LegacySkinSample : ISample @@ -538,43 +576,5 @@ namespace osu.Game.Skinning public IBindable AggregateTempo => sample.AggregateTempo; } - - private IEnumerable getLegacyLookupNames(HitSampleInfo hitSample) - { - var lookupNames = hitSample.LookupNames.SelectMany(getFallbackNames); - - if (!UseCustomSampleBanks && !string.IsNullOrEmpty(hitSample.Suffix)) - { - // for compatibility with stable, exclude the lookup names with the custom sample bank suffix, if they are not valid for use in this skin. - // using .EndsWith() is intentional as it ensures parity in all edge cases - // (see LegacyTaikoSampleInfo for an example of one - prioritising the taiko prefix should still apply, but the sample bank should not). - lookupNames = lookupNames.Where(name => !name.EndsWith(hitSample.Suffix, StringComparison.Ordinal)); - } - - foreach (var l in lookupNames) - yield return l; - - // also for compatibility, try falling back to non-bank samples (so-called "universal" samples) as the last resort. - // going forward specifying banks shall always be required, even for elements that wouldn't require it on stable, - // which is why this is done locally here. - yield return hitSample.Name; - } - - private IEnumerable getFallbackNames(string componentName) - { - // May be something like "Gameplay/osu/approachcircle" from lazer, or "Arrows/note1" from a user skin. - yield return componentName; - - // Fall back to using the last piece for components coming from lazer (e.g. "Gameplay/osu/approachcircle" -> "approachcircle"). - string lastPiece = componentName.Split('/').Last(); - yield return componentName.StartsWith("Gameplay/taiko/", StringComparison.Ordinal) ? "taiko-" + lastPiece : lastPiece; - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - Textures?.Dispose(); - Samples?.Dispose(); - } } } From 1fd76ea3fb9db1d7e80e92fbd9e9cdb40353683c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Feb 2021 17:14:00 +0900 Subject: [PATCH 033/390] Apply changes to UI components overriding functions with changing signatures --- osu.Game.Rulesets.Osu/Skinning/Default/NumberPiece.cs | 2 +- .../Ranking/TestSceneExpandedPanelMiddleContent.cs | 2 +- .../Visual/Settings/TestSceneKeyBindingPanel.cs | 4 ++-- .../Visual/SongSelect/TestSceneBeatmapInfoWedge.cs | 2 +- .../Visual/UserInterface/TestSceneDeleteLocalScore.cs | 2 +- osu.Game/Collections/CollectionFilterDropdown.cs | 5 +++-- osu.Game/Graphics/Sprites/GlowingSpriteText.cs | 3 ++- osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs | 3 ++- osu.Game/Graphics/UserInterface/OsuButton.cs | 5 +++-- osu.Game/Graphics/UserInterface/OsuDropdown.cs | 5 +++-- osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs | 3 ++- osu.Game/Graphics/UserInterface/ShowMoreButton.cs | 3 ++- osu.Game/Graphics/UserInterface/TriangleButton.cs | 2 +- .../Overlays/BeatmapListing/Panels/GridBeatmapPanel.cs | 1 - .../Overlays/BeatmapListing/Panels/ListBeatmapPanel.cs | 1 - osu.Game/Overlays/BeatmapSet/BasicStats.cs | 3 ++- .../BeatmapSet/Scores/TopScoreStatisticsSection.cs | 1 - osu.Game/Overlays/Chat/Selection/ChannelSection.cs | 9 ++------- .../Overlays/Chat/Selection/ChannelSelectionOverlay.cs | 6 +----- .../Overlays/Comments/Buttons/CommentRepliesButton.cs | 3 ++- osu.Game/Overlays/KeyBinding/KeyBindingRow.cs | 2 +- osu.Game/Overlays/Notifications/NotificationSection.cs | 7 ++++--- osu.Game/Overlays/NowPlayingOverlay.cs | 1 - osu.Game/Overlays/OverlaySortTabControl.cs | 3 ++- .../Sections/Historical/DrawableMostPlayedBeatmap.cs | 1 - .../Profile/Sections/Ranks/DrawableProfileScore.cs | 1 - .../Settings/Sections/Audio/AudioDevicesSettings.cs | 3 ++- .../Settings/Sections/Graphics/LayoutSettings.cs | 3 ++- osu.Game/Overlays/Settings/Sections/SkinSection.cs | 2 +- osu.Game/Overlays/Settings/SettingsCheckbox.cs | 8 +++++--- osu.Game/Overlays/Settings/SettingsItem.cs | 5 +++-- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 7 ++++--- osu.Game/Screens/Menu/SongTicker.cs | 1 - osu.Game/Screens/OnlinePlay/Components/BeatmapTitle.cs | 1 - .../Playlists/PlaylistsMatchSettingsOverlay.cs | 3 ++- osu.Game/Screens/Play/BeatmapMetadataDisplay.cs | 1 - .../Ranking/Expanded/ExpandedPanelMiddleContent.cs | 1 - osu.Game/Screens/Select/Carousel/SetPanelContent.cs | 1 - osu.Game/Screens/Select/Details/AdvancedStats.cs | 3 ++- osu.Game/Screens/Select/FooterButton.cs | 5 +++-- osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs | 5 +++-- osu.Game/Skinning/SkinnableSpriteText.cs | 5 +++-- 42 files changed, 68 insertions(+), 66 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/NumberPiece.cs b/osu.Game.Rulesets.Osu/Skinning/Default/NumberPiece.cs index bea6186501..43d8d1e27f 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/NumberPiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/NumberPiece.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default public string Text { - get => number.Text; + get => number.Text.ToString(); set => number.Text = value; } diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs index 7be44a62de..f9fe42131f 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs @@ -49,7 +49,7 @@ namespace osu.Game.Tests.Visual.Ranking })); AddAssert("mapped by text not present", () => - this.ChildrenOfType().All(spriteText => !containsAny(spriteText.Text, "mapped", "by"))); + this.ChildrenOfType().All(spriteText => !containsAny(spriteText.Text.ToString(), "mapped", "by"))); } private void showPanel(ScoreInfo score) => Child = new ExpandedPanelMiddleContentContainer(score); diff --git a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs index 8330b9b360..f495e0fb23 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs @@ -78,7 +78,7 @@ namespace osu.Game.Tests.Visual.Settings clickClearButton(); - AddAssert("first binding cleared", () => string.IsNullOrEmpty(multiBindingRow.ChildrenOfType().First().Text.Text)); + AddAssert("first binding cleared", () => string.IsNullOrEmpty(multiBindingRow.ChildrenOfType().First().Text.Text.ToString())); AddStep("click second binding", () => { @@ -90,7 +90,7 @@ namespace osu.Game.Tests.Visual.Settings clickClearButton(); - AddAssert("second binding cleared", () => string.IsNullOrEmpty(multiBindingRow.ChildrenOfType().ElementAt(1).Text.Text)); + AddAssert("second binding cleared", () => string.IsNullOrEmpty(multiBindingRow.ChildrenOfType().ElementAt(1).Text.Text.ToString())); void clickClearButton() { diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs index 0b2c0ce63b..fff4a9ba61 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs @@ -119,7 +119,7 @@ namespace osu.Game.Tests.Visual.SongSelect public void TestNullBeatmap() { selectBeatmap(null); - AddAssert("check empty version", () => string.IsNullOrEmpty(infoWedge.Info.VersionLabel.Text)); + AddAssert("check empty version", () => string.IsNullOrEmpty(infoWedge.Info.VersionLabel.Text.ToString())); AddAssert("check default title", () => infoWedge.Info.TitleLabel.Text == Beatmap.Default.BeatmapInfo.Metadata.Title); AddAssert("check default artist", () => infoWedge.Info.ArtistLabel.Text == Beatmap.Default.BeatmapInfo.Metadata.Artist); AddAssert("check empty author", () => !infoWedge.Info.MapperContainer.Children.Any()); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index 81862448a8..1516a7d621 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -145,7 +145,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("click delete option", () => { - InputManager.MoveMouseTo(contextMenuContainer.ChildrenOfType().First(i => i.Item.Text.Value.ToLowerInvariant() == "delete")); + InputManager.MoveMouseTo(contextMenuContainer.ChildrenOfType().First(i => i.Item.Text.Value.ToString().ToLowerInvariant() == "delete")); InputManager.Click(MouseButton.Left); }); diff --git a/osu.Game/Collections/CollectionFilterDropdown.cs b/osu.Game/Collections/CollectionFilterDropdown.cs index bb743d4ccc..1eceb56e33 100644 --- a/osu.Game/Collections/CollectionFilterDropdown.cs +++ b/osu.Game/Collections/CollectionFilterDropdown.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -121,7 +122,7 @@ namespace osu.Game.Collections Current.TriggerChange(); } - protected override string GenerateItemText(CollectionFilterMenuItem item) => item.CollectionName.Value; + protected override LocalisableString GenerateItemText(CollectionFilterMenuItem item) => item.CollectionName.Value; protected sealed override DropdownHeader CreateHeader() => CreateCollectionHeader().With(d => { @@ -139,7 +140,7 @@ namespace osu.Game.Collections public readonly Bindable SelectedItem = new Bindable(); private readonly Bindable collectionName = new Bindable(); - protected override string Label + protected override LocalisableString Label { get => base.Label; set { } // See updateText(). diff --git a/osu.Game/Graphics/Sprites/GlowingSpriteText.cs b/osu.Game/Graphics/Sprites/GlowingSpriteText.cs index 85df2d167f..fb273d7293 100644 --- a/osu.Game/Graphics/Sprites/GlowingSpriteText.cs +++ b/osu.Game/Graphics/Sprites/GlowingSpriteText.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osuTK; namespace osu.Game.Graphics.Sprites @@ -14,7 +15,7 @@ namespace osu.Game.Graphics.Sprites { private readonly OsuSpriteText spriteText, blurredText; - public string Text + public LocalisableString Text { get => spriteText.Text; set => blurredText.Text = spriteText.Text = value; diff --git a/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs b/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs index b499b26f38..8df2c1c2fd 100644 --- a/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Graphics.Sprites; using osuTK.Graphics; @@ -105,7 +106,7 @@ namespace osu.Game.Graphics.UserInterface protected class TextContainer : Container, IHasText { - public string Text + public LocalisableString Text { get => NormalText.Text; set diff --git a/osu.Game/Graphics/UserInterface/OsuButton.cs b/osu.Game/Graphics/UserInterface/OsuButton.cs index 9cf8f02024..d2114134cf 100644 --- a/osu.Game/Graphics/UserInterface/OsuButton.cs +++ b/osu.Game/Graphics/UserInterface/OsuButton.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Graphics.Sprites; using osuTK.Graphics; @@ -21,9 +22,9 @@ namespace osu.Game.Graphics.UserInterface /// public class OsuButton : Button { - public string Text + public LocalisableString Text { - get => SpriteText?.Text; + get => SpriteText.Text; set { if (SpriteText != null) diff --git a/osu.Game/Graphics/UserInterface/OsuDropdown.cs b/osu.Game/Graphics/UserInterface/OsuDropdown.cs index cc76c12975..15fb00ccb0 100644 --- a/osu.Game/Graphics/UserInterface/OsuDropdown.cs +++ b/osu.Game/Graphics/UserInterface/OsuDropdown.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Localisation; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osuTK; @@ -168,7 +169,7 @@ namespace osu.Game.Graphics.UserInterface protected new class Content : FillFlowContainer, IHasText { - public string Text + public LocalisableString Text { get => Label.Text; set => Label.Text = value; @@ -215,7 +216,7 @@ namespace osu.Game.Graphics.UserInterface { protected readonly SpriteText Text; - protected override string Label + protected override LocalisableString Label { get => Text.Text; set => Text.Text = value; diff --git a/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs b/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs index bdc95ee048..b66a4a58ce 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs @@ -11,6 +11,7 @@ using osu.Game.Graphics.Sprites; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; +using osu.Framework.Localisation; namespace osu.Game.Graphics.UserInterface { @@ -35,7 +36,7 @@ namespace osu.Game.Graphics.UserInterface } } - public string Text + public LocalisableString Text { get => text.Text; set => text.Text = value; diff --git a/osu.Game/Graphics/UserInterface/ShowMoreButton.cs b/osu.Game/Graphics/UserInterface/ShowMoreButton.cs index 924c7913f3..615895074c 100644 --- a/osu.Game/Graphics/UserInterface/ShowMoreButton.cs +++ b/osu.Game/Graphics/UserInterface/ShowMoreButton.cs @@ -11,6 +11,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Overlays; using osuTK; using System.Collections.Generic; +using osu.Framework.Localisation; namespace osu.Game.Graphics.UserInterface { @@ -18,7 +19,7 @@ namespace osu.Game.Graphics.UserInterface { private const int duration = 200; - public string Text + public LocalisableString Text { get => text.Text; set => text.Text = value; diff --git a/osu.Game/Graphics/UserInterface/TriangleButton.cs b/osu.Game/Graphics/UserInterface/TriangleButton.cs index 5baf794227..003a81f562 100644 --- a/osu.Game/Graphics/UserInterface/TriangleButton.cs +++ b/osu.Game/Graphics/UserInterface/TriangleButton.cs @@ -27,7 +27,7 @@ namespace osu.Game.Graphics.UserInterface }); } - public virtual IEnumerable FilterTerms => new[] { Text }; + public virtual IEnumerable FilterTerms => new[] { Text.ToString() }; public bool MatchingFilter { diff --git a/osu.Game/Overlays/BeatmapListing/Panels/GridBeatmapPanel.cs b/osu.Game/Overlays/BeatmapListing/Panels/GridBeatmapPanel.cs index c1d366bb82..97e7ce83a5 100644 --- a/osu.Game/Overlays/BeatmapListing/Panels/GridBeatmapPanel.cs +++ b/osu.Game/Overlays/BeatmapListing/Panels/GridBeatmapPanel.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; -using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; diff --git a/osu.Game/Overlays/BeatmapListing/Panels/ListBeatmapPanel.cs b/osu.Game/Overlays/BeatmapListing/Panels/ListBeatmapPanel.cs index 76a30d1c11..4a887ed571 100644 --- a/osu.Game/Overlays/BeatmapListing/Panels/ListBeatmapPanel.cs +++ b/osu.Game/Overlays/BeatmapListing/Panels/ListBeatmapPanel.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; -using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; diff --git a/osu.Game/Overlays/BeatmapSet/BasicStats.cs b/osu.Game/Overlays/BeatmapSet/BasicStats.cs index a2464bef09..cf74c0d4d3 100644 --- a/osu.Game/Overlays/BeatmapSet/BasicStats.cs +++ b/osu.Game/Overlays/BeatmapSet/BasicStats.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -96,7 +97,7 @@ namespace osu.Game.Overlays.BeatmapSet public string TooltipText { get; } - public string Value + public LocalisableString Value { get => value.Text; set => this.value.Text = value; diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs index 93744dd6a3..c281d7b432 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; -using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; diff --git a/osu.Game/Overlays/Chat/Selection/ChannelSection.cs b/osu.Game/Overlays/Chat/Selection/ChannelSection.cs index eac48ca5cb..e18302770c 100644 --- a/osu.Game/Overlays/Chat/Selection/ChannelSection.cs +++ b/osu.Game/Overlays/Chat/Selection/ChannelSection.cs @@ -4,12 +4,12 @@ using System; using System.Collections.Generic; using System.Linq; -using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.Chat; +using osuTK; namespace osu.Game.Overlays.Chat.Selection { @@ -29,12 +29,6 @@ namespace osu.Game.Overlays.Chat.Selection public bool FilteringActive { get; set; } - public string Header - { - get => header.Text; - set => header.Text = value.ToUpperInvariant(); - } - public IEnumerable Channels { set => ChannelFlow.ChildrenEnumerable = value.Select(c => new ChannelListItem(c)); @@ -50,6 +44,7 @@ namespace osu.Game.Overlays.Chat.Selection header = new OsuSpriteText { Font = OsuFont.GetFont(size: 15, weight: FontWeight.Bold), + Text = "All Channels".ToUpperInvariant() }, ChannelFlow = new FillFlowContainer { diff --git a/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs b/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs index be9ecc6746..231d7ca63c 100644 --- a/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs +++ b/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs @@ -131,11 +131,7 @@ namespace osu.Game.Overlays.Chat.Selection { sectionsFlow.ChildrenEnumerable = new[] { - new ChannelSection - { - Header = "All Channels", - Channels = channels, - }, + new ChannelSection { Channels = channels, }, }; foreach (ChannelSection s in sectionsFlow.Children) diff --git a/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs b/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs index 57bf2af4d2..2f7f16dd6f 100644 --- a/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs +++ b/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -16,7 +17,7 @@ namespace osu.Game.Overlays.Comments.Buttons { public abstract class CommentRepliesButton : CompositeDrawable { - protected string Text + protected LocalisableString Text { get => text.Text; set => text.Text = value; diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs index b808d49fa2..300fce962a 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -51,7 +51,7 @@ namespace osu.Game.Overlays.KeyBinding private FillFlowContainer cancelAndClearButtons; private FillFlowContainer buttons; - public IEnumerable FilterTerms => bindings.Select(b => b.KeyCombination.ReadableString()).Prepend((string)text.Text); + public IEnumerable FilterTerms => bindings.Select(b => b.KeyCombination.ReadableString()).Prepend(text.Text.ToString()); public KeyBindingRow(object action, IEnumerable bindings) { diff --git a/osu.Game/Overlays/Notifications/NotificationSection.cs b/osu.Game/Overlays/Notifications/NotificationSection.cs index 38ba712254..bc41311a6d 100644 --- a/osu.Game/Overlays/Notifications/NotificationSection.cs +++ b/osu.Game/Overlays/Notifications/NotificationSection.cs @@ -8,6 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osuTK; @@ -37,7 +38,7 @@ namespace osu.Game.Overlays.Notifications public NotificationSection(string title, string clearButtonText) { - this.clearButtonText = clearButtonText; + this.clearButtonText = clearButtonText.ToUpperInvariant(); titleText = title; } @@ -138,10 +139,10 @@ namespace osu.Game.Overlays.Notifications }; } - public string Text + public LocalisableString Text { get => text.Text; - set => text.Text = value.ToUpperInvariant(); + set => text.Text = value; } } diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index 2866d2ad6d..74317a143c 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -13,7 +13,6 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Input.Events; -using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; diff --git a/osu.Game/Overlays/OverlaySortTabControl.cs b/osu.Game/Overlays/OverlaySortTabControl.cs index b2212336ef..0ebabd424f 100644 --- a/osu.Game/Overlays/OverlaySortTabControl.cs +++ b/osu.Game/Overlays/OverlaySortTabControl.cs @@ -17,6 +17,7 @@ using osu.Game.Overlays.Comments; using JetBrains.Annotations; using System; using osu.Framework.Extensions; +using osu.Framework.Localisation; namespace osu.Game.Overlays { @@ -30,7 +31,7 @@ namespace osu.Game.Overlays set => current.Current = value; } - public string Title + public LocalisableString Title { get => text.Text; set => text.Text = value; diff --git a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs index 5b7c5efbe2..e485802095 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs @@ -5,7 +5,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs index 2c20dcc0ef..859637485f 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs @@ -7,7 +7,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; diff --git a/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs index bed74542c9..b31e7dc45b 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs @@ -6,6 +6,7 @@ using osu.Framework.Audio; using osu.Framework.Graphics; using System.Collections.Generic; using System.Linq; +using osu.Framework.Localisation; using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Settings.Sections.Audio @@ -76,7 +77,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio private class AudioDeviceDropdownControl : DropdownControl { - protected override string GenerateItemText(string item) + protected override LocalisableString GenerateItemText(string item) => string.IsNullOrEmpty(item) ? "Default" : base.GenerateItemText(item); } } diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index 7acbf038d8..4d5c2e06eb 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -11,6 +11,7 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; using osu.Framework.Platform; using osu.Game.Configuration; using osu.Game.Graphics.Containers; @@ -234,7 +235,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics private class ResolutionDropdownControl : DropdownControl { - protected override string GenerateItemText(Size item) + protected override LocalisableString GenerateItemText(Size item) { if (item == new Size(9999, 9999)) return "Default"; diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 7c8309fd56..75068bd611 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -178,7 +178,7 @@ namespace osu.Game.Overlays.Settings.Sections private class SkinDropdownControl : DropdownControl { - protected override string GenerateItemText(SkinInfo item) => item.ToString(); + protected override LocalisableString GenerateItemText(SkinInfo item) => item.ToString(); } } diff --git a/osu.Game/Overlays/Settings/SettingsCheckbox.cs b/osu.Game/Overlays/Settings/SettingsCheckbox.cs index 437b2e45b3..8b7ac80a5b 100644 --- a/osu.Game/Overlays/Settings/SettingsCheckbox.cs +++ b/osu.Game/Overlays/Settings/SettingsCheckbox.cs @@ -2,20 +2,22 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; +using osu.Framework.Localisation; using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Settings { public class SettingsCheckbox : SettingsItem { - private string labelText; + private LocalisableString labelText; protected override Drawable CreateControl() => new OsuCheckbox(); - public override string LabelText + public override LocalisableString LabelText { get => labelText; - set => ((OsuCheckbox)Control).LabelText = labelText = value; + // checkbox doesn't properly support localisation yet. + set => ((OsuCheckbox)Control).LabelText = (labelText = value).ToString(); } } } diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index af225889da..aafd7463a6 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -15,6 +15,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osuTK; @@ -39,7 +40,7 @@ namespace osu.Game.Overlays.Settings public string TooltipText { get; set; } - public virtual string LabelText + public virtual LocalisableString LabelText { get => labelText?.Text ?? string.Empty; set @@ -69,7 +70,7 @@ namespace osu.Game.Overlays.Settings set => controlWithCurrent.Current = value; } - public virtual IEnumerable FilterTerms => Keywords == null ? new[] { LabelText } : new List(Keywords) { LabelText }.ToArray(); + public virtual IEnumerable FilterTerms => Keywords == null ? new[] { LabelText.ToString() } : new List(Keywords) { LabelText.ToString() }.ToArray(); public IEnumerable Keywords { get; set; } diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index 83f2bdf6cb..7790a21e0a 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Containers; @@ -43,19 +44,19 @@ namespace osu.Game.Overlays.Toolbar Texture = textures.Get(texture), }); - public string Text + public LocalisableString Text { get => DrawableText.Text; set => DrawableText.Text = value; } - public string TooltipMain + public LocalisableString TooltipMain { get => tooltip1.Text; set => tooltip1.Text = value; } - public string TooltipSub + public LocalisableString TooltipSub { get => tooltip2.Text; set => tooltip2.Text = value; diff --git a/osu.Game/Screens/Menu/SongTicker.cs b/osu.Game/Screens/Menu/SongTicker.cs index c4943e77d5..fd9d9a3fac 100644 --- a/osu.Game/Screens/Menu/SongTicker.cs +++ b/osu.Game/Screens/Menu/SongTicker.cs @@ -9,7 +9,6 @@ using osuTK; using osu.Game.Graphics; using osu.Framework.Bindables; using osu.Game.Beatmaps; -using osu.Framework.Localisation; namespace osu.Game.Screens.Menu { diff --git a/osu.Game/Screens/OnlinePlay/Components/BeatmapTitle.cs b/osu.Game/Screens/OnlinePlay/Components/BeatmapTitle.cs index acb82360b3..b64ea37a59 100644 --- a/osu.Game/Screens/OnlinePlay/Components/BeatmapTitle.cs +++ b/osu.Game/Screens/OnlinePlay/Components/BeatmapTitle.cs @@ -4,7 +4,6 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsMatchSettingsOverlay.cs index 2a1efbc040..5062a296a8 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsMatchSettingsOverlay.cs @@ -10,6 +10,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -362,7 +363,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists Menu.MaxHeight = 100; } - protected override string GenerateItemText(TimeSpan item) => item.Humanize(); + protected override LocalisableString GenerateItemText(TimeSpan item) => item.Humanize(); } } } diff --git a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs index eff06e26ee..bb82b00100 100644 --- a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs +++ b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs @@ -7,7 +7,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index ff6203bc25..85a9b06a70 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -6,7 +6,6 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; diff --git a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs index 4e8d27f14d..82704c24fb 100644 --- a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs +++ b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs @@ -6,7 +6,6 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Localisation; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index ab4f3f4796..1627d3ddfc 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -15,6 +15,7 @@ using System.Collections.Generic; using osu.Game.Rulesets.Mods; using System.Linq; using System.Threading; +using osu.Framework.Localisation; using osu.Framework.Threading; using osu.Framework.Utils; using osu.Game.Configuration; @@ -180,7 +181,7 @@ namespace osu.Game.Screens.Select.Details [Resolved] private OsuColour colours { get; set; } - public string Title + public LocalisableString Title { get => name.Text; set => name.Text = value; diff --git a/osu.Game/Screens/Select/FooterButton.cs b/osu.Game/Screens/Select/FooterButton.cs index 35970cd960..7bdeacc91a 100644 --- a/osu.Game/Screens/Select/FooterButton.cs +++ b/osu.Game/Screens/Select/FooterButton.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Containers; @@ -21,9 +22,9 @@ namespace osu.Game.Screens.Select protected static readonly Vector2 SHEAR = new Vector2(SHEAR_WIDTH / Footer.HEIGHT, 0); - public string Text + public LocalisableString Text { - get => SpriteText?.Text; + get => SpriteText.Text; set { if (SpriteText != null) diff --git a/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs b/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs index 6e2f3cc9df..845c0a914e 100644 --- a/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs +++ b/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osuTK; @@ -39,13 +40,13 @@ namespace osu.Game.Screens.Select.Options set => iconText.Icon = value; } - public string FirstLineText + public LocalisableString FirstLineText { get => firstLine.Text; set => firstLine.Text = value; } - public string SecondLineText + public LocalisableString SecondLineText { get => secondLine.Text; set => secondLine.Text = value; diff --git a/osu.Game/Skinning/SkinnableSpriteText.cs b/osu.Game/Skinning/SkinnableSpriteText.cs index 567dd348e1..06461127b1 100644 --- a/osu.Game/Skinning/SkinnableSpriteText.cs +++ b/osu.Game/Skinning/SkinnableSpriteText.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; namespace osu.Game.Skinning { @@ -21,9 +22,9 @@ namespace osu.Game.Skinning textDrawable.Text = Text; } - private string text; + private LocalisableString text; - public string Text + public LocalisableString Text { get => text; set From 8a97e2e28da1f7ad257c3bdf28c98f7c6bfe826f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Feb 2021 17:14:13 +0900 Subject: [PATCH 034/390] Update LocalisedString usages to RomanisedString --- osu.Game.Tournament/Components/TournamentBeatmapPanel.cs | 4 ++-- .../Overlays/BeatmapListing/Panels/GridBeatmapPanel.cs | 5 +++-- .../Overlays/BeatmapListing/Panels/ListBeatmapPanel.cs | 5 +++-- .../BeatmapSet/Scores/TopScoreStatisticsSection.cs | 2 +- osu.Game/Overlays/Music/PlaylistItem.cs | 4 ++-- osu.Game/Overlays/NowPlayingOverlay.cs | 5 +++-- .../Sections/Historical/DrawableMostPlayedBeatmap.cs | 9 +++++---- .../Profile/Sections/Ranks/DrawableProfileScore.cs | 9 +++++---- osu.Game/Overlays/Settings/Sections/SkinSection.cs | 1 + osu.Game/Screens/Menu/SongTicker.cs | 5 +++-- osu.Game/Screens/OnlinePlay/Components/BeatmapTitle.cs | 5 +++-- osu.Game/Screens/Play/BeatmapMetadataDisplay.cs | 5 +++-- .../Ranking/Expanded/ExpandedPanelMiddleContent.cs | 5 +++-- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 4 ++-- osu.Game/Screens/Select/Carousel/SetPanelContent.cs | 5 +++-- 15 files changed, 42 insertions(+), 31 deletions(-) diff --git a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs index d1197b1a61..e6d73c6e83 100644 --- a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs +++ b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs @@ -74,9 +74,9 @@ namespace osu.Game.Tournament.Components { new TournamentSpriteText { - Text = new LocalisedString(( + Text = new RomanisableString( $"{Beatmap.Metadata.ArtistUnicode ?? Beatmap.Metadata.Artist} - {Beatmap.Metadata.TitleUnicode ?? Beatmap.Metadata.Title}", - $"{Beatmap.Metadata.Artist} - {Beatmap.Metadata.Title}")), + $"{Beatmap.Metadata.Artist} - {Beatmap.Metadata.Title}"), Font = OsuFont.Torus.With(weight: FontWeight.Bold), }, new FillFlowContainer diff --git a/osu.Game/Overlays/BeatmapListing/Panels/GridBeatmapPanel.cs b/osu.Game/Overlays/BeatmapListing/Panels/GridBeatmapPanel.cs index 97e7ce83a5..ba4725b49a 100644 --- a/osu.Game/Overlays/BeatmapListing/Panels/GridBeatmapPanel.cs +++ b/osu.Game/Overlays/BeatmapListing/Panels/GridBeatmapPanel.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; @@ -83,14 +84,14 @@ namespace osu.Game.Overlays.BeatmapListing.Panels { new OsuSpriteText { - Text = new LocalisedString((SetInfo.Metadata.TitleUnicode, SetInfo.Metadata.Title)), + Text = new RomanisableString(SetInfo.Metadata.Title, SetInfo.Metadata.TitleUnicode), Font = OsuFont.GetFont(size: 18, weight: FontWeight.Bold, italics: true) }, } }, new OsuSpriteText { - Text = new LocalisedString((SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist)), + Text = new RomanisableString(SetInfo.Metadata.Artist, SetInfo.Metadata.ArtistUnicode), Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true) }, }, diff --git a/osu.Game/Overlays/BeatmapListing/Panels/ListBeatmapPanel.cs b/osu.Game/Overlays/BeatmapListing/Panels/ListBeatmapPanel.cs index 4a887ed571..624cb89d1e 100644 --- a/osu.Game/Overlays/BeatmapListing/Panels/ListBeatmapPanel.cs +++ b/osu.Game/Overlays/BeatmapListing/Panels/ListBeatmapPanel.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; @@ -106,14 +107,14 @@ namespace osu.Game.Overlays.BeatmapListing.Panels { new OsuSpriteText { - Text = new LocalisedString((SetInfo.Metadata.TitleUnicode, SetInfo.Metadata.Title)), + Text = new RomanisableString(SetInfo.Metadata.Title, SetInfo.Metadata.TitleUnicode), Font = OsuFont.GetFont(size: 18, weight: FontWeight.Bold, italics: true) }, } }, new OsuSpriteText { - Text = new LocalisedString((SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist)), + Text = new RomanisableString(SetInfo.Metadata.Artist, SetInfo.Metadata.ArtistUnicode), Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true) }, } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs index c281d7b432..5cb834b510 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs @@ -203,7 +203,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores this.text = text; } - public LocalisedString Text + public string Text { set => text.Text = value; } diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs index 96dff39fae..dab9bc9629 100644 --- a/osu.Game/Overlays/Music/PlaylistItem.cs +++ b/osu.Game/Overlays/Music/PlaylistItem.cs @@ -48,8 +48,8 @@ namespace osu.Game.Overlays.Music artistColour = colours.Gray9; HandleColour = colours.Gray5; - title = localisation.GetLocalisedString(new LocalisedString((Model.Metadata.TitleUnicode, Model.Metadata.Title))); - artist = localisation.GetLocalisedString(new LocalisedString((Model.Metadata.ArtistUnicode, Model.Metadata.Artist))); + title = localisation.GetLocalisedString(new RomanisableString(Model.Metadata.Title, Model.Metadata.TitleUnicode)); + artist = localisation.GetLocalisedString(new RomanisableString(Model.Metadata.Artist, Model.Metadata.ArtistUnicode)); } protected override void LoadComplete() diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index 74317a143c..9c17392e25 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -292,8 +293,8 @@ namespace osu.Game.Overlays else { BeatmapMetadata metadata = beatmap.Metadata; - title.Text = new LocalisedString((metadata.TitleUnicode, metadata.Title)); - artist.Text = new LocalisedString((metadata.ArtistUnicode, metadata.Artist)); + title.Text = new RomanisableString(metadata.Title, metadata.TitleUnicode); + artist.Text = new RomanisableString(metadata.Artist, metadata.ArtistUnicode); } }); diff --git a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs index e485802095..48a0481b9e 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs @@ -12,6 +12,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osuTK; using osu.Framework.Graphics.Cursor; +using osu.Framework.Localisation; namespace osu.Game.Overlays.Profile.Sections.Historical { @@ -128,14 +129,14 @@ namespace osu.Game.Overlays.Profile.Sections.Historical { new OsuSpriteText { - Text = new LocalisedString(( - $"{beatmap.Metadata.TitleUnicode ?? beatmap.Metadata.Title} [{beatmap.Version}] ", - $"{beatmap.Metadata.Title ?? beatmap.Metadata.TitleUnicode} [{beatmap.Version}] ")), + Text = new RomanisableString( + $"{beatmap.Metadata.Title ?? beatmap.Metadata.TitleUnicode} [{beatmap.Version}] ", + $"{beatmap.Metadata.TitleUnicode ?? beatmap.Metadata.Title} [{beatmap.Version}] "), Font = OsuFont.GetFont(weight: FontWeight.Bold) }, new OsuSpriteText { - Text = "by " + new LocalisedString((beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist)), + Text = "by " + new RomanisableString(beatmap.Metadata.Artist, beatmap.Metadata.ArtistUnicode), Font = OsuFont.GetFont(weight: FontWeight.Regular) }, }; diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs index 859637485f..ca9e19cd56 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs @@ -7,6 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -255,16 +256,16 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, - Text = new LocalisedString(( - $"{beatmap.Metadata.TitleUnicode ?? beatmap.Metadata.Title} ", - $"{beatmap.Metadata.Title ?? beatmap.Metadata.TitleUnicode} ")), + Text = new RomanisableString( + $"{beatmap.Metadata.Title ?? beatmap.Metadata.TitleUnicode} ", + $"{beatmap.Metadata.TitleUnicode ?? beatmap.Metadata.Title} "), Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold, italics: true) }, new OsuSpriteText { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, - Text = "by " + new LocalisedString((beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist)), + Text = "by " + new RomanisableString(beatmap.Metadata.Artist, beatmap.Metadata.ArtistUnicode), Font = OsuFont.GetFont(size: 12, italics: true) }, }; diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 75068bd611..316837d27d 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -8,6 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osu.Framework.Logging; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; diff --git a/osu.Game/Screens/Menu/SongTicker.cs b/osu.Game/Screens/Menu/SongTicker.cs index fd9d9a3fac..2be446d71a 100644 --- a/osu.Game/Screens/Menu/SongTicker.cs +++ b/osu.Game/Screens/Menu/SongTicker.cs @@ -8,6 +8,7 @@ using osu.Game.Graphics.Sprites; using osuTK; using osu.Game.Graphics; using osu.Framework.Bindables; +using osu.Framework.Localisation; using osu.Game.Beatmaps; namespace osu.Game.Screens.Menu @@ -60,8 +61,8 @@ namespace osu.Game.Screens.Menu { var metadata = beatmap.Value.Metadata; - title.Text = new LocalisedString((metadata.TitleUnicode, metadata.Title)); - artist.Text = new LocalisedString((metadata.ArtistUnicode, metadata.Artist)); + title.Text = new RomanisableString(metadata.Title, metadata.TitleUnicode); + artist.Text = new RomanisableString(metadata.Artist, metadata.ArtistUnicode); this.FadeInFromZero(fade_duration / 2f) .Delay(4000) diff --git a/osu.Game/Screens/OnlinePlay/Components/BeatmapTitle.cs b/osu.Game/Screens/OnlinePlay/Components/BeatmapTitle.cs index b64ea37a59..299e3e3768 100644 --- a/osu.Game/Screens/OnlinePlay/Components/BeatmapTitle.cs +++ b/osu.Game/Screens/OnlinePlay/Components/BeatmapTitle.cs @@ -4,6 +4,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -72,7 +73,7 @@ namespace osu.Game.Screens.OnlinePlay.Components { new OsuSpriteText { - Text = new LocalisedString((beatmap.Value.Metadata.ArtistUnicode, beatmap.Value.Metadata.Artist)), + Text = new RomanisableString(beatmap.Value.Metadata.Artist, beatmap.Value.Metadata.ArtistUnicode), Font = OsuFont.GetFont(size: TextSize), }, new OsuSpriteText @@ -82,7 +83,7 @@ namespace osu.Game.Screens.OnlinePlay.Components }, new OsuSpriteText { - Text = new LocalisedString((beatmap.Value.Metadata.TitleUnicode, beatmap.Value.Metadata.Title)), + Text = new RomanisableString(beatmap.Value.Metadata.Title, beatmap.Value.Metadata.TitleUnicode), Font = OsuFont.GetFont(size: TextSize), } }, LinkAction.OpenBeatmap, beatmap.Value.OnlineBeatmapID.ToString(), "Open beatmap"); diff --git a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs index bb82b00100..0779a9c637 100644 --- a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs +++ b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs @@ -7,6 +7,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -72,7 +73,7 @@ namespace osu.Game.Screens.Play }), new OsuSpriteText { - Text = new LocalisedString((metadata.TitleUnicode, metadata.Title)), + Text = new RomanisableString(metadata.Title, metadata.TitleUnicode), Font = OsuFont.GetFont(size: 36, italics: true), Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, @@ -80,7 +81,7 @@ namespace osu.Game.Screens.Play }, new OsuSpriteText { - Text = new LocalisedString((metadata.ArtistUnicode, metadata.Artist)), + Text = new RomanisableString(metadata.Artist, metadata.ArtistUnicode), Font = OsuFont.GetFont(size: 26, italics: true), Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index 85a9b06a70..234e4f2023 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -6,6 +6,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -100,7 +101,7 @@ namespace osu.Game.Screens.Ranking.Expanded { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Text = new LocalisedString((metadata.TitleUnicode, metadata.Title)), + Text = new RomanisableString(metadata.Title, metadata.TitleUnicode), Font = OsuFont.Torus.With(size: 20, weight: FontWeight.SemiBold), MaxWidth = ScorePanel.EXPANDED_WIDTH - padding * 2, Truncate = true, @@ -109,7 +110,7 @@ namespace osu.Game.Screens.Ranking.Expanded { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Text = new LocalisedString((metadata.ArtistUnicode, metadata.Artist)), + Text = new RomanisableString(metadata.Artist, metadata.ArtistUnicode), Font = OsuFont.Torus.With(size: 14, weight: FontWeight.SemiBold), MaxWidth = ScorePanel.EXPANDED_WIDTH - padding * 2, Truncate = true, diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 86cb561bc7..0c5b67026c 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -187,8 +187,8 @@ namespace osu.Game.Screens.Select RelativeSizeAxes = Axes.Both; - titleBinding = localisation.GetLocalisedString(new LocalisedString((metadata.TitleUnicode, metadata.Title))); - artistBinding = localisation.GetLocalisedString(new LocalisedString((metadata.ArtistUnicode, metadata.Artist))); + titleBinding = localisation.GetLocalisedString(new RomanisableString(metadata.Title, metadata.TitleUnicode)); + artistBinding = localisation.GetLocalisedString(new RomanisableString(metadata.Artist, metadata.ArtistUnicode)); Children = new Drawable[] { diff --git a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs index 82704c24fb..0e99a4ce70 100644 --- a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs +++ b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs @@ -6,6 +6,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Localisation; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -40,13 +41,13 @@ namespace osu.Game.Screens.Select.Carousel { new OsuSpriteText { - Text = new LocalisedString((beatmapSet.Metadata.TitleUnicode, beatmapSet.Metadata.Title)), + Text = new RomanisableString(beatmapSet.Metadata.Title, beatmapSet.Metadata.TitleUnicode), Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 22, italics: true), Shadow = true, }, new OsuSpriteText { - Text = new LocalisedString((beatmapSet.Metadata.ArtistUnicode, beatmapSet.Metadata.Artist)), + Text = new RomanisableString(beatmapSet.Metadata.Artist, beatmapSet.Metadata.ArtistUnicode), Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 17, italics: true), Shadow = true, }, From 5e9040c29108cf423ddcf0d6ea418f4aa6690b46 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 22 Feb 2021 16:26:35 +0300 Subject: [PATCH 035/390] Use "pausing supported" conditional instead --- osu.Game/Screens/Play/Player.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 5a86ac646a..0046eea91c 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -427,7 +427,7 @@ namespace osu.Game.Screens.Play private void updatePauseOnFocusLostState() { - if (!PauseOnFocusLost || DrawableRuleset.HasReplayLoaded.Value || breakTracker.IsBreakTime.Value) + if (!PauseOnFocusLost || pausingSupportedByCurrentState || breakTracker.IsBreakTime.Value) return; if (gameActive.Value == false) From 5493c55da7287bea7f77651ea0474587cc426626 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 22 Feb 2021 16:59:35 +0300 Subject: [PATCH 036/390] Fix silly mistake --- osu.Game/Screens/Play/Player.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 0046eea91c..2ded1752da 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -427,7 +427,7 @@ namespace osu.Game.Screens.Play private void updatePauseOnFocusLostState() { - if (!PauseOnFocusLost || pausingSupportedByCurrentState || breakTracker.IsBreakTime.Value) + if (!PauseOnFocusLost || !pausingSupportedByCurrentState || breakTracker.IsBreakTime.Value) return; if (gameActive.Value == false) From f62120c66b6cbb852f96d2ede5e60b933214f08b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Feb 2021 22:45:55 +0100 Subject: [PATCH 037/390] Remove unused using directive --- .../Visual/Playlists/TestScenePlaylistsResultsScreen.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index e34da1ef0c..be8032cde8 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Linq; using System.Net; -using System.Threading.Tasks; using JetBrains.Annotations; using Newtonsoft.Json.Linq; using NUnit.Framework; From 6a5c6febc56567cd5acb43fdb274f9918c8ef230 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Feb 2021 13:23:32 +0900 Subject: [PATCH 038/390] Add inline comment explaining the retry loop --- osu.Game/Screens/Play/Player.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 2ded1752da..e81efdac78 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -434,6 +434,8 @@ namespace osu.Game.Screens.Play { bool paused = Pause(); + // if the initial pause could not be satisfied, the pause cooldown may be active. + // reschedule the pause attempt until it can be achieved. if (!paused) Scheduler.AddOnce(updatePauseOnFocusLostState); } From 996c0897d1faa058fa12ea07b13799e01b67aab9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Feb 2021 13:40:21 +0900 Subject: [PATCH 039/390] Seek via GameplayClockContainer for better reliability --- osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index e5959a3edf..5d070b424a 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -61,7 +61,7 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("press enter", () => InputManager.Key(Key.Enter)); AddUntilStep("wait for player", () => (player = Game.ScreenStack.CurrentScreen as Player) != null); - AddStep("seek to end", () => beatmap().Track.Seek(beatmap().Track.Length)); + AddStep("seek to end", () => player.ChildrenOfType().First().Seek(beatmap().Track.Length)); AddUntilStep("wait for pass", () => (results = Game.ScreenStack.CurrentScreen as ResultsScreen) != null && results.IsLoaded); AddStep("attempt to retry", () => results.ChildrenOfType().First().Action()); AddUntilStep("wait for player", () => Game.ScreenStack.CurrentScreen != player && Game.ScreenStack.CurrentScreen is Player); From 672fd3f9d2935099f24ffa5f2a879295c6970f77 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Feb 2021 14:24:24 +0900 Subject: [PATCH 040/390] When disable mouse buttons during gameplay is selected, disable more globally Until now the disable setting would only apply to left/right buttons, and only in gameplay. This change will cause any global actions bound to mouse buttons to also not work during gameplay. Closes #11879. --- osu.Game/Rulesets/UI/RulesetInputManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 07de2bf601..963c3427d0 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -109,9 +109,9 @@ namespace osu.Game.Rulesets.UI { switch (e) { - case MouseDownEvent mouseDown when mouseDown.Button == MouseButton.Left || mouseDown.Button == MouseButton.Right: + case MouseDownEvent _: if (mouseDisabled.Value) - return false; + return true; // importantly, block upwards propagation so global bindings also don't fire. break; From ec4b770cbac2260bfb731c64d8ba4f7990c9c02a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Feb 2021 14:56:03 +0900 Subject: [PATCH 041/390] Remove unused using statement --- osu.Game/Rulesets/UI/RulesetInputManager.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 963c3427d0..d6f002ea2c 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -16,7 +16,6 @@ using osu.Game.Configuration; using osu.Game.Input.Bindings; using osu.Game.Input.Handlers; using osu.Game.Screens.Play; -using osuTK.Input; using static osu.Game.Input.Handlers.ReplayInputHandler; namespace osu.Game.Rulesets.UI From 664d243003b3fab7d4d6b008302b95fdf7ac12c0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Feb 2021 15:22:46 +0900 Subject: [PATCH 042/390] Disable multiplayer/spectator on iOS until it can be supported again --- osu.Game/Online/API/APIAccess.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 8ffa0221c8..ce01378b17 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -10,6 +10,7 @@ using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json.Linq; +using osu.Framework; using osu.Framework.Bindables; using osu.Framework.Extensions.ExceptionExtensions; using osu.Framework.Extensions.ObjectExtensions; @@ -246,7 +247,14 @@ namespace osu.Game.Online.API this.password = password; } - public IHubClientConnector GetHubConnector(string clientName, string endpoint) => new HubClientConnector(clientName, endpoint, this, versionHash); + public IHubClientConnector GetHubConnector(string clientName, string endpoint) + { + // disabled until the underlying runtime issue is resolved, see https://github.com/mono/mono/issues/20805. + if (RuntimeInfo.OS == RuntimeInfo.Platform.iOS) + return null; + + return new HubClientConnector(clientName, endpoint, this, versionHash); + } public RegistrationRequest.RegistrationRequestErrors CreateAccount(string email, string username, string password) { From c514233141756f9700bb3b51881480ddba98f8ab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Feb 2021 15:57:41 +0900 Subject: [PATCH 043/390] Fix importing collections twice from stable causing a hard crash Somehow a bindable equality check failure got through review. Not sure if there's some way to protect against this going forward, but we may want to. --- osu.Game/Collections/CollectionManager.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index a65d9a415d..fb9c230c7a 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -138,10 +138,10 @@ namespace osu.Game.Collections PostNotification?.Invoke(notification); - var collection = readCollections(stream, notification); - await importCollections(collection); + var collections = readCollections(stream, notification); + await importCollections(collections); - notification.CompletionText = $"Imported {collection.Count} collections"; + notification.CompletionText = $"Imported {collections.Count} collections"; notification.State = ProgressNotificationState.Completed; } @@ -155,7 +155,7 @@ namespace osu.Game.Collections { foreach (var newCol in newCollections) { - var existing = Collections.FirstOrDefault(c => c.Name == newCol.Name); + var existing = Collections.FirstOrDefault(c => c.Name.Value == newCol.Name.Value); if (existing == null) Collections.Add(existing = new BeatmapCollection { Name = { Value = newCol.Name.Value } }); From f45cedeb8524206a37f7dd7a59cadf5ed20fde21 Mon Sep 17 00:00:00 2001 From: Ronnie Moir <7267697+H2n9@users.noreply.github.com> Date: Tue, 23 Feb 2021 15:38:09 +0000 Subject: [PATCH 044/390] Adjust initial and final rate ranges and prevent them from overlapping --- osu.Game/Rulesets/Mods/ModWindDown.cs | 13 +++++++++++-- osu.Game/Rulesets/Mods/ModWindUp.cs | 13 +++++++++++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModWindDown.cs b/osu.Game/Rulesets/Mods/ModWindDown.cs index 679b50057b..c47ec5fbde 100644 --- a/osu.Game/Rulesets/Mods/ModWindDown.cs +++ b/osu.Game/Rulesets/Mods/ModWindDown.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Mods [SettingSource("Initial rate", "The starting speed of the track")] public override BindableNumber InitialRate { get; } = new BindableDouble { - MinValue = 1, + MinValue = 0.5, MaxValue = 2, Default = 1, Value = 1, @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Mods public override BindableNumber FinalRate { get; } = new BindableDouble { MinValue = 0.5, - MaxValue = 0.99, + MaxValue = 2, Default = 0.75, Value = 0.75, Precision = 0.01, @@ -45,5 +45,14 @@ namespace osu.Game.Rulesets.Mods }; public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModWindUp)).ToArray(); + + public ModWindDown() + { + InitialRate.BindValueChanged(val => + InitialRate.Value = Math.Max(val.NewValue, FinalRate.Value + 0.01)); + + FinalRate.BindValueChanged(val => + FinalRate.Value = Math.Min(val.NewValue, InitialRate.Value - 0.01)); + } } } diff --git a/osu.Game/Rulesets/Mods/ModWindUp.cs b/osu.Game/Rulesets/Mods/ModWindUp.cs index b733bf423e..5a0fab5e67 100644 --- a/osu.Game/Rulesets/Mods/ModWindUp.cs +++ b/osu.Game/Rulesets/Mods/ModWindUp.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mods public override BindableNumber InitialRate { get; } = new BindableDouble { MinValue = 0.5, - MaxValue = 1, + MaxValue = 2, Default = 1, Value = 1, Precision = 0.01, @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Mods [SettingSource("Final rate", "The speed increase to ramp towards")] public override BindableNumber FinalRate { get; } = new BindableDouble { - MinValue = 1.01, + MinValue = 0.5, MaxValue = 2, Default = 1.5, Value = 1.5, @@ -45,5 +45,14 @@ namespace osu.Game.Rulesets.Mods }; public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModWindDown)).ToArray(); + + public ModWindUp() + { + InitialRate.BindValueChanged(val => + InitialRate.Value = Math.Min(val.NewValue, FinalRate.Value - 0.01)); + + FinalRate.BindValueChanged(val => + FinalRate.Value = Math.Max(val.NewValue, InitialRate.Value + 0.01)); + } } } From a6e840634b255ea86e21cfa8140df3794965ebe6 Mon Sep 17 00:00:00 2001 From: Ronnie Moir <7267697+H2n9@users.noreply.github.com> Date: Tue, 23 Feb 2021 15:52:53 +0000 Subject: [PATCH 045/390] Adjust scrubbing behaviour to allow dragging through rate values --- osu.Game/Rulesets/Mods/ModWindDown.cs | 8 ++++---- osu.Game/Rulesets/Mods/ModWindUp.cs | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModWindDown.cs b/osu.Game/Rulesets/Mods/ModWindDown.cs index c47ec5fbde..f9e6854dd4 100644 --- a/osu.Game/Rulesets/Mods/ModWindDown.cs +++ b/osu.Game/Rulesets/Mods/ModWindDown.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Mods [SettingSource("Initial rate", "The starting speed of the track")] public override BindableNumber InitialRate { get; } = new BindableDouble { - MinValue = 0.5, + MinValue = 0.51, MaxValue = 2, Default = 1, Value = 1, @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Mods public override BindableNumber FinalRate { get; } = new BindableDouble { MinValue = 0.5, - MaxValue = 2, + MaxValue = 1.99, Default = 0.75, Value = 0.75, Precision = 0.01, @@ -49,10 +49,10 @@ namespace osu.Game.Rulesets.Mods public ModWindDown() { InitialRate.BindValueChanged(val => - InitialRate.Value = Math.Max(val.NewValue, FinalRate.Value + 0.01)); + FinalRate.Value = Math.Min(FinalRate.Value, val.NewValue - 0.01)); FinalRate.BindValueChanged(val => - FinalRate.Value = Math.Min(val.NewValue, InitialRate.Value - 0.01)); + InitialRate.Value = Math.Max(InitialRate.Value, val.NewValue + 0.01)); } } } diff --git a/osu.Game/Rulesets/Mods/ModWindUp.cs b/osu.Game/Rulesets/Mods/ModWindUp.cs index 5a0fab5e67..0d57bbb52d 100644 --- a/osu.Game/Rulesets/Mods/ModWindUp.cs +++ b/osu.Game/Rulesets/Mods/ModWindUp.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mods public override BindableNumber InitialRate { get; } = new BindableDouble { MinValue = 0.5, - MaxValue = 2, + MaxValue = 1.99, Default = 1, Value = 1, Precision = 0.01, @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Mods [SettingSource("Final rate", "The speed increase to ramp towards")] public override BindableNumber FinalRate { get; } = new BindableDouble { - MinValue = 0.5, + MinValue = 0.51, MaxValue = 2, Default = 1.5, Value = 1.5, @@ -49,10 +49,10 @@ namespace osu.Game.Rulesets.Mods public ModWindUp() { InitialRate.BindValueChanged(val => - InitialRate.Value = Math.Min(val.NewValue, FinalRate.Value - 0.01)); + FinalRate.Value = Math.Max(FinalRate.Value, val.NewValue + 0.01)); FinalRate.BindValueChanged(val => - FinalRate.Value = Math.Max(val.NewValue, InitialRate.Value + 0.01)); + InitialRate.Value = Math.Min(InitialRate.Value, val.NewValue - 0.01)); } } } From 7394c62cc8ee4c30ce12543fa7c6609d7ee9dc58 Mon Sep 17 00:00:00 2001 From: Ronnie Moir <7267697+H2n9@users.noreply.github.com> Date: Tue, 23 Feb 2021 18:10:03 +0000 Subject: [PATCH 046/390] Make ModTimeRamp and ModRateAdjust incompatible --- osu.Game/Rulesets/Mods/ModRateAdjust.cs | 3 +++ osu.Game/Rulesets/Mods/ModTimeRamp.cs | 2 ++ 2 files changed, 5 insertions(+) diff --git a/osu.Game/Rulesets/Mods/ModRateAdjust.cs b/osu.Game/Rulesets/Mods/ModRateAdjust.cs index b016a6d43b..e66650f7b4 100644 --- a/osu.Game/Rulesets/Mods/ModRateAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModRateAdjust.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Bindables; @@ -24,6 +25,8 @@ namespace osu.Game.Rulesets.Mods public double ApplyToRate(double time, double rate) => rate * SpeedChange.Value; + public override Type[] IncompatibleMods => new[] { typeof(ModTimeRamp) }; + public override string SettingDescription => SpeedChange.IsDefault ? string.Empty : $"{SpeedChange.Value:N2}x"; } } diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index 330945d3d3..b5cd64dafa 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -30,6 +30,8 @@ namespace osu.Game.Rulesets.Mods [SettingSource("Adjust pitch", "Should pitch be adjusted with speed")] public abstract BindableBool AdjustPitch { get; } + public override Type[] IncompatibleMods => new[] { typeof(ModRateAdjust) }; + public override string SettingDescription => $"{InitialRate.Value:N2}x to {FinalRate.Value:N2}x"; private double finalRateTime; From dbde47fe94e5c26270e4b124f7539c953f32b5b4 Mon Sep 17 00:00:00 2001 From: Ronnie Moir <7267697+H2n9@users.noreply.github.com> Date: Tue, 23 Feb 2021 19:43:04 +0000 Subject: [PATCH 047/390] Fix test failure --- osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs index 7a0dd5b719..650ae68ffc 100644 --- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs @@ -101,7 +101,7 @@ namespace osu.Game.Tests.Gameplay break; case ModTimeRamp m: - m.InitialRate.Value = m.FinalRate.Value = expectedRate; + m.FinalRate.Value = m.InitialRate.Value = expectedRate; break; } From f6d3cd6413e55eb4f44dc87d66644da55ecb0699 Mon Sep 17 00:00:00 2001 From: Ronnie Moir <7267697+H2n9@users.noreply.github.com> Date: Tue, 23 Feb 2021 21:25:59 +0000 Subject: [PATCH 048/390] Change SamplePlaybackWithRateMods to use rate calulated from the sample Replace hardcoded numbers --- osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs | 9 +++++++-- osu.Game/Rulesets/Mods/ModWindDown.cs | 4 ++-- osu.Game/Rulesets/Mods/ModWindUp.cs | 4 ++-- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs index 650ae68ffc..10a1a13ba0 100644 --- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.Audio; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; using osu.Framework.Testing; +using osu.Framework.Utils; using osu.Game.Audio; using osu.Game.IO; using osu.Game.Rulesets; @@ -90,6 +91,7 @@ namespace osu.Game.Tests.Gameplay public void TestSamplePlaybackWithRateMods(Type expectedMod, double expectedRate) { GameplayClockContainer gameplayContainer = null; + StoryboardSampleInfo sampleInfo = null; TestDrawableStoryboardSample sample = null; Mod testedMod = Activator.CreateInstance(expectedMod) as Mod; @@ -117,7 +119,7 @@ namespace osu.Game.Tests.Gameplay Child = beatmapSkinSourceContainer }); - beatmapSkinSourceContainer.Add(sample = new TestDrawableStoryboardSample(new StoryboardSampleInfo("test-sample", 1, 1)) + beatmapSkinSourceContainer.Add(sample = new TestDrawableStoryboardSample(sampleInfo = new StoryboardSampleInfo("test-sample", 1, 1)) { Clock = gameplayContainer.GameplayClock }); @@ -125,7 +127,10 @@ namespace osu.Game.Tests.Gameplay AddStep("start", () => gameplayContainer.Start()); - AddAssert("sample playback rate matches mod rates", () => sample.ChildrenOfType().First().AggregateFrequency.Value == expectedRate); + AddAssert("sample playback rate matches mod rates", () => + testedMod != null && Precision.AlmostEquals( + sample.ChildrenOfType().First().AggregateFrequency.Value, + ((IApplicableToRate)testedMod).ApplyToRate(sampleInfo.StartTime))); } private class TestSkin : LegacySkin diff --git a/osu.Game/Rulesets/Mods/ModWindDown.cs b/osu.Game/Rulesets/Mods/ModWindDown.cs index f9e6854dd4..9bd5b5eefd 100644 --- a/osu.Game/Rulesets/Mods/ModWindDown.cs +++ b/osu.Game/Rulesets/Mods/ModWindDown.cs @@ -49,10 +49,10 @@ namespace osu.Game.Rulesets.Mods public ModWindDown() { InitialRate.BindValueChanged(val => - FinalRate.Value = Math.Min(FinalRate.Value, val.NewValue - 0.01)); + FinalRate.Value = Math.Min(FinalRate.Value, val.NewValue - FinalRate.Precision)); FinalRate.BindValueChanged(val => - InitialRate.Value = Math.Max(InitialRate.Value, val.NewValue + 0.01)); + InitialRate.Value = Math.Max(InitialRate.Value, val.NewValue + InitialRate.Precision)); } } } diff --git a/osu.Game/Rulesets/Mods/ModWindUp.cs b/osu.Game/Rulesets/Mods/ModWindUp.cs index 0d57bbb52d..39d3c9c5d5 100644 --- a/osu.Game/Rulesets/Mods/ModWindUp.cs +++ b/osu.Game/Rulesets/Mods/ModWindUp.cs @@ -49,10 +49,10 @@ namespace osu.Game.Rulesets.Mods public ModWindUp() { InitialRate.BindValueChanged(val => - FinalRate.Value = Math.Max(FinalRate.Value, val.NewValue + 0.01)); + FinalRate.Value = Math.Max(FinalRate.Value, val.NewValue + FinalRate.Precision)); FinalRate.BindValueChanged(val => - InitialRate.Value = Math.Min(InitialRate.Value, val.NewValue - 0.01)); + InitialRate.Value = Math.Min(InitialRate.Value, val.NewValue - InitialRate.Precision)); } } } From 71182347d677be782005acaf1e227c6cd21a0275 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Feb 2021 11:30:13 +0900 Subject: [PATCH 049/390] Also add a notifiation when trying to enter the multiplayer screen Turns out the only check required to get into this screen was that the API was online, which it always is even if the multiplayer component isn't. This provides a better end-user experience. --- osu.Game/Screens/Menu/ButtonSystem.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 81b1cb0bf1..dd1e318aa0 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -172,6 +172,23 @@ namespace osu.Game.Screens.Menu return; } + // disabled until the underlying runtime issue is resolved, see https://github.com/mono/mono/issues/20805. + if (RuntimeInfo.OS == RuntimeInfo.Platform.iOS) + { + notifications?.Post(new SimpleNotification + { + Text = "Multiplayer is temporarily unavailable on iOS as we figure out some low level issues.", + Icon = FontAwesome.Solid.AppleAlt, + Activated = () => + { + loginOverlay?.Show(); + return true; + } + }); + + return; + } + OnMultiplayer?.Invoke(); } From e1f71038e39b09134ae2587692f7a9b9fa884d75 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Feb 2021 12:13:55 +0900 Subject: [PATCH 050/390] Remove unncessary action --- osu.Game/Screens/Menu/ButtonSystem.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index dd1e318aa0..f93bfd7705 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -179,11 +179,6 @@ namespace osu.Game.Screens.Menu { Text = "Multiplayer is temporarily unavailable on iOS as we figure out some low level issues.", Icon = FontAwesome.Solid.AppleAlt, - Activated = () => - { - loginOverlay?.Show(); - return true; - } }); return; From 7000132d034c6cf012b475ec44178c7202ca4c3a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Feb 2021 12:45:00 +0900 Subject: [PATCH 051/390] Specify full filename inline for quick beatmap --- osu.Game.Tests/Resources/TestResources.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs index 14bc2c8733..c979b5c695 100644 --- a/osu.Game.Tests/Resources/TestResources.cs +++ b/osu.Game.Tests/Resources/TestResources.cs @@ -13,7 +13,7 @@ namespace osu.Game.Tests.Resources public static Stream OpenResource(string name) => GetStore().GetStream($"Resources/{name}"); - public static Stream GetTestBeatmapStream(bool virtualTrack = false, bool quick = false) => OpenResource($"Archives/241526 Soleily - Renatus{(virtualTrack ? "_virtual" : "")}{(quick ? "_quick" : "")}.osz"); + public static Stream GetTestBeatmapStream(bool virtualTrack = false) => OpenResource($"Archives/241526 Soleily - Renatus{(virtualTrack ? "_virtual" : "")}.osz"); /// /// Retrieve a path to a copy of a shortened (~10 second) beatmap archive with a virtual track. @@ -24,8 +24,7 @@ namespace osu.Game.Tests.Resources public static string GetQuickTestBeatmapForImport() { var tempPath = Path.GetTempFileName() + ".osz"; - - using (var stream = GetTestBeatmapStream(true, true)) + using (var stream = OpenResource($"Archives/241526 Soleily - Renatus_virtual_quick.osz")) using (var newFile = File.Create(tempPath)) stream.CopyTo(newFile); From 59e6bad0b9cf128c4f208a67fface6ad82ff48bd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Feb 2021 12:46:35 +0900 Subject: [PATCH 052/390] Remove unnecessary interpolated string specification --- osu.Game.Tests/Resources/TestResources.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs index c979b5c695..cef0532f9d 100644 --- a/osu.Game.Tests/Resources/TestResources.cs +++ b/osu.Game.Tests/Resources/TestResources.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Resources public static string GetQuickTestBeatmapForImport() { var tempPath = Path.GetTempFileName() + ".osz"; - using (var stream = OpenResource($"Archives/241526 Soleily - Renatus_virtual_quick.osz")) + using (var stream = OpenResource("Archives/241526 Soleily - Renatus_virtual_quick.osz")) using (var newFile = File.Create(tempPath)) stream.CopyTo(newFile); From dd702ccfd22ef251000985bdb72e71812855893e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 24 Feb 2021 13:39:15 +0900 Subject: [PATCH 053/390] Make mania FI/HD incompatible with each other --- .../Mods/ManiaModFadeIn.cs | 10 +++-- .../Mods/ManiaModHidden.cs | 33 +------------- .../Mods/ManiaModPlayfieldCover.cs | 43 +++++++++++++++++++ 3 files changed, 51 insertions(+), 35 deletions(-) create mode 100644 osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs index cbdcd49c5b..f80c9e1f7c 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs @@ -1,18 +1,20 @@ // 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.Graphics.Sprites; -using osu.Game.Graphics; +using System; +using System.Linq; using osu.Game.Rulesets.Mania.UI; namespace osu.Game.Rulesets.Mania.Mods { - public class ManiaModFadeIn : ManiaModHidden + public class ManiaModFadeIn : ManiaModPlayfieldCover { public override string Name => "Fade In"; public override string Acronym => "FI"; - public override IconUsage? Icon => OsuIcon.ModHidden; public override string Description => @"Keys appear out of nowhere!"; + public override double ScoreMultiplier => 1; + + public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ManiaModHidden)).ToArray(); protected override CoverExpandDirection ExpandDirection => CoverExpandDirection.AlongScroll; } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs index 4bdb15526f..a68f12cb84 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs @@ -3,43 +3,14 @@ using System; using System.Linq; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.Mania.UI; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Mania.Mods { - public class ManiaModHidden : ModHidden, IApplicableToDrawableRuleset + public class ManiaModHidden : ManiaModPlayfieldCover { public override string Description => @"Keys fade out before you hit them!"; public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => new[] { typeof(ModFlashlight) }; - /// - /// The direction in which the cover should expand. - /// - protected virtual CoverExpandDirection ExpandDirection => CoverExpandDirection.AgainstScroll; - - public virtual void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) - { - ManiaPlayfield maniaPlayfield = (ManiaPlayfield)drawableRuleset.Playfield; - - foreach (Column column in maniaPlayfield.Stages.SelectMany(stage => stage.Columns)) - { - HitObjectContainer hoc = column.HitObjectArea.HitObjectContainer; - Container hocParent = (Container)hoc.Parent; - - hocParent.Remove(hoc); - hocParent.Add(new PlayfieldCoveringWrapper(hoc).With(c => - { - c.RelativeSizeAxes = Axes.Both; - c.Direction = ExpandDirection; - c.Coverage = 0.5f; - })); - } - } + public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ManiaModFadeIn)).ToArray(); } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs new file mode 100644 index 0000000000..78c3331fbf --- /dev/null +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs @@ -0,0 +1,43 @@ +// 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.Linq; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Mania.Mods +{ + public abstract class ManiaModPlayfieldCover : ModHidden, IApplicableToDrawableRuleset + { + public override Type[] IncompatibleMods => new[] { typeof(ModFlashlight) }; + + /// + /// The direction in which the cover should expand. + /// + protected virtual CoverExpandDirection ExpandDirection => CoverExpandDirection.AgainstScroll; + + public virtual void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + { + ManiaPlayfield maniaPlayfield = (ManiaPlayfield)drawableRuleset.Playfield; + + foreach (Column column in maniaPlayfield.Stages.SelectMany(stage => stage.Columns)) + { + HitObjectContainer hoc = column.HitObjectArea.HitObjectContainer; + Container hocParent = (Container)hoc.Parent; + + hocParent.Remove(hoc); + hocParent.Add(new PlayfieldCoveringWrapper(hoc).With(c => + { + c.RelativeSizeAxes = Axes.Both; + c.Direction = ExpandDirection; + c.Coverage = 0.5f; + })); + } + } + } +} From 30a58691f04b48126fb8714331a6d84cf88b6cd6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 24 Feb 2021 14:32:50 +0900 Subject: [PATCH 054/390] Make SD and PF incompatible with each other --- osu.Game/Rulesets/Mods/ModFailCondition.cs | 25 ++++++++++++++++++++++ osu.Game/Rulesets/Mods/ModPerfect.cs | 9 +++++++- osu.Game/Rulesets/Mods/ModSuddenDeath.cs | 15 ++++--------- 3 files changed, 37 insertions(+), 12 deletions(-) create mode 100644 osu.Game/Rulesets/Mods/ModFailCondition.cs diff --git a/osu.Game/Rulesets/Mods/ModFailCondition.cs b/osu.Game/Rulesets/Mods/ModFailCondition.cs new file mode 100644 index 0000000000..40a0843e06 --- /dev/null +++ b/osu.Game/Rulesets/Mods/ModFailCondition.cs @@ -0,0 +1,25 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Mods +{ + public abstract class ModFailCondition : Mod, IApplicableToHealthProcessor, IApplicableFailOverride + { + public override Type[] IncompatibleMods => new[] { typeof(ModNoFail), typeof(ModRelax), typeof(ModAutoplay) }; + + public bool PerformFail() => true; + + public bool RestartOnFail => true; + + public void ApplyToHealthProcessor(HealthProcessor healthProcessor) + { + healthProcessor.FailConditions += FailCondition; + } + + protected abstract bool FailCondition(HealthProcessor healthProcessor, JudgementResult result); + } +} diff --git a/osu.Game/Rulesets/Mods/ModPerfect.cs b/osu.Game/Rulesets/Mods/ModPerfect.cs index df0fc9c4b6..d0b09b50f2 100644 --- a/osu.Game/Rulesets/Mods/ModPerfect.cs +++ b/osu.Game/Rulesets/Mods/ModPerfect.cs @@ -1,6 +1,8 @@ // 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.Linq; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Rulesets.Judgements; @@ -8,13 +10,18 @@ using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mods { - public abstract class ModPerfect : ModSuddenDeath + public abstract class ModPerfect : ModFailCondition { public override string Name => "Perfect"; public override string Acronym => "PF"; public override IconUsage? Icon => OsuIcon.ModPerfect; + public override ModType Type => ModType.DifficultyIncrease; + public override bool Ranked => true; + public override double ScoreMultiplier => 1; public override string Description => "SS or quit."; + public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModSuddenDeath)).ToArray(); + protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result) => result.Type.AffectsAccuracy() && result.Type != result.Judgement.MaxResult; diff --git a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs index ae71041a64..617ae38feb 100644 --- a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs +++ b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Rulesets.Judgements; @@ -9,7 +10,7 @@ using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mods { - public abstract class ModSuddenDeath : Mod, IApplicableToHealthProcessor, IApplicableFailOverride + public abstract class ModSuddenDeath : ModFailCondition { public override string Name => "Sudden Death"; public override string Acronym => "SD"; @@ -18,18 +19,10 @@ namespace osu.Game.Rulesets.Mods public override string Description => "Miss and fail."; public override double ScoreMultiplier => 1; public override bool Ranked => true; - public override Type[] IncompatibleMods => new[] { typeof(ModNoFail), typeof(ModRelax), typeof(ModAutoplay) }; - public bool PerformFail() => true; + public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModPerfect)).ToArray(); - public bool RestartOnFail => true; - - public void ApplyToHealthProcessor(HealthProcessor healthProcessor) - { - healthProcessor.FailConditions += FailCondition; - } - - protected virtual bool FailCondition(HealthProcessor healthProcessor, JudgementResult result) + protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result) => result.Type.AffectsCombo() && !result.IsHit; } From 14160b897e238ebcba242f5fa09f6b237066c960 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 24 Feb 2021 14:42:04 +0900 Subject: [PATCH 055/390] Fix references to ModSuddenDeath --- osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs | 2 +- osu.Game/Rulesets/Mods/ModAutoplay.cs | 2 +- osu.Game/Rulesets/Mods/ModNoFail.cs | 2 +- osu.Game/Rulesets/Mods/ModRelax.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index 77de0cb45b..aac830801b 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override ModType Type => ModType.Automation; public override string Description => @"Automatic cursor movement - just follow the rhythm."; public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail), typeof(ModAutoplay) }; + public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail), typeof(ModAutoplay) }; public bool PerformFail() => false; diff --git a/osu.Game/Rulesets/Mods/ModAutoplay.cs b/osu.Game/Rulesets/Mods/ModAutoplay.cs index d1d23def67..d6e1d46b06 100644 --- a/osu.Game/Rulesets/Mods/ModAutoplay.cs +++ b/osu.Game/Rulesets/Mods/ModAutoplay.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Mods public bool RestartOnFail => false; - public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail) }; + public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail) }; public override bool HasImplementation => GetType().GenericTypeArguments.Length == 0; diff --git a/osu.Game/Rulesets/Mods/ModNoFail.cs b/osu.Game/Rulesets/Mods/ModNoFail.cs index b95ec7490e..c0f24e116a 100644 --- a/osu.Game/Rulesets/Mods/ModNoFail.cs +++ b/osu.Game/Rulesets/Mods/ModNoFail.cs @@ -16,6 +16,6 @@ namespace osu.Game.Rulesets.Mods public override string Description => "You can't fail, no matter what."; public override double ScoreMultiplier => 0.5; public override bool Ranked => true; - public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModAutoplay) }; + public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModFailCondition), typeof(ModAutoplay) }; } } diff --git a/osu.Game/Rulesets/Mods/ModRelax.cs b/osu.Game/Rulesets/Mods/ModRelax.cs index b6fec42f43..e5995ff180 100644 --- a/osu.Game/Rulesets/Mods/ModRelax.cs +++ b/osu.Game/Rulesets/Mods/ModRelax.cs @@ -14,6 +14,6 @@ namespace osu.Game.Rulesets.Mods public override IconUsage? Icon => OsuIcon.ModRelax; public override ModType Type => ModType.Automation; public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModNoFail), typeof(ModSuddenDeath) }; + public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModNoFail), typeof(ModFailCondition) }; } } From 0b44d2483b6f02dd415c461ab6d3081e96cd9971 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 24 Feb 2021 15:03:37 +0900 Subject: [PATCH 056/390] Make some properties virtual I think they were intended to be this way from the beginning. --- osu.Game/Rulesets/Mods/ModFailCondition.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModFailCondition.cs b/osu.Game/Rulesets/Mods/ModFailCondition.cs index 40a0843e06..c0d7bae2b2 100644 --- a/osu.Game/Rulesets/Mods/ModFailCondition.cs +++ b/osu.Game/Rulesets/Mods/ModFailCondition.cs @@ -11,9 +11,9 @@ namespace osu.Game.Rulesets.Mods { public override Type[] IncompatibleMods => new[] { typeof(ModNoFail), typeof(ModRelax), typeof(ModAutoplay) }; - public bool PerformFail() => true; + public virtual bool PerformFail() => true; - public bool RestartOnFail => true; + public virtual bool RestartOnFail => true; public void ApplyToHealthProcessor(HealthProcessor healthProcessor) { From 6b6811063b617b3cf5c0e38a6eae95193759dd18 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 24 Feb 2021 15:05:12 +0900 Subject: [PATCH 057/390] Make ExpandDirection abstract --- osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs | 3 +++ osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs index a68f12cb84..e3ac624a6e 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using osu.Game.Rulesets.Mania.UI; namespace osu.Game.Rulesets.Mania.Mods { @@ -12,5 +13,7 @@ namespace osu.Game.Rulesets.Mania.Mods public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ManiaModFadeIn)).ToArray(); + + protected override CoverExpandDirection ExpandDirection => CoverExpandDirection.AgainstScroll; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs index 78c3331fbf..87501d07a5 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Mods /// /// The direction in which the cover should expand. /// - protected virtual CoverExpandDirection ExpandDirection => CoverExpandDirection.AgainstScroll; + protected abstract CoverExpandDirection ExpandDirection { get; } public virtual void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { From 165da3204454999cd8497d0f55987d871774b34c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Feb 2021 18:41:42 +0900 Subject: [PATCH 058/390] Fix dropdown crash on collection name collisions --- osu.Game/Collections/CollectionFilterMenuItem.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game/Collections/CollectionFilterMenuItem.cs b/osu.Game/Collections/CollectionFilterMenuItem.cs index fe79358223..0617996872 100644 --- a/osu.Game/Collections/CollectionFilterMenuItem.cs +++ b/osu.Game/Collections/CollectionFilterMenuItem.cs @@ -36,7 +36,19 @@ namespace osu.Game.Collections } public bool Equals(CollectionFilterMenuItem other) - => other != null && CollectionName.Value == other.CollectionName.Value; + { + if (other == null) + return false; + + // collections may have the same name, so compare first on reference equality. + // this relies on the assumption that only one instance of the BeatmapCollection exists game-wide, managed by CollectionManager. + if (Collection != null) + return Collection == other.Collection; + + // fallback to name-based comparison. + // this is required for special dropdown items which don't have a collection (all beatmaps / manage collections items below). + return CollectionName.Value == other.CollectionName.Value; + } public override int GetHashCode() => CollectionName.Value.GetHashCode(); } From 6e6fb31c050ad03ce1f064fb8077f8df4d0f7027 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Feb 2021 18:42:26 +0900 Subject: [PATCH 059/390] Add test coverage --- .../TestSceneManageCollectionsDialog.cs | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index 1655adf811..eca857f9e5 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -44,7 +44,7 @@ namespace osu.Game.Tests.Visual.Collections { manager = new CollectionManager(LocalStorage), Content, - dialogOverlay = new DialogOverlay() + dialogOverlay = new DialogOverlay(), }); Dependencies.Cache(manager); @@ -134,6 +134,27 @@ namespace osu.Game.Tests.Visual.Collections assertCollectionName(0, "2"); } + [Test] + public void TestCollectionNameCollisions() + { + AddStep("add dropdown", () => + { + Add(new CollectionFilterDropdown + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.X, + Width = 0.4f, + } + ); + }); + AddStep("add two collections with same name", () => manager.Collections.AddRange(new[] + { + new BeatmapCollection { Name = { Value = "1" } }, + new BeatmapCollection { Name = { Value = "1" }, Beatmaps = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0] } }, + })); + } + [Test] public void TestRemoveCollectionViaButton() { From 5dc0aefb2bf36f7ab18e8c41a1643bcc31b05c98 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Feb 2021 19:54:52 +0900 Subject: [PATCH 060/390] Cancel request on leaving results screen --- osu.Game/Screens/Ranking/SoloResultsScreen.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Ranking/SoloResultsScreen.cs b/osu.Game/Screens/Ranking/SoloResultsScreen.cs index 76b549da1a..4c35096910 100644 --- a/osu.Game/Screens/Ranking/SoloResultsScreen.cs +++ b/osu.Game/Screens/Ranking/SoloResultsScreen.cs @@ -15,6 +15,8 @@ namespace osu.Game.Screens.Ranking { public class SoloResultsScreen : ResultsScreen { + private GetScoresRequest getScoreRequest; + [Resolved] private RulesetStore rulesets { get; set; } @@ -28,9 +30,16 @@ namespace osu.Game.Screens.Ranking if (Score.Beatmap.OnlineBeatmapID == null || Score.Beatmap.Status <= BeatmapSetOnlineStatus.Pending) return null; - var req = new GetScoresRequest(Score.Beatmap, Score.Ruleset); - req.Success += r => scoresCallback?.Invoke(r.Scores.Where(s => s.OnlineScoreID != Score.OnlineScoreID).Select(s => s.CreateScoreInfo(rulesets))); - return req; + getScoreRequest = new GetScoresRequest(Score.Beatmap, Score.Ruleset); + getScoreRequest.Success += r => scoresCallback?.Invoke(r.Scores.Where(s => s.OnlineScoreID != this.Score.OnlineScoreID).Select(s => s.CreateScoreInfo(rulesets))); + return getScoreRequest; + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + getScoreRequest?.Cancel(); } } } From 9ed8d902f7ca20f47179d5f6387e0c9583b8b320 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Feb 2021 19:57:42 +0900 Subject: [PATCH 061/390] Fix requests being indefinitely queued when user is offline --- osu.Game/Online/API/APIAccess.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index ce01378b17..569481d491 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -381,7 +381,13 @@ namespace osu.Game.Online.API public void Queue(APIRequest request) { - lock (queue) queue.Enqueue(request); + lock (queue) + { + if (state.Value == APIState.Offline) + return; + + queue.Enqueue(request); + } } private void flushQueue(bool failOldRequests = true) @@ -402,8 +408,6 @@ namespace osu.Game.Online.API public void Logout() { - flushQueue(); - password = null; authentication.Clear(); @@ -415,6 +419,7 @@ namespace osu.Game.Online.API }); state.Value = APIState.Offline; + flushQueue(); } private static User createGuestUser() => new GuestUser(); From fa6d797adf9860bbde472efffcca6fa77256fb14 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Feb 2021 20:30:17 +0900 Subject: [PATCH 062/390] Remove redundant prefix --- osu.Game/Screens/Ranking/SoloResultsScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/SoloResultsScreen.cs b/osu.Game/Screens/Ranking/SoloResultsScreen.cs index 4c35096910..9bc696948f 100644 --- a/osu.Game/Screens/Ranking/SoloResultsScreen.cs +++ b/osu.Game/Screens/Ranking/SoloResultsScreen.cs @@ -31,7 +31,7 @@ namespace osu.Game.Screens.Ranking return null; getScoreRequest = new GetScoresRequest(Score.Beatmap, Score.Ruleset); - getScoreRequest.Success += r => scoresCallback?.Invoke(r.Scores.Where(s => s.OnlineScoreID != this.Score.OnlineScoreID).Select(s => s.CreateScoreInfo(rulesets))); + getScoreRequest.Success += r => scoresCallback?.Invoke(r.Scores.Where(s => s.OnlineScoreID != Score.OnlineScoreID).Select(s => s.CreateScoreInfo(rulesets))); return getScoreRequest; } From 73d6a3687eacd25e26501cc2b9ea061b86512c38 Mon Sep 17 00:00:00 2001 From: Ronnie Moir <7267697+H2n9@users.noreply.github.com> Date: Wed, 24 Feb 2021 14:40:56 +0000 Subject: [PATCH 063/390] Change rate correction logic to be more explicit --- osu.Game/Rulesets/Mods/ModWindDown.cs | 10 ++++++++-- osu.Game/Rulesets/Mods/ModWindUp.cs | 10 ++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModWindDown.cs b/osu.Game/Rulesets/Mods/ModWindDown.cs index 9bd5b5eefd..c8d79325a3 100644 --- a/osu.Game/Rulesets/Mods/ModWindDown.cs +++ b/osu.Game/Rulesets/Mods/ModWindDown.cs @@ -49,10 +49,16 @@ namespace osu.Game.Rulesets.Mods public ModWindDown() { InitialRate.BindValueChanged(val => - FinalRate.Value = Math.Min(FinalRate.Value, val.NewValue - FinalRate.Precision)); + { + if (val.NewValue <= FinalRate.Value) + FinalRate.Value = val.NewValue - FinalRate.Precision; + }); FinalRate.BindValueChanged(val => - InitialRate.Value = Math.Max(InitialRate.Value, val.NewValue + InitialRate.Precision)); + { + if (val.NewValue >= InitialRate.Value) + InitialRate.Value = val.NewValue + FinalRate.Precision; + }); } } } diff --git a/osu.Game/Rulesets/Mods/ModWindUp.cs b/osu.Game/Rulesets/Mods/ModWindUp.cs index 39d3c9c5d5..4fc1f61e02 100644 --- a/osu.Game/Rulesets/Mods/ModWindUp.cs +++ b/osu.Game/Rulesets/Mods/ModWindUp.cs @@ -49,10 +49,16 @@ namespace osu.Game.Rulesets.Mods public ModWindUp() { InitialRate.BindValueChanged(val => - FinalRate.Value = Math.Max(FinalRate.Value, val.NewValue + FinalRate.Precision)); + { + if (val.NewValue >= FinalRate.Value) + FinalRate.Value = val.NewValue + FinalRate.Precision; + }); FinalRate.BindValueChanged(val => - InitialRate.Value = Math.Min(InitialRate.Value, val.NewValue - InitialRate.Precision)); + { + if (val.NewValue <= InitialRate.Value) + InitialRate.Value = val.NewValue - FinalRate.Precision; + }); } } } From 421b7877d4eb9eed06942666c37d60d962d78b27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 24 Feb 2021 19:16:10 +0100 Subject: [PATCH 064/390] Avoid mixing precision across time ramp bindables Bears no functional difference, it's just a bit less of an eyesore. --- osu.Game/Rulesets/Mods/ModWindDown.cs | 2 +- osu.Game/Rulesets/Mods/ModWindUp.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModWindDown.cs b/osu.Game/Rulesets/Mods/ModWindDown.cs index c8d79325a3..08bd44f7bd 100644 --- a/osu.Game/Rulesets/Mods/ModWindDown.cs +++ b/osu.Game/Rulesets/Mods/ModWindDown.cs @@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Mods FinalRate.BindValueChanged(val => { if (val.NewValue >= InitialRate.Value) - InitialRate.Value = val.NewValue + FinalRate.Precision; + InitialRate.Value = val.NewValue + InitialRate.Precision; }); } } diff --git a/osu.Game/Rulesets/Mods/ModWindUp.cs b/osu.Game/Rulesets/Mods/ModWindUp.cs index 4fc1f61e02..df8f781148 100644 --- a/osu.Game/Rulesets/Mods/ModWindUp.cs +++ b/osu.Game/Rulesets/Mods/ModWindUp.cs @@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Mods FinalRate.BindValueChanged(val => { if (val.NewValue <= InitialRate.Value) - InitialRate.Value = val.NewValue - FinalRate.Precision; + InitialRate.Value = val.NewValue - InitialRate.Precision; }); } } From a362382d381e6128b2eabc55ff7a6717eb1722ef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Feb 2021 14:06:21 +0900 Subject: [PATCH 065/390] Add back more correct null checks --- osu.Game/Graphics/UserInterface/OsuButton.cs | 2 +- osu.Game/Screens/Select/FooterButton.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuButton.cs b/osu.Game/Graphics/UserInterface/OsuButton.cs index d2114134cf..a22c837080 100644 --- a/osu.Game/Graphics/UserInterface/OsuButton.cs +++ b/osu.Game/Graphics/UserInterface/OsuButton.cs @@ -24,7 +24,7 @@ namespace osu.Game.Graphics.UserInterface { public LocalisableString Text { - get => SpriteText.Text; + get => SpriteText?.Text ?? default; set { if (SpriteText != null) diff --git a/osu.Game/Screens/Select/FooterButton.cs b/osu.Game/Screens/Select/FooterButton.cs index 7bdeacc91a..cd7c1c449f 100644 --- a/osu.Game/Screens/Select/FooterButton.cs +++ b/osu.Game/Screens/Select/FooterButton.cs @@ -24,7 +24,7 @@ namespace osu.Game.Screens.Select public LocalisableString Text { - get => SpriteText.Text; + get => SpriteText?.Text ?? default; set { if (SpriteText != null) From 63d48f0c7d786ea069da6ac88fcc1d7e053356e0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Feb 2021 14:06:29 +0900 Subject: [PATCH 066/390] Fix incorrect unicode/romanised string order --- osu.Game.Tournament/Components/TournamentBeatmapPanel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs index e6d73c6e83..a86699a9b5 100644 --- a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs +++ b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs @@ -75,8 +75,8 @@ namespace osu.Game.Tournament.Components new TournamentSpriteText { Text = new RomanisableString( - $"{Beatmap.Metadata.ArtistUnicode ?? Beatmap.Metadata.Artist} - {Beatmap.Metadata.TitleUnicode ?? Beatmap.Metadata.Title}", - $"{Beatmap.Metadata.Artist} - {Beatmap.Metadata.Title}"), + $"{Beatmap.Metadata.Artist} - {Beatmap.Metadata.Title}", + $"{Beatmap.Metadata.ArtistUnicode ?? Beatmap.Metadata.Artist} - {Beatmap.Metadata.TitleUnicode ?? Beatmap.Metadata.Title}"), Font = OsuFont.Torus.With(weight: FontWeight.Bold), }, new FillFlowContainer From 4cdde422280004f4013124ba29a78cf871f52bc0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Feb 2021 14:08:01 +0900 Subject: [PATCH 067/390] Remove unnecessary backing field --- osu.Game/Overlays/Chat/Selection/ChannelSection.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Chat/Selection/ChannelSection.cs b/osu.Game/Overlays/Chat/Selection/ChannelSection.cs index e18302770c..537ac975ac 100644 --- a/osu.Game/Overlays/Chat/Selection/ChannelSection.cs +++ b/osu.Game/Overlays/Chat/Selection/ChannelSection.cs @@ -15,8 +15,6 @@ namespace osu.Game.Overlays.Chat.Selection { public class ChannelSection : Container, IHasFilterableChildren { - private readonly OsuSpriteText header; - public readonly FillFlowContainer ChannelFlow; public IEnumerable FilterableChildren => ChannelFlow.Children; @@ -41,7 +39,7 @@ namespace osu.Game.Overlays.Chat.Selection Children = new Drawable[] { - header = new OsuSpriteText + new OsuSpriteText { Font = OsuFont.GetFont(size: 15, weight: FontWeight.Bold), Text = "All Channels".ToUpperInvariant() From e82eaffaed6097e59262aa6106b4784edcb29157 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Feb 2021 14:12:59 +0900 Subject: [PATCH 068/390] Flip order back to original for romanisable strings --- osu.Game.Tournament/Components/TournamentBeatmapPanel.cs | 4 ++-- osu.Game/Overlays/BeatmapListing/Panels/GridBeatmapPanel.cs | 4 ++-- osu.Game/Overlays/BeatmapListing/Panels/ListBeatmapPanel.cs | 4 ++-- osu.Game/Overlays/Music/PlaylistItem.cs | 4 ++-- osu.Game/Overlays/NowPlayingOverlay.cs | 4 ++-- .../Sections/Historical/DrawableMostPlayedBeatmap.cs | 6 +++--- .../Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs | 6 +++--- osu.Game/Screens/Menu/SongTicker.cs | 4 ++-- osu.Game/Screens/OnlinePlay/Components/BeatmapTitle.cs | 4 ++-- osu.Game/Screens/Play/BeatmapMetadataDisplay.cs | 4 ++-- .../Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs | 4 ++-- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 4 ++-- osu.Game/Screens/Select/Carousel/SetPanelContent.cs | 4 ++-- 13 files changed, 28 insertions(+), 28 deletions(-) diff --git a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs index a86699a9b5..e6d73c6e83 100644 --- a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs +++ b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs @@ -75,8 +75,8 @@ namespace osu.Game.Tournament.Components new TournamentSpriteText { Text = new RomanisableString( - $"{Beatmap.Metadata.Artist} - {Beatmap.Metadata.Title}", - $"{Beatmap.Metadata.ArtistUnicode ?? Beatmap.Metadata.Artist} - {Beatmap.Metadata.TitleUnicode ?? Beatmap.Metadata.Title}"), + $"{Beatmap.Metadata.ArtistUnicode ?? Beatmap.Metadata.Artist} - {Beatmap.Metadata.TitleUnicode ?? Beatmap.Metadata.Title}", + $"{Beatmap.Metadata.Artist} - {Beatmap.Metadata.Title}"), Font = OsuFont.Torus.With(weight: FontWeight.Bold), }, new FillFlowContainer diff --git a/osu.Game/Overlays/BeatmapListing/Panels/GridBeatmapPanel.cs b/osu.Game/Overlays/BeatmapListing/Panels/GridBeatmapPanel.cs index ba4725b49a..4d5c387c4a 100644 --- a/osu.Game/Overlays/BeatmapListing/Panels/GridBeatmapPanel.cs +++ b/osu.Game/Overlays/BeatmapListing/Panels/GridBeatmapPanel.cs @@ -84,14 +84,14 @@ namespace osu.Game.Overlays.BeatmapListing.Panels { new OsuSpriteText { - Text = new RomanisableString(SetInfo.Metadata.Title, SetInfo.Metadata.TitleUnicode), + Text = new RomanisableString(SetInfo.Metadata.TitleUnicode, SetInfo.Metadata.Title), Font = OsuFont.GetFont(size: 18, weight: FontWeight.Bold, italics: true) }, } }, new OsuSpriteText { - Text = new RomanisableString(SetInfo.Metadata.Artist, SetInfo.Metadata.ArtistUnicode), + Text = new RomanisableString(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist), Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true) }, }, diff --git a/osu.Game/Overlays/BeatmapListing/Panels/ListBeatmapPanel.cs b/osu.Game/Overlays/BeatmapListing/Panels/ListBeatmapPanel.cs index 624cb89d1e..00ffd168c1 100644 --- a/osu.Game/Overlays/BeatmapListing/Panels/ListBeatmapPanel.cs +++ b/osu.Game/Overlays/BeatmapListing/Panels/ListBeatmapPanel.cs @@ -107,14 +107,14 @@ namespace osu.Game.Overlays.BeatmapListing.Panels { new OsuSpriteText { - Text = new RomanisableString(SetInfo.Metadata.Title, SetInfo.Metadata.TitleUnicode), + Text = new RomanisableString(SetInfo.Metadata.TitleUnicode, SetInfo.Metadata.Title), Font = OsuFont.GetFont(size: 18, weight: FontWeight.Bold, italics: true) }, } }, new OsuSpriteText { - Text = new RomanisableString(SetInfo.Metadata.Artist, SetInfo.Metadata.ArtistUnicode), + Text = new RomanisableString(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist), Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true) }, } diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs index dab9bc9629..571b14428e 100644 --- a/osu.Game/Overlays/Music/PlaylistItem.cs +++ b/osu.Game/Overlays/Music/PlaylistItem.cs @@ -48,8 +48,8 @@ namespace osu.Game.Overlays.Music artistColour = colours.Gray9; HandleColour = colours.Gray5; - title = localisation.GetLocalisedString(new RomanisableString(Model.Metadata.Title, Model.Metadata.TitleUnicode)); - artist = localisation.GetLocalisedString(new RomanisableString(Model.Metadata.Artist, Model.Metadata.ArtistUnicode)); + title = localisation.GetLocalisedString(new RomanisableString(Model.Metadata.TitleUnicode, Model.Metadata.Title)); + artist = localisation.GetLocalisedString(new RomanisableString(Model.Metadata.ArtistUnicode, Model.Metadata.Artist)); } protected override void LoadComplete() diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index 9c17392e25..81bf71cdec 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -293,8 +293,8 @@ namespace osu.Game.Overlays else { BeatmapMetadata metadata = beatmap.Metadata; - title.Text = new RomanisableString(metadata.Title, metadata.TitleUnicode); - artist.Text = new RomanisableString(metadata.Artist, metadata.ArtistUnicode); + title.Text = new RomanisableString(metadata.TitleUnicode, metadata.Title); + artist.Text = new RomanisableString(metadata.ArtistUnicode, metadata.Artist); } }); diff --git a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs index 48a0481b9e..20e40569e8 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs @@ -130,13 +130,13 @@ namespace osu.Game.Overlays.Profile.Sections.Historical new OsuSpriteText { Text = new RomanisableString( - $"{beatmap.Metadata.Title ?? beatmap.Metadata.TitleUnicode} [{beatmap.Version}] ", - $"{beatmap.Metadata.TitleUnicode ?? beatmap.Metadata.Title} [{beatmap.Version}] "), + $"{beatmap.Metadata.TitleUnicode ?? beatmap.Metadata.Title} [{beatmap.Version}] ", + $"{beatmap.Metadata.Title ?? beatmap.Metadata.TitleUnicode} [{beatmap.Version}] "), Font = OsuFont.GetFont(weight: FontWeight.Bold) }, new OsuSpriteText { - Text = "by " + new RomanisableString(beatmap.Metadata.Artist, beatmap.Metadata.ArtistUnicode), + Text = "by " + new RomanisableString(beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist), Font = OsuFont.GetFont(weight: FontWeight.Regular) }, }; diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs index ca9e19cd56..713303285a 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs @@ -257,15 +257,15 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, Text = new RomanisableString( - $"{beatmap.Metadata.Title ?? beatmap.Metadata.TitleUnicode} ", - $"{beatmap.Metadata.TitleUnicode ?? beatmap.Metadata.Title} "), + $"{beatmap.Metadata.TitleUnicode ?? beatmap.Metadata.Title} ", + $"{beatmap.Metadata.Title ?? beatmap.Metadata.TitleUnicode} "), Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold, italics: true) }, new OsuSpriteText { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, - Text = "by " + new RomanisableString(beatmap.Metadata.Artist, beatmap.Metadata.ArtistUnicode), + Text = "by " + new RomanisableString(beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist), Font = OsuFont.GetFont(size: 12, italics: true) }, }; diff --git a/osu.Game/Screens/Menu/SongTicker.cs b/osu.Game/Screens/Menu/SongTicker.cs index 2be446d71a..237fe43168 100644 --- a/osu.Game/Screens/Menu/SongTicker.cs +++ b/osu.Game/Screens/Menu/SongTicker.cs @@ -61,8 +61,8 @@ namespace osu.Game.Screens.Menu { var metadata = beatmap.Value.Metadata; - title.Text = new RomanisableString(metadata.Title, metadata.TitleUnicode); - artist.Text = new RomanisableString(metadata.Artist, metadata.ArtistUnicode); + title.Text = new RomanisableString(metadata.TitleUnicode, metadata.Title); + artist.Text = new RomanisableString(metadata.ArtistUnicode, metadata.Artist); this.FadeInFromZero(fade_duration / 2f) .Delay(4000) diff --git a/osu.Game/Screens/OnlinePlay/Components/BeatmapTitle.cs b/osu.Game/Screens/OnlinePlay/Components/BeatmapTitle.cs index 299e3e3768..e5a5e35897 100644 --- a/osu.Game/Screens/OnlinePlay/Components/BeatmapTitle.cs +++ b/osu.Game/Screens/OnlinePlay/Components/BeatmapTitle.cs @@ -73,7 +73,7 @@ namespace osu.Game.Screens.OnlinePlay.Components { new OsuSpriteText { - Text = new RomanisableString(beatmap.Value.Metadata.Artist, beatmap.Value.Metadata.ArtistUnicode), + Text = new RomanisableString(beatmap.Value.Metadata.ArtistUnicode, beatmap.Value.Metadata.Artist), Font = OsuFont.GetFont(size: TextSize), }, new OsuSpriteText @@ -83,7 +83,7 @@ namespace osu.Game.Screens.OnlinePlay.Components }, new OsuSpriteText { - Text = new RomanisableString(beatmap.Value.Metadata.Title, beatmap.Value.Metadata.TitleUnicode), + Text = new RomanisableString(beatmap.Value.Metadata.TitleUnicode, beatmap.Value.Metadata.Title), Font = OsuFont.GetFont(size: TextSize), } }, LinkAction.OpenBeatmap, beatmap.Value.OnlineBeatmapID.ToString(), "Open beatmap"); diff --git a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs index 0779a9c637..c56344a8fb 100644 --- a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs +++ b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs @@ -73,7 +73,7 @@ namespace osu.Game.Screens.Play }), new OsuSpriteText { - Text = new RomanisableString(metadata.Title, metadata.TitleUnicode), + Text = new RomanisableString(metadata.TitleUnicode, metadata.Title), Font = OsuFont.GetFont(size: 36, italics: true), Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, @@ -81,7 +81,7 @@ namespace osu.Game.Screens.Play }, new OsuSpriteText { - Text = new RomanisableString(metadata.Artist, metadata.ArtistUnicode), + Text = new RomanisableString(metadata.ArtistUnicode, metadata.Artist), Font = OsuFont.GetFont(size: 26, italics: true), Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index 234e4f2023..6a6b39b61c 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -101,7 +101,7 @@ namespace osu.Game.Screens.Ranking.Expanded { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Text = new RomanisableString(metadata.Title, metadata.TitleUnicode), + Text = new RomanisableString(metadata.TitleUnicode, metadata.Title), Font = OsuFont.Torus.With(size: 20, weight: FontWeight.SemiBold), MaxWidth = ScorePanel.EXPANDED_WIDTH - padding * 2, Truncate = true, @@ -110,7 +110,7 @@ namespace osu.Game.Screens.Ranking.Expanded { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Text = new RomanisableString(metadata.Artist, metadata.ArtistUnicode), + Text = new RomanisableString(metadata.ArtistUnicode, metadata.Artist), Font = OsuFont.Torus.With(size: 14, weight: FontWeight.SemiBold), MaxWidth = ScorePanel.EXPANDED_WIDTH - padding * 2, Truncate = true, diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 0c5b67026c..1c1623e334 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -187,8 +187,8 @@ namespace osu.Game.Screens.Select RelativeSizeAxes = Axes.Both; - titleBinding = localisation.GetLocalisedString(new RomanisableString(metadata.Title, metadata.TitleUnicode)); - artistBinding = localisation.GetLocalisedString(new RomanisableString(metadata.Artist, metadata.ArtistUnicode)); + titleBinding = localisation.GetLocalisedString(new RomanisableString(metadata.TitleUnicode, metadata.Title)); + artistBinding = localisation.GetLocalisedString(new RomanisableString(metadata.ArtistUnicode, metadata.Artist)); Children = new Drawable[] { diff --git a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs index 0e99a4ce70..23a02547b2 100644 --- a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs +++ b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs @@ -41,13 +41,13 @@ namespace osu.Game.Screens.Select.Carousel { new OsuSpriteText { - Text = new RomanisableString(beatmapSet.Metadata.Title, beatmapSet.Metadata.TitleUnicode), + Text = new RomanisableString(beatmapSet.Metadata.TitleUnicode, beatmapSet.Metadata.Title), Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 22, italics: true), Shadow = true, }, new OsuSpriteText { - Text = new RomanisableString(beatmapSet.Metadata.Artist, beatmapSet.Metadata.ArtistUnicode), + Text = new RomanisableString(beatmapSet.Metadata.ArtistUnicode, beatmapSet.Metadata.Artist), Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 17, italics: true), Shadow = true, }, From a08a3d44c796bafb3fac49b8612869b3f8131bd8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Feb 2021 14:51:23 +0900 Subject: [PATCH 069/390] Add failing test coverage for using hotkeys from main menu before toolbar displayed --- .../Navigation/TestSceneScreenNavigation.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 5d070b424a..fc49517cdf 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -214,6 +214,21 @@ namespace osu.Game.Tests.Visual.Navigation AddAssert("Options overlay still visible", () => songSelect.BeatmapOptionsOverlay.State.Value == Visibility.Visible); } + [Test] + public void TestSettingsViaHotkeyFromMainMenu() + { + AddAssert("toolbar not displayed", () => Game.Toolbar.State.Value == Visibility.Hidden); + + AddStep("press settings hotkey", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.Key(Key.O); + InputManager.ReleaseKey(Key.ControlLeft); + }); + + AddUntilStep("settings displayed", () => Game.Settings.State.Value == Visibility.Visible); + } + private void pushEscape() => AddStep("Press escape", () => InputManager.Key(Key.Escape)); From 2c8e62ae3589a28fd00dd68a72c20e38f53ca60b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Feb 2021 14:52:51 +0900 Subject: [PATCH 070/390] Fix toolbar not completing enough of layout to propagate hotkeys to buttons before initial display --- osu.Game/Overlays/Toolbar/Toolbar.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index 393e349bd0..0ccb22df3a 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -37,6 +37,15 @@ namespace osu.Game.Overlays.Toolbar { RelativeSizeAxes = Axes.X; Size = new Vector2(1, HEIGHT); + AlwaysPresent = true; + } + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + // this only needed to be set for the initial LoadComplete/Update, so layout completes and gets buttons in a state they can correctly handle keyboard input for hotkeys. + AlwaysPresent = false; } [BackgroundDependencyLoader(true)] From 154dc03a8c9f3c0eb4b880cc9c25f0baaf0939a0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 25 Feb 2021 15:31:50 +0900 Subject: [PATCH 071/390] Update analyser package --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 2e1873a9ed..53ad973e47 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -18,7 +18,7 @@ - + $(MSBuildThisFileDirectory)CodeAnalysis\osu.ruleset From 996b6a1e57c639617fa4f6d897b75dd3fbe47845 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 25 Feb 2021 15:38:43 +0900 Subject: [PATCH 072/390] Add Enum.HasFlag to banned symbols --- .editorconfig | 5 ++++- CodeAnalysis/BannedSymbols.txt | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.editorconfig b/.editorconfig index a5f7795882..0cdf3b92d3 100644 --- a/.editorconfig +++ b/.editorconfig @@ -194,4 +194,7 @@ dotnet_diagnostic.IDE0068.severity = none dotnet_diagnostic.IDE0069.severity = none #Disable operator overloads requiring alternate named methods -dotnet_diagnostic.CA2225.severity = none \ No newline at end of file +dotnet_diagnostic.CA2225.severity = none + +# Banned APIs +dotnet_diagnostic.RS0030.severity = error \ No newline at end of file diff --git a/CodeAnalysis/BannedSymbols.txt b/CodeAnalysis/BannedSymbols.txt index 47839608c9..60cce39176 100644 --- a/CodeAnalysis/BannedSymbols.txt +++ b/CodeAnalysis/BannedSymbols.txt @@ -7,3 +7,4 @@ M:osu.Framework.Graphics.Sprites.SpriteText.#ctor;Use OsuSpriteText. M:osu.Framework.Bindables.IBindableList`1.GetBoundCopy();Fails on iOS. Use manual ctor + BindTo instead. (see https://github.com/mono/mono/issues/19900) T:Microsoft.EntityFrameworkCore.Internal.EnumerableExtensions;Don't use internal extension methods. T:Microsoft.EntityFrameworkCore.Internal.TypeExtensions;Don't use internal extension methods. +M:System.Enum.HasFlagFast(System.Enum);Use osu.Framework.Extensions.EnumExtensions.HasFlagFast() instead. \ No newline at end of file From dff1d80f3943c705cb4dfdb8b67123b5e80e1592 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 25 Feb 2021 15:38:56 +0900 Subject: [PATCH 073/390] Update HasFlag usages to HasFlagFast --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 27 +++++----- .../TestSceneNotes.cs | 3 +- .../Legacy/DistanceObjectPatternGenerator.cs | 17 ++++--- .../Legacy/HitObjectPatternGenerator.cs | 33 ++++++------ osu.Game.Rulesets.Mania/ManiaRuleset.cs | 51 ++++++++++--------- osu.Game.Rulesets.Osu/OsuRuleset.cs | 35 ++++++------- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 29 ++++++----- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 5 +- osu.Game/Graphics/UserInterface/BarGraph.cs | 9 ++-- .../Graphics/UserInterface/TwoLayerButton.cs | 11 ++-- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 5 +- osu.Game/Replays/Legacy/LegacyReplayFrame.cs | 9 ++-- .../Objects/Legacy/ConvertHitObjectParser.cs | 19 +++---- .../Drawables/DrawableStoryboardAnimation.cs | 9 ++-- .../Drawables/DrawableStoryboardSprite.cs | 9 ++-- 15 files changed, 143 insertions(+), 128 deletions(-) diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 0a817eca0d..f4ddbd3021 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -21,6 +21,7 @@ using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using System; +using osu.Framework.Extensions.EnumExtensions; using osu.Game.Rulesets.Catch.Skinning.Legacy; using osu.Game.Skinning; @@ -50,40 +51,40 @@ namespace osu.Game.Rulesets.Catch public override IEnumerable ConvertFromLegacyMods(LegacyMods mods) { - if (mods.HasFlag(LegacyMods.Nightcore)) + if (mods.HasFlagFast(LegacyMods.Nightcore)) yield return new CatchModNightcore(); - else if (mods.HasFlag(LegacyMods.DoubleTime)) + else if (mods.HasFlagFast(LegacyMods.DoubleTime)) yield return new CatchModDoubleTime(); - if (mods.HasFlag(LegacyMods.Perfect)) + if (mods.HasFlagFast(LegacyMods.Perfect)) yield return new CatchModPerfect(); - else if (mods.HasFlag(LegacyMods.SuddenDeath)) + else if (mods.HasFlagFast(LegacyMods.SuddenDeath)) yield return new CatchModSuddenDeath(); - if (mods.HasFlag(LegacyMods.Cinema)) + if (mods.HasFlagFast(LegacyMods.Cinema)) yield return new CatchModCinema(); - else if (mods.HasFlag(LegacyMods.Autoplay)) + else if (mods.HasFlagFast(LegacyMods.Autoplay)) yield return new CatchModAutoplay(); - if (mods.HasFlag(LegacyMods.Easy)) + if (mods.HasFlagFast(LegacyMods.Easy)) yield return new CatchModEasy(); - if (mods.HasFlag(LegacyMods.Flashlight)) + if (mods.HasFlagFast(LegacyMods.Flashlight)) yield return new CatchModFlashlight(); - if (mods.HasFlag(LegacyMods.HalfTime)) + if (mods.HasFlagFast(LegacyMods.HalfTime)) yield return new CatchModHalfTime(); - if (mods.HasFlag(LegacyMods.HardRock)) + if (mods.HasFlagFast(LegacyMods.HardRock)) yield return new CatchModHardRock(); - if (mods.HasFlag(LegacyMods.Hidden)) + if (mods.HasFlagFast(LegacyMods.Hidden)) yield return new CatchModHidden(); - if (mods.HasFlag(LegacyMods.NoFail)) + if (mods.HasFlagFast(LegacyMods.NoFail)) yield return new CatchModNoFail(); - if (mods.HasFlag(LegacyMods.Relax)) + if (mods.HasFlagFast(LegacyMods.Relax)) yield return new CatchModRelax(); } diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs index 6b8f5d5d9d..706268e478 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs @@ -6,6 +6,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -97,7 +98,7 @@ namespace osu.Game.Rulesets.Mania.Tests } private bool verifyAnchors(DrawableHitObject hitObject, Anchor expectedAnchor) - => hitObject.Anchor.HasFlag(expectedAnchor) && hitObject.Origin.HasFlag(expectedAnchor); + => hitObject.Anchor.HasFlagFast(expectedAnchor) && hitObject.Origin.HasFlagFast(expectedAnchor); private bool verifyAnchors(DrawableHoldNote holdNote, Anchor expectedAnchor) => verifyAnchors((DrawableHitObject)holdNote, expectedAnchor) && holdNote.NestedHitObjects.All(n => verifyAnchors(n, expectedAnchor)); diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index 30d33de06e..c81710ed18 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using osu.Framework.Extensions.EnumExtensions; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.MathUtils; @@ -141,7 +142,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy if (ConversionDifficulty > 6.5) { - if (convertType.HasFlag(PatternType.LowProbability)) + if (convertType.HasFlagFast(PatternType.LowProbability)) return generateNRandomNotes(StartTime, 0.78, 0.3, 0); return generateNRandomNotes(StartTime, 0.85, 0.36, 0.03); @@ -149,7 +150,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy if (ConversionDifficulty > 4) { - if (convertType.HasFlag(PatternType.LowProbability)) + if (convertType.HasFlagFast(PatternType.LowProbability)) return generateNRandomNotes(StartTime, 0.43, 0.08, 0); return generateNRandomNotes(StartTime, 0.56, 0.18, 0); @@ -157,13 +158,13 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy if (ConversionDifficulty > 2.5) { - if (convertType.HasFlag(PatternType.LowProbability)) + if (convertType.HasFlagFast(PatternType.LowProbability)) return generateNRandomNotes(StartTime, 0.3, 0, 0); return generateNRandomNotes(StartTime, 0.37, 0.08, 0); } - if (convertType.HasFlag(PatternType.LowProbability)) + if (convertType.HasFlagFast(PatternType.LowProbability)) return generateNRandomNotes(StartTime, 0.17, 0, 0); return generateNRandomNotes(StartTime, 0.27, 0, 0); @@ -221,7 +222,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy var pattern = new Pattern(); int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); - if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns) + if (convertType.HasFlagFast(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns) nextColumn = FindAvailableColumn(nextColumn, PreviousPattern); int lastColumn = nextColumn; @@ -373,7 +374,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy static bool isDoubleSample(HitSampleInfo sample) => sample.Name == HitSampleInfo.HIT_CLAP || sample.Name == HitSampleInfo.HIT_FINISH; - bool canGenerateTwoNotes = !convertType.HasFlag(PatternType.LowProbability); + bool canGenerateTwoNotes = !convertType.HasFlagFast(PatternType.LowProbability); canGenerateTwoNotes &= HitObject.Samples.Any(isDoubleSample) || sampleInfoListAt(StartTime).Any(isDoubleSample); if (canGenerateTwoNotes) @@ -406,7 +407,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy int endTime = startTime + SegmentDuration * SpanCount; int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); - if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns) + if (convertType.HasFlagFast(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns) nextColumn = FindAvailableColumn(nextColumn, PreviousPattern); for (int i = 0; i < columnRepeat; i++) @@ -435,7 +436,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy var pattern = new Pattern(); int holdColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); - if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns) + if (convertType.HasFlagFast(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns) holdColumn = FindAvailableColumn(holdColumn, PreviousPattern); // Create the hold note diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs index bc4ab55767..8e9020ee13 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Extensions.EnumExtensions; using osuTK; using osu.Game.Audio; using osu.Game.Beatmaps; @@ -78,7 +79,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy else convertType |= PatternType.LowProbability; - if (!convertType.HasFlag(PatternType.KeepSingle)) + if (!convertType.HasFlagFast(PatternType.KeepSingle)) { if (HitObject.Samples.Any(s => s.Name == HitSampleInfo.HIT_FINISH) && TotalColumns != 8) convertType |= PatternType.Mirror; @@ -101,7 +102,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy int lastColumn = PreviousPattern.HitObjects.FirstOrDefault()?.Column ?? 0; - if (convertType.HasFlag(PatternType.Reverse) && PreviousPattern.HitObjects.Any()) + if (convertType.HasFlagFast(PatternType.Reverse) && PreviousPattern.HitObjects.Any()) { // Generate a new pattern by copying the last hit objects in reverse-column order for (int i = RandomStart; i < TotalColumns; i++) @@ -113,7 +114,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy return pattern; } - if (convertType.HasFlag(PatternType.Cycle) && PreviousPattern.HitObjects.Count() == 1 + if (convertType.HasFlagFast(PatternType.Cycle) && PreviousPattern.HitObjects.Count() == 1 // If we convert to 7K + 1, let's not overload the special key && (TotalColumns != 8 || lastColumn != 0) // Make sure the last column was not the centre column @@ -126,7 +127,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy return pattern; } - if (convertType.HasFlag(PatternType.ForceStack) && PreviousPattern.HitObjects.Any()) + if (convertType.HasFlagFast(PatternType.ForceStack) && PreviousPattern.HitObjects.Any()) { // Generate a new pattern by placing on the already filled columns for (int i = RandomStart; i < TotalColumns; i++) @@ -140,7 +141,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy if (PreviousPattern.HitObjects.Count() == 1) { - if (convertType.HasFlag(PatternType.Stair)) + if (convertType.HasFlagFast(PatternType.Stair)) { // Generate a new pattern by placing on the next column, cycling back to the start if there is no "next" int targetColumn = lastColumn + 1; @@ -151,7 +152,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy return pattern; } - if (convertType.HasFlag(PatternType.ReverseStair)) + if (convertType.HasFlagFast(PatternType.ReverseStair)) { // Generate a new pattern by placing on the previous column, cycling back to the end if there is no "previous" int targetColumn = lastColumn - 1; @@ -163,10 +164,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy } } - if (convertType.HasFlag(PatternType.KeepSingle)) + if (convertType.HasFlagFast(PatternType.KeepSingle)) return generateRandomNotes(1); - if (convertType.HasFlag(PatternType.Mirror)) + if (convertType.HasFlagFast(PatternType.Mirror)) { if (ConversionDifficulty > 6.5) return generateRandomPatternWithMirrored(0.12, 0.38, 0.12); @@ -178,7 +179,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy if (ConversionDifficulty > 6.5) { - if (convertType.HasFlag(PatternType.LowProbability)) + if (convertType.HasFlagFast(PatternType.LowProbability)) return generateRandomPattern(0.78, 0.42, 0, 0); return generateRandomPattern(1, 0.62, 0, 0); @@ -186,7 +187,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy if (ConversionDifficulty > 4) { - if (convertType.HasFlag(PatternType.LowProbability)) + if (convertType.HasFlagFast(PatternType.LowProbability)) return generateRandomPattern(0.35, 0.08, 0, 0); return generateRandomPattern(0.52, 0.15, 0, 0); @@ -194,7 +195,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy if (ConversionDifficulty > 2) { - if (convertType.HasFlag(PatternType.LowProbability)) + if (convertType.HasFlagFast(PatternType.LowProbability)) return generateRandomPattern(0.18, 0, 0, 0); return generateRandomPattern(0.45, 0, 0, 0); @@ -207,9 +208,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy foreach (var obj in p.HitObjects) { - if (convertType.HasFlag(PatternType.Stair) && obj.Column == TotalColumns - 1) + if (convertType.HasFlagFast(PatternType.Stair) && obj.Column == TotalColumns - 1) StairType = PatternType.ReverseStair; - if (convertType.HasFlag(PatternType.ReverseStair) && obj.Column == RandomStart) + if (convertType.HasFlagFast(PatternType.ReverseStair) && obj.Column == RandomStart) StairType = PatternType.Stair; } @@ -229,7 +230,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy { var pattern = new Pattern(); - bool allowStacking = !convertType.HasFlag(PatternType.ForceNotStack); + bool allowStacking = !convertType.HasFlagFast(PatternType.ForceNotStack); if (!allowStacking) noteCount = Math.Min(noteCount, TotalColumns - RandomStart - PreviousPattern.ColumnWithObjects); @@ -249,7 +250,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy int getNextColumn(int last) { - if (convertType.HasFlag(PatternType.Gathered)) + if (convertType.HasFlagFast(PatternType.Gathered)) { last++; if (last == TotalColumns) @@ -296,7 +297,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// The containing the hit objects. private Pattern generateRandomPatternWithMirrored(double centreProbability, double p2, double p3) { - if (convertType.HasFlag(PatternType.ForceNotStack)) + if (convertType.HasFlagFast(PatternType.ForceNotStack)) return generateRandomPattern(1 / 2f + p2 / 2, p2, (p2 + p3) / 2, p3); var pattern = new Pattern(); diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 4c729fef83..d624e094ad 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -9,6 +9,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; using System.Collections.Generic; using System.Linq; +using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; @@ -59,76 +60,76 @@ namespace osu.Game.Rulesets.Mania public override IEnumerable ConvertFromLegacyMods(LegacyMods mods) { - if (mods.HasFlag(LegacyMods.Nightcore)) + if (mods.HasFlagFast(LegacyMods.Nightcore)) yield return new ManiaModNightcore(); - else if (mods.HasFlag(LegacyMods.DoubleTime)) + else if (mods.HasFlagFast(LegacyMods.DoubleTime)) yield return new ManiaModDoubleTime(); - if (mods.HasFlag(LegacyMods.Perfect)) + if (mods.HasFlagFast(LegacyMods.Perfect)) yield return new ManiaModPerfect(); - else if (mods.HasFlag(LegacyMods.SuddenDeath)) + else if (mods.HasFlagFast(LegacyMods.SuddenDeath)) yield return new ManiaModSuddenDeath(); - if (mods.HasFlag(LegacyMods.Cinema)) + if (mods.HasFlagFast(LegacyMods.Cinema)) yield return new ManiaModCinema(); - else if (mods.HasFlag(LegacyMods.Autoplay)) + else if (mods.HasFlagFast(LegacyMods.Autoplay)) yield return new ManiaModAutoplay(); - if (mods.HasFlag(LegacyMods.Easy)) + if (mods.HasFlagFast(LegacyMods.Easy)) yield return new ManiaModEasy(); - if (mods.HasFlag(LegacyMods.FadeIn)) + if (mods.HasFlagFast(LegacyMods.FadeIn)) yield return new ManiaModFadeIn(); - if (mods.HasFlag(LegacyMods.Flashlight)) + if (mods.HasFlagFast(LegacyMods.Flashlight)) yield return new ManiaModFlashlight(); - if (mods.HasFlag(LegacyMods.HalfTime)) + if (mods.HasFlagFast(LegacyMods.HalfTime)) yield return new ManiaModHalfTime(); - if (mods.HasFlag(LegacyMods.HardRock)) + if (mods.HasFlagFast(LegacyMods.HardRock)) yield return new ManiaModHardRock(); - if (mods.HasFlag(LegacyMods.Hidden)) + if (mods.HasFlagFast(LegacyMods.Hidden)) yield return new ManiaModHidden(); - if (mods.HasFlag(LegacyMods.Key1)) + if (mods.HasFlagFast(LegacyMods.Key1)) yield return new ManiaModKey1(); - if (mods.HasFlag(LegacyMods.Key2)) + if (mods.HasFlagFast(LegacyMods.Key2)) yield return new ManiaModKey2(); - if (mods.HasFlag(LegacyMods.Key3)) + if (mods.HasFlagFast(LegacyMods.Key3)) yield return new ManiaModKey3(); - if (mods.HasFlag(LegacyMods.Key4)) + if (mods.HasFlagFast(LegacyMods.Key4)) yield return new ManiaModKey4(); - if (mods.HasFlag(LegacyMods.Key5)) + if (mods.HasFlagFast(LegacyMods.Key5)) yield return new ManiaModKey5(); - if (mods.HasFlag(LegacyMods.Key6)) + if (mods.HasFlagFast(LegacyMods.Key6)) yield return new ManiaModKey6(); - if (mods.HasFlag(LegacyMods.Key7)) + if (mods.HasFlagFast(LegacyMods.Key7)) yield return new ManiaModKey7(); - if (mods.HasFlag(LegacyMods.Key8)) + if (mods.HasFlagFast(LegacyMods.Key8)) yield return new ManiaModKey8(); - if (mods.HasFlag(LegacyMods.Key9)) + if (mods.HasFlagFast(LegacyMods.Key9)) yield return new ManiaModKey9(); - if (mods.HasFlag(LegacyMods.KeyCoop)) + if (mods.HasFlagFast(LegacyMods.KeyCoop)) yield return new ManiaModDualStages(); - if (mods.HasFlag(LegacyMods.NoFail)) + if (mods.HasFlagFast(LegacyMods.NoFail)) yield return new ManiaModNoFail(); - if (mods.HasFlag(LegacyMods.Random)) + if (mods.HasFlagFast(LegacyMods.Random)) yield return new ManiaModRandom(); - if (mods.HasFlag(LegacyMods.Mirror)) + if (mods.HasFlagFast(LegacyMods.Mirror)) yield return new ManiaModMirror(); } diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 18324a18a8..838d707d64 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -29,6 +29,7 @@ using osu.Game.Scoring; using osu.Game.Skinning; using System; using System.Linq; +using osu.Framework.Extensions.EnumExtensions; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Skinning.Legacy; using osu.Game.Rulesets.Osu.Statistics; @@ -58,52 +59,52 @@ namespace osu.Game.Rulesets.Osu public override IEnumerable ConvertFromLegacyMods(LegacyMods mods) { - if (mods.HasFlag(LegacyMods.Nightcore)) + if (mods.HasFlagFast(LegacyMods.Nightcore)) yield return new OsuModNightcore(); - else if (mods.HasFlag(LegacyMods.DoubleTime)) + else if (mods.HasFlagFast(LegacyMods.DoubleTime)) yield return new OsuModDoubleTime(); - if (mods.HasFlag(LegacyMods.Perfect)) + if (mods.HasFlagFast(LegacyMods.Perfect)) yield return new OsuModPerfect(); - else if (mods.HasFlag(LegacyMods.SuddenDeath)) + else if (mods.HasFlagFast(LegacyMods.SuddenDeath)) yield return new OsuModSuddenDeath(); - if (mods.HasFlag(LegacyMods.Autopilot)) + if (mods.HasFlagFast(LegacyMods.Autopilot)) yield return new OsuModAutopilot(); - if (mods.HasFlag(LegacyMods.Cinema)) + if (mods.HasFlagFast(LegacyMods.Cinema)) yield return new OsuModCinema(); - else if (mods.HasFlag(LegacyMods.Autoplay)) + else if (mods.HasFlagFast(LegacyMods.Autoplay)) yield return new OsuModAutoplay(); - if (mods.HasFlag(LegacyMods.Easy)) + if (mods.HasFlagFast(LegacyMods.Easy)) yield return new OsuModEasy(); - if (mods.HasFlag(LegacyMods.Flashlight)) + if (mods.HasFlagFast(LegacyMods.Flashlight)) yield return new OsuModFlashlight(); - if (mods.HasFlag(LegacyMods.HalfTime)) + if (mods.HasFlagFast(LegacyMods.HalfTime)) yield return new OsuModHalfTime(); - if (mods.HasFlag(LegacyMods.HardRock)) + if (mods.HasFlagFast(LegacyMods.HardRock)) yield return new OsuModHardRock(); - if (mods.HasFlag(LegacyMods.Hidden)) + if (mods.HasFlagFast(LegacyMods.Hidden)) yield return new OsuModHidden(); - if (mods.HasFlag(LegacyMods.NoFail)) + if (mods.HasFlagFast(LegacyMods.NoFail)) yield return new OsuModNoFail(); - if (mods.HasFlag(LegacyMods.Relax)) + if (mods.HasFlagFast(LegacyMods.Relax)) yield return new OsuModRelax(); - if (mods.HasFlag(LegacyMods.SpunOut)) + if (mods.HasFlagFast(LegacyMods.SpunOut)) yield return new OsuModSpunOut(); - if (mods.HasFlag(LegacyMods.Target)) + if (mods.HasFlagFast(LegacyMods.Target)) yield return new OsuModTarget(); - if (mods.HasFlag(LegacyMods.TouchDevice)) + if (mods.HasFlagFast(LegacyMods.TouchDevice)) yield return new OsuModTouchDevice(); } diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index f2b5d195b4..56f58f404b 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -22,6 +22,7 @@ using osu.Game.Rulesets.Taiko.Scoring; using osu.Game.Scoring; using System; using System.Linq; +using osu.Framework.Extensions.EnumExtensions; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Taiko.Edit; using osu.Game.Rulesets.Taiko.Objects; @@ -57,43 +58,43 @@ namespace osu.Game.Rulesets.Taiko public override IEnumerable ConvertFromLegacyMods(LegacyMods mods) { - if (mods.HasFlag(LegacyMods.Nightcore)) + if (mods.HasFlagFast(LegacyMods.Nightcore)) yield return new TaikoModNightcore(); - else if (mods.HasFlag(LegacyMods.DoubleTime)) + else if (mods.HasFlagFast(LegacyMods.DoubleTime)) yield return new TaikoModDoubleTime(); - if (mods.HasFlag(LegacyMods.Perfect)) + if (mods.HasFlagFast(LegacyMods.Perfect)) yield return new TaikoModPerfect(); - else if (mods.HasFlag(LegacyMods.SuddenDeath)) + else if (mods.HasFlagFast(LegacyMods.SuddenDeath)) yield return new TaikoModSuddenDeath(); - if (mods.HasFlag(LegacyMods.Cinema)) + if (mods.HasFlagFast(LegacyMods.Cinema)) yield return new TaikoModCinema(); - else if (mods.HasFlag(LegacyMods.Autoplay)) + else if (mods.HasFlagFast(LegacyMods.Autoplay)) yield return new TaikoModAutoplay(); - if (mods.HasFlag(LegacyMods.Easy)) + if (mods.HasFlagFast(LegacyMods.Easy)) yield return new TaikoModEasy(); - if (mods.HasFlag(LegacyMods.Flashlight)) + if (mods.HasFlagFast(LegacyMods.Flashlight)) yield return new TaikoModFlashlight(); - if (mods.HasFlag(LegacyMods.HalfTime)) + if (mods.HasFlagFast(LegacyMods.HalfTime)) yield return new TaikoModHalfTime(); - if (mods.HasFlag(LegacyMods.HardRock)) + if (mods.HasFlagFast(LegacyMods.HardRock)) yield return new TaikoModHardRock(); - if (mods.HasFlag(LegacyMods.Hidden)) + if (mods.HasFlagFast(LegacyMods.Hidden)) yield return new TaikoModHidden(); - if (mods.HasFlag(LegacyMods.NoFail)) + if (mods.HasFlagFast(LegacyMods.NoFail)) yield return new TaikoModNoFail(); - if (mods.HasFlag(LegacyMods.Relax)) + if (mods.HasFlagFast(LegacyMods.Relax)) yield return new TaikoModRelax(); - if (mods.HasFlag(LegacyMods.Random)) + if (mods.HasFlagFast(LegacyMods.Random)) yield return new TaikoModRandom(); } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 37ab489da5..99dffa7041 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using osu.Framework.Extensions; +using osu.Framework.Extensions.EnumExtensions; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Legacy; using osu.Game.Beatmaps.Timing; @@ -348,8 +349,8 @@ namespace osu.Game.Beatmaps.Formats if (split.Length >= 8) { LegacyEffectFlags effectFlags = (LegacyEffectFlags)Parsing.ParseInt(split[7]); - kiaiMode = effectFlags.HasFlag(LegacyEffectFlags.Kiai); - omitFirstBarSignature = effectFlags.HasFlag(LegacyEffectFlags.OmitFirstBarLine); + kiaiMode = effectFlags.HasFlagFast(LegacyEffectFlags.Kiai); + omitFirstBarSignature = effectFlags.HasFlagFast(LegacyEffectFlags.OmitFirstBarLine); } string stringSampleSet = sampleSet.ToString().ToLowerInvariant(); diff --git a/osu.Game/Graphics/UserInterface/BarGraph.cs b/osu.Game/Graphics/UserInterface/BarGraph.cs index 953f3985f9..407bf6a923 100644 --- a/osu.Game/Graphics/UserInterface/BarGraph.cs +++ b/osu.Game/Graphics/UserInterface/BarGraph.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using System.Collections.Generic; using System.Linq; +using osu.Framework.Extensions.EnumExtensions; namespace osu.Game.Graphics.UserInterface { @@ -24,11 +25,11 @@ namespace osu.Game.Graphics.UserInterface set { direction = value; - base.Direction = direction.HasFlag(BarDirection.Horizontal) ? FillDirection.Vertical : FillDirection.Horizontal; + base.Direction = direction.HasFlagFast(BarDirection.Horizontal) ? FillDirection.Vertical : FillDirection.Horizontal; foreach (var bar in Children) { - bar.Size = direction.HasFlag(BarDirection.Horizontal) ? new Vector2(1, 1.0f / Children.Count) : new Vector2(1.0f / Children.Count, 1); + bar.Size = direction.HasFlagFast(BarDirection.Horizontal) ? new Vector2(1, 1.0f / Children.Count) : new Vector2(1.0f / Children.Count, 1); bar.Direction = direction; } } @@ -56,14 +57,14 @@ namespace osu.Game.Graphics.UserInterface if (bar.Bar != null) { bar.Bar.Length = length; - bar.Bar.Size = direction.HasFlag(BarDirection.Horizontal) ? new Vector2(1, size) : new Vector2(size, 1); + bar.Bar.Size = direction.HasFlagFast(BarDirection.Horizontal) ? new Vector2(1, size) : new Vector2(size, 1); } else { Add(new Bar { RelativeSizeAxes = Axes.Both, - Size = direction.HasFlag(BarDirection.Horizontal) ? new Vector2(1, size) : new Vector2(size, 1), + Size = direction.HasFlagFast(BarDirection.Horizontal) ? new Vector2(1, size) : new Vector2(size, 1), Length = length, Direction = Direction, }); diff --git a/osu.Game/Graphics/UserInterface/TwoLayerButton.cs b/osu.Game/Graphics/UserInterface/TwoLayerButton.cs index 120149d8c1..8f03c7073c 100644 --- a/osu.Game/Graphics/UserInterface/TwoLayerButton.cs +++ b/osu.Game/Graphics/UserInterface/TwoLayerButton.cs @@ -12,6 +12,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Beatmaps.ControlPoints; using osu.Framework.Audio.Track; using System; +using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; @@ -56,15 +57,15 @@ namespace osu.Game.Graphics.UserInterface set { base.Origin = value; - c1.Origin = c1.Anchor = value.HasFlag(Anchor.x2) ? Anchor.TopLeft : Anchor.TopRight; - c2.Origin = c2.Anchor = value.HasFlag(Anchor.x2) ? Anchor.TopRight : Anchor.TopLeft; + c1.Origin = c1.Anchor = value.HasFlagFast(Anchor.x2) ? Anchor.TopLeft : Anchor.TopRight; + c2.Origin = c2.Anchor = value.HasFlagFast(Anchor.x2) ? Anchor.TopRight : Anchor.TopLeft; - X = value.HasFlag(Anchor.x2) ? SIZE_RETRACTED.X * shear.X * 0.5f : 0; + X = value.HasFlagFast(Anchor.x2) ? SIZE_RETRACTED.X * shear.X * 0.5f : 0; Remove(c1); Remove(c2); - c1.Depth = value.HasFlag(Anchor.x2) ? 0 : 1; - c2.Depth = value.HasFlag(Anchor.x2) ? 1 : 0; + c1.Depth = value.HasFlagFast(Anchor.x2) ? 0 : 1; + c2.Depth = value.HasFlagFast(Anchor.x2) ? 1 : 0; Add(c1); Add(c2); } diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index 83f2bdf6cb..5939f7a42f 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Caching; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; @@ -127,9 +128,9 @@ namespace osu.Game.Overlays.Toolbar { Direction = FillDirection.Vertical, RelativeSizeAxes = Axes.Both, // stops us being considered in parent's autosize - Anchor = TooltipAnchor.HasFlag(Anchor.x0) ? Anchor.BottomLeft : Anchor.BottomRight, + Anchor = TooltipAnchor.HasFlagFast(Anchor.x0) ? Anchor.BottomLeft : Anchor.BottomRight, Origin = TooltipAnchor, - Position = new Vector2(TooltipAnchor.HasFlag(Anchor.x0) ? 5 : -5, 5), + Position = new Vector2(TooltipAnchor.HasFlagFast(Anchor.x0) ? 5 : -5, 5), Alpha = 0, Children = new Drawable[] { diff --git a/osu.Game/Replays/Legacy/LegacyReplayFrame.cs b/osu.Game/Replays/Legacy/LegacyReplayFrame.cs index ab9ccda9b9..f6abf259e8 100644 --- a/osu.Game/Replays/Legacy/LegacyReplayFrame.cs +++ b/osu.Game/Replays/Legacy/LegacyReplayFrame.cs @@ -3,6 +3,7 @@ using MessagePack; using Newtonsoft.Json; +using osu.Framework.Extensions.EnumExtensions; using osu.Game.Rulesets.Replays; using osuTK; @@ -31,19 +32,19 @@ namespace osu.Game.Replays.Legacy [JsonIgnore] [IgnoreMember] - public bool MouseLeft1 => ButtonState.HasFlag(ReplayButtonState.Left1); + public bool MouseLeft1 => ButtonState.HasFlagFast(ReplayButtonState.Left1); [JsonIgnore] [IgnoreMember] - public bool MouseRight1 => ButtonState.HasFlag(ReplayButtonState.Right1); + public bool MouseRight1 => ButtonState.HasFlagFast(ReplayButtonState.Right1); [JsonIgnore] [IgnoreMember] - public bool MouseLeft2 => ButtonState.HasFlag(ReplayButtonState.Left2); + public bool MouseLeft2 => ButtonState.HasFlagFast(ReplayButtonState.Left2); [JsonIgnore] [IgnoreMember] - public bool MouseRight2 => ButtonState.HasFlag(ReplayButtonState.Right2); + public bool MouseRight2 => ButtonState.HasFlagFast(ReplayButtonState.Right2); [Key(3)] public ReplayButtonState ButtonState; diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 72025de131..8419dd66de 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -10,6 +10,7 @@ using osu.Game.Beatmaps.Formats; using osu.Game.Audio; using System.Linq; using JetBrains.Annotations; +using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Utils; using osu.Game.Beatmaps.Legacy; using osu.Game.Skinning; @@ -54,7 +55,7 @@ namespace osu.Game.Rulesets.Objects.Legacy int comboOffset = (int)(type & LegacyHitObjectType.ComboOffset) >> 4; type &= ~LegacyHitObjectType.ComboOffset; - bool combo = type.HasFlag(LegacyHitObjectType.NewCombo); + bool combo = type.HasFlagFast(LegacyHitObjectType.NewCombo); type &= ~LegacyHitObjectType.NewCombo; var soundType = (LegacyHitSoundType)Parsing.ParseInt(split[4]); @@ -62,14 +63,14 @@ namespace osu.Game.Rulesets.Objects.Legacy HitObject result = null; - if (type.HasFlag(LegacyHitObjectType.Circle)) + if (type.HasFlagFast(LegacyHitObjectType.Circle)) { result = CreateHit(pos, combo, comboOffset); if (split.Length > 5) readCustomSampleBanks(split[5], bankInfo); } - else if (type.HasFlag(LegacyHitObjectType.Slider)) + else if (type.HasFlagFast(LegacyHitObjectType.Slider)) { double? length = null; @@ -141,7 +142,7 @@ namespace osu.Game.Rulesets.Objects.Legacy result = CreateSlider(pos, combo, comboOffset, convertPathString(split[5], pos), length, repeatCount, nodeSamples); } - else if (type.HasFlag(LegacyHitObjectType.Spinner)) + else if (type.HasFlagFast(LegacyHitObjectType.Spinner)) { double duration = Math.Max(0, Parsing.ParseDouble(split[5]) + Offset - startTime); @@ -150,7 +151,7 @@ namespace osu.Game.Rulesets.Objects.Legacy if (split.Length > 6) readCustomSampleBanks(split[6], bankInfo); } - else if (type.HasFlag(LegacyHitObjectType.Hold)) + else if (type.HasFlagFast(LegacyHitObjectType.Hold)) { // Note: Hold is generated by BMS converts @@ -436,16 +437,16 @@ namespace osu.Game.Rulesets.Objects.Legacy new LegacyHitSampleInfo(HitSampleInfo.HIT_NORMAL, bankInfo.Normal, bankInfo.Volume, bankInfo.CustomSampleBank, // if the sound type doesn't have the Normal flag set, attach it anyway as a layered sample. // None also counts as a normal non-layered sample: https://osu.ppy.sh/help/wiki/osu!_File_Formats/Osu_(file_format)#hitsounds - type != LegacyHitSoundType.None && !type.HasFlag(LegacyHitSoundType.Normal)) + type != LegacyHitSoundType.None && !type.HasFlagFast(LegacyHitSoundType.Normal)) }; - if (type.HasFlag(LegacyHitSoundType.Finish)) + if (type.HasFlagFast(LegacyHitSoundType.Finish)) soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_FINISH, bankInfo.Add, bankInfo.Volume, bankInfo.CustomSampleBank)); - if (type.HasFlag(LegacyHitSoundType.Whistle)) + if (type.HasFlagFast(LegacyHitSoundType.Whistle)) soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_WHISTLE, bankInfo.Add, bankInfo.Volume, bankInfo.CustomSampleBank)); - if (type.HasFlag(LegacyHitSoundType.Clap)) + if (type.HasFlagFast(LegacyHitSoundType.Clap)) soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_CLAP, bankInfo.Add, bankInfo.Volume, bankInfo.CustomSampleBank)); return soundTypes; diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs index 7eac994e07..81623a9307 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Textures; @@ -80,17 +81,17 @@ namespace osu.Game.Storyboards.Drawables if (FlipH) { - if (origin.HasFlag(Anchor.x0)) + if (origin.HasFlagFast(Anchor.x0)) origin = Anchor.x2 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2)); - else if (origin.HasFlag(Anchor.x2)) + else if (origin.HasFlagFast(Anchor.x2)) origin = Anchor.x0 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2)); } if (FlipV) { - if (origin.HasFlag(Anchor.y0)) + if (origin.HasFlagFast(Anchor.y0)) origin = Anchor.y2 | (origin & (Anchor.x0 | Anchor.x1 | Anchor.x2)); - else if (origin.HasFlag(Anchor.y2)) + else if (origin.HasFlagFast(Anchor.y2)) origin = Anchor.y0 | (origin & (Anchor.x0 | Anchor.x1 | Anchor.x2)); } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs index 7b1a6d54da..eb877f3dff 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Textures; @@ -80,17 +81,17 @@ namespace osu.Game.Storyboards.Drawables if (FlipH) { - if (origin.HasFlag(Anchor.x0)) + if (origin.HasFlagFast(Anchor.x0)) origin = Anchor.x2 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2)); - else if (origin.HasFlag(Anchor.x2)) + else if (origin.HasFlagFast(Anchor.x2)) origin = Anchor.x0 | (origin & (Anchor.y0 | Anchor.y1 | Anchor.y2)); } if (FlipV) { - if (origin.HasFlag(Anchor.y0)) + if (origin.HasFlagFast(Anchor.y0)) origin = Anchor.y2 | (origin & (Anchor.x0 | Anchor.x1 | Anchor.x2)); - else if (origin.HasFlag(Anchor.y2)) + else if (origin.HasFlagFast(Anchor.y2)) origin = Anchor.y0 | (origin & (Anchor.x0 | Anchor.x1 | Anchor.x2)); } From 9f3ceb99eba64a20a600474963b84ac35b256147 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Feb 2021 16:05:08 +0900 Subject: [PATCH 074/390] Fix the star rating display at song select flashing to zero when changing mods Due to the use of bindable flow provided by `BeatmapDifficultyCache` in this usage, the display would briefly flash to zero while difficulty calculation was still running (as there is no way for a consumer of the provided bindable to know whether the returned 0 is an actual 0 SR or a "pending" calculation). While I hope to fix this by making the bindable flow return nullable values, I think this particular use case works better with non-bindable flow so have switched across to that. --- osu.Game/Screens/Select/Details/AdvancedStats.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index ab4f3f4796..0c2cce0bb1 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -15,6 +15,7 @@ using System.Collections.Generic; using osu.Game.Rulesets.Mods; using System.Linq; using System.Threading; +using System.Threading.Tasks; using osu.Framework.Threading; using osu.Framework.Utils; using osu.Game.Configuration; @@ -137,8 +138,6 @@ namespace osu.Game.Screens.Select.Details updateStarDifficulty(); } - private IBindable normalStarDifficulty; - private IBindable moddedStarDifficulty; private CancellationTokenSource starDifficultyCancellationSource; private void updateStarDifficulty() @@ -150,13 +149,13 @@ namespace osu.Game.Screens.Select.Details starDifficultyCancellationSource = new CancellationTokenSource(); - normalStarDifficulty = difficultyCache.GetBindableDifficulty(Beatmap, ruleset.Value, null, starDifficultyCancellationSource.Token); - moddedStarDifficulty = difficultyCache.GetBindableDifficulty(Beatmap, ruleset.Value, mods.Value, starDifficultyCancellationSource.Token); + var normalStarDifficulty = difficultyCache.GetDifficultyAsync(Beatmap, ruleset.Value, null, starDifficultyCancellationSource.Token); + var moddedStarDifficulty = difficultyCache.GetDifficultyAsync(Beatmap, ruleset.Value, mods.Value, starDifficultyCancellationSource.Token); - normalStarDifficulty.BindValueChanged(_ => updateDisplay()); - moddedStarDifficulty.BindValueChanged(_ => updateDisplay(), true); - - void updateDisplay() => starDifficulty.Value = ((float)normalStarDifficulty.Value.Stars, (float)moddedStarDifficulty.Value.Stars); + Task.WhenAll(normalStarDifficulty, moddedStarDifficulty).ContinueWith(_ => Schedule(() => + { + starDifficulty.Value = ((float)normalStarDifficulty.Result.Stars, (float)moddedStarDifficulty.Result.Stars); + }), starDifficultyCancellationSource.Token, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Current); } protected override void Dispose(bool isDisposing) From dcda7f62dff49a69cd1c0fdc8e38c937a490c02b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 25 Feb 2021 16:10:27 +0900 Subject: [PATCH 075/390] Fix incorrect banned symbol --- CodeAnalysis/BannedSymbols.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CodeAnalysis/BannedSymbols.txt b/CodeAnalysis/BannedSymbols.txt index 60cce39176..46c50dbfa2 100644 --- a/CodeAnalysis/BannedSymbols.txt +++ b/CodeAnalysis/BannedSymbols.txt @@ -7,4 +7,4 @@ M:osu.Framework.Graphics.Sprites.SpriteText.#ctor;Use OsuSpriteText. M:osu.Framework.Bindables.IBindableList`1.GetBoundCopy();Fails on iOS. Use manual ctor + BindTo instead. (see https://github.com/mono/mono/issues/19900) T:Microsoft.EntityFrameworkCore.Internal.EnumerableExtensions;Don't use internal extension methods. T:Microsoft.EntityFrameworkCore.Internal.TypeExtensions;Don't use internal extension methods. -M:System.Enum.HasFlagFast(System.Enum);Use osu.Framework.Extensions.EnumExtensions.HasFlagFast() instead. \ No newline at end of file +M:System.Enum.HasFlag(System.Enum);Use osu.Framework.Extensions.EnumExtensions.HasFlagFast() instead. \ No newline at end of file From 03771ce8ecedc5adcf405e28f7b07531f5da8f1c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Feb 2021 16:19:01 +0900 Subject: [PATCH 076/390] Allow determining a BeatmapDifficultyCache's bindable return's completion state via nullability --- osu.Game/Beatmaps/BeatmapDifficultyCache.cs | 8 ++++---- osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 8 ++++++-- osu.Game/Scoring/ScoreManager.cs | 8 ++++++-- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 4 ++-- .../Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 7 +++++-- 5 files changed, 23 insertions(+), 12 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs index 37d262abe5..72a9b36c6f 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs @@ -70,7 +70,7 @@ namespace osu.Game.Beatmaps /// The to get the difficulty of. /// An optional which stops updating the star difficulty for the given . /// A bindable that is updated to contain the star difficulty when it becomes available. - public IBindable GetBindableDifficulty([NotNull] BeatmapInfo beatmapInfo, CancellationToken cancellationToken = default) + public IBindable GetBindableDifficulty([NotNull] BeatmapInfo beatmapInfo, CancellationToken cancellationToken = default) { var bindable = createBindable(beatmapInfo, currentRuleset.Value, currentMods.Value, cancellationToken); @@ -91,8 +91,8 @@ namespace osu.Game.Beatmaps /// The s to get the difficulty with. If null, no mods will be assumed. /// An optional which stops updating the star difficulty for the given . /// A bindable that is updated to contain the star difficulty when it becomes available. - public IBindable GetBindableDifficulty([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo rulesetInfo, [CanBeNull] IEnumerable mods, - CancellationToken cancellationToken = default) + public IBindable GetBindableDifficulty([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo rulesetInfo, [CanBeNull] IEnumerable mods, + CancellationToken cancellationToken = default) => createBindable(beatmapInfo, rulesetInfo, mods, cancellationToken); /// @@ -313,7 +313,7 @@ namespace osu.Game.Beatmaps } } - private class BindableStarDifficulty : Bindable + private class BindableStarDifficulty : Bindable { public readonly BeatmapInfo Beatmap; public readonly CancellationToken CancellationToken; diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index 96e18f120a..c62b803d1a 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -151,7 +151,7 @@ namespace osu.Game.Beatmaps.Drawables this.mods = mods; } - private IBindable localStarDifficulty; + private IBindable localStarDifficulty; [BackgroundDependencyLoader] private void load() @@ -160,7 +160,11 @@ namespace osu.Game.Beatmaps.Drawables localStarDifficulty = ruleset != null ? difficultyCache.GetBindableDifficulty(beatmap, ruleset, mods, difficultyCancellation.Token) : difficultyCache.GetBindableDifficulty(beatmap, difficultyCancellation.Token); - localStarDifficulty.BindValueChanged(difficulty => StarDifficulty.Value = difficulty.NewValue); + localStarDifficulty.BindValueChanged(d => + { + if (d.NewValue is StarDifficulty diff) + StarDifficulty.Value = diff; + }); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index a6beb19876..96ec9644b5 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -137,7 +137,7 @@ namespace osu.Game.Scoring ScoringMode.BindValueChanged(onScoringModeChanged, true); } - private IBindable difficultyBindable; + private IBindable difficultyBindable; private CancellationTokenSource difficultyCancellationSource; private void onScoringModeChanged(ValueChangedEvent mode) @@ -168,7 +168,11 @@ namespace osu.Game.Scoring // We can compute the max combo locally after the async beatmap difficulty computation. difficultyBindable = difficulties().GetBindableDifficulty(score.Beatmap, score.Ruleset, score.Mods, (difficultyCancellationSource = new CancellationTokenSource()).Token); - difficultyBindable.BindValueChanged(d => updateScore(d.NewValue.MaxCombo), true); + difficultyBindable.BindValueChanged(d => + { + if (d.NewValue is StarDifficulty diff) + updateScore(diff.MaxCombo); + }, true); return; } diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 86cb561bc7..3b3ed88ccb 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -43,7 +43,7 @@ namespace osu.Game.Screens.Select [Resolved] private BeatmapDifficultyCache difficultyCache { get; set; } - private IBindable beatmapDifficulty; + private IBindable beatmapDifficulty; protected BufferedWedgeInfo Info; @@ -132,7 +132,7 @@ namespace osu.Game.Screens.Select return; } - LoadComponentAsync(loadingInfo = new BufferedWedgeInfo(beatmap, ruleset.Value, beatmapDifficulty.Value) + LoadComponentAsync(loadingInfo = new BufferedWedgeInfo(beatmap, ruleset.Value, beatmapDifficulty.Value ?? new StarDifficulty()) { Shear = -Shear, Depth = Info?.Depth + 1 ?? 0 diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index e66469ff8d..633ef9297e 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -63,7 +63,7 @@ namespace osu.Game.Screens.Select.Carousel [Resolved(CanBeNull = true)] private ManageCollectionsDialog manageCollectionsDialog { get; set; } - private IBindable starDifficultyBindable; + private IBindable starDifficultyBindable; private CancellationTokenSource starDifficultyCancellationSource; public DrawableCarouselBeatmap(CarouselBeatmap panel) @@ -217,7 +217,10 @@ namespace osu.Game.Screens.Select.Carousel { // We've potentially cancelled the computation above so a new bindable is required. starDifficultyBindable = difficultyCache.GetBindableDifficulty(beatmap, (starDifficultyCancellationSource = new CancellationTokenSource()).Token); - starDifficultyBindable.BindValueChanged(d => starCounter.Current = (float)d.NewValue.Stars, true); + starDifficultyBindable.BindValueChanged(d => + { + starCounter.Current = (float)(d.NewValue?.Stars ?? 0); + }, true); } base.ApplyState(); From 5fa9bf61b6a8d2abfd374759da0553d8e807bc27 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Feb 2021 16:22:40 +0900 Subject: [PATCH 077/390] Update xmldoc --- osu.Game/Beatmaps/BeatmapDifficultyCache.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs index 72a9b36c6f..53d82c385d 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs @@ -69,7 +69,7 @@ namespace osu.Game.Beatmaps /// /// The to get the difficulty of. /// An optional which stops updating the star difficulty for the given . - /// A bindable that is updated to contain the star difficulty when it becomes available. + /// A bindable that is updated to contain the star difficulty when it becomes available. Will be null while in an initial calculating state (but not during updates to ruleset and mods if a stale value is already propagated). public IBindable GetBindableDifficulty([NotNull] BeatmapInfo beatmapInfo, CancellationToken cancellationToken = default) { var bindable = createBindable(beatmapInfo, currentRuleset.Value, currentMods.Value, cancellationToken); @@ -90,7 +90,7 @@ namespace osu.Game.Beatmaps /// The to get the difficulty with. If null, the 's ruleset is used. /// The s to get the difficulty with. If null, no mods will be assumed. /// An optional which stops updating the star difficulty for the given . - /// A bindable that is updated to contain the star difficulty when it becomes available. + /// A bindable that is updated to contain the star difficulty when it becomes available. Will be null while in an initial calculating state. public IBindable GetBindableDifficulty([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo rulesetInfo, [CanBeNull] IEnumerable mods, CancellationToken cancellationToken = default) => createBindable(beatmapInfo, rulesetInfo, mods, cancellationToken); From 31c52bd585a55f925676313671c23c437aff28e2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Feb 2021 17:00:42 +0900 Subject: [PATCH 078/390] Update the displayed BPM at song select with rate adjust mods This only covers constant rate rate adjust mods. Mods like wind up/wind down will need a more complex implementation which we haven't really planned yet. --- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 86cb561bc7..13ec106694 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -383,10 +383,18 @@ namespace osu.Game.Screens.Select return labels.ToArray(); } + [Resolved] + private IBindable> mods { get; set; } + private string getBPMRange(IBeatmap beatmap) { - double bpmMax = beatmap.ControlPointInfo.BPMMaximum; - double bpmMin = beatmap.ControlPointInfo.BPMMinimum; + // this doesn't consider mods which apply variable rates, yet. + double rate = 1; + foreach (var mod in mods.Value.OfType()) + rate = mod.ApplyToRate(0, rate); + + double bpmMax = beatmap.ControlPointInfo.BPMMaximum * rate; + double bpmMin = beatmap.ControlPointInfo.BPMMinimum * rate; if (Precision.AlmostEquals(bpmMin, bpmMax)) return $"{bpmMin:0}"; From 2db4b793d7ab2c064c1a6a1b924ef379b71a86ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Feb 2021 17:04:39 +0900 Subject: [PATCH 079/390] Also handle most common BPM display --- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 13ec106694..311ed6ffb9 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -395,11 +395,12 @@ namespace osu.Game.Screens.Select double bpmMax = beatmap.ControlPointInfo.BPMMaximum * rate; double bpmMin = beatmap.ControlPointInfo.BPMMinimum * rate; + double mostCommonBPM = 60000 / beatmap.GetMostCommonBeatLength() * rate; if (Precision.AlmostEquals(bpmMin, bpmMax)) return $"{bpmMin:0}"; - return $"{bpmMin:0}-{bpmMax:0} (mostly {60000 / beatmap.GetMostCommonBeatLength():0})"; + return $"{bpmMin:0}-{bpmMax:0} (mostly {mostCommonBPM:0})"; } private OsuSpriteText[] getMapper(BeatmapMetadata metadata) From 6d1c5979eafaf2dac1182b49c79c0e3a9e369c4d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Feb 2021 17:28:59 +0900 Subject: [PATCH 080/390] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 183ac61c90..8ea7cfac5b 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 37d730bf42..6ff08ae63c 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -29,7 +29,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index ca11952cc8..d7a1b7d692 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -91,7 +91,7 @@ - + From 3802cb29a42056927da2f2c5535cd55bbfc5cf0f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Feb 2021 17:46:35 +0900 Subject: [PATCH 081/390] Fix failing tests doing reference comparisons between string and LocalisedString --- .../Ranking/TestSceneExpandedPanelMiddleContent.cs | 4 ++-- .../Visual/SongSelect/TestSceneBeatmapInfoWedge.cs | 14 +++++++------- .../UserInterface/TestSceneFooterButtonMods.cs | 2 +- osu.Game/Configuration/SettingSourceAttribute.cs | 3 ++- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs index f9fe42131f..2f558a6379 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs @@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.Ranking Beatmap = createTestBeatmap(author) })); - AddAssert("mapper name present", () => this.ChildrenOfType().Any(spriteText => spriteText.Text == "mapper_name")); + AddAssert("mapper name present", () => this.ChildrenOfType().Any(spriteText => spriteText.Current.Value == "mapper_name")); } [Test] @@ -49,7 +49,7 @@ namespace osu.Game.Tests.Visual.Ranking })); AddAssert("mapped by text not present", () => - this.ChildrenOfType().All(spriteText => !containsAny(spriteText.Text.ToString(), "mapped", "by"))); + this.ChildrenOfType().All(spriteText => !containsAny(spriteText.Current.Value, "mapped", "by"))); } private void showPanel(ScoreInfo score) => Child = new ExpandedPanelMiddleContentContainer(score); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs index fff4a9ba61..07b67ca3ad 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs @@ -103,10 +103,10 @@ namespace osu.Game.Tests.Visual.SongSelect private void testBeatmapLabels(Ruleset ruleset) { - AddAssert("check version", () => infoWedge.Info.VersionLabel.Text == $"{ruleset.ShortName}Version"); - AddAssert("check title", () => infoWedge.Info.TitleLabel.Text == $"{ruleset.ShortName}Source — {ruleset.ShortName}Title"); - AddAssert("check artist", () => infoWedge.Info.ArtistLabel.Text == $"{ruleset.ShortName}Artist"); - AddAssert("check author", () => infoWedge.Info.MapperContainer.Children.OfType().Any(s => s.Text == $"{ruleset.ShortName}Author")); + AddAssert("check version", () => infoWedge.Info.VersionLabel.Current.Value == $"{ruleset.ShortName}Version"); + AddAssert("check title", () => infoWedge.Info.TitleLabel.Current.Value == $"{ruleset.ShortName}Source — {ruleset.ShortName}Title"); + AddAssert("check artist", () => infoWedge.Info.ArtistLabel.Current.Value == $"{ruleset.ShortName}Artist"); + AddAssert("check author", () => infoWedge.Info.MapperContainer.Children.OfType().Any(s => s.Current.Value == $"{ruleset.ShortName}Author")); } private void testInfoLabels(int expectedCount) @@ -119,9 +119,9 @@ namespace osu.Game.Tests.Visual.SongSelect public void TestNullBeatmap() { selectBeatmap(null); - AddAssert("check empty version", () => string.IsNullOrEmpty(infoWedge.Info.VersionLabel.Text.ToString())); - AddAssert("check default title", () => infoWedge.Info.TitleLabel.Text == Beatmap.Default.BeatmapInfo.Metadata.Title); - AddAssert("check default artist", () => infoWedge.Info.ArtistLabel.Text == Beatmap.Default.BeatmapInfo.Metadata.Artist); + AddAssert("check empty version", () => string.IsNullOrEmpty(infoWedge.Info.VersionLabel.Current.Value)); + AddAssert("check default title", () => infoWedge.Info.TitleLabel.Current.Value == Beatmap.Default.BeatmapInfo.Metadata.Title); + AddAssert("check default artist", () => infoWedge.Info.ArtistLabel.Current.Value == Beatmap.Default.BeatmapInfo.Metadata.Artist); AddAssert("check empty author", () => !infoWedge.Info.MapperContainer.Children.Any()); AddAssert("check no info labels", () => !infoWedge.Info.InfoLabelContainer.Children.Any()); } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonMods.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonMods.cs index 1e3b1c2ffd..546e905ded 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonMods.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonMods.cs @@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual.UserInterface var multiplier = mods.Aggregate(1.0, (current, mod) => current * mod.ScoreMultiplier); var expectedValue = multiplier.Equals(1.0) ? string.Empty : $"{multiplier:N2}x"; - return expectedValue == footerButtonMods.MultiplierText.Text; + return expectedValue == footerButtonMods.MultiplierText.Current.Value; } private class TestFooterButtonMods : FooterButtonMods diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index 70d67aaaa0..65a5a6d1b4 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -8,6 +8,7 @@ using System.Reflection; using JetBrains.Annotations; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Localisation; using osu.Game.Overlays.Settings; namespace osu.Game.Configuration @@ -24,7 +25,7 @@ namespace osu.Game.Configuration [AttributeUsage(AttributeTargets.Property)] public class SettingSourceAttribute : Attribute { - public string Label { get; } + public LocalisableString Label { get; } public string Description { get; } From cf4c88c647f2bbfc03984218b01d8dc81d396bbe Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 25 Feb 2021 21:38:21 +0900 Subject: [PATCH 082/390] Fix spacing --- .../Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs index 8e9020ee13..54c37e9742 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs @@ -115,10 +115,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy } if (convertType.HasFlagFast(PatternType.Cycle) && PreviousPattern.HitObjects.Count() == 1 - // If we convert to 7K + 1, let's not overload the special key - && (TotalColumns != 8 || lastColumn != 0) - // Make sure the last column was not the centre column - && (TotalColumns % 2 == 0 || lastColumn != TotalColumns / 2)) + // If we convert to 7K + 1, let's not overload the special key + && (TotalColumns != 8 || lastColumn != 0) + // Make sure the last column was not the centre column + && (TotalColumns % 2 == 0 || lastColumn != TotalColumns / 2)) { // Generate a new pattern by cycling backwards (similar to Reverse but for only one hit object) int column = RandomStart + TotalColumns - lastColumn - 1; From 98313a98bf4e0b89deb9b47219fc4e0fd3aaef4a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 25 Feb 2021 21:48:02 +0900 Subject: [PATCH 083/390] DI mods in parent class and pass them down --- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 311ed6ffb9..37808f6e94 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -39,6 +39,7 @@ namespace osu.Game.Screens.Select private static readonly Vector2 wedged_container_shear = new Vector2(shear_width / SongSelect.WEDGE_HEIGHT, 0); private readonly IBindable ruleset = new Bindable(); + private readonly IBindable> mods = new Bindable>(Array.Empty()); [Resolved] private BeatmapDifficultyCache difficultyCache { get; set; } @@ -64,9 +65,11 @@ namespace osu.Game.Screens.Select } [BackgroundDependencyLoader(true)] - private void load([CanBeNull] Bindable parentRuleset) + private void load([CanBeNull] Bindable parentRuleset, [CanBeNull] Bindable> parentMods) { ruleset.BindTo(parentRuleset); + mods.BindTo(parentMods); + ruleset.ValueChanged += _ => updateDisplay(); } @@ -132,7 +135,7 @@ namespace osu.Game.Screens.Select return; } - LoadComponentAsync(loadingInfo = new BufferedWedgeInfo(beatmap, ruleset.Value, beatmapDifficulty.Value) + LoadComponentAsync(loadingInfo = new BufferedWedgeInfo(beatmap, ruleset.Value, mods.Value, beatmapDifficulty.Value) { Shear = -Shear, Depth = Info?.Depth + 1 ?? 0 @@ -167,13 +170,15 @@ namespace osu.Game.Screens.Select private readonly WorkingBeatmap beatmap; private readonly RulesetInfo ruleset; + private readonly IReadOnlyList mods; private readonly StarDifficulty starDifficulty; - public BufferedWedgeInfo(WorkingBeatmap beatmap, RulesetInfo userRuleset, StarDifficulty difficulty) + public BufferedWedgeInfo(WorkingBeatmap beatmap, RulesetInfo userRuleset, IReadOnlyList mods, StarDifficulty difficulty) : base(pixelSnapping: true) { this.beatmap = beatmap; ruleset = userRuleset ?? beatmap.BeatmapInfo.Ruleset; + this.mods = mods; starDifficulty = difficulty; } @@ -383,14 +388,11 @@ namespace osu.Game.Screens.Select return labels.ToArray(); } - [Resolved] - private IBindable> mods { get; set; } - private string getBPMRange(IBeatmap beatmap) { // this doesn't consider mods which apply variable rates, yet. double rate = 1; - foreach (var mod in mods.Value.OfType()) + foreach (var mod in mods.OfType()) rate = mod.ApplyToRate(0, rate); double bpmMax = beatmap.ControlPointInfo.BPMMaximum * rate; From de417a660d7121589abb9c0b0fe635f4e2f44eb0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 25 Feb 2021 21:51:32 +0900 Subject: [PATCH 084/390] Make BPM update with changes in mod settings --- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 113 ++++++++++++-------- 1 file changed, 68 insertions(+), 45 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 37808f6e94..9084435f44 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -25,6 +25,7 @@ using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Framework.Logging; +using osu.Game.Configuration; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; @@ -167,12 +168,15 @@ namespace osu.Game.Screens.Select private ILocalisedBindableString titleBinding; private ILocalisedBindableString artistBinding; + private Container bpmLabelContainer; private readonly WorkingBeatmap beatmap; private readonly RulesetInfo ruleset; private readonly IReadOnlyList mods; private readonly StarDifficulty starDifficulty; + private ModSettingChangeTracker settingChangeTracker; + public BufferedWedgeInfo(WorkingBeatmap beatmap, RulesetInfo userRuleset, IReadOnlyList mods, StarDifficulty difficulty) : base(pixelSnapping: true) { @@ -189,9 +193,11 @@ namespace osu.Game.Screens.Select var metadata = beatmapInfo.Metadata ?? beatmap.BeatmapSetInfo?.Metadata ?? new BeatmapMetadata(); CacheDrawnFrameBuffer = true; - RelativeSizeAxes = Axes.Both; + settingChangeTracker = new ModSettingChangeTracker(mods); + settingChangeTracker.SettingChanged += _ => updateBPM(); + titleBinding = localisation.GetLocalisedString(new LocalisedString((metadata.TitleUnicode, metadata.Title))); artistBinding = localisation.GetLocalisedString(new LocalisedString((metadata.ArtistUnicode, metadata.Artist))); @@ -312,7 +318,25 @@ namespace osu.Game.Screens.Select Margin = new MarginPadding { Top = 20 }, Spacing = new Vector2(20, 0), AutoSizeAxes = Axes.Both, - Children = getInfoLabels() + Children = new Drawable[] + { + new InfoLabel(new BeatmapStatistic + { + Name = "Length", + CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Length), + Content = TimeSpan.FromMilliseconds(beatmapInfo.Length).ToString(@"m\:ss"), + }), + bpmLabelContainer = new Container + { + AutoSizeAxes = Axes.Both, + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(20, 0), + Children = getRulesetInfoLabels() + } + } } } } @@ -324,6 +348,8 @@ namespace osu.Game.Screens.Select // no difficulty means it can't have a status to show if (beatmapInfo.Version == null) StatusPill.Hide(); + + updateBPM(); } private static Drawable createStarRatingDisplay(StarDifficulty difficulty) => difficulty.Stars > 0 @@ -340,69 +366,60 @@ namespace osu.Game.Screens.Select ForceRedraw(); } - private InfoLabel[] getInfoLabels() + private InfoLabel[] getRulesetInfoLabels() { - var b = beatmap.Beatmap; - - List labels = new List(); - - if (b?.HitObjects?.Any() == true) + try { - labels.Add(new InfoLabel(new BeatmapStatistic - { - Name = "Length", - CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Length), - Content = TimeSpan.FromMilliseconds(b.BeatmapInfo.Length).ToString(@"m\:ss"), - })); - - labels.Add(new InfoLabel(new BeatmapStatistic - { - Name = "BPM", - CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Bpm), - Content = getBPMRange(b), - })); + IBeatmap playableBeatmap; try { - IBeatmap playableBeatmap; - - try - { - // Try to get the beatmap with the user's ruleset - playableBeatmap = beatmap.GetPlayableBeatmap(ruleset, Array.Empty()); - } - catch (BeatmapInvalidForRulesetException) - { - // Can't be converted to the user's ruleset, so use the beatmap's own ruleset - playableBeatmap = beatmap.GetPlayableBeatmap(beatmap.BeatmapInfo.Ruleset, Array.Empty()); - } - - labels.AddRange(playableBeatmap.GetStatistics().Select(s => new InfoLabel(s))); + // Try to get the beatmap with the user's ruleset + playableBeatmap = beatmap.GetPlayableBeatmap(ruleset, Array.Empty()); } - catch (Exception e) + catch (BeatmapInvalidForRulesetException) { - Logger.Error(e, "Could not load beatmap successfully!"); + // Can't be converted to the user's ruleset, so use the beatmap's own ruleset + playableBeatmap = beatmap.GetPlayableBeatmap(beatmap.BeatmapInfo.Ruleset, Array.Empty()); } + + return playableBeatmap.GetStatistics().Select(s => new InfoLabel(s)).ToArray(); + } + catch (Exception e) + { + Logger.Error(e, "Could not load beatmap successfully!"); } - return labels.ToArray(); + return Array.Empty(); } - private string getBPMRange(IBeatmap beatmap) + private void updateBPM() { + var b = beatmap.Beatmap; + if (b == null) + return; + // this doesn't consider mods which apply variable rates, yet. double rate = 1; foreach (var mod in mods.OfType()) rate = mod.ApplyToRate(0, rate); - double bpmMax = beatmap.ControlPointInfo.BPMMaximum * rate; - double bpmMin = beatmap.ControlPointInfo.BPMMinimum * rate; - double mostCommonBPM = 60000 / beatmap.GetMostCommonBeatLength() * rate; + double bpmMax = b.ControlPointInfo.BPMMaximum * rate; + double bpmMin = b.ControlPointInfo.BPMMinimum * rate; + double mostCommonBPM = 60000 / b.GetMostCommonBeatLength() * rate; - if (Precision.AlmostEquals(bpmMin, bpmMax)) - return $"{bpmMin:0}"; + string labelText = Precision.AlmostEquals(bpmMin, bpmMax) + ? $"{bpmMin:0}" + : $"{bpmMin:0}-{bpmMax:0} (mostly {mostCommonBPM:0})"; - return $"{bpmMin:0}-{bpmMax:0} (mostly {mostCommonBPM:0})"; + bpmLabelContainer.Child = new InfoLabel(new BeatmapStatistic + { + Name = "BPM", + CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Bpm), + Content = labelText + }); + + ForceRedraw(); } private OsuSpriteText[] getMapper(BeatmapMetadata metadata) @@ -425,6 +442,12 @@ namespace osu.Game.Screens.Select }; } + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + settingChangeTracker?.Dispose(); + } + public class InfoLabel : Container, IHasTooltip { public string TooltipText { get; } From 649ce20e354b2f30b08a3c60db94695e24aa5e34 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 25 Feb 2021 22:01:53 +0900 Subject: [PATCH 085/390] Fix up super weird and super wrong DI --- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index d1b28e6607..97fe099975 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading; -using JetBrains.Annotations; using osuTK; using osuTK.Graphics; using osu.Framework.Allocation; @@ -39,8 +38,11 @@ namespace osu.Game.Screens.Select private static readonly Vector2 wedged_container_shear = new Vector2(shear_width / SongSelect.WEDGE_HEIGHT, 0); - private readonly IBindable ruleset = new Bindable(); - private readonly IBindable> mods = new Bindable>(Array.Empty()); + [Resolved] + private IBindable ruleset { get; set; } + + [Resolved] + private IBindable> mods { get; set; } [Resolved] private BeatmapDifficultyCache difficultyCache { get; set; } @@ -65,13 +67,10 @@ namespace osu.Game.Screens.Select }; } - [BackgroundDependencyLoader(true)] - private void load([CanBeNull] Bindable parentRuleset, [CanBeNull] Bindable> parentMods) + protected override void LoadComplete() { - ruleset.BindTo(parentRuleset); - mods.BindTo(parentMods); - - ruleset.ValueChanged += _ => updateDisplay(); + base.LoadComplete(); + ruleset.BindValueChanged(_ => updateDisplay(), true); } protected override void PopIn() From c3eb44137bfd109006f826fc196a2394a2675196 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 25 Feb 2021 22:09:41 +0900 Subject: [PATCH 086/390] Move ValueChanged bind back to load() --- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 97fe099975..fe2b7b7525 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -67,10 +67,10 @@ namespace osu.Game.Screens.Select }; } - protected override void LoadComplete() + [BackgroundDependencyLoader] + private void load() { - base.LoadComplete(); - ruleset.BindValueChanged(_ => updateDisplay(), true); + ruleset.BindValueChanged(_ => updateDisplay()); } protected override void PopIn() From 01a48154126fbc2ca75cea6632349689ea41ce4f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 25 Feb 2021 23:36:02 +0900 Subject: [PATCH 087/390] Make labels disappear on null beatmap/no hitobjects --- .../SongSelect/TestSceneBeatmapInfoWedge.cs | 7 ++- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 61 +++++++++++-------- 2 files changed, 39 insertions(+), 29 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs index 07b67ca3ad..7ea6373763 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs @@ -7,6 +7,7 @@ using JetBrains.Annotations; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets; @@ -111,8 +112,8 @@ namespace osu.Game.Tests.Visual.SongSelect private void testInfoLabels(int expectedCount) { - AddAssert("check info labels exists", () => infoWedge.Info.InfoLabelContainer.Children.Any()); - AddAssert("check info labels count", () => infoWedge.Info.InfoLabelContainer.Children.Count == expectedCount); + AddAssert("check info labels exists", () => infoWedge.Info.ChildrenOfType().Any()); + AddAssert("check info labels count", () => infoWedge.Info.ChildrenOfType().Count() == expectedCount); } [Test] @@ -123,7 +124,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("check default title", () => infoWedge.Info.TitleLabel.Current.Value == Beatmap.Default.BeatmapInfo.Metadata.Title); AddAssert("check default artist", () => infoWedge.Info.ArtistLabel.Current.Value == Beatmap.Default.BeatmapInfo.Metadata.Artist); AddAssert("check empty author", () => !infoWedge.Info.MapperContainer.Children.Any()); - AddAssert("check no info labels", () => !infoWedge.Info.InfoLabelContainer.Children.Any()); + AddAssert("check no info labels", () => !infoWedge.Info.ChildrenOfType().Any()); } [Test] diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index fe2b7b7525..36cc19cce3 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -163,10 +163,10 @@ namespace osu.Game.Screens.Select public OsuSpriteText ArtistLabel { get; private set; } public BeatmapSetOnlineStatusPill StatusPill { get; private set; } public FillFlowContainer MapperContainer { get; private set; } - public FillFlowContainer InfoLabelContainer { get; private set; } private ILocalisedBindableString titleBinding; private ILocalisedBindableString artistBinding; + private FillFlowContainer infoLabelContainer; private Container bpmLabelContainer; private readonly WorkingBeatmap beatmap; @@ -194,9 +194,6 @@ namespace osu.Game.Screens.Select CacheDrawnFrameBuffer = true; RelativeSizeAxes = Axes.Both; - settingChangeTracker = new ModSettingChangeTracker(mods); - settingChangeTracker.SettingChanged += _ => updateBPM(); - titleBinding = localisation.GetLocalisedString(new RomanisableString(metadata.TitleUnicode, metadata.Title)); artistBinding = localisation.GetLocalisedString(new RomanisableString(metadata.ArtistUnicode, metadata.Artist)); @@ -312,30 +309,11 @@ namespace osu.Game.Screens.Select AutoSizeAxes = Axes.Both, Children = getMapper(metadata) }, - InfoLabelContainer = new FillFlowContainer + infoLabelContainer = new FillFlowContainer { Margin = new MarginPadding { Top = 20 }, Spacing = new Vector2(20, 0), AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - new InfoLabel(new BeatmapStatistic - { - Name = "Length", - CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Length), - Content = TimeSpan.FromMilliseconds(beatmapInfo.Length).ToString(@"m\:ss"), - }), - bpmLabelContainer = new Container - { - AutoSizeAxes = Axes.Both, - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Spacing = new Vector2(20, 0), - Children = getRulesetInfoLabels() - } - } } } } @@ -348,7 +326,7 @@ namespace osu.Game.Screens.Select if (beatmapInfo.Version == null) StatusPill.Hide(); - updateBPM(); + addInfoLabels(); } private static Drawable createStarRatingDisplay(StarDifficulty difficulty) => difficulty.Stars > 0 @@ -365,6 +343,37 @@ namespace osu.Game.Screens.Select ForceRedraw(); } + private void addInfoLabels() + { + if (beatmap.Beatmap?.HitObjects?.Any() != true) + return; + + infoLabelContainer.Children = new Drawable[] + { + new InfoLabel(new BeatmapStatistic + { + Name = "Length", + CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Length), + Content = TimeSpan.FromMilliseconds(beatmap.BeatmapInfo.Length).ToString(@"m\:ss"), + }), + bpmLabelContainer = new Container + { + AutoSizeAxes = Axes.Both, + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(20, 0), + Children = getRulesetInfoLabels() + } + }; + + settingChangeTracker = new ModSettingChangeTracker(mods); + settingChangeTracker.SettingChanged += _ => refreshBPMLabel(); + + refreshBPMLabel(); + } + private InfoLabel[] getRulesetInfoLabels() { try @@ -392,7 +401,7 @@ namespace osu.Game.Screens.Select return Array.Empty(); } - private void updateBPM() + private void refreshBPMLabel() { var b = beatmap.Beatmap; if (b == null) From 254f9bb58be27c0981dd32df8a1d6038a6a090fb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Feb 2021 13:37:58 +0900 Subject: [PATCH 088/390] Show API human readable error message when chat posting fails Closes #11902. --- osu.Game/Online/Chat/ChannelManager.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 036ec4d0f3..a980f4c54b 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -152,7 +152,7 @@ namespace osu.Game.Online.Chat createNewPrivateMessageRequest.Failure += exception => { - Logger.Error(exception, "Posting message failed."); + handlePostException(exception); target.ReplaceMessage(message, null); dequeueAndRun(); }; @@ -171,7 +171,7 @@ namespace osu.Game.Online.Chat req.Failure += exception => { - Logger.Error(exception, "Posting message failed."); + handlePostException(exception); target.ReplaceMessage(message, null); dequeueAndRun(); }; @@ -184,6 +184,14 @@ namespace osu.Game.Online.Chat dequeueAndRun(); } + private static void handlePostException(Exception exception) + { + if (exception is APIException apiException) + Logger.Log(apiException.Message, level: LogLevel.Important); + else + Logger.Error(exception, "Posting message failed."); + } + /// /// Posts a command locally. Commands like /help will result in a help message written in the current channel. /// From cd1c1bf534947585db53eb6b4641acef41f1a12f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Feb 2021 14:15:12 +0900 Subject: [PATCH 089/390] Centralise cases of performing actions on the current selection By moving this to a central location, we can avoid invoking the EditorChangeHandler when there is no selection made. This helps alleviate the issue pointed out in https://github.com/ppy/osu/issues/11901, but not fix it completely. --- .../Edit/ManiaSelectionHandler.cs | 8 +++-- .../Edit/TaikoSelectionHandler.cs | 32 +++++++------------ .../Compose/Components/BlueprintContainer.cs | 3 +- .../Compose/Components/SelectionHandler.cs | 27 ++++------------ osu.Game/Screens/Edit/EditorBeatmap.cs | 16 ++++++++++ 5 files changed, 42 insertions(+), 44 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs index 50629f41a9..2689ed4112 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs @@ -45,6 +45,7 @@ namespace osu.Game.Rulesets.Mania.Edit int minColumn = int.MaxValue; int maxColumn = int.MinValue; + // find min/max in an initial pass before actually performing the movement. foreach (var obj in EditorBeatmap.SelectedHitObjects.OfType()) { if (obj.Column < minColumn) @@ -55,8 +56,11 @@ namespace osu.Game.Rulesets.Mania.Edit columnDelta = Math.Clamp(columnDelta, -minColumn, maniaPlayfield.TotalColumns - 1 - maxColumn); - foreach (var obj in EditorBeatmap.SelectedHitObjects.OfType()) - obj.Column += columnDelta; + EditorBeatmap.PerformOnSelection(h => + { + if (h is ManiaHitObject maniaObj) + maniaObj.Column += columnDelta; + }); } } } diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs index 3fbcee44af..ac2dd4bdb6 100644 --- a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs @@ -52,32 +52,24 @@ namespace osu.Game.Rulesets.Taiko.Edit public void SetStrongState(bool state) { - var hits = EditorBeatmap.SelectedHitObjects.OfType(); - - EditorBeatmap.BeginChange(); - - foreach (var h in hits) + EditorBeatmap.PerformOnSelection(h => { - if (h.IsStrong != state) - { - h.IsStrong = state; - EditorBeatmap.Update(h); - } - } + if (!(h is Hit taikoHit)) return; - EditorBeatmap.EndChange(); + if (taikoHit.IsStrong != state) + { + taikoHit.IsStrong = state; + EditorBeatmap.Update(taikoHit); + } + }); } public void SetRimState(bool state) { - var hits = EditorBeatmap.SelectedHitObjects.OfType(); - - EditorBeatmap.BeginChange(); - - foreach (var h in hits) - h.Type = state ? HitType.Rim : HitType.Centre; - - EditorBeatmap.EndChange(); + EditorBeatmap.PerformOnSelection(h => + { + if (h is Hit taikoHit) taikoHit.Type = state ? HitType.Rim : HitType.Centre; + }); } protected override IEnumerable GetContextMenuItemsForSelection(IEnumerable selection) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 5371beac60..051d0766bf 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -495,8 +495,7 @@ namespace osu.Game.Screens.Edit.Compose.Components // Apply the start time at the newly snapped-to position double offset = result.Time.Value - movementBlueprints.First().HitObject.StartTime; - foreach (HitObject obj in Beatmap.SelectedHitObjects) - obj.StartTime += offset; + Beatmap.PerformOnSelection(obj => obj.StartTime += offset); } return true; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 788b485449..018d4d081c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -320,18 +320,14 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The name of the hit sample. public void AddHitSample(string sampleName) { - EditorBeatmap.BeginChange(); - - foreach (var h in EditorBeatmap.SelectedHitObjects) + EditorBeatmap.PerformOnSelection(h => { // Make sure there isn't already an existing sample if (h.Samples.Any(s => s.Name == sampleName)) - continue; + return; h.Samples.Add(new HitSampleInfo(sampleName)); - } - - EditorBeatmap.EndChange(); + }); } /// @@ -341,19 +337,15 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Throws if any selected object doesn't implement public void SetNewCombo(bool state) { - EditorBeatmap.BeginChange(); - - foreach (var h in EditorBeatmap.SelectedHitObjects) + EditorBeatmap.PerformOnSelection(h => { var comboInfo = h as IHasComboInformation; - if (comboInfo == null || comboInfo.NewCombo == state) continue; + if (comboInfo == null || comboInfo.NewCombo == state) return; comboInfo.NewCombo = state; EditorBeatmap.Update(h); - } - - EditorBeatmap.EndChange(); + }); } /// @@ -362,12 +354,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The name of the hit sample. public void RemoveHitSample(string sampleName) { - EditorBeatmap.BeginChange(); - - foreach (var h in EditorBeatmap.SelectedHitObjects) - h.SamplesBindable.RemoveAll(s => s.Name == sampleName); - - EditorBeatmap.EndChange(); + EditorBeatmap.PerformOnSelection(h => h.SamplesBindable.RemoveAll(s => s.Name == sampleName)); } #endregion diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 174ff1478b..4f1b0484d2 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -100,6 +100,22 @@ namespace osu.Game.Screens.Edit private readonly HashSet batchPendingUpdates = new HashSet(); + /// + /// Perform the provided action on every selected hitobject. + /// Changes will be grouped as one history action. + /// + /// The action to perform. + public void PerformOnSelection(Action action) + { + if (SelectedHitObjects.Count == 0) + return; + + BeginChange(); + foreach (var h in SelectedHitObjects) + action(h); + EndChange(); + } + /// /// Adds a collection of s to this . /// From 3e65dfb9e7df4fe11c7d884e85efb30e461041b6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Feb 2021 17:11:47 +0900 Subject: [PATCH 090/390] Reduce allocation overhead when notification overlay has visible notifications --- .../Notifications/NotificationSection.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Notifications/NotificationSection.cs b/osu.Game/Overlays/Notifications/NotificationSection.cs index bc41311a6d..2316199049 100644 --- a/osu.Game/Overlays/Notifications/NotificationSection.cs +++ b/osu.Game/Overlays/Notifications/NotificationSection.cs @@ -10,9 +10,9 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Localisation; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osuTK; -using osu.Game.Graphics.Containers; namespace osu.Game.Overlays.Notifications { @@ -122,7 +122,20 @@ namespace osu.Game.Overlays.Notifications { base.Update(); - countDrawable.Text = notifications.Children.Count(c => c.Alpha > 0.99f).ToString(); + countDrawable.Text = getVisibleCount().ToString(); + } + + private int getVisibleCount() + { + int count = 0; + + foreach (var c in notifications) + { + if (c.Alpha > 0.99f) + count++; + } + + return count; } private class ClearAllButton : OsuClickableContainer From 7e6bd0e995fe8ec1f33b5cbfa510ad7cac69c04e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Feb 2021 17:30:59 +0900 Subject: [PATCH 091/390] Fix "failed to import" message showing when importing from a stable install with no beatmaps --- osu.Game/Database/ArchiveModelManager.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 03b8db2cb8..daaba9098e 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -141,6 +141,13 @@ namespace osu.Game.Database protected async Task> Import(ProgressNotification notification, params ImportTask[] tasks) { + if (tasks.Length == 0) + { + notification.CompletionText = $"No {HumanisedModelName}s were found to import!"; + notification.State = ProgressNotificationState.Completed; + return Enumerable.Empty(); + } + notification.Progress = 0; notification.Text = $"{HumanisedModelName.Humanize(LetterCasing.Title)} import is initialising..."; From 1ab449b73e081284f88125c696845c51c35ae984 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Feb 2021 17:54:51 +0900 Subject: [PATCH 092/390] Add test scene for drawings screen --- .../Screens/TestSceneDrawingsScreen.cs | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 osu.Game.Tournament.Tests/Screens/TestSceneDrawingsScreen.cs diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneDrawingsScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneDrawingsScreen.cs new file mode 100644 index 0000000000..e2954c8f10 --- /dev/null +++ b/osu.Game.Tournament.Tests/Screens/TestSceneDrawingsScreen.cs @@ -0,0 +1,35 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.IO; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Platform; +using osu.Game.Graphics.Cursor; +using osu.Game.Tournament.Screens.Drawings; + +namespace osu.Game.Tournament.Tests.Screens +{ + public class TestSceneDrawingsScreen : TournamentTestScene + { + [BackgroundDependencyLoader] + private void load(Storage storage) + { + using (var stream = storage.GetStream("drawings.txt", FileAccess.Write)) + using (var writer = new StreamWriter(stream)) + { + writer.WriteLine("KR : South Korea : KOR"); + writer.WriteLine("US : United States : USA"); + writer.WriteLine("PH : Philippines : PHL"); + writer.WriteLine("BR : Brazil : BRA"); + writer.WriteLine("JP : Japan : JPN"); + } + + Add(new OsuContextMenuContainer + { + RelativeSizeAxes = Axes.Both, + Child = new DrawingsScreen() + }); + } + } +} From 1ac82af19abce0e1e2ce97facf22808652d9d305 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Feb 2021 17:58:21 +0900 Subject: [PATCH 093/390] Adjust flag size to fit again --- .../Screens/Drawings/Components/ScrollingTeamContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs b/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs index 3ff4718b75..c7060bd538 100644 --- a/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs +++ b/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs @@ -345,7 +345,7 @@ namespace osu.Game.Tournament.Screens.Drawings.Components Flag.Anchor = Anchor.Centre; Flag.Origin = Anchor.Centre; - Flag.Scale = new Vector2(0.9f); + Flag.Scale = new Vector2(0.7f); InternalChildren = new Drawable[] { From 98d525d1dbb6f8b854b682a45d5ba600f30da6ea Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Feb 2021 19:56:10 +0900 Subject: [PATCH 094/390] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 8ea7cfac5b..5d83bb9583 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 6ff08ae63c..84a74502c2 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -29,7 +29,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index d7a1b7d692..2cea2e4b13 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -91,7 +91,7 @@ - + From 4fd8501c860989e09f1fdfedc9405bdde39aa70c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Feb 2021 20:03:03 +0900 Subject: [PATCH 095/390] Remove unnecessary using (underlying enumerator change) --- osu.Game/Screens/Play/PlayerLoader.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 5b4bd11216..7d906cdc5b 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -3,7 +3,6 @@ using System; using System.Diagnostics; -using System.Linq; using System.Threading.Tasks; using JetBrains.Annotations; using osu.Framework.Allocation; From 52e81385a6ba594cd325e24cf71c6580a7922727 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 27 Feb 2021 11:33:08 +0100 Subject: [PATCH 096/390] Fix restore default button mutating transforms during load --- osu.Game/Overlays/Settings/SettingsItem.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index aafd7463a6..4cb8d7f83c 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -207,7 +207,9 @@ namespace osu.Game.Overlays.Settings UpdateState(); } - public void UpdateState() + public void UpdateState() => Scheduler.AddOnce(updateState); + + private void updateState() { if (bindable == null) return; From 87b73da73edddc47f93d5c1a5edc6f8bae3ce6fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 27 Feb 2021 14:46:48 +0100 Subject: [PATCH 097/390] Add failing test case --- .../Mods/SettingsSourceAttributeTest.cs | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 osu.Game.Tests/Mods/SettingsSourceAttributeTest.cs diff --git a/osu.Game.Tests/Mods/SettingsSourceAttributeTest.cs b/osu.Game.Tests/Mods/SettingsSourceAttributeTest.cs new file mode 100644 index 0000000000..240d617dc7 --- /dev/null +++ b/osu.Game.Tests/Mods/SettingsSourceAttributeTest.cs @@ -0,0 +1,36 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Bindables; +using osu.Game.Configuration; + +namespace osu.Game.Tests.Mods +{ + [TestFixture] + public class SettingsSourceAttributeTest + { + [Test] + public void TestOrdering() + { + var objectWithSettings = new ClassWithSettings(); + + var orderedSettings = objectWithSettings.GetOrderedSettingsSourceProperties().ToArray(); + + Assert.That(orderedSettings, Has.Length.EqualTo(3)); + } + + private class ClassWithSettings + { + [SettingSource("Second setting", "Another description", 2)] + public BindableBool SecondSetting { get; set; } = new BindableBool(); + + [SettingSource("First setting", "A description", 1)] + public BindableDouble FirstSetting { get; set; } = new BindableDouble(); + + [SettingSource("Third setting", "Yet another description", 3)] + public BindableInt ThirdSetting { get; set; } = new BindableInt(); + } + } +} From 528de5869e305ec377d75098496ecaeadb949b69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 27 Feb 2021 14:47:09 +0100 Subject: [PATCH 098/390] Fix multiple enumerations when ordering setting sources This was not spotted previously, because the base `Attribute` overrides `Equals()` to have semantics similar to structs (per-field equality) by using reflection. That masked the issue when strings were used, and migrating to `LocalisableString` revealed it, as that struct's implementation of equality currently uses instance checks. Whether `LocalisableString.Equals()` is the correct implementation may still be up for discussion, but allowing multiple enumeration is wrong anyway, since the underlying enumerables are live (one especially is a yield iterator, causing new object instances to be allocated). --- osu.Game/Configuration/SettingSourceAttribute.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index 65a5a6d1b4..d0d2480e62 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -139,9 +139,12 @@ namespace osu.Game.Configuration public static IEnumerable<(SettingSourceAttribute, PropertyInfo)> GetOrderedSettingsSourceProperties(this object obj) { - var original = obj.GetSettingsSourceProperties(); + var original = obj.GetSettingsSourceProperties().ToArray(); - var orderedRelative = original.Where(attr => attr.Item1.OrderPosition != null).OrderBy(attr => attr.Item1.OrderPosition); + var orderedRelative = original + .Where(attr => attr.Item1.OrderPosition != null) + .OrderBy(attr => attr.Item1.OrderPosition) + .ToArray(); var unordered = original.Except(orderedRelative); return orderedRelative.Concat(unordered); From dd2f63f3137c8a9923509f04b2bb5656eda0093a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 27 Feb 2021 14:57:37 +0100 Subject: [PATCH 099/390] Add assertions to actually check order --- osu.Game.Tests/Mods/SettingsSourceAttributeTest.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Tests/Mods/SettingsSourceAttributeTest.cs b/osu.Game.Tests/Mods/SettingsSourceAttributeTest.cs index 240d617dc7..7fce1a6ce5 100644 --- a/osu.Game.Tests/Mods/SettingsSourceAttributeTest.cs +++ b/osu.Game.Tests/Mods/SettingsSourceAttributeTest.cs @@ -19,6 +19,10 @@ namespace osu.Game.Tests.Mods var orderedSettings = objectWithSettings.GetOrderedSettingsSourceProperties().ToArray(); Assert.That(orderedSettings, Has.Length.EqualTo(3)); + + Assert.That(orderedSettings[0].Item2.Name, Is.EqualTo(nameof(ClassWithSettings.FirstSetting))); + Assert.That(orderedSettings[1].Item2.Name, Is.EqualTo(nameof(ClassWithSettings.SecondSetting))); + Assert.That(orderedSettings[2].Item2.Name, Is.EqualTo(nameof(ClassWithSettings.ThirdSetting))); } private class ClassWithSettings From 7b6e53680c6035f6b7843f3ac5bd65b90e41128d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 27 Feb 2021 15:14:25 +0100 Subject: [PATCH 100/390] Add coverage for the unordered case --- osu.Game.Tests/Mods/SettingsSourceAttributeTest.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Mods/SettingsSourceAttributeTest.cs b/osu.Game.Tests/Mods/SettingsSourceAttributeTest.cs index 7fce1a6ce5..883c9d1ac2 100644 --- a/osu.Game.Tests/Mods/SettingsSourceAttributeTest.cs +++ b/osu.Game.Tests/Mods/SettingsSourceAttributeTest.cs @@ -18,15 +18,19 @@ namespace osu.Game.Tests.Mods var orderedSettings = objectWithSettings.GetOrderedSettingsSourceProperties().ToArray(); - Assert.That(orderedSettings, Has.Length.EqualTo(3)); + Assert.That(orderedSettings, Has.Length.EqualTo(4)); Assert.That(orderedSettings[0].Item2.Name, Is.EqualTo(nameof(ClassWithSettings.FirstSetting))); Assert.That(orderedSettings[1].Item2.Name, Is.EqualTo(nameof(ClassWithSettings.SecondSetting))); Assert.That(orderedSettings[2].Item2.Name, Is.EqualTo(nameof(ClassWithSettings.ThirdSetting))); + Assert.That(orderedSettings[3].Item2.Name, Is.EqualTo(nameof(ClassWithSettings.UnorderedSetting))); } private class ClassWithSettings { + [SettingSource("Unordered setting", "Should be last")] + public BindableFloat UnorderedSetting { get; set; } = new BindableFloat(); + [SettingSource("Second setting", "Another description", 2)] public BindableBool SecondSetting { get; set; } = new BindableBool(); From 1e56d2cbba16edec86632b5c1f780f1abaac2d1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 27 Feb 2021 15:30:05 +0100 Subject: [PATCH 101/390] Make `SettingSourceAttribute` implement `IComparable` --- .../Configuration/SettingSourceAttribute.cs | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index d0d2480e62..4cc31e14ac 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -23,7 +23,7 @@ namespace osu.Game.Configuration /// [MeansImplicitUse] [AttributeUsage(AttributeTargets.Property)] - public class SettingSourceAttribute : Attribute + public class SettingSourceAttribute : Attribute, IComparable { public LocalisableString Label { get; } @@ -42,6 +42,21 @@ namespace osu.Game.Configuration { OrderPosition = orderPosition; } + + public int CompareTo(SettingSourceAttribute other) + { + if (OrderPosition == other.OrderPosition) + return 0; + + // unordered items come last (are greater than any ordered items). + if (OrderPosition == null) + return 1; + if (other.OrderPosition == null) + return -1; + + // ordered items are sorted by the order value. + return OrderPosition.Value.CompareTo(other.OrderPosition); + } } public static class SettingSourceExtensions @@ -137,17 +152,13 @@ namespace osu.Game.Configuration } } - public static IEnumerable<(SettingSourceAttribute, PropertyInfo)> GetOrderedSettingsSourceProperties(this object obj) + public static ICollection<(SettingSourceAttribute, PropertyInfo)> GetOrderedSettingsSourceProperties(this object obj) { var original = obj.GetSettingsSourceProperties().ToArray(); - var orderedRelative = original - .Where(attr => attr.Item1.OrderPosition != null) - .OrderBy(attr => attr.Item1.OrderPosition) - .ToArray(); - var unordered = original.Except(orderedRelative); - - return orderedRelative.Concat(unordered); + return original + .OrderBy(attr => attr.Item1) + .ToArray(); } } } From 7e17c5ab7180c460f6fe142a37a2b0fdf3b8c987 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 27 Feb 2021 15:46:18 +0100 Subject: [PATCH 102/390] Trim yet another array copy --- osu.Game/Configuration/SettingSourceAttribute.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index 4cc31e14ac..cfce615130 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -153,12 +153,8 @@ namespace osu.Game.Configuration } public static ICollection<(SettingSourceAttribute, PropertyInfo)> GetOrderedSettingsSourceProperties(this object obj) - { - var original = obj.GetSettingsSourceProperties().ToArray(); - - return original - .OrderBy(attr => attr.Item1) - .ToArray(); - } + => obj.GetSettingsSourceProperties() + .OrderBy(attr => attr.Item1) + .ToArray(); } } From 41b43dd39a8b0b5e76a0f82e2d06b19ee633d696 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 28 Feb 2021 21:32:56 +0300 Subject: [PATCH 103/390] Add nested legacy-simulating coordinates container --- .../Skinning/Legacy/LegacySpinner.cs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs index ec7ecb0d28..94b6a906d0 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs @@ -127,5 +127,33 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy if (DrawableSpinner != null) DrawableSpinner.ApplyCustomUpdateState -= UpdateStateTransforms; } + + /// + /// A simulating osu!stable's absolute screen-space, + /// for perfect placements of legacy spinner components with legacy coordinates. + /// + protected class LegacyCoordinatesContainer : Container + { + /// + /// An offset that simulates stable's spinner top offset, + /// for positioning some legacy spinner components perfectly as in stable. + /// (e.g. 'spin' sprite, 'clear' sprite, metre in old-style spinners) + /// + public const float SPINNER_TOP_OFFSET = 29f; + + public LegacyCoordinatesContainer() + { + // legacy spinners relied heavily on absolute screen-space coordinate values. + // wrap everything in a container simulating absolute coords to preserve alignment + // as there are skins that depend on it. + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + Size = new Vector2(640, 480); + + // since legacy coordinates were on screen-space, they were accounting for the playfield shift offset. + // therefore cancel it from here. + Position = new Vector2(0, -8f); + } + } } } From d528ef426fac4f30f380e35d12a2b4a99f59b69f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 28 Feb 2021 21:41:11 +0300 Subject: [PATCH 104/390] Reposition legacy spinner components in-line with osu!stable --- .../Skinning/Legacy/LegacyOldStyleSpinner.cs | 44 ++++++++----------- .../Skinning/Legacy/LegacySpinner.cs | 40 +++++++++-------- 2 files changed, 39 insertions(+), 45 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyOldStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyOldStyleSpinner.cs index 4e07cb60b3..7e9f73a89b 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyOldStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyOldStyleSpinner.cs @@ -33,39 +33,31 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { spinnerBlink = source.GetConfig(OsuSkinConfiguration.SpinnerNoBlink)?.Value != true; - AddInternal(new Container + AddRangeInternal(new Drawable[] { - // the old-style spinner relied heavily on absolute screen-space coordinate values. - // wrap everything in a container simulating absolute coords to preserve alignment - // as there are skins that depend on it. - Width = 640, - Height = 480, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Children = new Drawable[] + new Sprite { - new Sprite - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Texture = source.GetTexture("spinner-background"), - Scale = new Vector2(SPRITE_SCALE) - }, - disc = new Sprite - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Texture = source.GetTexture("spinner-circle"), - Scale = new Vector2(SPRITE_SCALE) - }, - metre = new Container + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Texture = source.GetTexture("spinner-background"), + Scale = new Vector2(SPRITE_SCALE) + }, + disc = new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Texture = source.GetTexture("spinner-circle"), + Scale = new Vector2(SPRITE_SCALE) + }, + new LegacyCoordinatesContainer + { + Child = metre = new Container { AutoSizeAxes = Axes.Both, // this anchor makes no sense, but that's what stable uses. Anchor = Anchor.TopLeft, Origin = Anchor.TopLeft, - // adjustment for stable (metre has additional offset) - Margin = new MarginPadding { Top = 20 }, + Margin = new MarginPadding { Top = LegacyCoordinatesContainer.SPINNER_TOP_OFFSET }, Masking = true, Child = metreSprite = new Sprite { diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs index 94b6a906d0..1f1fd1fbd9 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs @@ -30,27 +30,29 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy DrawableSpinner = (DrawableSpinner)drawableHitObject; - AddRangeInternal(new[] + AddInternal(new LegacyCoordinatesContainer { - spin = new Sprite + Depth = float.MinValue, + Children = new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Depth = float.MinValue, - Texture = source.GetTexture("spinner-spin"), - Scale = new Vector2(SPRITE_SCALE), - Y = 120 - 45 // offset temporarily to avoid overlapping default spin counter - }, - clear = new Sprite - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Depth = float.MinValue, - Alpha = 0, - Texture = source.GetTexture("spinner-clear"), - Scale = new Vector2(SPRITE_SCALE), - Y = -60 - }, + spin = new Sprite + { + Anchor = Anchor.TopCentre, + Origin = Anchor.Centre, + Texture = source.GetTexture("spinner-spin"), + Scale = new Vector2(SPRITE_SCALE), + Y = LegacyCoordinatesContainer.SPINNER_TOP_OFFSET + 335, + }, + clear = new Sprite + { + Alpha = 0, + Anchor = Anchor.TopCentre, + Origin = Anchor.Centre, + Texture = source.GetTexture("spinner-clear"), + Scale = new Vector2(SPRITE_SCALE), + Y = LegacyCoordinatesContainer.SPINNER_TOP_OFFSET + 115, + }, + } }); } From 97bb217830e2f2e28942bb04083d3682c0c31c31 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Mar 2021 17:24:05 +0900 Subject: [PATCH 105/390] Fix test room playlist items not getting ids --- .../Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs index 5e12156f3c..022c297ccd 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs @@ -35,6 +35,7 @@ namespace osu.Game.Tests.Visual.Multiplayer int currentScoreId = 0; int currentRoomId = 0; + int currentPlaylistItemId = 0; ((DummyAPIAccess)api).HandleRequest = req => { @@ -46,6 +47,9 @@ namespace osu.Game.Tests.Visual.Multiplayer createdRoom.CopyFrom(createRoomRequest.Room); createdRoom.RoomID.Value ??= currentRoomId++; + for (int i = 0; i < createdRoom.Playlist.Count; i++) + createdRoom.Playlist[i].ID = currentPlaylistItemId++; + rooms.Add(createdRoom); createRoomRequest.TriggerSuccess(createdRoom); break; From f7e4cfa4d0dce04258aad29b03917048954b93c4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Mar 2021 17:24:32 +0900 Subject: [PATCH 106/390] Fix initial room settings not being returned correctly --- .../Multiplayer/TestMultiplayerClient.cs | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 379bb758c5..67679b2659 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -25,6 +25,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [Resolved] private IAPIProvider api { get; set; } = null!; + [Resolved] + private Room apiRoom { get; set; } = null!; + public void Connect() => isConnected.Value = true; public void Disconnect() => isConnected.Value = false; @@ -89,13 +92,28 @@ namespace osu.Game.Tests.Visual.Multiplayer protected override Task JoinRoom(long roomId) { - var user = new MultiplayerRoomUser(api.LocalUser.Value.Id) { User = api.LocalUser.Value }; + Debug.Assert(apiRoom != null); - var room = new MultiplayerRoom(roomId); - room.Users.Add(user); + var user = new MultiplayerRoomUser(api.LocalUser.Value.Id) + { + User = api.LocalUser.Value + }; - if (room.Users.Count == 1) - room.Host = user; + var room = new MultiplayerRoom(roomId) + { + Settings = + { + Name = apiRoom.Name.Value, + BeatmapID = apiRoom.Playlist.Last().BeatmapID, + RulesetID = apiRoom.Playlist.Last().RulesetID, + BeatmapChecksum = apiRoom.Playlist.Last().Beatmap.Value.MD5Hash, + RequiredMods = apiRoom.Playlist.Last().RequiredMods.Select(m => new APIMod(m)).ToArray(), + AllowedMods = apiRoom.Playlist.Last().AllowedMods.Select(m => new APIMod(m)).ToArray(), + PlaylistItemId = apiRoom.Playlist.Last().ID + }, + Users = { user }, + Host = user + }; return Task.FromResult(room); } From 7adb33f40e352137e26b9cb82fc1ad675da881af Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Mar 2021 17:24:54 +0900 Subject: [PATCH 107/390] Fix beatmap getting nulled due to failing web request --- .../Online/Multiplayer/MultiplayerClient.cs | 25 ++++++++++++ .../Multiplayer/StatefulMultiplayerClient.cs | 38 ++++++++++--------- .../Multiplayer/TestMultiplayerClient.cs | 20 ++++++++++ 3 files changed, 65 insertions(+), 18 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 95d76f384f..4529dfd0a7 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -9,7 +9,9 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR.Client; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Game.Beatmaps; using osu.Game.Online.API; +using osu.Game.Online.API.Requests; using osu.Game.Online.Rooms; namespace osu.Game.Online.Multiplayer @@ -121,6 +123,29 @@ namespace osu.Game.Online.Multiplayer return connection.InvokeAsync(nameof(IMultiplayerServer.StartMatch)); } + protected override Task GetOnlineBeatmapSet(int beatmapId, CancellationToken cancellationToken = default) + { + var tcs = new TaskCompletionSource(); + var req = new GetBeatmapSetRequest(beatmapId, BeatmapSetLookupType.BeatmapId); + + req.Success += res => + { + if (cancellationToken.IsCancellationRequested) + { + tcs.SetCanceled(); + return; + } + + tcs.SetResult(res.ToBeatmapSet(Rulesets)); + }; + + req.Failure += e => tcs.SetException(e); + + API.Queue(req); + + return tcs.Task; + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index bfd505fb19..73100be505 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -17,8 +17,6 @@ using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Online.API; -using osu.Game.Online.API.Requests; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Rooms; using osu.Game.Online.Rooms.RoomStatuses; using osu.Game.Rulesets; @@ -71,7 +69,7 @@ namespace osu.Game.Online.Multiplayer /// /// The corresponding to the local player, if available. /// - public MultiplayerRoomUser? LocalUser => Room?.Users.SingleOrDefault(u => u.User?.Id == api.LocalUser.Value.Id); + public MultiplayerRoomUser? LocalUser => Room?.Users.SingleOrDefault(u => u.User?.Id == API.LocalUser.Value.Id); /// /// Whether the is the host in . @@ -85,15 +83,15 @@ namespace osu.Game.Online.Multiplayer } } + [Resolved] + protected IAPIProvider API { get; private set; } = null!; + + [Resolved] + protected RulesetStore Rulesets { get; private set; } = null!; + [Resolved] private UserLookupCache userLookupCache { get; set; } = null!; - [Resolved] - private IAPIProvider api { get; set; } = null!; - - [Resolved] - private RulesetStore rulesets { get; set; } = null!; - // Only exists for compatibility with old osu-server-spectator build. // Todo: Can be removed on 2021/02/26. private long defaultPlaylistItemId; @@ -515,30 +513,26 @@ namespace osu.Game.Online.Multiplayer RoomUpdated?.Invoke(); - var req = new GetBeatmapSetRequest(settings.BeatmapID, BeatmapSetLookupType.BeatmapId); - req.Success += res => + GetOnlineBeatmapSet(settings.BeatmapID, cancellationToken).ContinueWith(set => Schedule(() => { if (cancellationToken.IsCancellationRequested) return; - updatePlaylist(settings, res); - }; - - api.Queue(req); + updatePlaylist(settings, set.Result); + }), TaskContinuationOptions.OnlyOnRanToCompletion); }, cancellationToken); - private void updatePlaylist(MultiplayerRoomSettings settings, APIBeatmapSet onlineSet) + private void updatePlaylist(MultiplayerRoomSettings settings, BeatmapSetInfo beatmapSet) { if (Room == null || !Room.Settings.Equals(settings)) return; Debug.Assert(apiRoom != null); - var beatmapSet = onlineSet.ToBeatmapSet(rulesets); var beatmap = beatmapSet.Beatmaps.Single(b => b.OnlineBeatmapID == settings.BeatmapID); beatmap.MD5Hash = settings.BeatmapChecksum; - var ruleset = rulesets.GetRuleset(settings.RulesetID).CreateInstance(); + var ruleset = Rulesets.GetRuleset(settings.RulesetID).CreateInstance(); var mods = settings.RequiredMods.Select(m => m.ToMod(ruleset)); var allowedMods = settings.AllowedMods.Select(m => m.ToMod(ruleset)); @@ -568,6 +562,14 @@ namespace osu.Game.Online.Multiplayer } } + /// + /// Retrieves a from an online source. + /// + /// The beatmap set ID. + /// A token to cancel the request. + /// The retrieval task. + protected abstract Task GetOnlineBeatmapSet(int beatmapId, CancellationToken cancellationToken = default); + /// /// For the provided user ID, update whether the user is included in . /// diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 67679b2659..6a901fc45b 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -3,12 +3,15 @@ #nullable enable +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; @@ -28,6 +31,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [Resolved] private Room apiRoom { get; set; } = null!; + [Resolved] + private BeatmapManager beatmaps { get; set; } = null!; + public void Connect() => isConnected.Value = true; public void Disconnect() => isConnected.Value = false; @@ -168,5 +174,19 @@ namespace osu.Game.Tests.Visual.Multiplayer return ((IMultiplayerClient)this).LoadRequested(); } + + protected override Task GetOnlineBeatmapSet(int beatmapId, CancellationToken cancellationToken = default) + { + Debug.Assert(Room != null); + Debug.Assert(apiRoom != null); + + var set = apiRoom.Playlist.FirstOrDefault(p => p.BeatmapID == beatmapId)?.Beatmap.Value.BeatmapSet + ?? beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == beatmapId)?.BeatmapSet; + + if (set == null) + throw new InvalidOperationException("Beatmap not found."); + + return Task.FromResult(set); + } } } From 5cfaf1de1b6d72cbbf900d0667bf2c2e48e6f77c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Mar 2021 17:43:03 +0900 Subject: [PATCH 108/390] Fix duplicate ongoing operation tracker --- .../Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index 2344ebea0e..8869718fd1 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -3,12 +3,10 @@ using System.Linq; using NUnit.Framework; -using osu.Framework.Allocation; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Online.Rooms; using osu.Game.Rulesets.Osu; -using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; using osu.Game.Tests.Beatmaps; @@ -20,9 +18,6 @@ namespace osu.Game.Tests.Visual.Multiplayer { private MultiplayerMatchSubScreen screen; - [Cached] - private OngoingOperationTracker ongoingOperationTracker = new OngoingOperationTracker(); - public TestSceneMultiplayerMatchSubScreen() : base(false) { From fe54a51b5a9c5995db488e1c8873fd1691463a3a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 1 Mar 2021 22:41:09 +0300 Subject: [PATCH 109/390] Remove `UserRanks` object and move to outer `country_rank` property --- osu.Game/Users/UserStatistics.cs | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/osu.Game/Users/UserStatistics.cs b/osu.Game/Users/UserStatistics.cs index 78e6f5a05a..dc926898fc 100644 --- a/osu.Game/Users/UserStatistics.cs +++ b/osu.Game/Users/UserStatistics.cs @@ -29,16 +29,9 @@ namespace osu.Game.Users [JsonProperty(@"global_rank")] public int? GlobalRank; + [JsonProperty(@"country_rank")] public int? CountryRank; - [JsonProperty(@"rank")] - private UserRanks ranks - { - // eventually that will also become an own json property instead of reading from a `rank` object. - // see https://github.com/ppy/osu-web/blob/cb79bb72186c8f1a25f6a6f5ef315123decb4231/app/Transformers/UserStatisticsTransformer.php#L53. - set => CountryRank = value.Country; - } - // populated via User model, as that's where the data currently lives. public RankHistoryData RankHistory; @@ -119,13 +112,5 @@ namespace osu.Game.Users } } } - -#pragma warning disable 649 - private struct UserRanks - { - [JsonProperty(@"country")] - public int? Country; - } -#pragma warning restore 649 } } From 51a5652666d7d5e653fa28ddc0c74e23ddafe0f6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 1 Mar 2021 22:42:53 +0300 Subject: [PATCH 110/390] Refetch tournament users on null country rank --- osu.Game.Tournament/TournamentGameBase.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index d506724017..2ee52c35aa 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -150,7 +150,9 @@ namespace osu.Game.Tournament { foreach (var p in t.Players) { - if (string.IsNullOrEmpty(p.Username) || p.Statistics?.GlobalRank == null) + if (string.IsNullOrEmpty(p.Username) + || p.Statistics?.GlobalRank == null + || p.Statistics?.CountryRank == null) { PopulateUser(p, immediate: true); addedInfo = true; From 2d3c3c18d4c1c3e1174079f2363a5d2e03b29c16 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 1 Mar 2021 20:05:35 +0000 Subject: [PATCH 111/390] Bump SharpCompress from 0.27.1 to 0.28.1 Bumps [SharpCompress](https://github.com/adamhathcock/sharpcompress) from 0.27.1 to 0.28.1. - [Release notes](https://github.com/adamhathcock/sharpcompress/releases) - [Commits](https://github.com/adamhathcock/sharpcompress/compare/0.27.1...0.28.1) Signed-off-by: dependabot-preview[bot] --- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 84a74502c2..4d086844e4 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -32,7 +32,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 2cea2e4b13..c0cfb7a96d 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -92,7 +92,7 @@ - + From 9db37e62d8dc33bd19ef35861dab19b5f861af86 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 1 Mar 2021 20:05:53 +0000 Subject: [PATCH 112/390] Bump Microsoft.AspNetCore.SignalR.Protocols.MessagePack Bumps [Microsoft.AspNetCore.SignalR.Protocols.MessagePack](https://github.com/dotnet/aspnetcore) from 5.0.2 to 5.0.3. - [Release notes](https://github.com/dotnet/aspnetcore/releases) - [Commits](https://github.com/dotnet/aspnetcore/compare/v5.0.2...v5.0.3) Signed-off-by: dependabot-preview[bot] --- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 84a74502c2..ca39c160a4 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -22,7 +22,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 2cea2e4b13..c854ae7dff 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -80,7 +80,7 @@ - + From 2609b22d53626ff13206a88e70714b952ff5ff35 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 1 Mar 2021 23:07:25 +0300 Subject: [PATCH 113/390] Replace usage of `CurrentModeRank` in line with API change --- osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs | 4 ++-- .../Visual/Playlists/TestScenePlaylistsParticipantsList.cs | 2 +- osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs | 2 +- osu.Game/Users/User.cs | 5 +---- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs index 9bece39ca0..e8d9ff72af 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs @@ -51,7 +51,7 @@ namespace osu.Game.Tests.Visual.Online Username = "flyte", Id = 3103765, IsOnline = true, - CurrentModeRank = 1111, + Statistics = new UserStatistics { GlobalRank = 1111 }, Country = new Country { FlagName = "JP" }, CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c6.jpg" }, @@ -60,7 +60,7 @@ namespace osu.Game.Tests.Visual.Online Username = "peppy", Id = 2, IsOnline = false, - CurrentModeRank = 2222, + Statistics = new UserStatistics { GlobalRank = 2222 }, Country = new Country { FlagName = "AU" }, CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", IsSupporter = true, diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs index 8dd81e02e2..255f147ec9 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs @@ -20,7 +20,7 @@ namespace osu.Game.Tests.Visual.Playlists Room.RecentParticipants.Add(new User { Username = "peppy", - CurrentModeRank = 1234, + Statistics = new UserStatistics { GlobalRank = 1234 }, Id = 2 }); } diff --git a/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs b/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs index e6fe6ac749..0922ce5ecc 100644 --- a/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs +++ b/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs @@ -244,7 +244,7 @@ namespace osu.Game.Overlays.Dashboard.Friends return unsorted.OrderByDescending(u => u.LastVisit).ToList(); case UserSortCriteria.Rank: - return unsorted.OrderByDescending(u => u.CurrentModeRank.HasValue).ThenBy(u => u.CurrentModeRank ?? 0).ToList(); + return unsorted.OrderByDescending(u => u.Statistics.GlobalRank.HasValue).ThenBy(u => u.Statistics.GlobalRank ?? 0).ToList(); case UserSortCriteria.Username: return unsorted.OrderBy(u => u.Username).ToList(); diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs index 4a6fd540c7..4d537b91bd 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -72,9 +72,6 @@ namespace osu.Game.Users [JsonProperty(@"support_level")] public int SupportLevel; - [JsonProperty(@"current_mode_rank")] - public int? CurrentModeRank; - [JsonProperty(@"is_gmt")] public bool IsGMT; @@ -182,7 +179,7 @@ namespace osu.Game.Users private UserStatistics statistics; /// - /// User statistics for the requested ruleset (in the case of a response). + /// User statistics for the requested ruleset (in the case of a or response). /// Otherwise empty. /// [JsonProperty(@"statistics")] From d6925d09609c81bc8b8dc426d66440f7f25cedad Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 2 Mar 2021 00:43:44 +0000 Subject: [PATCH 114/390] Bump Moq from 4.16.0 to 4.16.1 Bumps [Moq](https://github.com/moq/moq4) from 4.16.0 to 4.16.1. - [Release notes](https://github.com/moq/moq4/releases) - [Changelog](https://github.com/moq/moq4/blob/main/CHANGELOG.md) - [Commits](https://github.com/moq/moq4/compare/v4.16.0...v4.16.1) Signed-off-by: dependabot-preview[bot] --- osu.Game.Tests.Android/osu.Game.Tests.Android.csproj | 2 +- osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj | 2 +- osu.Game.Tests/osu.Game.Tests.csproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj b/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj index 19e36a63f1..543f2f35a7 100644 --- a/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj +++ b/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj @@ -71,7 +71,7 @@ - + \ No newline at end of file diff --git a/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj b/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj index 67b2298f4c..e83bef4a95 100644 --- a/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj +++ b/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj @@ -45,7 +45,7 @@ - + \ No newline at end of file diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 7e3868bd3b..877f41fbff 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -7,7 +7,7 @@ - + WinExe From b03efd69402995a6bc4ce62cf3b903ace5de396b Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 2 Mar 2021 00:43:45 +0000 Subject: [PATCH 115/390] Bump Microsoft.NET.Test.Sdk from 16.8.3 to 16.9.1 Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 16.8.3 to 16.9.1. - [Release notes](https://github.com/microsoft/vstest/releases) - [Commits](https://github.com/microsoft/vstest/compare/v16.8.3...v16.9.1) Signed-off-by: dependabot-preview[bot] --- .../osu.Game.Rulesets.Catch.Tests.csproj | 2 +- .../osu.Game.Rulesets.Mania.Tests.csproj | 2 +- osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj | 2 +- .../osu.Game.Rulesets.Taiko.Tests.csproj | 2 +- osu.Game.Tests/osu.Game.Tests.csproj | 2 +- osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj index bf3aba5859..728af5124e 100644 --- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj +++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj index fcc0cafefc..af16f39563 100644 --- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj +++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj index b4c686ccea..3d2d1f3fec 100644 --- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj +++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj index 2b084f3bee..fa00922706 100644 --- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj +++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 7e3868bd3b..6c5ca937e2 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -3,7 +3,7 @@ - + diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj index 77ae06d89c..b20583dd7e 100644 --- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj +++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj @@ -5,7 +5,7 @@ - + From 7829a0636e5c021db48d058df16a6554313182d6 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 2 Mar 2021 00:43:47 +0000 Subject: [PATCH 116/390] Bump Sentry from 3.0.1 to 3.0.7 Bumps [Sentry](https://github.com/getsentry/sentry-dotnet) from 3.0.1 to 3.0.7. - [Release notes](https://github.com/getsentry/sentry-dotnet/releases) - [Changelog](https://github.com/getsentry/sentry-dotnet/blob/main/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-dotnet/compare/3.0.1...3.0.7) Signed-off-by: dependabot-preview[bot] --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 5ec7fb81fc..9916122a2a 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -31,7 +31,7 @@ - + From fa959291216feeb9e24174d74f9c3bc9a3882f36 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Mar 2021 16:07:09 +0900 Subject: [PATCH 117/390] Remove easy to remove finalizers --- osu.Game/Database/DatabaseWriteUsage.cs | 5 ----- osu.Game/Utils/SentryLogger.cs | 5 ----- 2 files changed, 10 deletions(-) diff --git a/osu.Game/Database/DatabaseWriteUsage.cs b/osu.Game/Database/DatabaseWriteUsage.cs index ddafd77066..84c39e3532 100644 --- a/osu.Game/Database/DatabaseWriteUsage.cs +++ b/osu.Game/Database/DatabaseWriteUsage.cs @@ -54,10 +54,5 @@ namespace osu.Game.Database Dispose(true); GC.SuppressFinalize(this); } - - ~DatabaseWriteUsage() - { - Dispose(false); - } } } diff --git a/osu.Game/Utils/SentryLogger.cs b/osu.Game/Utils/SentryLogger.cs index be9d01cde6..8f12760a6b 100644 --- a/osu.Game/Utils/SentryLogger.cs +++ b/osu.Game/Utils/SentryLogger.cs @@ -86,11 +86,6 @@ namespace osu.Game.Utils #region Disposal - ~SentryLogger() - { - Dispose(false); - } - public void Dispose() { Dispose(true); From c4ba045df158275175e0a84675106986ae96b946 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Mar 2021 16:07:51 +0900 Subject: [PATCH 118/390] Add note about finalizers required for audio store clean-up --- osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs | 1 + osu.Game/Skinning/Skin.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs index b31884d246..14aa3fe99a 100644 --- a/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs +++ b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs @@ -63,6 +63,7 @@ namespace osu.Game.Rulesets.UI ~DrawableRulesetDependencies() { + // required to potentially clean up sample store from audio hierarchy. Dispose(false); } diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index e8d84b49f9..6b435cff0f 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -36,6 +36,7 @@ namespace osu.Game.Skinning ~Skin() { + // required to potentially clean up sample store from audio hierarchy. Dispose(false); } From 103dd4a6cea72ec399ac995acfe5270bd1da0de7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Mar 2021 16:14:43 +0900 Subject: [PATCH 119/390] Remove WorkingBeatmap's finalizer --- osu.Game/Beatmaps/BeatmapManager.cs | 4 ++++ osu.Game/Beatmaps/WorkingBeatmap.cs | 10 ---------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 3c6a6ba302..d653e5386b 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -20,6 +20,7 @@ using osu.Framework.IO.Stores; using osu.Framework.Lists; using osu.Framework.Logging; using osu.Framework.Platform; +using osu.Framework.Statistics; using osu.Framework.Testing; using osu.Game.Beatmaps.Formats; using osu.Game.Database; @@ -311,6 +312,9 @@ namespace osu.Game.Beatmaps workingCache.Add(working = new BeatmapManagerWorkingBeatmap(beatmapInfo, this)); + // best effort; may be higher than expected. + GlobalStatistics.Get(nameof(Beatmaps), $"Cached {nameof(WorkingBeatmap)}s").Value = workingCache.Count(); + return working; } } diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index aab8ff6bd6..f7f276230f 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -12,7 +12,6 @@ using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Graphics.Textures; using osu.Framework.Logging; -using osu.Framework.Statistics; using osu.Framework.Testing; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; @@ -34,8 +33,6 @@ namespace osu.Game.Beatmaps protected AudioManager AudioManager { get; } - private static readonly GlobalStatistic total_count = GlobalStatistics.Get(nameof(Beatmaps), $"Total {nameof(WorkingBeatmap)}s"); - protected WorkingBeatmap(BeatmapInfo beatmapInfo, AudioManager audioManager) { AudioManager = audioManager; @@ -47,8 +44,6 @@ namespace osu.Game.Beatmaps waveform = new RecyclableLazy(GetWaveform); storyboard = new RecyclableLazy(GetStoryboard); skin = new RecyclableLazy(GetSkin); - - total_count.Value++; } protected virtual Track GetVirtualTrack(double emptyLength = 0) @@ -331,11 +326,6 @@ namespace osu.Game.Beatmaps protected virtual ISkin GetSkin() => new DefaultSkin(); private readonly RecyclableLazy skin; - ~WorkingBeatmap() - { - total_count.Value--; - } - public class RecyclableLazy { private Lazy lazy; From 6372a0265af98f48afa15d4c4499a1a33250db6c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Mar 2021 17:44:56 +0900 Subject: [PATCH 120/390] Fix confine mode dropdown becoming visible again after filtering Changes from a hidden to a disabled state, with a tooltip explaining why. Closes #11851. --- .../Settings/Sections/Input/MouseSettings.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs index 455e13711d..768a18cca0 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs @@ -68,7 +68,21 @@ namespace osu.Game.Overlays.Settings.Sections.Input }; windowMode = config.GetBindable(FrameworkSetting.WindowMode); - windowMode.BindValueChanged(mode => confineMouseModeSetting.Alpha = mode.NewValue == WindowMode.Fullscreen ? 0 : 1, true); + windowMode.BindValueChanged(mode => + { + var isFullscreen = mode.NewValue == WindowMode.Fullscreen; + + if (isFullscreen) + { + confineMouseModeSetting.Current.Disabled = true; + confineMouseModeSetting.TooltipText = "Not applicable in full screen mode"; + } + else + { + confineMouseModeSetting.Current.Disabled = false; + confineMouseModeSetting.TooltipText = string.Empty; + } + }, true); if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows) { From 0300a554476c72fb0e07774350f0fc79687718c2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Mar 2021 18:00:50 +0900 Subject: [PATCH 121/390] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 5d83bb9583..c428cd2546 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 9916122a2a..2528292e17 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -29,7 +29,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index b4f981162a..56a24bea12 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -91,7 +91,7 @@ - + From 30ff0b83c199a20ddcd2e86beb643842f1263daf Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Mar 2021 19:06:21 +0900 Subject: [PATCH 122/390] Fix test failures due to unpopulated room --- .../Tests/Visual/Multiplayer/MultiplayerTestScene.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs index 2e8c834c65..7775c2bd24 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs @@ -7,8 +7,10 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Online.Multiplayer; +using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Lounge.Components; +using osu.Game.Tests.Beatmaps; namespace osu.Game.Tests.Visual.Multiplayer { @@ -48,7 +50,16 @@ namespace osu.Game.Tests.Visual.Multiplayer RoomManager.Schedule(() => RoomManager.PartRoom()); if (joinRoom) + { + Room.Name.Value = "test name"; + Room.Playlist.Add(new PlaylistItem + { + Beatmap = { Value = new TestBeatmap(Ruleset.Value).BeatmapInfo }, + Ruleset = { Value = Ruleset.Value } + }); + RoomManager.Schedule(() => RoomManager.CreateRoom(Room)); + } }); public override void SetUpSteps() From 711cf3e5111e46f293b8aba2216c082378944eb3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 2 Mar 2021 17:25:36 +0300 Subject: [PATCH 123/390] Add mobile logs location to issue templates --- .github/ISSUE_TEMPLATE/01-bug-issues.md | 2 ++ .github/ISSUE_TEMPLATE/02-crash-issues.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/01-bug-issues.md b/.github/ISSUE_TEMPLATE/01-bug-issues.md index 0b80ce44dd..6050036cbf 100644 --- a/.github/ISSUE_TEMPLATE/01-bug-issues.md +++ b/.github/ISSUE_TEMPLATE/01-bug-issues.md @@ -13,4 +13,6 @@ about: Issues regarding encountered bugs. *please attach logs here, which are located at:* - `%AppData%/osu/logs` *(on Windows),* - `~/.local/share/osu/logs` *(on Linux & macOS).* +- `Android/Data/sh.ppy.osulazer/logs` *(on Android)*, +- on iOS they can be obtained by connecting your device to your desktop and copying the `logs` directory from the app's own document storage using iTunes. (https://support.apple.com/en-us/HT201301#copy-to-computer) --> diff --git a/.github/ISSUE_TEMPLATE/02-crash-issues.md b/.github/ISSUE_TEMPLATE/02-crash-issues.md index ada8de73c0..04170312d1 100644 --- a/.github/ISSUE_TEMPLATE/02-crash-issues.md +++ b/.github/ISSUE_TEMPLATE/02-crash-issues.md @@ -13,6 +13,8 @@ about: Issues regarding crashes or permanent freezes. *please attach logs here, which are located at:* - `%AppData%/osu/logs` *(on Windows),* - `~/.local/share/osu/logs` *(on Linux & macOS).* +- `Android/Data/sh.ppy.osulazer/logs` *(on Android)*, +- on iOS they can be obtained by connecting your device to your desktop and copying the `logs` directory from the app's own document storage using iTunes. (https://support.apple.com/en-us/HT201301#copy-to-computer) --> **Computer Specifications:** From 40a28367c63a2cf7e3c87eeccaf617b1f25564a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 2 Mar 2021 18:50:33 +0100 Subject: [PATCH 124/390] Fix restore-to-default buttons never showing if initially hidden --- osu.Game/Overlays/Settings/SettingsItem.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index 4cb8d7f83c..c5890a6fbb 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -147,6 +147,7 @@ namespace osu.Game.Overlays.Settings RelativeSizeAxes = Axes.Y; Width = SettingsPanel.CONTENT_MARGINS; Alpha = 0f; + AlwaysPresent = true; } [BackgroundDependencyLoader] From 3b125a26a863e61d20a9a5018ab10383c2486611 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 2 Mar 2021 19:18:01 +0100 Subject: [PATCH 125/390] Add test coverage --- .../Visual/Settings/TestSceneSettingsItem.cs | 43 +++++++++++++++++++ osu.Game/Overlays/Settings/SettingsItem.cs | 2 +- 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs diff --git a/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs b/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs new file mode 100644 index 0000000000..8f1c17ed29 --- /dev/null +++ b/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs @@ -0,0 +1,43 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Testing; +using osu.Game.Overlays.Settings; + +namespace osu.Game.Tests.Visual.Settings +{ + [TestFixture] + public class TestSceneSettingsItem : OsuTestScene + { + [Test] + public void TestRestoreDefaultValueButtonVisibility() + { + TestSettingsTextBox textBox = null; + + AddStep("create settings item", () => Child = textBox = new TestSettingsTextBox + { + Current = new Bindable + { + Default = "test", + Value = "test" + } + }); + AddAssert("restore button hidden", () => textBox.RestoreDefaultValueButton.Alpha == 0); + + AddStep("change value from default", () => textBox.Current.Value = "non-default"); + AddUntilStep("restore button shown", () => textBox.RestoreDefaultValueButton.Alpha > 0); + + AddStep("restore default", () => textBox.Current.SetDefault()); + AddUntilStep("restore button hidden", () => textBox.RestoreDefaultValueButton.Alpha == 0); + } + + private class TestSettingsTextBox : SettingsTextBox + { + public new Drawable RestoreDefaultValueButton => this.ChildrenOfType().Single(); + } + } +} diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index c5890a6fbb..8631b8ac7b 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -121,7 +121,7 @@ namespace osu.Game.Overlays.Settings labelText.Alpha = controlWithCurrent.Current.Disabled ? 0.3f : 1; } - private class RestoreDefaultValueButton : Container, IHasTooltip + protected internal class RestoreDefaultValueButton : Container, IHasTooltip { private Bindable bindable; From 26736d990f792f15bd0c92f7f5a100a8f800816d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 2 Mar 2021 19:42:47 +0100 Subject: [PATCH 126/390] Enable filter parsing extensibility --- osu.Game/Screens/Select/FilterQueryParser.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 4b6b3be45c..3f1b80ee1c 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -11,7 +11,7 @@ namespace osu.Game.Screens.Select internal static class FilterQueryParser { private static readonly Regex query_syntax_regex = new Regex( - @"\b(?stars|ar|dr|hp|cs|divisor|length|objects|bpm|status|creator|artist)(?[=:><]+)(?("".*"")|(\S*))", + @"\b(?\w+)(?[=:><]+)(?("".*"")|(\S*))", RegexOptions.Compiled | RegexOptions.IgnoreCase); internal static void ApplyQueries(FilterCriteria criteria, string query) @@ -22,15 +22,14 @@ namespace osu.Game.Screens.Select var op = match.Groups["op"].Value; var value = match.Groups["value"].Value; - parseKeywordCriteria(criteria, key, value, op); - - query = query.Replace(match.ToString(), ""); + if (tryParseKeywordCriteria(criteria, key, value, op)) + query = query.Replace(match.ToString(), ""); } criteria.SearchText = query; } - private static void parseKeywordCriteria(FilterCriteria criteria, string key, string value, string op) + private static bool tryParseKeywordCriteria(FilterCriteria criteria, string key, string value, string op) { switch (key) { @@ -75,7 +74,12 @@ namespace osu.Game.Screens.Select case "artist": updateCriteriaText(ref criteria.Artist, op, value); break; + + default: + return false; } + + return true; } private static int getLengthScale(string value) => From e46543a4a924d6bab3b7ef67fa8d3d992d6504bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 2 Mar 2021 19:56:36 +0100 Subject: [PATCH 127/390] Constrain operator parsing better --- .../Filtering/FilterQueryParserTest.cs | 9 ++ osu.Game/Screens/Select/Filter/Operator.cs | 17 +++ osu.Game/Screens/Select/FilterQueryParser.cs | 131 ++++++++++-------- 3 files changed, 99 insertions(+), 58 deletions(-) create mode 100644 osu.Game/Screens/Select/Filter/Operator.cs diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index d15682b1eb..e121cb835c 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -194,5 +194,14 @@ namespace osu.Game.Tests.NonVisual.Filtering Assert.AreEqual(1, filterCriteria.SearchTerms.Length); Assert.AreEqual("double\"quote", filterCriteria.Artist.SearchTerm); } + + [Test] + public void TestOperatorParsing() + { + const string query = "artist=>. Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Screens.Select.Filter +{ + /// + /// Defines logical operators that can be used in the song select search box keyword filters. + /// + public enum Operator + { + Less, + LessOrEqual, + Equal, + GreaterOrEqual, + Greater + } +} diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 3f1b80ee1c..d2d33b13f5 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -5,13 +5,14 @@ using System; using System.Globalization; using System.Text.RegularExpressions; using osu.Game.Beatmaps; +using osu.Game.Screens.Select.Filter; namespace osu.Game.Screens.Select { internal static class FilterQueryParser { private static readonly Regex query_syntax_regex = new Regex( - @"\b(?\w+)(?[=:><]+)(?("".*"")|(\S*))", + @"\b(?\w+)(?(:|=|(>|<)(:|=)?))(?("".*"")|(\S*))", RegexOptions.Compiled | RegexOptions.IgnoreCase); internal static void ApplyQueries(FilterCriteria criteria, string query) @@ -19,7 +20,7 @@ namespace osu.Game.Screens.Select foreach (Match match in query_syntax_regex.Matches(query)) { var key = match.Groups["key"].Value.ToLower(); - var op = match.Groups["op"].Value; + var op = parseOperator(match.Groups["op"].Value); var value = match.Groups["value"].Value; if (tryParseKeywordCriteria(criteria, key, value, op)) @@ -29,57 +30,72 @@ namespace osu.Game.Screens.Select criteria.SearchText = query; } - private static bool tryParseKeywordCriteria(FilterCriteria criteria, string key, string value, string op) + private static bool tryParseKeywordCriteria(FilterCriteria criteria, string key, string value, Operator op) { switch (key) { case "stars" when parseFloatWithPoint(value, out var stars): - updateCriteriaRange(ref criteria.StarDifficulty, op, stars, 0.01f / 2); - break; + return updateCriteriaRange(ref criteria.StarDifficulty, op, stars, 0.01f / 2); case "ar" when parseFloatWithPoint(value, out var ar): - updateCriteriaRange(ref criteria.ApproachRate, op, ar, 0.1f / 2); - break; + return updateCriteriaRange(ref criteria.ApproachRate, op, ar, 0.1f / 2); case "dr" when parseFloatWithPoint(value, out var dr): case "hp" when parseFloatWithPoint(value, out dr): - updateCriteriaRange(ref criteria.DrainRate, op, dr, 0.1f / 2); - break; + return updateCriteriaRange(ref criteria.DrainRate, op, dr, 0.1f / 2); case "cs" when parseFloatWithPoint(value, out var cs): - updateCriteriaRange(ref criteria.CircleSize, op, cs, 0.1f / 2); - break; + return updateCriteriaRange(ref criteria.CircleSize, op, cs, 0.1f / 2); case "bpm" when parseDoubleWithPoint(value, out var bpm): - updateCriteriaRange(ref criteria.BPM, op, bpm, 0.01d / 2); - break; + return updateCriteriaRange(ref criteria.BPM, op, bpm, 0.01d / 2); case "length" when parseDoubleWithPoint(value.TrimEnd('m', 's', 'h'), out var length): var scale = getLengthScale(value); - updateCriteriaRange(ref criteria.Length, op, length * scale, scale / 2.0); - break; + return updateCriteriaRange(ref criteria.Length, op, length * scale, scale / 2.0); case "divisor" when parseInt(value, out var divisor): - updateCriteriaRange(ref criteria.BeatDivisor, op, divisor); - break; + return updateCriteriaRange(ref criteria.BeatDivisor, op, divisor); case "status" when Enum.TryParse(value, true, out var statusValue): - updateCriteriaRange(ref criteria.OnlineStatus, op, statusValue); - break; + return updateCriteriaRange(ref criteria.OnlineStatus, op, statusValue); case "creator": - updateCriteriaText(ref criteria.Creator, op, value); - break; + return updateCriteriaText(ref criteria.Creator, op, value); case "artist": - updateCriteriaText(ref criteria.Artist, op, value); - break; + return updateCriteriaText(ref criteria.Artist, op, value); default: return false; } + } - return true; + private static Operator parseOperator(string value) + { + switch (value) + { + case "=": + case ":": + return Operator.Equal; + + case "<": + return Operator.Less; + + case "<=": + case "<:": + return Operator.LessOrEqual; + + case ">": + return Operator.Greater; + + case ">=": + case ">:": + return Operator.GreaterOrEqual; + + default: + throw new ArgumentOutOfRangeException(nameof(value), $"Unsupported operator {value}"); + } } private static int getLengthScale(string value) => @@ -97,120 +113,119 @@ namespace osu.Game.Screens.Select private static bool parseInt(string value, out int result) => int.TryParse(value, NumberStyles.None, CultureInfo.InvariantCulture, out result); - private static void updateCriteriaText(ref FilterCriteria.OptionalTextFilter textFilter, string op, string value) + private static bool updateCriteriaText(ref FilterCriteria.OptionalTextFilter textFilter, Operator op, string value) { switch (op) { - case "=": - case ":": + case Operator.Equal: textFilter.SearchTerm = value.Trim('"'); - break; + return true; + + default: + return false; } } - private static void updateCriteriaRange(ref FilterCriteria.OptionalRange range, string op, float value, float tolerance = 0.05f) + private static bool updateCriteriaRange(ref FilterCriteria.OptionalRange range, Operator op, float value, float tolerance = 0.05f) { switch (op) { default: - return; + return false; - case "=": - case ":": + case Operator.Equal: range.Min = value - tolerance; range.Max = value + tolerance; break; - case ">": + case Operator.Greater: range.Min = value + tolerance; break; - case ">=": - case ">:": + case Operator.GreaterOrEqual: range.Min = value - tolerance; break; - case "<": + case Operator.Less: range.Max = value - tolerance; break; - case "<=": - case "<:": + case Operator.LessOrEqual: range.Max = value + tolerance; break; } + + return true; } - private static void updateCriteriaRange(ref FilterCriteria.OptionalRange range, string op, double value, double tolerance = 0.05) + private static bool updateCriteriaRange(ref FilterCriteria.OptionalRange range, Operator op, double value, double tolerance = 0.05) { switch (op) { default: - return; + return false; - case "=": - case ":": + case Operator.Equal: range.Min = value - tolerance; range.Max = value + tolerance; break; - case ">": + case Operator.Greater: range.Min = value + tolerance; break; - case ">=": - case ">:": + case Operator.GreaterOrEqual: range.Min = value - tolerance; break; - case "<": + case Operator.Less: range.Max = value - tolerance; break; - case "<=": - case "<:": + case Operator.LessOrEqual: range.Max = value + tolerance; break; } + + return true; } - private static void updateCriteriaRange(ref FilterCriteria.OptionalRange range, string op, T value) + private static bool updateCriteriaRange(ref FilterCriteria.OptionalRange range, Operator op, T value) where T : struct { switch (op) { default: - return; + return false; - case "=": - case ":": + case Operator.Equal: range.IsLowerInclusive = range.IsUpperInclusive = true; range.Min = value; range.Max = value; break; - case ">": + case Operator.Greater: range.IsLowerInclusive = false; range.Min = value; break; - case ">=": - case ">:": + case Operator.GreaterOrEqual: range.IsLowerInclusive = true; range.Min = value; break; - case "<": + case Operator.Less: range.IsUpperInclusive = false; range.Max = value; break; - case "<=": - case "<:": + case Operator.LessOrEqual: range.IsUpperInclusive = true; range.Max = value; break; } + + return true; } } } From 14e249a13405e834a7ea90b32cc3e8246efc37be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 2 Mar 2021 20:07:11 +0100 Subject: [PATCH 128/390] Add ruleset interface for extending filter criteria --- .../Rulesets/Filter/IRulesetFilterCriteria.cs | 44 +++++++++++++++++++ osu.Game/Rulesets/Ruleset.cs | 7 +++ 2 files changed, 51 insertions(+) create mode 100644 osu.Game/Rulesets/Filter/IRulesetFilterCriteria.cs diff --git a/osu.Game/Rulesets/Filter/IRulesetFilterCriteria.cs b/osu.Game/Rulesets/Filter/IRulesetFilterCriteria.cs new file mode 100644 index 0000000000..a83f87d72b --- /dev/null +++ b/osu.Game/Rulesets/Filter/IRulesetFilterCriteria.cs @@ -0,0 +1,44 @@ +// 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.Beatmaps; +using osu.Game.Screens.Select; +using osu.Game.Screens.Select.Filter; + +namespace osu.Game.Rulesets.Filter +{ + /// + /// Allows for extending the beatmap filtering capabilities of song select (as implemented in ) + /// with ruleset-specific criteria. + /// + public interface IRulesetFilterCriteria + { + /// + /// Checks whether the supplied satisfies ruleset-specific custom criteria, + /// in addition to the ones mandated by song select. + /// + /// The beatmap to test the criteria against. + /// + /// true if the beatmap matches the ruleset-specific custom filtering criteria, + /// false otherwise. + /// + bool Matches(BeatmapInfo beatmap); + + /// + /// Attempts to parse a single custom keyword criterion, given by the user via the song select search box. + /// The format of the criterion is: + /// + /// {key}{op}{value} + /// + /// + /// The key (name) of the criterion. + /// The operator in the criterion. + /// The value of the criterion. + /// + /// true if the keyword criterion is valid, false if it has been ignored. + /// Valid criteria are stripped from , + /// while ignored criteria are included in . + /// + bool TryParseCustomKeywordCriteria(string key, Operator op, string value); + } +} diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index dbc2bd4d01..38d30a2e31 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -26,6 +26,7 @@ using JetBrains.Annotations; using osu.Framework.Extensions; using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Testing; +using osu.Game.Rulesets.Filter; using osu.Game.Screens.Ranking.Statistics; namespace osu.Game.Rulesets @@ -306,5 +307,11 @@ namespace osu.Game.Rulesets /// The result type to get the name for. /// The display name. public virtual string GetDisplayNameForHitResult(HitResult result) => result.GetDescription(); + + /// + /// Creates ruleset-specific beatmap filter criteria to be used on the song select screen. + /// + [CanBeNull] + public virtual IRulesetFilterCriteria CreateRulesetFilterCriteria() => null; } } From c375be6b07b7d4bc19d29683c61a9f4da6529d52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 2 Mar 2021 20:10:03 +0100 Subject: [PATCH 129/390] Instantiate ruleset criteria --- osu.Game/Screens/Select/FilterControl.cs | 8 ++++++++ osu.Game/Screens/Select/FilterCriteria.cs | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index eafd8a87d1..983928ac51 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -34,8 +35,13 @@ namespace osu.Game.Screens.Select private Bindable groupMode; + [Resolved] + private RulesetStore rulesets { get; set; } + public FilterCriteria CreateCriteria() { + Debug.Assert(ruleset.Value.ID != null); + var query = searchTextBox.Text; var criteria = new FilterCriteria @@ -53,6 +59,8 @@ namespace osu.Game.Screens.Select if (!maximumStars.IsDefault) criteria.UserStarDifficulty.Max = maximumStars.Value; + criteria.RulesetCriteria = rulesets.GetRuleset(ruleset.Value.ID.Value).CreateInstance().CreateRulesetFilterCriteria(); + FilterQueryParser.ApplyQueries(criteria, query); return criteria; } diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index 7bddb3e51b..208048380a 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -8,6 +8,7 @@ using JetBrains.Annotations; using osu.Game.Beatmaps; using osu.Game.Collections; using osu.Game.Rulesets; +using osu.Game.Rulesets.Filter; using osu.Game.Screens.Select.Filter; namespace osu.Game.Screens.Select @@ -69,6 +70,9 @@ namespace osu.Game.Screens.Select [CanBeNull] public BeatmapCollection Collection; + [CanBeNull] + public IRulesetFilterCriteria RulesetCriteria { get; set; } + public struct OptionalRange : IEquatable> where T : struct { From 42c3309d4918db8044c312d28f3efbc7422caae1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 2 Mar 2021 20:11:21 +0100 Subject: [PATCH 130/390] Use ruleset criteria in parsing and filtering --- osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs | 3 +++ osu.Game/Screens/Select/FilterQueryParser.cs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 1aab50037a..521b90202d 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -73,6 +73,9 @@ namespace osu.Game.Screens.Select.Carousel if (match) match &= criteria.Collection?.Beatmaps.Contains(Beatmap) ?? true; + if (match && criteria.RulesetCriteria != null) + match &= criteria.RulesetCriteria.Matches(Beatmap); + Filtered.Value = !match; } diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index d2d33b13f5..c81a72d938 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -67,7 +67,7 @@ namespace osu.Game.Screens.Select return updateCriteriaText(ref criteria.Artist, op, value); default: - return false; + return criteria.RulesetCriteria?.TryParseCustomKeywordCriteria(key, op, value) ?? false; } } From bf72f9ad1e988f14dbd8ca5b87f55c64b52d9c5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 2 Mar 2021 20:22:56 +0100 Subject: [PATCH 131/390] Add tests for custom parsing logic --- .../Filtering/FilterQueryParserTest.cs | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index e121cb835c..d835e58b29 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -4,7 +4,9 @@ using System; using NUnit.Framework; using osu.Game.Beatmaps; +using osu.Game.Rulesets.Filter; using osu.Game.Screens.Select; +using osu.Game.Screens.Select.Filter; namespace osu.Game.Tests.NonVisual.Filtering { @@ -203,5 +205,43 @@ namespace osu.Game.Tests.NonVisual.Filtering FilterQueryParser.ApplyQueries(filterCriteria, query); Assert.AreEqual("> true; + + public bool TryParseCustomKeywordCriteria(string key, Operator op, string value) + { + if (key == "custom" && op == Operator.Equal) + { + CustomValue = value; + return true; + } + + return false; + } + } } } From faf5fbf49b5a940b41ba3e7b1336fbefd868895a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 2 Mar 2021 20:27:50 +0100 Subject: [PATCH 132/390] Add tests for custom matching logic --- .../NonVisual/Filtering/FilterMatchingTest.cs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs index 24a0a662ba..8ff2743b6a 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs @@ -4,8 +4,10 @@ using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Rulesets; +using osu.Game.Rulesets.Filter; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Carousel; +using osu.Game.Screens.Select.Filter; namespace osu.Game.Tests.NonVisual.Filtering { @@ -214,5 +216,31 @@ namespace osu.Game.Tests.NonVisual.Filtering Assert.AreEqual(filtered, carouselItem.Filtered.Value); } + + [Test] + public void TestCustomRulesetCriteria([Values(null, true, false)] bool? matchCustomCriteria) + { + var beatmap = getExampleBeatmap(); + + var customCriteria = matchCustomCriteria is bool match ? new CustomCriteria(match) : null; + var criteria = new FilterCriteria { RulesetCriteria = customCriteria }; + var carouselItem = new CarouselBeatmap(beatmap); + carouselItem.Filter(criteria); + + Assert.AreEqual(matchCustomCriteria == false, carouselItem.Filtered.Value); + } + + private class CustomCriteria : IRulesetFilterCriteria + { + private readonly bool match; + + public CustomCriteria(bool shouldMatch) + { + match = shouldMatch; + } + + public bool Matches(BeatmapInfo beatmap) => match; + public bool TryParseCustomKeywordCriteria(string key, Operator op, string value) => false; + } } } From 6e75ebbb06fb6bf394e257bc44877b3f7171923f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Mar 2021 14:02:01 +0900 Subject: [PATCH 133/390] Add interface to handle local beatmap presentation logic --- osu.Game/Screens/IHandlePresentBeatmap.cs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 osu.Game/Screens/IHandlePresentBeatmap.cs diff --git a/osu.Game/Screens/IHandlePresentBeatmap.cs b/osu.Game/Screens/IHandlePresentBeatmap.cs new file mode 100644 index 0000000000..b94df630ef --- /dev/null +++ b/osu.Game/Screens/IHandlePresentBeatmap.cs @@ -0,0 +1,23 @@ +// 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.Beatmaps; +using osu.Game.Rulesets; + +namespace osu.Game.Screens +{ + /// + /// Denotes a screen which can handle beatmap / ruleset selection via local logic. + /// This is used in the flow to handle cases which require custom logic, + /// for instance, if a lease is held on the Beatmap. + /// + public interface IHandlePresentBeatmap + { + /// + /// Invoked with a requested beatmap / ruleset for selection. + /// + /// The beatmap to be selected. + /// The ruleset to be selected. + public void PresentBeatmap(WorkingBeatmap beatmap, RulesetInfo ruleset); + } +} From 36e1fb6da80a1416900d07ae9c69c9b12e8876ff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Mar 2021 14:04:00 +0900 Subject: [PATCH 134/390] Add flow to allow MatchSubScreen to handle beatmap presentation locally --- osu.Game/OsuGame.cs | 13 ++++++++++--- osu.Game/PerformFromMenuRunner.cs | 7 +------ .../Multiplayer/MultiplayerMatchSongSelect.cs | 19 +++++++++++++++++++ .../Multiplayer/MultiplayerMatchSubScreen.cs | 12 +++++++++++- 4 files changed, 41 insertions(+), 10 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 771bcd2310..1e0cb587e9 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -381,9 +381,16 @@ namespace osu.Game ?? beatmaps.FirstOrDefault(b => b.Ruleset.Equals(Ruleset.Value)) ?? beatmaps.First(); - Ruleset.Value = selection.Ruleset; - Beatmap.Value = BeatmapManager.GetWorkingBeatmap(selection); - }, validScreens: new[] { typeof(SongSelect) }); + if (screen is IHandlePresentBeatmap presentableScreen) + { + presentableScreen.PresentBeatmap(BeatmapManager.GetWorkingBeatmap(selection), selection.Ruleset); + } + else + { + Ruleset.Value = selection.Ruleset; + Beatmap.Value = BeatmapManager.GetWorkingBeatmap(selection); + } + }, validScreens: new[] { typeof(SongSelect), typeof(IHandlePresentBeatmap) }); } /// diff --git a/osu.Game/PerformFromMenuRunner.cs b/osu.Game/PerformFromMenuRunner.cs index fe75a3a607..6f979b8dc8 100644 --- a/osu.Game/PerformFromMenuRunner.cs +++ b/osu.Game/PerformFromMenuRunner.cs @@ -5,11 +5,9 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Screens; using osu.Framework.Threading; -using osu.Game.Beatmaps; using osu.Game.Overlays; using osu.Game.Overlays.Dialog; using osu.Game.Overlays.Notifications; @@ -30,9 +28,6 @@ namespace osu.Game [Resolved] private DialogOverlay dialogOverlay { get; set; } - [Resolved] - private IBindable beatmap { get; set; } - [Resolved(canBeNull: true)] private OsuGame game { get; set; } @@ -90,7 +85,7 @@ namespace osu.Game var type = current.GetType(); // check if we are already at a valid target screen. - if (validScreens.Any(t => t.IsAssignableFrom(type)) && !beatmap.Disabled) + if (validScreens.Any(t => t.IsAssignableFrom(type))) { finalAction(current); Cancel(); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index f17d97c3fd..c9f0f6de90 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -4,9 +4,11 @@ using osu.Framework.Allocation; 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; @@ -19,6 +21,23 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer private LoadingLayer loadingLayer; + /// + /// Construct a new instance of multiplayer song select. + /// + /// An optional initial beatmap selection to perform. + /// An optional initial ruleset selection to perform. + public MultiplayerMatchSongSelect(WorkingBeatmap beatmap = null, RulesetInfo ruleset = null) + { + if (beatmap != null || ruleset != null) + { + Schedule(() => + { + if (beatmap != null) Beatmap.Value = beatmap; + if (ruleset != null) Ruleset.Value = ruleset; + }); + } + } + [BackgroundDependencyLoader] private void load() { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 4fbea4e3be..06d83e495c 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -12,9 +12,11 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Framework.Threading; +using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; +using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Match; @@ -29,7 +31,7 @@ using ParticipantsList = osu.Game.Screens.OnlinePlay.Multiplayer.Participants.Pa namespace osu.Game.Screens.OnlinePlay.Multiplayer { [Cached] - public class MultiplayerMatchSubScreen : RoomSubScreen + public class MultiplayerMatchSubScreen : RoomSubScreen, IHandlePresentBeatmap { public override string Title { get; } @@ -394,5 +396,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer modSettingChangeTracker?.Dispose(); } + + public void PresentBeatmap(WorkingBeatmap beatmap, RulesetInfo ruleset) + { + if (!this.IsCurrentScreen()) + return; + + this.Push(new MultiplayerMatchSongSelect(beatmap, ruleset)); + } } } From fcea900a5327cc3d421c7332ed001026f6521388 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Mar 2021 14:06:39 +0900 Subject: [PATCH 135/390] Move main menu (song select) presentation logic to a local implementation Reduces cross-dependencies between OsuGame and MainMenu. --- osu.Game/OsuGame.cs | 4 ---- osu.Game/Screens/Menu/MainMenu.cs | 18 +++++++++++++----- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 1e0cb587e9..6f760a1aa7 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -361,10 +361,6 @@ namespace osu.Game PerformFromScreen(screen => { - // we might already be at song select, so a check is required before performing the load to solo. - if (screen is MainMenu) - menuScreen.LoadToSolo(); - // we might even already be at the song if (Beatmap.Value.BeatmapSetInfo.Hash == databasedSet.Hash && (difficultyCriteria?.Invoke(Beatmap.Value.BeatmapInfo) ?? true)) return; diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 424e6d2cd5..baeb86c976 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -9,12 +9,14 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Platform; using osu.Framework.Screens; +using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.IO; using osu.Game.Online.API; using osu.Game.Overlays; +using osu.Game.Rulesets; using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Edit; using osu.Game.Screens.OnlinePlay.Multiplayer; @@ -23,7 +25,7 @@ using osu.Game.Screens.Select; namespace osu.Game.Screens.Menu { - public class MainMenu : OsuScreen + public class MainMenu : OsuScreen, IHandlePresentBeatmap { public const float FADE_IN_DURATION = 300; @@ -104,7 +106,7 @@ namespace osu.Game.Screens.Menu Beatmap.SetDefault(); this.Push(new Editor()); }, - OnSolo = onSolo, + OnSolo = loadSoloSongSelect, OnMultiplayer = () => this.Push(new Multiplayer()), OnPlaylists = () => this.Push(new Playlists()), OnExit = confirmAndExit, @@ -160,9 +162,7 @@ namespace osu.Game.Screens.Menu LoadComponentAsync(songSelect = new PlaySongSelect()); } - public void LoadToSolo() => Schedule(onSolo); - - private void onSolo() => this.Push(consumeSongSelect()); + private void loadSoloSongSelect() => this.Push(consumeSongSelect()); private Screen consumeSongSelect() { @@ -289,5 +289,13 @@ namespace osu.Game.Screens.Menu this.FadeOut(3000); return base.OnExiting(next); } + + public void PresentBeatmap(WorkingBeatmap beatmap, RulesetInfo ruleset) + { + Beatmap.Value = beatmap; + Ruleset.Value = ruleset; + + Schedule(loadSoloSongSelect); + } } } From 7c5904008247a113525396f9c4e1603ef470a464 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Mar 2021 14:17:06 +0900 Subject: [PATCH 136/390] Re-present even when already the current beatmap This feels better and closer to what a user would expect. --- osu.Game/OsuGame.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 6f760a1aa7..7db85d0d66 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -361,10 +361,6 @@ namespace osu.Game PerformFromScreen(screen => { - // we might even already be at the song - if (Beatmap.Value.BeatmapSetInfo.Hash == databasedSet.Hash && (difficultyCriteria?.Invoke(Beatmap.Value.BeatmapInfo) ?? true)) - return; - // Find beatmaps that match our predicate. var beatmaps = databasedSet.Beatmaps.Where(b => difficultyCriteria?.Invoke(b) ?? true).ToList(); From 7dce9b04fa0a7870d7576c803c8130a095124484 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Mar 2021 14:50:45 +0900 Subject: [PATCH 137/390] Add a more basic ConfirmDialog implementation --- osu.Game/Overlays/Dialog/ConfirmDialog.cs | 45 ++++++++++++++++++++++ osu.Game/Screens/Menu/ConfirmExitDialog.cs | 26 +++---------- 2 files changed, 50 insertions(+), 21 deletions(-) create mode 100644 osu.Game/Overlays/Dialog/ConfirmDialog.cs diff --git a/osu.Game/Overlays/Dialog/ConfirmDialog.cs b/osu.Game/Overlays/Dialog/ConfirmDialog.cs new file mode 100644 index 0000000000..6f160daf97 --- /dev/null +++ b/osu.Game/Overlays/Dialog/ConfirmDialog.cs @@ -0,0 +1,45 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Graphics.Sprites; + +namespace osu.Game.Overlays.Dialog +{ + /// + /// A dialog which confirms a user action. + /// + public class ConfirmDialog : PopupDialog + { + protected PopupDialogOkButton ButtonConfirm; + protected PopupDialogCancelButton ButtonCancel; + + /// + /// Construct a new dialog. + /// + /// The description of the action to be displayed to the user. + /// An action to perform on confirmation. + /// An optional action to perform on cancel. + public ConfirmDialog(string description, Action onConfirm, Action onCancel = null) + { + HeaderText = $"Are you sure you want to {description}?"; + BodyText = "Last chance to back out."; + + Icon = FontAwesome.Solid.ExclamationTriangle; + + Buttons = new PopupDialogButton[] + { + ButtonConfirm = new PopupDialogOkButton + { + Text = @"Yes", + Action = onConfirm + }, + ButtonCancel = new PopupDialogCancelButton + { + Text = @"Cancel", + Action = onCancel + }, + }; + } + } +} diff --git a/osu.Game/Screens/Menu/ConfirmExitDialog.cs b/osu.Game/Screens/Menu/ConfirmExitDialog.cs index d120eb21a8..41cc7b480c 100644 --- a/osu.Game/Screens/Menu/ConfirmExitDialog.cs +++ b/osu.Game/Screens/Menu/ConfirmExitDialog.cs @@ -2,33 +2,17 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Graphics.Sprites; using osu.Game.Overlays.Dialog; namespace osu.Game.Screens.Menu { - public class ConfirmExitDialog : PopupDialog + public class ConfirmExitDialog : ConfirmDialog { - public ConfirmExitDialog(Action confirm, Action cancel) + public ConfirmExitDialog(Action confirm, Action onCancel = null) + : base("exit osu!", confirm, onCancel) { - HeaderText = "Are you sure you want to exit?"; - BodyText = "Last chance to back out."; - - Icon = FontAwesome.Solid.ExclamationTriangle; - - Buttons = new PopupDialogButton[] - { - new PopupDialogOkButton - { - Text = @"Goodbye", - Action = confirm - }, - new PopupDialogCancelButton - { - Text = @"Just a little more", - Action = cancel - }, - }; + ButtonConfirm.Text = "Let me out!"; + ButtonCancel.Text = "Just a little more..."; } } } From d332fd2414c131a363437e1c71fb27047eaa48c1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Mar 2021 14:53:47 +0900 Subject: [PATCH 138/390] Handle case where local user tries to change beatmap while not the host --- .../OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 06d83e495c..e09e1fc3d4 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -402,6 +402,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer if (!this.IsCurrentScreen()) return; + if (!client.IsHost) + { + // todo: should handle this when the request queue is implemented. + // if we decide that the presentation should exit the user from the multiplayer game, the PresentBeatmap + // flow may need to change to support an "unable to present" return value. + return; + } + this.Push(new MultiplayerMatchSongSelect(beatmap, ruleset)); } } From cb4c3503a01da92d0a68222702fa3b958da05fe1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Mar 2021 14:50:54 +0900 Subject: [PATCH 139/390] Confirm exiting a multiplayer match --- .../Multiplayer/MultiplayerMatchSubScreen.cs | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 4fbea4e3be..51445b0668 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -15,6 +15,8 @@ using osu.Framework.Threading; using osu.Game.Configuration; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; +using osu.Game.Overlays; +using osu.Game.Overlays.Dialog; using osu.Game.Rulesets.Mods; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Match; @@ -279,14 +281,36 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer Mods.Value = client.LocalUser.Mods.Select(m => m.ToMod(ruleset)).Concat(SelectedItem.Value.RequiredMods).ToList(); } + [Resolved] + private DialogOverlay dialogOverlay { get; set; } + + private bool exitConfirmed; + public override bool OnBackButton() { - if (client.Room != null && settingsOverlay.State.Value == Visibility.Visible) + if (client.Room == null) + { + // room has not been created yet; exit immediately. + return base.OnBackButton(); + } + + if (settingsOverlay.State.Value == Visibility.Visible) { settingsOverlay.Hide(); return true; } + if (!exitConfirmed) + { + dialogOverlay.Push(new ConfirmDialog("leave this multiplayer match", () => + { + exitConfirmed = true; + this.Exit(); + })); + + return true; + } + return base.OnBackButton(); } From 0ede28da2f0f79b11cf2355cda4264f4d686ad12 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Mar 2021 15:24:55 +0900 Subject: [PATCH 140/390] Fix test failures due to missing dependency --- .../OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 51445b0668..f1d8bf97fd 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -281,7 +281,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer Mods.Value = client.LocalUser.Mods.Select(m => m.ToMod(ruleset)).Concat(SelectedItem.Value.RequiredMods).ToList(); } - [Resolved] + [Resolved(canBeNull: true)] private DialogOverlay dialogOverlay { get; set; } private bool exitConfirmed; @@ -300,7 +300,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer return true; } - if (!exitConfirmed) + if (!exitConfirmed && dialogOverlay != null) { dialogOverlay.Push(new ConfirmDialog("leave this multiplayer match", () => { From 002646370ccd589f94bf1d6947982961393003e6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Mar 2021 16:47:42 +0900 Subject: [PATCH 141/390] Move bindable logic in MouseSettings to LoadComplete --- .../Settings/Sections/Input/MouseSettings.cs | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs index 768a18cca0..7599a748ab 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs @@ -17,7 +17,11 @@ namespace osu.Game.Overlays.Settings.Sections.Input protected override string Header => "Mouse"; private readonly BindableBool rawInputToggle = new BindableBool(); - private Bindable sensitivityBindable = new BindableDouble(); + + private Bindable configSensitivity; + + private Bindable localSensitivity = new BindableDouble(); + private Bindable ignoredInputHandlers; private Bindable windowMode; @@ -26,12 +30,12 @@ namespace osu.Game.Overlays.Settings.Sections.Input [BackgroundDependencyLoader] private void load(OsuConfigManager osuConfig, FrameworkConfigManager config) { - var configSensitivity = config.GetBindable(FrameworkSetting.CursorSensitivity); - // use local bindable to avoid changing enabled state of game host's bindable. - sensitivityBindable = configSensitivity.GetUnboundCopy(); - configSensitivity.BindValueChanged(val => sensitivityBindable.Value = val.NewValue); - sensitivityBindable.BindValueChanged(val => configSensitivity.Value = val.NewValue); + configSensitivity = config.GetBindable(FrameworkSetting.CursorSensitivity); + localSensitivity = configSensitivity.GetUnboundCopy(); + + windowMode = config.GetBindable(FrameworkSetting.WindowMode); + ignoredInputHandlers = config.GetBindable(FrameworkSetting.IgnoredInputHandlers); Children = new Drawable[] { @@ -43,7 +47,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input new SensitivitySetting { LabelText = "Cursor sensitivity", - Current = sensitivityBindable + Current = localSensitivity }, new SettingsCheckbox { @@ -66,8 +70,15 @@ namespace osu.Game.Overlays.Settings.Sections.Input Current = osuConfig.GetBindable(OsuSetting.MouseDisableButtons) }, }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + configSensitivity.BindValueChanged(val => localSensitivity.Value = val.NewValue, true); + localSensitivity.BindValueChanged(val => configSensitivity.Value = val.NewValue); - windowMode = config.GetBindable(FrameworkSetting.WindowMode); windowMode.BindValueChanged(mode => { var isFullscreen = mode.NewValue == WindowMode.Fullscreen; @@ -87,7 +98,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows) { rawInputToggle.Disabled = true; - sensitivityBindable.Disabled = true; + localSensitivity.Disabled = true; } else { @@ -100,12 +111,11 @@ namespace osu.Game.Overlays.Settings.Sections.Input ignoredInputHandlers.Value = enabled.NewValue ? standard_mouse_handlers : raw_mouse_handler; }; - ignoredInputHandlers = config.GetBindable(FrameworkSetting.IgnoredInputHandlers); ignoredInputHandlers.ValueChanged += handler => { bool raw = !handler.NewValue.Contains("Raw"); rawInputToggle.Value = raw; - sensitivityBindable.Disabled = !raw; + localSensitivity.Disabled = !raw; }; ignoredInputHandlers.TriggerChange(); From 012b48dbe51e972b291f37f88dfe0788cd9adb84 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 3 Mar 2021 19:03:44 +0900 Subject: [PATCH 142/390] Remove explicit public definition Interface members are public by default. --- osu.Game/Screens/IHandlePresentBeatmap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/IHandlePresentBeatmap.cs b/osu.Game/Screens/IHandlePresentBeatmap.cs index b94df630ef..60801fb3eb 100644 --- a/osu.Game/Screens/IHandlePresentBeatmap.cs +++ b/osu.Game/Screens/IHandlePresentBeatmap.cs @@ -18,6 +18,6 @@ namespace osu.Game.Screens /// /// The beatmap to be selected. /// The ruleset to be selected. - public void PresentBeatmap(WorkingBeatmap beatmap, RulesetInfo ruleset); + void PresentBeatmap(WorkingBeatmap beatmap, RulesetInfo ruleset); } } From 6affe33fb275acb9d3feee55d08433f88fb1e25a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 3 Mar 2021 19:40:19 +0900 Subject: [PATCH 143/390] Fix another test scene --- .../TestSceneMultiplayerRoomManager.cs | 46 +++++++++++++------ .../Multiplayer/TestMultiplayerClient.cs | 14 ++++-- .../TestMultiplayerRoomContainer.cs | 10 ++-- .../Multiplayer/TestMultiplayerRoomManager.cs | 8 ++-- 4 files changed, 52 insertions(+), 26 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs index 6de5704410..91c15de69f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs @@ -1,10 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Online.Rooms; +using osu.Game.Screens.OnlinePlay.Components; +using osu.Game.Tests.Beatmaps; namespace osu.Game.Tests.Visual.Multiplayer { @@ -21,15 +24,15 @@ namespace osu.Game.Tests.Visual.Multiplayer { createRoomManager().With(d => d.OnLoadComplete += _ => { - roomManager.CreateRoom(new Room { Name = { Value = "1" } }); + roomManager.CreateRoom(createRoom(r => r.Name.Value = "1")); roomManager.PartRoom(); - roomManager.CreateRoom(new Room { Name = { Value = "2" } }); + roomManager.CreateRoom(createRoom(r => r.Name.Value = "2")); roomManager.PartRoom(); roomManager.ClearRooms(); }); }); - AddAssert("manager polled for rooms", () => roomManager.Rooms.Count == 2); + AddAssert("manager polled for rooms", () => ((RoomManager)roomManager).Rooms.Count == 2); AddAssert("initial rooms received", () => roomManager.InitialRoomsReceived.Value); } @@ -40,16 +43,16 @@ namespace osu.Game.Tests.Visual.Multiplayer { createRoomManager().With(d => d.OnLoadComplete += _ => { - roomManager.CreateRoom(new Room()); + roomManager.CreateRoom(createRoom()); roomManager.PartRoom(); - roomManager.CreateRoom(new Room()); + roomManager.CreateRoom(createRoom()); roomManager.PartRoom(); }); }); AddStep("disconnect", () => roomContainer.Client.Disconnect()); - AddAssert("rooms cleared", () => roomManager.Rooms.Count == 0); + AddAssert("rooms cleared", () => ((RoomManager)roomManager).Rooms.Count == 0); AddAssert("initial rooms not received", () => !roomManager.InitialRoomsReceived.Value); } @@ -60,9 +63,9 @@ namespace osu.Game.Tests.Visual.Multiplayer { createRoomManager().With(d => d.OnLoadComplete += _ => { - roomManager.CreateRoom(new Room()); + roomManager.CreateRoom(createRoom()); roomManager.PartRoom(); - roomManager.CreateRoom(new Room()); + roomManager.CreateRoom(createRoom()); roomManager.PartRoom(); }); }); @@ -70,7 +73,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("disconnect", () => roomContainer.Client.Disconnect()); AddStep("connect", () => roomContainer.Client.Connect()); - AddAssert("manager polled for rooms", () => roomManager.Rooms.Count == 2); + AddAssert("manager polled for rooms", () => ((RoomManager)roomManager).Rooms.Count == 2); AddAssert("initial rooms received", () => roomManager.InitialRoomsReceived.Value); } @@ -81,12 +84,12 @@ namespace osu.Game.Tests.Visual.Multiplayer { createRoomManager().With(d => d.OnLoadComplete += _ => { - roomManager.CreateRoom(new Room()); + roomManager.CreateRoom(createRoom()); roomManager.ClearRooms(); }); }); - AddAssert("manager not polled for rooms", () => roomManager.Rooms.Count == 0); + AddAssert("manager not polled for rooms", () => ((RoomManager)roomManager).Rooms.Count == 0); AddAssert("initial rooms not received", () => !roomManager.InitialRoomsReceived.Value); } @@ -97,7 +100,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { createRoomManager().With(d => d.OnLoadComplete += _ => { - roomManager.CreateRoom(new Room()); + roomManager.CreateRoom(createRoom()); }); }); @@ -111,7 +114,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { createRoomManager().With(d => d.OnLoadComplete += _ => { - roomManager.CreateRoom(new Room()); + roomManager.CreateRoom(createRoom()); roomManager.PartRoom(); }); }); @@ -126,7 +129,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { createRoomManager().With(d => d.OnLoadComplete += _ => { - var r = new Room(); + var r = createRoom(); roomManager.CreateRoom(r); roomManager.PartRoom(); roomManager.JoinRoom(r); @@ -136,6 +139,21 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("multiplayer room joined", () => roomContainer.Client.Room != null); } + private Room createRoom(Action initFunc = null) + { + var room = new Room(); + + room.Name.Value = "test room"; + room.Playlist.Add(new PlaylistItem + { + Beatmap = { Value = new TestBeatmap(Ruleset.Value).BeatmapInfo }, + Ruleset = { Value = Ruleset.Value } + }); + + initFunc?.Invoke(room); + return room; + } + private TestMultiplayerRoomManager createRoomManager() { Child = roomContainer = new TestMultiplayerRoomContainer diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 6a901fc45b..c03364a391 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -28,12 +28,16 @@ namespace osu.Game.Tests.Visual.Multiplayer [Resolved] private IAPIProvider api { get; set; } = null!; - [Resolved] - private Room apiRoom { get; set; } = null!; - [Resolved] private BeatmapManager beatmaps { get; set; } = null!; + private readonly TestMultiplayerRoomManager roomManager; + + public TestMultiplayerClient(TestMultiplayerRoomManager roomManager) + { + this.roomManager = roomManager; + } + public void Connect() => isConnected.Value = true; public void Disconnect() => isConnected.Value = false; @@ -98,7 +102,7 @@ namespace osu.Game.Tests.Visual.Multiplayer protected override Task JoinRoom(long roomId) { - Debug.Assert(apiRoom != null); + var apiRoom = roomManager.Rooms.Single(r => r.RoomID.Value == roomId); var user = new MultiplayerRoomUser(api.LocalUser.Value.Id) { @@ -178,8 +182,8 @@ namespace osu.Game.Tests.Visual.Multiplayer protected override Task GetOnlineBeatmapSet(int beatmapId, CancellationToken cancellationToken = default) { Debug.Assert(Room != null); - Debug.Assert(apiRoom != null); + var apiRoom = roomManager.Rooms.Single(r => r.RoomID.Value == Room.RoomID); var set = apiRoom.Playlist.FirstOrDefault(p => p.BeatmapID == beatmapId)?.Beatmap.Value.BeatmapSet ?? beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == beatmapId)?.BeatmapSet; diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs index 860caef071..e57411d04d 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs @@ -32,11 +32,15 @@ namespace osu.Game.Tests.Visual.Multiplayer { RelativeSizeAxes = Axes.Both; + RoomManager = new TestMultiplayerRoomManager(); + Client = new TestMultiplayerClient(RoomManager); + OngoingOperationTracker = new OngoingOperationTracker(); + AddRangeInternal(new Drawable[] { - Client = new TestMultiplayerClient(), - RoomManager = new TestMultiplayerRoomManager(), - OngoingOperationTracker = new OngoingOperationTracker(), + Client, + RoomManager, + OngoingOperationTracker, content = new Container { RelativeSizeAxes = Axes.Both } }); } diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs index 022c297ccd..7e824c4d7c 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs @@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Cached] public readonly Bindable Filter = new Bindable(new FilterCriteria()); - private readonly List rooms = new List(); + public new readonly List Rooms = new List(); protected override void LoadComplete() { @@ -50,7 +50,7 @@ namespace osu.Game.Tests.Visual.Multiplayer for (int i = 0; i < createdRoom.Playlist.Count; i++) createdRoom.Playlist[i].ID = currentPlaylistItemId++; - rooms.Add(createdRoom); + Rooms.Add(createdRoom); createRoomRequest.TriggerSuccess(createdRoom); break; @@ -65,7 +65,7 @@ namespace osu.Game.Tests.Visual.Multiplayer case GetRoomsRequest getRoomsRequest: var roomsWithoutParticipants = new List(); - foreach (var r in rooms) + foreach (var r in Rooms) { var newRoom = new Room(); @@ -79,7 +79,7 @@ namespace osu.Game.Tests.Visual.Multiplayer break; case GetRoomRequest getRoomRequest: - getRoomRequest.TriggerSuccess(rooms.Single(r => r.RoomID.Value == getRoomRequest.RoomId)); + getRoomRequest.TriggerSuccess(Rooms.Single(r => r.RoomID.Value == getRoomRequest.RoomId)); break; case GetBeatmapSetRequest getBeatmapSetRequest: From 0f5bce70ad4eec310f54113341e747e552b2a4e4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Mar 2021 20:34:36 +0900 Subject: [PATCH 144/390] Split confirmation dialog classes apart --- osu.Game/Overlays/Dialog/ConfirmDialog.cs | 17 +++++----- osu.Game/Screens/Menu/ConfirmExitDialog.cs | 31 ++++++++++++++++--- .../Multiplayer/MultiplayerMatchSubScreen.cs | 2 +- 3 files changed, 34 insertions(+), 16 deletions(-) diff --git a/osu.Game/Overlays/Dialog/ConfirmDialog.cs b/osu.Game/Overlays/Dialog/ConfirmDialog.cs index 6f160daf97..a87c06ffdf 100644 --- a/osu.Game/Overlays/Dialog/ConfirmDialog.cs +++ b/osu.Game/Overlays/Dialog/ConfirmDialog.cs @@ -11,30 +11,27 @@ namespace osu.Game.Overlays.Dialog /// public class ConfirmDialog : PopupDialog { - protected PopupDialogOkButton ButtonConfirm; - protected PopupDialogCancelButton ButtonCancel; - /// - /// Construct a new dialog. + /// Construct a new confirmation dialog. /// - /// The description of the action to be displayed to the user. + /// The description of the action to be displayed to the user. /// An action to perform on confirmation. /// An optional action to perform on cancel. - public ConfirmDialog(string description, Action onConfirm, Action onCancel = null) + public ConfirmDialog(string message, Action onConfirm, Action onCancel = null) { - HeaderText = $"Are you sure you want to {description}?"; - BodyText = "Last chance to back out."; + HeaderText = message; + BodyText = "Last chance to turn back"; Icon = FontAwesome.Solid.ExclamationTriangle; Buttons = new PopupDialogButton[] { - ButtonConfirm = new PopupDialogOkButton + new PopupDialogOkButton { Text = @"Yes", Action = onConfirm }, - ButtonCancel = new PopupDialogCancelButton + new PopupDialogCancelButton { Text = @"Cancel", Action = onCancel diff --git a/osu.Game/Screens/Menu/ConfirmExitDialog.cs b/osu.Game/Screens/Menu/ConfirmExitDialog.cs index 41cc7b480c..6488a2fd63 100644 --- a/osu.Game/Screens/Menu/ConfirmExitDialog.cs +++ b/osu.Game/Screens/Menu/ConfirmExitDialog.cs @@ -2,17 +2,38 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Graphics.Sprites; using osu.Game.Overlays.Dialog; namespace osu.Game.Screens.Menu { - public class ConfirmExitDialog : ConfirmDialog + public class ConfirmExitDialog : PopupDialog { - public ConfirmExitDialog(Action confirm, Action onCancel = null) - : base("exit osu!", confirm, onCancel) + /// + /// Construct a new exit confirmation dialog. + /// + /// An action to perform on confirmation. + /// An optional action to perform on cancel. + public ConfirmExitDialog(Action onConfirm, Action onCancel = null) { - ButtonConfirm.Text = "Let me out!"; - ButtonCancel.Text = "Just a little more..."; + HeaderText = "Are you sure you want to exit osu!?"; + BodyText = "Last chance to turn back"; + + Icon = FontAwesome.Solid.ExclamationTriangle; + + Buttons = new PopupDialogButton[] + { + new PopupDialogOkButton + { + Text = @"Let me out!", + Action = onConfirm + }, + new PopupDialogCancelButton + { + Text = @"Just a little more...", + Action = onCancel + }, + }; } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index f1d8bf97fd..5a9a26d997 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -302,7 +302,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer if (!exitConfirmed && dialogOverlay != null) { - dialogOverlay.Push(new ConfirmDialog("leave this multiplayer match", () => + dialogOverlay.Push(new ConfirmDialog("Are you sure you want to leave this multiplayer match?", () => { exitConfirmed = true; this.Exit(); From 534e16237a5e74dd448af1d37e72d580393e5ba3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Mar 2021 20:36:41 +0900 Subject: [PATCH 145/390] Remove unnecessary intial construction of bindable --- osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs index 7599a748ab..c3deb385cd 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs @@ -20,7 +20,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input private Bindable configSensitivity; - private Bindable localSensitivity = new BindableDouble(); + private Bindable localSensitivity; private Bindable ignoredInputHandlers; From 1ecb1d122a55500204eaea01d2321a1a9c71c707 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 3 Mar 2021 21:54:34 +0900 Subject: [PATCH 146/390] Fix up TestSceneMultiplayer --- .../Multiplayer/TestSceneMultiplayer.cs | 36 ++++++------------- 1 file changed, 11 insertions(+), 25 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 2e39471dc0..bb5db5b803 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -1,13 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Game.Online.Multiplayer; using osu.Game.Screens.OnlinePlay.Components; -using osu.Game.Users; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneMultiplayer : MultiplayerTestScene + public class TestSceneMultiplayer : ScreenTestScene { public TestSceneMultiplayer() { @@ -17,30 +17,16 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for loaded", () => multi.IsLoaded); } - [Test] - public void TestOneUserJoinedMultipleTimes() - { - var user = new User { Id = 33 }; - - AddRepeatStep("add user multiple times", () => Client.AddUser(user), 3); - - AddAssert("room has 2 users", () => Client.Room?.Users.Count == 2); - } - - [Test] - public void TestOneUserLeftMultipleTimes() - { - var user = new User { Id = 44 }; - - AddStep("add user", () => Client.AddUser(user)); - AddAssert("room has 2 users", () => Client.Room?.Users.Count == 2); - - AddRepeatStep("remove user multiple times", () => Client.RemoveUser(user), 3); - AddAssert("room has 1 user", () => Client.Room?.Users.Count == 1); - } - private class TestMultiplayer : Screens.OnlinePlay.Multiplayer.Multiplayer { + [Cached(typeof(StatefulMultiplayerClient))] + public readonly TestMultiplayerClient Client; + + public TestMultiplayer() + { + AddInternal(Client = new TestMultiplayerClient((TestMultiplayerRoomManager)RoomManager)); + } + protected override RoomManager CreateRoomManager() => new TestMultiplayerRoomManager(); } } From 0f83b66cdabb1aad42d7f9d1c205b38089d7b2c2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 3 Mar 2021 22:01:03 +0900 Subject: [PATCH 147/390] Add separate test for stateful multiplayer client --- .../StatefulMultiplayerClientTest.cs | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 osu.Game.Tests/OnlinePlay/StatefulMultiplayerClientTest.cs diff --git a/osu.Game.Tests/OnlinePlay/StatefulMultiplayerClientTest.cs b/osu.Game.Tests/OnlinePlay/StatefulMultiplayerClientTest.cs new file mode 100644 index 0000000000..82ce588c6f --- /dev/null +++ b/osu.Game.Tests/OnlinePlay/StatefulMultiplayerClientTest.cs @@ -0,0 +1,35 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Testing; +using osu.Game.Tests.Visual.Multiplayer; +using osu.Game.Users; + +namespace osu.Game.Tests.OnlinePlay +{ + [HeadlessTest] + public class StatefulMultiplayerClientTest : MultiplayerTestScene + { + [Test] + public void TestUserAddedOnJoin() + { + var user = new User { Id = 33 }; + + AddRepeatStep("add user multiple times", () => Client.AddUser(user), 3); + AddAssert("room has 2 users", () => Client.Room?.Users.Count == 2); + } + + [Test] + public void TestUserRemovedOnLeave() + { + var user = new User { Id = 44 }; + + AddStep("add user", () => Client.AddUser(user)); + AddAssert("room has 2 users", () => Client.Room?.Users.Count == 2); + + AddRepeatStep("remove user multiple times", () => Client.RemoveUser(user), 3); + AddAssert("room has 1 user", () => Client.Room?.Users.Count == 1); + } + } +} From 77607c06eba37de48cb0670a4e7e09d9a9c8e4ae Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 3 Mar 2021 22:07:39 +0900 Subject: [PATCH 148/390] Fix not being able to enter gameplay in TestSceneMultiplayer --- .../Visual/Multiplayer/TestSceneMultiplayer.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index bb5db5b803..78bc51e47b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -9,12 +9,21 @@ namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneMultiplayer : ScreenTestScene { + private TestMultiplayer multiplayerScreen; + public TestSceneMultiplayer() { - var multi = new TestMultiplayer(); + AddStep("show", () => + { + multiplayerScreen = new TestMultiplayer(); - AddStep("show", () => LoadScreen(multi)); - AddUntilStep("wait for loaded", () => multi.IsLoaded); + // Needs to be added at a higher level since the multiplayer screen becomes non-current. + Child = multiplayerScreen.Client; + + LoadScreen(multiplayerScreen); + }); + + AddUntilStep("wait for loaded", () => multiplayerScreen.IsLoaded); } private class TestMultiplayer : Screens.OnlinePlay.Multiplayer.Multiplayer @@ -24,7 +33,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public TestMultiplayer() { - AddInternal(Client = new TestMultiplayerClient((TestMultiplayerRoomManager)RoomManager)); + Client = new TestMultiplayerClient((TestMultiplayerRoomManager)RoomManager); } protected override RoomManager CreateRoomManager() => new TestMultiplayerRoomManager(); From f9148eec206b1126ed0af01f24610d2eca2ab00d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 3 Mar 2021 21:33:41 +0100 Subject: [PATCH 149/390] Refactor filter query parsing helper methods In preparation for exposition as public. --- .../Filtering/FilterQueryParserTest.cs | 11 +++ osu.Game/Screens/Select/FilterQueryParser.cs | 75 ++++++++++++------- 2 files changed, 59 insertions(+), 27 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index d835e58b29..49389e67aa 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -215,6 +215,17 @@ namespace osu.Game.Tests.NonVisual.Filtering Assert.AreEqual("unrecognised=keyword", filterCriteria.SearchText); } + [TestCase("cs=nope")] + [TestCase("bpm>=bad")] + [TestCase("divisor(value, true, out var statusValue): - return updateCriteriaRange(ref criteria.OnlineStatus, op, statusValue); + case "status": + return tryUpdateCriteriaRange(ref criteria.OnlineStatus, op, value, + (string s, out BeatmapSetOnlineStatus val) => Enum.TryParse(value, true, out val)); case "creator": - return updateCriteriaText(ref criteria.Creator, op, value); + return tryUpdateCriteriaText(ref criteria.Creator, op, value); case "artist": - return updateCriteriaText(ref criteria.Artist, op, value); + return tryUpdateCriteriaText(ref criteria.Artist, op, value); default: return criteria.RulesetCriteria?.TryParseCustomKeywordCriteria(key, op, value) ?? false; @@ -104,16 +104,16 @@ namespace osu.Game.Screens.Select value.EndsWith('m') ? 60000 : value.EndsWith('h') ? 3600000 : 1000; - private static bool parseFloatWithPoint(string value, out float result) => + private static bool tryParseFloatWithPoint(string value, out float result) => float.TryParse(value, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out result); - private static bool parseDoubleWithPoint(string value, out double result) => + private static bool tryParseDoubleWithPoint(string value, out double result) => double.TryParse(value, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out result); - private static bool parseInt(string value, out int result) => + private static bool tryParseInt(string value, out int result) => int.TryParse(value, NumberStyles.None, CultureInfo.InvariantCulture, out result); - private static bool updateCriteriaText(ref FilterCriteria.OptionalTextFilter textFilter, Operator op, string value) + private static bool tryUpdateCriteriaText(ref FilterCriteria.OptionalTextFilter textFilter, Operator op, string value) { switch (op) { @@ -126,7 +126,10 @@ namespace osu.Game.Screens.Select } } - private static bool updateCriteriaRange(ref FilterCriteria.OptionalRange range, Operator op, float value, float tolerance = 0.05f) + private static bool tryUpdateCriteriaRange(ref FilterCriteria.OptionalRange range, Operator op, string val, float tolerance = 0.05f) + => tryParseFloatWithPoint(val, out float value) && tryUpdateCriteriaRange(ref range, op, value, tolerance); + + private static bool tryUpdateCriteriaRange(ref FilterCriteria.OptionalRange range, Operator op, float value, float tolerance = 0.05f) { switch (op) { @@ -158,7 +161,10 @@ namespace osu.Game.Screens.Select return true; } - private static bool updateCriteriaRange(ref FilterCriteria.OptionalRange range, Operator op, double value, double tolerance = 0.05) + private static bool tryUpdateCriteriaRange(ref FilterCriteria.OptionalRange range, Operator op, string val, double tolerance = 0.05) + => tryParseDoubleWithPoint(val, out double value) && tryUpdateCriteriaRange(ref range, op, value, tolerance); + + private static bool tryUpdateCriteriaRange(ref FilterCriteria.OptionalRange range, Operator op, double value, double tolerance = 0.05) { switch (op) { @@ -190,7 +196,13 @@ namespace osu.Game.Screens.Select return true; } - private static bool updateCriteriaRange(ref FilterCriteria.OptionalRange range, Operator op, T value) + private delegate bool TryParseFunction(string val, out T value); + + private static bool tryUpdateCriteriaRange(ref FilterCriteria.OptionalRange range, Operator op, string value, TryParseFunction conversionFunc) + where T : struct + => conversionFunc.Invoke(value, out var converted) && tryUpdateCriteriaRange(ref range, op, converted); + + private static bool tryUpdateCriteriaRange(ref FilterCriteria.OptionalRange range, Operator op, T value) where T : struct { switch (op) @@ -227,5 +239,14 @@ namespace osu.Game.Screens.Select return true; } + + private static bool tryUpdateLengthRange(FilterCriteria criteria, Operator op, string val) + { + if (!tryParseDoubleWithPoint(val.TrimEnd('m', 's', 'h'), out var length)) + return false; + + var scale = getLengthScale(val); + return tryUpdateCriteriaRange(ref criteria.Length, op, length * scale, scale / 2.0); + } } } From f733d1ec1fcf08a147d24aeab20d3e8187936c71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 3 Mar 2021 21:58:34 +0100 Subject: [PATCH 150/390] Expose and document query parser and helpers --- .../Rulesets/Filter/IRulesetFilterCriteria.cs | 11 +++ osu.Game/Screens/Select/FilterQueryParser.cs | 89 +++++++++++++++---- 2 files changed, 84 insertions(+), 16 deletions(-) diff --git a/osu.Game/Rulesets/Filter/IRulesetFilterCriteria.cs b/osu.Game/Rulesets/Filter/IRulesetFilterCriteria.cs index a83f87d72b..13cc41f8e0 100644 --- a/osu.Game/Rulesets/Filter/IRulesetFilterCriteria.cs +++ b/osu.Game/Rulesets/Filter/IRulesetFilterCriteria.cs @@ -31,6 +31,17 @@ namespace osu.Game.Rulesets.Filter /// {key}{op}{value} /// /// + /// + /// + /// For adding optional string criteria, can be used for matching, + /// along with for parsing. + /// + /// + /// For adding numerical-type range criteria, can be used for matching, + /// along with + /// and - and -typed overloads for parsing. + /// + /// /// The key (name) of the criterion. /// The operator in the criterion. /// The value of the criterion. diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index ce937d07b1..ea7f233bea 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -9,7 +9,10 @@ using osu.Game.Screens.Select.Filter; namespace osu.Game.Screens.Select { - internal static class FilterQueryParser + /// + /// Utility class used for parsing song select filter queries entered via the search box. + /// + public static class FilterQueryParser { private static readonly Regex query_syntax_regex = new Regex( @"\b(?\w+)(?(:|=|(>|<)(:|=)?))(?("".*"")|(\S*))", @@ -35,36 +38,36 @@ namespace osu.Game.Screens.Select switch (key) { case "stars": - return tryUpdateCriteriaRange(ref criteria.StarDifficulty, op, value, 0.01d / 2); + return TryUpdateCriteriaRange(ref criteria.StarDifficulty, op, value, 0.01d / 2); case "ar": - return tryUpdateCriteriaRange(ref criteria.ApproachRate, op, value); + return TryUpdateCriteriaRange(ref criteria.ApproachRate, op, value); case "dr": case "hp": - return tryUpdateCriteriaRange(ref criteria.DrainRate, op, value); + return TryUpdateCriteriaRange(ref criteria.DrainRate, op, value); case "cs": - return tryUpdateCriteriaRange(ref criteria.CircleSize, op, value); + return TryUpdateCriteriaRange(ref criteria.CircleSize, op, value); case "bpm": - return tryUpdateCriteriaRange(ref criteria.BPM, op, value, 0.01d / 2); + return TryUpdateCriteriaRange(ref criteria.BPM, op, value, 0.01d / 2); case "length": return tryUpdateLengthRange(criteria, op, value); case "divisor": - return tryUpdateCriteriaRange(ref criteria.BeatDivisor, op, value, tryParseInt); + return TryUpdateCriteriaRange(ref criteria.BeatDivisor, op, value, tryParseInt); case "status": - return tryUpdateCriteriaRange(ref criteria.OnlineStatus, op, value, + return TryUpdateCriteriaRange(ref criteria.OnlineStatus, op, value, (string s, out BeatmapSetOnlineStatus val) => Enum.TryParse(value, true, out val)); case "creator": - return tryUpdateCriteriaText(ref criteria.Creator, op, value); + return TryUpdateCriteriaText(ref criteria.Creator, op, value); case "artist": - return tryUpdateCriteriaText(ref criteria.Artist, op, value); + return TryUpdateCriteriaText(ref criteria.Artist, op, value); default: return criteria.RulesetCriteria?.TryParseCustomKeywordCriteria(key, op, value) ?? false; @@ -113,7 +116,18 @@ namespace osu.Game.Screens.Select private static bool tryParseInt(string value, out int result) => int.TryParse(value, NumberStyles.None, CultureInfo.InvariantCulture, out result); - private static bool tryUpdateCriteriaText(ref FilterCriteria.OptionalTextFilter textFilter, Operator op, string value) + /// + /// Attempts to parse a keyword filter with the specified and textual . + /// If the value indicates a valid textual filter, the function returns true and the resulting data is stored into + /// . + /// + /// The to store the parsed data into, if successful. + /// + /// The operator for the keyword filter. + /// Only is valid for textual filters. + /// + /// The value of the keyword filter. + public static bool TryUpdateCriteriaText(ref FilterCriteria.OptionalTextFilter textFilter, Operator op, string value) { switch (op) { @@ -126,7 +140,20 @@ namespace osu.Game.Screens.Select } } - private static bool tryUpdateCriteriaRange(ref FilterCriteria.OptionalRange range, Operator op, string val, float tolerance = 0.05f) + /// + /// Attempts to parse a keyword filter of type + /// from the specified and . + /// If can be parsed as a , the function returns true + /// and the resulting range constraint is stored into . + /// + /// + /// The -typed + /// to store the parsed data into, if successful. + /// + /// The operator for the keyword filter. + /// The value of the keyword filter. + /// Allowed tolerance of the parsed range boundary value. + public static bool TryUpdateCriteriaRange(ref FilterCriteria.OptionalRange range, Operator op, string val, float tolerance = 0.05f) => tryParseFloatWithPoint(val, out float value) && tryUpdateCriteriaRange(ref range, op, value, tolerance); private static bool tryUpdateCriteriaRange(ref FilterCriteria.OptionalRange range, Operator op, float value, float tolerance = 0.05f) @@ -161,7 +188,20 @@ namespace osu.Game.Screens.Select return true; } - private static bool tryUpdateCriteriaRange(ref FilterCriteria.OptionalRange range, Operator op, string val, double tolerance = 0.05) + /// + /// Attempts to parse a keyword filter of type + /// from the specified and . + /// If can be parsed as a , the function returns true + /// and the resulting range constraint is stored into . + /// + /// + /// The -typed + /// to store the parsed data into, if successful. + /// + /// The operator for the keyword filter. + /// The value of the keyword filter. + /// Allowed tolerance of the parsed range boundary value. + public static bool TryUpdateCriteriaRange(ref FilterCriteria.OptionalRange range, Operator op, string val, double tolerance = 0.05) => tryParseDoubleWithPoint(val, out double value) && tryUpdateCriteriaRange(ref range, op, value, tolerance); private static bool tryUpdateCriteriaRange(ref FilterCriteria.OptionalRange range, Operator op, double value, double tolerance = 0.05) @@ -196,11 +236,28 @@ namespace osu.Game.Screens.Select return true; } - private delegate bool TryParseFunction(string val, out T value); + /// + /// Used to determine whether the string value can be converted to type . + /// If conversion can be performed, the delegate returns true + /// and the conversion result is returned in the out parameter . + /// + /// The string value to attempt parsing for. + /// The parsed value, if conversion is possible. + public delegate bool TryParseFunction(string val, out T parsed); - private static bool tryUpdateCriteriaRange(ref FilterCriteria.OptionalRange range, Operator op, string value, TryParseFunction conversionFunc) + /// + /// Attempts to parse a keyword filter of type , + /// from the specified and . + /// If can be parsed into using , the function returns true + /// and the resulting range constraint is stored into . + /// + /// The to store the parsed data into, if successful. + /// The operator for the keyword filter. + /// The value of the keyword filter. + /// Function used to determine if can be converted to type . + public static bool TryUpdateCriteriaRange(ref FilterCriteria.OptionalRange range, Operator op, string val, TryParseFunction parseFunction) where T : struct - => conversionFunc.Invoke(value, out var converted) && tryUpdateCriteriaRange(ref range, op, converted); + => parseFunction.Invoke(val, out var converted) && tryUpdateCriteriaRange(ref range, op, converted); private static bool tryUpdateCriteriaRange(ref FilterCriteria.OptionalRange range, Operator op, T value) where T : struct From fe64c3dbd4de6ada5be2ca5112c65c2f17abb607 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 4 Mar 2021 14:59:08 +0300 Subject: [PATCH 151/390] Refrain from disabling cursor sensitivity at config-level --- osu.Game/OsuGame.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 7db85d0d66..203cc458e0 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -880,13 +880,8 @@ namespace osu.Game switch (action) { case GlobalAction.ResetInputSettings: - var sensitivity = frameworkConfig.GetBindable(FrameworkSetting.CursorSensitivity); - - sensitivity.Disabled = false; - sensitivity.Value = 1; - sensitivity.Disabled = true; - - frameworkConfig.Set(FrameworkSetting.IgnoredInputHandlers, string.Empty); + frameworkConfig.GetBindable(FrameworkSetting.IgnoredInputHandlers).SetDefault(); + frameworkConfig.GetBindable(FrameworkSetting.CursorSensitivity).SetDefault(); frameworkConfig.GetBindable(FrameworkSetting.ConfineMouseMode).SetDefault(); return true; From 132fcda08987f880767bf20d2eb4a785f09dd9dd Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 4 Mar 2021 15:00:46 +0300 Subject: [PATCH 152/390] Force config sensitivity value to local setting bindable Re-enable the local bindable to update the sensitivity value then change back to whatever state it was in previously. --- .../Overlays/Settings/Sections/Input/MouseSettings.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs index c3deb385cd..3a78cff890 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs @@ -76,7 +76,15 @@ namespace osu.Game.Overlays.Settings.Sections.Input { base.LoadComplete(); - configSensitivity.BindValueChanged(val => localSensitivity.Value = val.NewValue, true); + configSensitivity.BindValueChanged(val => + { + var disabled = localSensitivity.Disabled; + + localSensitivity.Disabled = false; + localSensitivity.Value = val.NewValue; + localSensitivity.Disabled = disabled; + }, true); + localSensitivity.BindValueChanged(val => configSensitivity.Value = val.NewValue); windowMode.BindValueChanged(mode => From 12b7d9e06d16b52a600c5506913c5569ee12b2ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 6 Mar 2021 12:16:01 +0100 Subject: [PATCH 153/390] Simplify custom filter criteria retrieval --- osu.Game/Screens/Select/FilterControl.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 983928ac51..298b6e49bd 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -35,9 +35,6 @@ namespace osu.Game.Screens.Select private Bindable groupMode; - [Resolved] - private RulesetStore rulesets { get; set; } - public FilterCriteria CreateCriteria() { Debug.Assert(ruleset.Value.ID != null); @@ -59,7 +56,7 @@ namespace osu.Game.Screens.Select if (!maximumStars.IsDefault) criteria.UserStarDifficulty.Max = maximumStars.Value; - criteria.RulesetCriteria = rulesets.GetRuleset(ruleset.Value.ID.Value).CreateInstance().CreateRulesetFilterCriteria(); + criteria.RulesetCriteria = ruleset.Value.CreateInstance().CreateRulesetFilterCriteria(); FilterQueryParser.ApplyQueries(criteria, query); return criteria; From 06e42b4b4c2bab783e277fcbc86ef5f26efc50aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 6 Mar 2021 16:02:20 +0100 Subject: [PATCH 154/390] Fix taiko leaving behind empty judgements on legacy skins --- .../Skinning/Legacy/TaikoLegacySkinTransformer.cs | 2 +- osu.Game/Rulesets/Judgements/DrawableJudgement.cs | 14 +++++--------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs index 9f29675230..40dc149ec9 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy { // if a taiko skin is providing explosion sprites, hide the judgements completely if (hasExplosion.Value) - return Drawable.Empty(); + return Drawable.Empty().With(d => d.LifetimeEnd = double.MinValue); } if (!(component is TaikoSkinComponent taikoComponent)) diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index da9bb8a09d..feeafb7151 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -150,17 +150,13 @@ namespace osu.Game.Rulesets.Judgements } if (JudgementBody.Drawable is IAnimatableJudgement animatable) - { - var drawableAnimation = (Drawable)animatable; - animatable.PlayAnimation(); - // a derived version of DrawableJudgement may be proposing a lifetime. - // if not adjusted (or the skinned portion requires greater bounds than calculated) use the skinned source's lifetime. - double lastTransformTime = drawableAnimation.LatestTransformEndTime; - if (LifetimeEnd == double.MaxValue || lastTransformTime > LifetimeEnd) - LifetimeEnd = lastTransformTime; - } + // a derived version of DrawableJudgement may be proposing a lifetime. + // if not adjusted (or the skinned portion requires greater bounds than calculated) use the skinned source's lifetime. + double lastTransformTime = JudgementBody.Drawable.LatestTransformEndTime; + if (LifetimeEnd == double.MaxValue || lastTransformTime > LifetimeEnd) + LifetimeEnd = lastTransformTime; } } From 1525480e73196e9cbbef3128b012e04e130c84b6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 6 Mar 2021 19:18:40 +0300 Subject: [PATCH 155/390] Demonstrate value of `SPINNER_TOP_OFFSET` to being more sensible --- osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs index 1f1fd1fbd9..5df8f8a485 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs @@ -141,7 +141,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy /// for positioning some legacy spinner components perfectly as in stable. /// (e.g. 'spin' sprite, 'clear' sprite, metre in old-style spinners) /// - public const float SPINNER_TOP_OFFSET = 29f; + public static readonly float SPINNER_TOP_OFFSET = (float)Math.Ceiling(45f * SPRITE_SCALE); public LegacyCoordinatesContainer() { From 8f4dadb06a393f760565ee61c3066644975b8c60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 6 Mar 2021 15:06:16 +0100 Subject: [PATCH 156/390] Enable pooling for taiko judgements --- .../UI/DrawableTaikoJudgement.cs | 11 -------- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 25 ++++++++++++++----- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs index b5e35f88b5..1ad1e4495c 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs @@ -3,7 +3,6 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.Taiko.UI { @@ -12,16 +11,6 @@ namespace osu.Game.Rulesets.Taiko.UI /// public class DrawableTaikoJudgement : DrawableJudgement { - /// - /// Creates a new judgement text. - /// - /// The object which is being judged. - /// The judgement to visualise. - public DrawableTaikoJudgement(JudgementResult result, DrawableHitObject judgedObject) - : base(result, judgedObject) - { - } - protected override void ApplyHitAnimations() { this.MoveToY(-100, 500); diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 148ec7755e..d2e7b604bb 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -2,10 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Pooling; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Drawables; @@ -17,6 +19,7 @@ using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.Taiko.Objects.Drawables; using osu.Game.Rulesets.Taiko.Judgements; using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.Scoring; using osu.Game.Skinning; using osuTK; @@ -38,6 +41,8 @@ namespace osu.Game.Rulesets.Taiko.UI internal Drawable HitTarget; private SkinnableDrawable mascot; + private readonly IDictionary> judgementPools = new Dictionary>(); + private ProxyContainer topLevelHitContainer; private Container rightArea; private Container leftArea; @@ -159,6 +164,12 @@ namespace osu.Game.Rulesets.Taiko.UI RegisterPool(5); RegisterPool(100); + + var hitWindows = new TaikoHitWindows(); + foreach (var result in Enum.GetValues(typeof(HitResult)).OfType().Where(r => hitWindows.IsHitResultAllowed(r))) + judgementPools.Add(result, new DrawablePool(15)); + + AddRangeInternal(judgementPools.Values); } protected override void LoadComplete() @@ -283,13 +294,15 @@ namespace osu.Game.Rulesets.Taiko.UI break; default: - judgementContainer.Add(new DrawableTaikoJudgement(result, judgedObject) + judgementContainer.Add(judgementPools[result.Type].Get(j => { - Anchor = result.IsHit ? Anchor.TopLeft : Anchor.CentreLeft, - Origin = result.IsHit ? Anchor.BottomCentre : Anchor.Centre, - RelativePositionAxes = Axes.X, - X = result.IsHit ? judgedObject.Position.X : 0, - }); + j.Apply(result, judgedObject); + + j.Anchor = result.IsHit ? Anchor.TopLeft : Anchor.CentreLeft; + j.Origin = result.IsHit ? Anchor.BottomCentre : Anchor.Centre; + j.RelativePositionAxes = Axes.X; + j.X = result.IsHit ? judgedObject.Position.X : 0; + })); var type = (judgedObject.HitObject as Hit)?.Type ?? HitType.Centre; addExplosion(judgedObject, result.Type, type); From 97f56340af36e85a13d85b02d477edee596f7f34 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 1 Mar 2021 20:57:25 +0300 Subject: [PATCH 157/390] Add legacy score font to testing old skin --- .../Resources/old-skin/score-0.png | Bin 0 -> 3092 bytes .../Resources/old-skin/score-1.png | Bin 0 -> 1237 bytes .../Resources/old-skin/score-2.png | Bin 0 -> 3134 bytes .../Resources/old-skin/score-3.png | Bin 0 -> 3712 bytes .../Resources/old-skin/score-4.png | Bin 0 -> 2395 bytes .../Resources/old-skin/score-5.png | Bin 0 -> 3067 bytes .../Resources/old-skin/score-6.png | Bin 0 -> 3337 bytes .../Resources/old-skin/score-7.png | Bin 0 -> 1910 bytes .../Resources/old-skin/score-8.png | Bin 0 -> 3652 bytes .../Resources/old-skin/score-9.png | Bin 0 -> 3561 bytes .../Resources/old-skin/score-comma.png | Bin 0 -> 865 bytes .../Resources/old-skin/score-dot.png | Bin 0 -> 771 bytes .../Resources/old-skin/score-percent.png | Bin 0 -> 4904 bytes .../Resources/old-skin/score-x.png | Bin 0 -> 2536 bytes .../Resources/old-skin/skin.ini | 6 +++++- 15 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-0.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-1.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-2.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-3.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-4.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-5.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-6.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-7.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-8.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-9.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-comma.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-dot.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-percent.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-x.png diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-0.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-0.png new file mode 100644 index 0000000000000000000000000000000000000000..8304617d8c94a8400b50a90f364941bb02983065 GIT binary patch literal 3092 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 Y0OO;-v8NaCZ2$lO07*qoM6N<$f&jDokN^Mx literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-3.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-3.png new file mode 100644 index 0000000000000000000000000000000000000000..82bec3babebd59263dc486e5900a3a01ea4aaed7 GIT binary patch literal 3712 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 JPDHLkV1kmK?iT<6 literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-6.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-6.png new file mode 100644 index 0000000000000000000000000000000000000000..b4cf81f26e5cab5a068ce282ee22b15b92d0df12 GIT binary patch literal 3337 zcmV+k4fgVhP)oInXkiAowm zxg2vDgN+Ra8+@(L^|fAmr|*rvpF50qmLrXnksfBpyR&b;^L^jgq3gQ#Lp>Z%`8lV2 zR{d1aO$b|Fe{bXz5f|VFVg#}B+GQd~QvE>4f&uwm__4{IJ$u&nmkSmwP^3s84)6d8 zCV1t%Ti&M&u^`Y3bPKwG9yxCi#rF<8ikv$NF-0m?h$I7Pz;HpTob$?i6uFRzP&?2D zv zJ$tsx)XH`phgI{ zh=qef(mMD_!?d{b% zJ3F;6zW5@vckkZPt5>fUOU@kBkNRm);C{Aum?ea*VGp?ra zgGY`W`RVh|KOaMDxm>QND~fP?e0;n%a^y&D%a$!k^XJcBv3>jY47fU82ste^v47BV z@=LD-`~u*Yz~6T5+SS?C*7n%ef*bWvC}dd5?%=g(#mkp3>v?&3m+?J&fu93&B)%Ez zE`yhlixGD|3#QJoI_0yqG7b_W81M-oFV(_eb& zC1%-a;IF_R5gR3JSVz+_+(TqjYgmQ4wS8Fz_qj$Kqa(OH7z588davm@$j@ z?b|m7MfRckWj^3at)--B0czx;9r{+l6uyk>G4aAyYJWOuL~-MxEP`}EUKFPqwrU;1N6DGd1;lJhNM z$=a{K{<^8BrzhgI>2CIGv09R{T!N5GU%Ys6tTiUg2Qo{0_uY59rGeL4EcTG}k=K1f zkU`#9TwHw5&T@=Bem6Lb7Bv(*NDjsD8K2K*+oIUW>C(?X|GY|wHA>L;ipq_c*z$X3 zUo%4CzTfYU7X;6z_3BDXOZyle zwgS`b?ol`F7II0jS|U^0?x6I62xu3<*sR>iJA~AVkvYOV@C=5NkmUR8>+0%Sk@3E` zxVVS!0y>Zez!T;~ZeDcXBC8tiE@sTqU)7t2p*FErgAilbbQ{wZ`e|yk!b`K4m9fwp zvOOTBhZl`WO-&t+L1z>Oo#6=y32_LII8)3~{e~bAdZ9j=&zw1PU&5kQ2>2!Io8D* zBRk^}Q)y`K6F&a<B-&wu^(*EPgsNLCiTQa=Zr-(}mRW>fY{D|kNI zdW~_(iEPrONe-x-e7JZPPLDH{s3<9F*DO)KMur#41Y7m)*9I~qh z)Q(6t7gY6P2-tg4P7JZBKJi%lGg^d1Fu79zG+;KUmJ4-D&C;a+GiHV*Cnx8uTemI; zgN`<0#0V`?@1_NL18=?cRvMIW?rj`6{eahXn|5kZ54M+Ew{B@`)~xA)>@Jphp;iiL zuMEP999g7wbadoE0Z&6)%16klU_n^{d))c1H7#Qo{J2!CkXcJ{&lG`gEA;!|tv`dp&Kje(a4p z$T`h2op;rC{rdH8TFFJ!xeUeFu7Tp$U3-6X2;d@i&&6^i>Mp20eiKUcwNRewkfBt+Gv&k+lSjmx1 zS<+t86hYY`54j&9mMlvm-*eABmmY1CprkqyPf4htBIid^+&y~qXpk+*t`4nUy*fn} zJ|iUyseav(o#usioDfTwjnOkNzW8Fyqt+bA(vWl6Mx2zrH}3vio=`DEue@s(Fz$-~SLbrdWZurl!V&i%gw7 zd9u?wi7{gtd${}L&p!LCQ_4!u;Qsc1gHEbgYZ5R6(rDpFAAOXam6c^Xqfo(ARaI$) zg@xb1bC;xuH@ZxZkBp0B0?L33jGc)!yLfSKgV4f!K#xdihgd5~;wwwGlQV#M@4WNQ zEX12;=Tc@Uy|ri09zWcAN;(R6Fs&|=o-@*(5*jnXggJBOcsLuFpEamm8q{kn#=8vM zh|?7KBP1RtiK5Mi8p)@SSqp1ZVC#%K4ulelZ z!w)|UqS;5B5xC1mUDRKD?KK84S79fCe*i}*2J-Xsn~REybZ+9KExgszT6V(QfuG3D zF*Mx90|{9tBuJT{8qI@PTm-qZvY?>AO}%A}7iJe1I~azwW5_jr2?%~Es-|3ec84L=P0t}b;+m;)2b+OITeog)gu%P$B%bnpuU1Ga8(xERg#%aYO`d1IA`}rQ`M}&_9G_G zlAohMbUfVR%goI5qK3tSloP$zfl}jOvT!ctJbv-w#Q`9!rQH7S(=nLX83vrXEPU#!$D=TsqQ)`zW2$cX(! z#9|mE)#ABFT8dv>o+$8|h{c$ehe~YrnZ#y5$OPnEujzSz_`Cddg!L~YRLhGqEe>6l zKj9cK5ey3Y1pSZmml98-Y@L=r<#3wduqfZK`ym-$bXUJ)PBIne+3u-Y^mm; TZ*tsI00000NkvXXu0mjfuoP_o literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-7.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-7.png new file mode 100644 index 0000000000000000000000000000000000000000..a23f5379b223d61079e055162fdd93f107f0ec02 GIT binary patch literal 1910 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 literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-comma.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-comma.png new file mode 100644 index 0000000000000000000000000000000000000000..f68d32957ff8dc2e6e26e2b739eab85385548faf GIT binary patch literal 865 zcmV-n1D^beP) z8ION{U-N9C`MxinYv|$`M+}Q$F)W6~uoxD@Vpt6OzhDOaIXgR>o2*u=8V;)D@TF?C z+T17S{{H?ym;%w+TADXmhT?$-O-NWvN0;UR_GXqT)+8VcT7_OfX+F1jnFIZS?x8!# zh8}URz_+nygyLs1rfHeIXpZ(*xcNFpGu`( zwcG8nVHo$9mzUrA{eGSAow*&jOrP%XnP4jnsY0KQkB@6tS64o6^aq1MaO`wCezV#1 zPfkt<_8IyJm7o-H)Y71MrP!@^ySuwPg+f7WY;1^ZHX9uC`MfBVO5*6~=mXZgWiM7_ zg2J#BUQj3&i!V*nT&N{Y(}Zo?M9#738`=bqh+giU0>f>loV9|82+ zaU80#CVuw0IXW_vYCm)NDP(>JPj7d--Su=j9k?q&C<*&31~y-*}fwjTm!p*P&Jvpnyz z)m?^=o3|X6i423GymcH7s?X++lz!!LDcrGp4_y83T(4ux)LkG8*W#nj518@;Wz|pB rcm*5_XP0^3eKYf&7eE$5NN3S}(it^R$P@B}JRwiW6Y_*SAy4SP5!!0C zTE9FJKtC>>NlHB>x7U?fC3eMrUE}-6&6|p1Lzp5^Zgp4;-?Tka0Ir& z1rTBaEJ%Zoz;`eQ3$UbTh&3rgX9|7}x(Kd<2wyLO^E@RI`3er0F^e8jrdymkWwHaJ zU>EG6-At#`*8_n7WhgqG&Jb^YGairM;r=~O+gUez%_x%?%@8~chr`cux!mVouO}vx ziI~l1Vmh6Q;czJ0?Y5Q8W=ZG?xDRe~_LPX7{ta*sJS`LoBN8x8Qyg!)u8YB7AXw}% zxJ%jddP>*89q=fXO1TEF6vaC~s48xE{ zqtTROY?u4ELr$I4>-A5|~39QXWMIpY}VCkbsmq$cd@;G3M5vml{BBvrEa%t<@5R1rBdky#=iw0 zIjg12+aCmBcDQpy_7jQ3UL+DBYmj&PwOZ{J`d!cgAHWx$%}VBs{OkvExyH8F_z-XN zhAjBd4tYuwo)#^QA>$xAWkltmAR9EuCtu}5>6DRg%ppIcnq=Aa2%R;=oZ@xF169W7 zDr`CzH^QVBIE8JK`;M1drwJD)wp9Li|3LU5zyS0^RhH-3lmq|(002ovPDHLkV1mea BUy%R+ literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-percent.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/score-percent.png new file mode 100644 index 0000000000000000000000000000000000000000..fc750abc7e80287192efb65633e58b6b19e47125 GIT binary patch literal 4904 zcmV+@6W8pCP)>HPEA?bv`x0yR5sa^IhC4IP-&%2W!qCT)6~GN6jP8z zltl$(hYNRk=iGM=@9+a<{p0;+&Rp*D`+dJ>|D1Ea=v3#IPV=h$PLh+zA^z^}r3W$! zGSIW_KsG`4u0GIsjfJ#}={1b#=re=(4i|0#YNprjY=)m@0_q3k3FJZV@v;Kd0W|n>QZAbRG^nNiO?^UR0E#l_jgTq9~z?!8NzghdU+poJL2g+hsf zr%s(3>2NrVMMXvR`T6-ZbeqY?$H!~YqD2vtCQb6+wr$(ljEsy-_`CopALtxOAAjEj zYuGe?=SEiIO=||zdXeGb;n7b$^;GQbx8L61U@)iw0|q>G>#eu0fbXu5AobnyNm!iC z4z)E9C>-cIe}DhP6)RT!WB&a4{vIA4N~hB)tJSLZ?Ahb^^2;xA!^W3iepw$D7N$%l zlbSejV#3_Hb7Ph-U;alyK|vN-05e)gekv`XA88NQ97OAmNJ~pgUb=LtH!!Mj-IkUX zjSNns|XW)x3H0^tau1n;SBX zn?MHf>esKInlWRBY5Vr=6Ysg_p1_kQPX@rpK|sYoRTMCIU1YF7xdbv8mYSNH^vENR z1SBLRC|uKMG%Balsp{(L)P@ZkPK)5?=uYJ3;9;i4!hqsIgHtwc+&E|Y^yx<2I5HzI zWE2-FuvbA>_Bc1})?TD*91RaRCO`cV;31^JRgM9}_V?m^S0O^aH#Y*}zzT%68g zvABfB&uwmQR^WS%l$4au;q4xv<76FG@VTw4nq($@$l0SFeDJ|3@X&S)+U<7L(9obR zT)3dX9M#ydW6_M1o10sk-?_WHtEi|bwPVK)pC5nxal+1>J4ff`miEGX(4U%`nq05AY1g8_2nq&;fsEnr zQ2Y3bpW%0`#RCTps3}vXd=GDT0Uf4*hJnW_qjhJRG*S3QMnje?S(4!E>)VDwyc!!D zReE|lGMG=%(|Yvi(Sbkw@IxZ_uK~=i)i+zSJD4+W8cANrELsIJhMFoVDN)~j_nrFn z*I(7<&70NW!Gm4DBQ0Vo$^*JUDGBp%OII0GBFb^v7<|huw|JS&<~BdV4OLZDsWofX zoQJoVoN_3L1ikalJI!a#oSE?ItFNli(9kx*vDs`cM(}=S3_n8+6&4oS_U+r(yldAk z=P$qff>B+qUcFkOfKk)7lu8+O)|5 zzIY7Y{t0x3g2*6X-n4b=R=?S^XGaeiGNcWYcpoHjmlc3kTPiCnFJFQSHeYt-%9SQs z8pH|t~*}3o*C3~41znaK}Kr%Bk?P!W^q8gaph!G?3&%c4t3bKg) zB0(YZEr4n)vu4e5`7Q>ZnwlEa;sv3_ik@mw1|Aps04W-@riwlVzGD+uw3;xglR`O~ zT@z^B4rI^<9(bgc@+4Y7E_lKK39#2FM#W>fwhU z_C*NCxcKLve{T8x_ur5Al@^tzbaF6UlNv!k0}NWu0R-qnZ@lrwfvgb;1OTcvLDDaw&AO2 zbN~JK>m)wK`w_8h*|H^%Hrz~t8Vs=QI!OG%oH9_0APDrulN3><7Hz5zE@l!V*v0(( z{8PJk?`}fL@`V}#O${Q(VzAx>N+gPZ;DHB*vDry54^h&(fB*iY6g-^NoC>?^uDcRP zjT)unNQ^>-#9v2REYxVx(N|11(?ikG^Im-M#RE_)I@vd|ℜ1r5i{-0$U=)+!&zI zbLPy6PfJTv(p1sh&z?Q&gy$TjG*CmP8VmqF7E*=}rwj~C0Myk7AAE3(e6g4iRAXQG zqD&zzRET0xS^(Po11gH(H{N(7Iv^mxm81bYtIt0BEcBy~KJqLpE31VxV*~~Rq4GC# zS%#?x{Scz56U;XYo|8$NZXt{s3MnI&QwD1h%oJiy)!*ccmnrchg9hpzax`nxxbBXY zb(+oqnq?HwsFakH$#1{?_E?a<9yP^Gp!87-2;`B_uMjYx4?=<4ylT~|%wxxn?I+FD zP#-5IHa2z!m~eDpV4zDVXep;ool>yQ9$@lkDu@fH7sM?RH&TR-KC_YK$>y~y$6qUt zV&ByP5^JUidy#V|MB3VE)28L!ci(;E0YDE44-a?4ik(prGK`u)^Mp#n`st^i7DAvo zNcgmn{M>_m@yREj93i!imCf3f=?iphJx@ZrM;4IMhvAF6impr9ZxD3ohJdriRL#fpjwZYCF#Z`YC* z5ZR2GF=NJ@jEsyBe}Dg0&4bpRQ>IM$5x)PGYSc1f#GBGnAelKgwQa;y4f!oo6@6Au z47)}XQnN1Szz={?yXdC1&`p)JaUbwCZy*oayn%klV}WY1XakvAFr%BNpMH7>rwma_ zl~O6idDJYxLL^)5Bp4mtgK28i zv(G*|3lLEtcJVMsVGMwL>H`)K3|Khfr=NbR19vLkxpQYWSP^&Xi^%LRlhCna@2Xtd zAZk*v4ZdV;Hj(L>#9+rDqMcncy)uyxB85W!jCtsxhgwsHrj*JfO*E7LVfQ?7-MV#i z7c5xd)xUrLc37B!#=ZC6>jkJOs<^m##OBSLOViWS4*|0$$%jp1f39Gq>&noC#t3o_ zsWf4ot)f5qVFJ+PS6+GL&PN}8)B`o*ng%sAC>$?9irEfteUb+il&U>N)`uRs6%^Cx!f*zs@bK%5~rK=ZZgx;h!^LSw01uf{h;hF&L_wNB}G@Mp3H>TmZz3%*@P8 z%FD|u6Rny?eIKAE0|g`o6T?7TNjKkob8D5s@Qpw?nrJBnER3iD@$vC-)2B~2X`9e! zipU5scnIF^rF3%u=OOSckXMo7<{4@fQ`G$V^T$%^^CRmr=zFO}9{E`)x8cAj3VbmP zCCq+>n_a(teGP!{zX&Msv={?L5C~*A1}Euj{FA@;aIGHkVv%s7JUtthlLb=R~g>%zQH(9j(UJNo{ zfBm(iy1M!lygf}mRzaJ`drQurKM#+$weo=4jEIQPlNP1QNDhwSpw7Ys(sk2~XrX-V^b??#flZ-GG(9^0qxkKoI2V;CtMy}>hNe9G6`wNDmw4n&@z#L(TXfk zZc8WA5C!$pFb(yLH?{-LPQ;4|wD}!zMcR_;(8+lqT&o44;vc zlhYuAj6+P|Jn2A1Ahu;BlNbjO;z2o9B&s5tz88rfQ%1~l&pj82wIcflhJL6FuoKSh zj8aV}oEbz-HNt((+O9or7Dz9GM;ivO*3d>@hMLnMqH$BVm=r0)k4|e1hmt4>4-|3A z5IxjvFBnG`8QCjQ}&_+znZV4n~m{1|DGJK>F*PB=4( zsO2(L8TkPDYGgwjH$QRWL_Kv1t`iOI(fIegdGn$}LPFa3BZ7yFjEq{SEl*IoVkRA3 z(Ioprf7arJ2@}RHT)5C(HkDBu@4x?k4FsK2;yhigwi8Z&bFUZ8st|%ymTYLF`2%QI zW5$e$q^^AuP%^$k+K6AZYL#AE5I3|TW$h&am&&f+i-j**1g0W<{<7sRsHpgh%CQ1@?6h4=vJ01E>eQ+J!NI}q z%piks@WlcuWaT`grIUFDcf_m35zqP~M~++qItEq#5C}dIk&%%Y?DPo6;c|l2G#@^E zxEOxJNa{SbQ;Ny_YXl7ZUkAOD=xXK`BQ~5CFJAmF001{47{?3HMZ@gbvp=C(Mz-js zc6z{%0EUr#o`<^wi2NuH^dk_W6Qln+0{a!jScL%d)($)9ooSG6#D`wkv}x1VfM)Vj zQ&SUDQc{M!_S$RPs12B}(PF0u8OUsHBF%6^`w}tj$<@6W`ZSAqNF7n5I3d<<_@nCv zUga4{5J}pT&`Y&w*H@?>oJSSDZxEG+o0w|JhPF+${kIHxV8d~>(Op*;Hw%d6NgGfM z&(#0^XT#bD15|mw=vx0=)1_nDx-Krt^`A))|L2WOGcW9uIe%YXx*kecJ1&rt{lB~Z aBftRVw!D&5)M2&&0000P)WhT)HwQ6c>V(brp7#o$S{ZV6ai&_S<5m(%CHc|Lo%3rgpYX*kJ~<30B~?|r{_eclgEx~^*<>SfkG;A=>^ zA?1dY8&Ymaxgq6-lp9j+!?>*X?%nJ6VAiZzCftlLUW)wbrKP3r!Tb>9nkmnCH_CN; z-E|Xw#Q3sB6b5XB)gVm0=mZ?R3tW)l8bB!v<%+x@11tu)BBu|q^X`#rH7F+k4gtb| z;XpVL3ItO@oiFwR-9Q)ct$fD&eNbkh@4@R8^PZQ=28dcbpo#`S3bL~TwtOQ z5fB5621WwWd}gE7dMTt0cty1HnVmaUmr`svWsWq+jiSPCZcyZXLj&o`Ggd=lC_$qW z5)#rgGBRfL^z_6zoz6(N+bz4)&1Q3NcXxM7Sy@>XK7U2jskni)Bo?rg&YXmx zprHQQfME#AYEPd&&Dpwj>*o(2KKz^(Qn4tCDXWR=MgZf1w5e04W^doVeeR4IGlFqx zQ0A@NzP>)Krluyfu(0rNbhjrSjmjzO4TI&PVq;@95#oJ`f-x~Mw$rCi&tJA|nO0j{ z>!eHVbcdaC#C2nUiC91D;K74A)2C0jz)G6Q^xq-?3u?W+z1o>GXG&hYc+sOW)#E9z zRFme1)RVOJ$O+MbYaJOOG}HsX3d&r ztUu28ri!J2X}~IZzG%^+x~8Tk9pT_31M#b0y?WIJ%U;LYJYXKFA^|h_7t)3+CciC(<4VkBH-n@Alt)5DV zt22)rIWj#tIa%{gEQz!B_I52lKmR2ZEWx8HW_>GboYNC(VmX8LB*Wr?nZR1${JM4P zI-sD(js7liS3^UCK55bxX(IyzoMc- z;;aJe_It%yun$WzDn|v)To}jLSqU7?%F3!~Y;5#v4fVqh9z4)d&pd7u_j$WtvUcIZ zgpzIpS;Z~c?x$jr>tHf-3S z4I4Jhr=CgOJbd_Y_syF(rK?DW)&eh?gnj-fm|wC<3e?9Jn|-i3A&WKQ_qYXa8NS@*T#+=+b`$`A?Ljhq0R3YN)*)&VKWvQ6qB8uJ!-{@6`l+o z=#s=hQZ`n9UTD92qX>OPW;W#Z1AJ2NLtnBddQRXy{*WSy+vwQJX8(y1gWD1%NA zr?dB0j_$M?sJ85QBC31V%9SgvVPRoDN(q|<0npGXeB=qqT(V?|dG+em(%^r@-W|tK zu?2Fk`5o>Q^Cti)aAPJ~Q-VKtO4eeY&=xFMAhg|wV#)f5hzJdH$B1#`#!X}29>X{c zVf>lisaybePKG%{`TV8Hkn0`@#*u5#I<`BxjsSCBwWGG=CR&Dn@(g&>>+OcY!-{ z-@kwVH|Qij9Yncz+qP}PQ&Us3NSBi6AvL_4eUxL?D0hzHM3oxIopQly8mwlos;UyR zKLligD!YIG{vC{ZcKP1t3^%gQ`x35)xYW;|KQE@e25bf9(;yL4@U7*Pa*_1^4ultOuF} zPSL!FGXYHugKw!!8|5jc?C=~|LzvZYb{lRu6IKU#>O5GTI;wM9yK-lM9<2_lgQ;i; ze@7Ks7v~f^W%v3VSkuSK*LG@e^Eyd)8BWmEX}UYm93ao-7?$t#aWiHEjI>>y4Z02R y`8$L6-yckV`2V2hfbVjdhW^vb|D$sM5nupg@=t#XK}yW+zD literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/skin.ini b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/skin.ini index 5369de24e9..89bcd68343 100644 --- a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/skin.ini +++ b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/skin.ini @@ -1,2 +1,6 @@ [General] -Version: 1.0 \ No newline at end of file +Version: 1.0 + +[Fonts] +HitCircleOverlap: 3 +ScoreOverlap: 3 \ No newline at end of file From e4afe717d5b569fe9a6a7bca1bb8891b2cffee62 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 2 Mar 2021 21:23:38 +0300 Subject: [PATCH 158/390] Publicize legacy coordinates container and sprite scale --- osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs index 5df8f8a485..06443ca8b8 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { public abstract class LegacySpinner : CompositeDrawable { - protected const float SPRITE_SCALE = 0.625f; + public const float SPRITE_SCALE = 0.625f; protected DrawableSpinner DrawableSpinner { get; private set; } @@ -134,7 +134,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy /// A simulating osu!stable's absolute screen-space, /// for perfect placements of legacy spinner components with legacy coordinates. /// - protected class LegacyCoordinatesContainer : Container + public class LegacyCoordinatesContainer : Container { /// /// An offset that simulates stable's spinner top offset, From 1841a4d1c977e04377d7427681a93ad09d8c293d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 2 Mar 2021 21:37:25 +0300 Subject: [PATCH 159/390] Extract legacy spinner presence to lazy field --- .../Legacy/OsuLegacySkinTransformer.cs | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index d74f885573..d4a403fbd2 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -13,6 +13,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { private Lazy hasHitCircle; + private Lazy spinnerStyle; + + private bool hasSpinner => spinnerStyle.Value != SpinnerStyle.Modern; + /// /// On osu-stable, hitcircles have 5 pixels of transparent padding on each side to allow for shadows etc. /// Their hittable area is 128px, but the actual circle portion is 118px. @@ -30,6 +34,19 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private void sourceChanged() { hasHitCircle = new Lazy(() => Source.GetTexture("hitcircle") != null); + + spinnerStyle = new Lazy(() => + { + bool hasBackground = Source.GetTexture("spinner-background") != null; + + if (Source.GetTexture("spinner-top") != null && !hasBackground) + return SpinnerStyle.NewLegacy; + + if (hasBackground) + return SpinnerStyle.OldLegacy; + + return SpinnerStyle.Modern; + }); } public override Drawable GetDrawableComponent(ISkinComponent component) @@ -110,11 +127,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy }; case OsuSkinComponents.SpinnerBody: - bool hasBackground = Source.GetTexture("spinner-background") != null; - - if (Source.GetTexture("spinner-top") != null && !hasBackground) + if (spinnerStyle.Value == SpinnerStyle.NewLegacy) return new LegacyNewStyleSpinner(); - else if (hasBackground) + else if (spinnerStyle.Value == SpinnerStyle.OldLegacy) return new LegacyOldStyleSpinner(); return null; @@ -151,5 +166,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy return Source.GetConfig(lookup); } + + private enum SpinnerStyle + { + NewLegacy, + OldLegacy, + Modern, + } } } From c441e993ff5961cba259e56b162c7ee384f0afc7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 2 Mar 2021 21:43:32 +0300 Subject: [PATCH 160/390] Separate "gained bonus" to a read-only bindable --- .../Objects/Drawables/DrawableSpinner.cs | 17 +++---- osu.Game.Rulesets.Osu/OsuSkinComponents.cs | 3 +- .../Skinning/Default/SpinnerBonusDisplay.cs | 47 ------------------- 3 files changed, 11 insertions(+), 56 deletions(-) delete mode 100644 osu.Game.Rulesets.Osu/Skinning/Default/SpinnerBonusDisplay.cs diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index d02376b6c3..f16c1fc9d9 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -33,12 +33,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public SpinnerSpmCounter SpmCounter { get; private set; } private Container ticks; - private SpinnerBonusDisplay bonusDisplay; private PausableSkinnableSound spinningSample; private Bindable isSpinning; private bool spinnerFrequencyModulate; + /// + /// The amount of bonus score gained from spinning after the required number of spins, for display purposes. + /// + public IBindable GainedBonus => gainedBonus; + + private readonly Bindable gainedBonus = new Bindable(); + public DrawableSpinner() : this(null) { @@ -76,12 +82,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Y = 120, Alpha = 0 }, - bonusDisplay = new SpinnerBonusDisplay - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Y = -120, - }, spinningSample = new PausableSkinnableSound { Volume = { Value = 0 }, @@ -293,8 +293,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (tick != null) { tick.TriggerResult(true); + if (tick is DrawableSpinnerBonusTick) - bonusDisplay.SetBonusCount(spins - HitObject.SpinsRequired); + gainedBonus.Value = tick.Result.Judgement.MaxNumericResult * (spins - HitObject.SpinsRequired); } wholeSpins++; diff --git a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs index 2883f0c187..131645406e 100644 --- a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs +++ b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs @@ -18,6 +18,7 @@ namespace osu.Game.Rulesets.Osu SliderFollowCircle, SliderBall, SliderBody, - SpinnerBody + SpinnerBody, + SpinnerBonusCounter, } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerBonusDisplay.cs b/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerBonusDisplay.cs deleted file mode 100644 index c0db6228ef..0000000000 --- a/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerBonusDisplay.cs +++ /dev/null @@ -1,47 +0,0 @@ -// 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.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Osu.Objects; - -namespace osu.Game.Rulesets.Osu.Skinning.Default -{ - /// - /// Shows incremental bonus score achieved for a spinner. - /// - public class SpinnerBonusDisplay : CompositeDrawable - { - private static readonly int score_per_tick = new SpinnerBonusTick().CreateJudgement().MaxNumericResult; - - private readonly OsuSpriteText bonusCounter; - - public SpinnerBonusDisplay() - { - AutoSizeAxes = Axes.Both; - - InternalChild = bonusCounter = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.Numeric.With(size: 24), - Alpha = 0, - }; - } - - private int displayedCount; - - public void SetBonusCount(int count) - { - if (displayedCount == count) - return; - - displayedCount = count; - bonusCounter.Text = $"{score_per_tick * count}"; - bonusCounter.FadeOutFromOne(1500); - bonusCounter.ScaleTo(1.5f).Then().ScaleTo(1f, 1000, Easing.OutQuint); - } - } -} From 3f1d36ee6bced20b47a167b8780d2a736001c282 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 2 Mar 2021 21:49:38 +0300 Subject: [PATCH 161/390] Add default spinner bonus counter piece --- .../Objects/Drawables/DrawableSpinner.cs | 1 + .../Default/DefaultSpinnerBonusCounter.cs | 51 +++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinnerBonusCounter.cs diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index f16c1fc9d9..4f5afc85ab 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -82,6 +82,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Y = 120, Alpha = 0 }, + new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SpinnerBonusCounter), _ => new DefaultSpinnerBonusCounter()), spinningSample = new PausableSkinnableSound { Volume = { Value = 0 }, diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinnerBonusCounter.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinnerBonusCounter.cs new file mode 100644 index 0000000000..633766290f --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinnerBonusCounter.cs @@ -0,0 +1,51 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables; + +namespace osu.Game.Rulesets.Osu.Skinning.Default +{ + public class DefaultSpinnerBonusCounter : CompositeDrawable + { + private OsuSpriteText bonusCounter; + + private DrawableSpinner drawableSpinner; + + private IBindable gainedBonus; + + [BackgroundDependencyLoader] + private void load(DrawableHitObject drawableHitObject) + { + drawableSpinner = (DrawableSpinner)drawableHitObject; + + InternalChild = bonusCounter = new OsuSpriteText + { + Alpha = 0, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.Numeric.With(size: 24), + Y = -120, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + gainedBonus = drawableSpinner.GainedBonus.GetBoundCopy(); + gainedBonus.BindValueChanged(bonus => + { + bonusCounter.Text = $"{bonus.NewValue}"; + bonusCounter.FadeOutFromOne(1500); + bonusCounter.ScaleTo(1.5f).Then().ScaleTo(1f, 1000, Easing.OutQuint); + }); + } + } +} From 30f07aa9fcc22167e429be3bb798309ab3964ec4 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 2 Mar 2021 21:49:46 +0300 Subject: [PATCH 162/390] Add legacy spinner bonus counter piece --- .../Legacy/LegacySpinnerBonusCounter.cs | 56 +++++++++++++++++++ .../Legacy/OsuLegacySkinTransformer.cs | 9 +++ 2 files changed, 65 insertions(+) create mode 100644 osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinnerBonusCounter.cs diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinnerBonusCounter.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinnerBonusCounter.cs new file mode 100644 index 0000000000..3c4a6be4dd --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinnerBonusCounter.cs @@ -0,0 +1,56 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Skinning; +using osuTK; +using static osu.Game.Rulesets.Osu.Skinning.Legacy.LegacySpinner; + +namespace osu.Game.Rulesets.Osu.Skinning.Legacy +{ + public class LegacySpinnerBonusCounter : CompositeDrawable + { + private LegacySpriteText bonusCounter; + + private DrawableSpinner drawableSpinner; + + private IBindable gainedBonus; + + [BackgroundDependencyLoader] + private void load(DrawableHitObject drawableHitObject, ISkinSource source) + { + drawableSpinner = (DrawableSpinner)drawableHitObject; + + InternalChild = new LegacyCoordinatesContainer + { + Child = bonusCounter = ((LegacySpriteText)source.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreText))).With(s => + { + s.Alpha = 0f; + s.Anchor = Anchor.TopCentre; + s.Origin = Anchor.Centre; + s.Font = s.Font.With(fixedWidth: false); + s.Scale = new Vector2(SPRITE_SCALE); + s.Y = LegacyCoordinatesContainer.SPINNER_TOP_OFFSET + 299; + }), + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + gainedBonus = drawableSpinner.GainedBonus.GetBoundCopy(); + gainedBonus.BindValueChanged(bonus => + { + bonusCounter.Text = $"{bonus.NewValue}"; + bonusCounter.FadeOutFromOne(800, Easing.Out); + bonusCounter.ScaleTo(SPRITE_SCALE * 2f).Then().ScaleTo(SPRITE_SCALE * 1.28f, 800, Easing.Out); + }); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index d4a403fbd2..ed09031fc1 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Skinning; using osuTK; +using static osu.Game.Skinning.LegacySkinConfiguration; namespace osu.Game.Rulesets.Osu.Skinning.Legacy { @@ -17,6 +18,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private bool hasSpinner => spinnerStyle.Value != SpinnerStyle.Modern; + private bool hasScoreFont => this.HasFont(GetConfig(LegacySetting.ScorePrefix)?.Value ?? "score"); + /// /// On osu-stable, hitcircles have 5 pixels of transparent padding on each side to allow for shadows etc. /// Their hittable area is 128px, but the actual circle portion is 118px. @@ -133,6 +136,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy return new LegacyOldStyleSpinner(); return null; + + case OsuSkinComponents.SpinnerBonusCounter: + if (hasSpinner && hasScoreFont) + return new LegacySpinnerBonusCounter(); + + return null; } return null; From ad1b86e33a566b009ee115812c72461d87388669 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 6 Mar 2021 18:54:25 +0100 Subject: [PATCH 163/390] Change `LifetimeEnd` idiom to `Expire()` for readability --- .../Skinning/Legacy/TaikoLegacySkinTransformer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs index 40dc149ec9..d97da40ef2 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy { // if a taiko skin is providing explosion sprites, hide the judgements completely if (hasExplosion.Value) - return Drawable.Empty().With(d => d.LifetimeEnd = double.MinValue); + return Drawable.Empty().With(d => d.Expire()); } if (!(component is TaikoSkinComponent taikoComponent)) @@ -118,7 +118,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy // suppress the default kiai explosion if the skin brings its own sprites. // the drawable needs to expire as soon as possible to avoid accumulating empty drawables on the playfield. if (hasExplosion.Value) - return Drawable.Empty().With(d => d.LifetimeEnd = double.MinValue); + return Drawable.Empty().With(d => d.Expire()); return null; From 3e4dfdb6755f7ea4bb721deb22029edb3dd408d0 Mon Sep 17 00:00:00 2001 From: Joehu Date: Sat, 6 Mar 2021 20:37:27 -0800 Subject: [PATCH 164/390] Fix pop out count being above displayed count on legacy combo counter --- osu.Game/Screens/Play/HUD/LegacyComboCounter.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs index 4784bca7dd..81b22b68b2 100644 --- a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs @@ -84,14 +84,14 @@ namespace osu.Game.Screens.Play.HUD { InternalChildren = new[] { - displayedCountSpriteText = createSpriteText().With(s => - { - s.Alpha = 0; - }), popOutCount = createSpriteText().With(s => { s.Alpha = 0; s.Margin = new MarginPadding(0.05f); + }), + displayedCountSpriteText = createSpriteText().With(s => + { + s.Alpha = 0; }) }; From 413cbb30a0f45b757941c053db0e983d1053d83f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 7 Mar 2021 13:39:46 +0300 Subject: [PATCH 165/390] Reword playfield shift counteract comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs index 5df8f8a485..9ce9fb9fd0 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs @@ -152,8 +152,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy Origin = Anchor.Centre; Size = new Vector2(640, 480); - // since legacy coordinates were on screen-space, they were accounting for the playfield shift offset. - // therefore cancel it from here. + // counteracts the playfield shift from OsuPlayfieldAdjustmentContainer. Position = new Vector2(0, -8f); } } From 503f29609a69451ee2cf0de0f5473bd1939495dd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 7 Mar 2021 23:40:09 +0900 Subject: [PATCH 166/390] Also set additive mode to match stable --- osu.Game/Screens/Play/HUD/LegacyComboCounter.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs index 81b22b68b2..81183a425a 100644 --- a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs @@ -88,6 +88,7 @@ namespace osu.Game.Screens.Play.HUD { s.Alpha = 0; s.Margin = new MarginPadding(0.05f); + s.Blending = BlendingParameters.Additive; }), displayedCountSpriteText = createSpriteText().With(s => { From fbfaa378fc25ce640eb809a0b3817511edb1042e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 7 Mar 2021 20:47:16 +0300 Subject: [PATCH 167/390] Move spinner top offset constant outside --- .../Skinning/Legacy/LegacyOldStyleSpinner.cs | 2 +- .../Skinning/Legacy/LegacySpinner.cs | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyOldStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyOldStyleSpinner.cs index 7e9f73a89b..5c25c38504 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyOldStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyOldStyleSpinner.cs @@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy // this anchor makes no sense, but that's what stable uses. Anchor = Anchor.TopLeft, Origin = Anchor.TopLeft, - Margin = new MarginPadding { Top = LegacyCoordinatesContainer.SPINNER_TOP_OFFSET }, + Margin = new MarginPadding { Top = SPINNER_TOP_OFFSET }, Masking = true, Child = metreSprite = new Sprite { diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs index 9ce9fb9fd0..421c43fd7a 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs @@ -16,6 +16,13 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { public abstract class LegacySpinner : CompositeDrawable { + /// + /// An offset that simulates stable's spinner top offset, can be used with + /// for positioning some legacy spinner components perfectly as in stable. + /// (e.g. 'spin' sprite, 'clear' sprite, metre in old-style spinners) + /// + public static readonly float SPINNER_TOP_OFFSET = (float)Math.Ceiling(45f * SPRITE_SCALE); + protected const float SPRITE_SCALE = 0.625f; protected DrawableSpinner DrawableSpinner { get; private set; } @@ -41,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy Origin = Anchor.Centre, Texture = source.GetTexture("spinner-spin"), Scale = new Vector2(SPRITE_SCALE), - Y = LegacyCoordinatesContainer.SPINNER_TOP_OFFSET + 335, + Y = SPINNER_TOP_OFFSET + 335, }, clear = new Sprite { @@ -50,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy Origin = Anchor.Centre, Texture = source.GetTexture("spinner-clear"), Scale = new Vector2(SPRITE_SCALE), - Y = LegacyCoordinatesContainer.SPINNER_TOP_OFFSET + 115, + Y = SPINNER_TOP_OFFSET + 115, }, } }); @@ -136,13 +143,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy /// protected class LegacyCoordinatesContainer : Container { - /// - /// An offset that simulates stable's spinner top offset, - /// for positioning some legacy spinner components perfectly as in stable. - /// (e.g. 'spin' sprite, 'clear' sprite, metre in old-style spinners) - /// - public static readonly float SPINNER_TOP_OFFSET = (float)Math.Ceiling(45f * SPRITE_SCALE); - public LegacyCoordinatesContainer() { // legacy spinners relied heavily on absolute screen-space coordinate values. From 0ad3073c1aa3863edbfb4c4f485ad970e507127e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 7 Mar 2021 21:21:44 +0300 Subject: [PATCH 168/390] Use MathF utility class instead Co-authored-by: Berkan Diler --- osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs index 421c43fd7a..406c19e76a 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy /// for positioning some legacy spinner components perfectly as in stable. /// (e.g. 'spin' sprite, 'clear' sprite, metre in old-style spinners) /// - public static readonly float SPINNER_TOP_OFFSET = (float)Math.Ceiling(45f * SPRITE_SCALE); + public static readonly float SPINNER_TOP_OFFSET = MathF.Ceiling(45f * SPRITE_SCALE); protected const float SPRITE_SCALE = 0.625f; From d961d110bf5294e26d7d51987dce30098a53e168 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 8 Mar 2021 02:58:52 +0000 Subject: [PATCH 169/390] Bump Microsoft.Extensions.Configuration.Abstractions from 2.2.0 to 5.0.0 Bumps [Microsoft.Extensions.Configuration.Abstractions](https://github.com/dotnet/runtime) from 2.2.0 to 5.0.0. - [Release notes](https://github.com/dotnet/runtime/releases) - [Commits](https://github.com/dotnet/runtime/commits/v5.0.0) Signed-off-by: dependabot-preview[bot] --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 2528292e17..c7aa6a8e11 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -26,7 +26,7 @@ - + From 74fc5d5b8cdb3452a69b70c59fa9c8c394103d3d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 8 Mar 2021 13:29:09 +0900 Subject: [PATCH 170/390] Fix potential cross-thread drawable mutation in IntroTriangles --- osu.Game/Screens/BackgroundScreen.cs | 2 ++ osu.Game/Screens/Menu/IntroTriangles.cs | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/BackgroundScreen.cs b/osu.Game/Screens/BackgroundScreen.cs index 48c5523883..a6fb94b151 100644 --- a/osu.Game/Screens/BackgroundScreen.cs +++ b/osu.Game/Screens/BackgroundScreen.cs @@ -13,6 +13,8 @@ namespace osu.Game.Screens { private readonly bool animateOnEnter; + public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks; + protected BackgroundScreen(bool animateOnEnter = true) { this.animateOnEnter = animateOnEnter; diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index ffe6882a72..abe6c62461 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -170,7 +170,7 @@ namespace osu.Game.Screens.Menu rulesets.Hide(); lazerLogo.Hide(); - background.Hide(); + background.ApplyToBackground(b => b.Hide()); using (BeginAbsoluteSequence(0, true)) { @@ -231,7 +231,8 @@ namespace osu.Game.Screens.Menu lazerLogo.Dispose(); // explicit disposal as we are pushing a new screen and the expire may not get run. logo.FadeIn(); - background.FadeIn(); + + background.ApplyToBackground(b => b.Show()); game.Add(new GameWideFlash()); From 7763e1dbe130233a61493ddf1cfacab1be0f3063 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 7 Mar 2021 12:39:46 +0900 Subject: [PATCH 171/390] Apply workaround for runtime iOS failures See https://github.com/mono/mono/issues/20805#issuecomment-791440473. --- osu.iOS.props | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.iOS.props b/osu.iOS.props index 56a24bea12..729d692e0e 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -77,12 +77,14 @@ $(NoWarn);NU1605 + - - - - - + + none + + + none + From 765cc5cf37120b9892e9233127573c5189b92456 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 8 Mar 2021 13:29:47 +0900 Subject: [PATCH 172/390] Remove iOS multiplayer blocking code --- osu.Game/Online/API/APIAccess.cs | 11 ++--------- osu.Game/Screens/Menu/ButtonSystem.cs | 12 ------------ 2 files changed, 2 insertions(+), 21 deletions(-) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 569481d491..ede64c0340 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -10,7 +10,6 @@ using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json.Linq; -using osu.Framework; using osu.Framework.Bindables; using osu.Framework.Extensions.ExceptionExtensions; using osu.Framework.Extensions.ObjectExtensions; @@ -247,14 +246,8 @@ namespace osu.Game.Online.API this.password = password; } - public IHubClientConnector GetHubConnector(string clientName, string endpoint) - { - // disabled until the underlying runtime issue is resolved, see https://github.com/mono/mono/issues/20805. - if (RuntimeInfo.OS == RuntimeInfo.Platform.iOS) - return null; - - return new HubClientConnector(clientName, endpoint, this, versionHash); - } + public IHubClientConnector GetHubConnector(string clientName, string endpoint) => + new HubClientConnector(clientName, endpoint, this, versionHash); public RegistrationRequest.RegistrationRequestErrors CreateAccount(string email, string username, string password) { diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index f93bfd7705..81b1cb0bf1 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -172,18 +172,6 @@ namespace osu.Game.Screens.Menu return; } - // disabled until the underlying runtime issue is resolved, see https://github.com/mono/mono/issues/20805. - if (RuntimeInfo.OS == RuntimeInfo.Platform.iOS) - { - notifications?.Post(new SimpleNotification - { - Text = "Multiplayer is temporarily unavailable on iOS as we figure out some low level issues.", - Icon = FontAwesome.Solid.AppleAlt, - }); - - return; - } - OnMultiplayer?.Invoke(); } From b1cd01ceb82b9d04ea0c20b6a15392b2413f6bc4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 8 Mar 2021 12:57:16 +0900 Subject: [PATCH 173/390] Apply ConfigureAwait changes to game side --- CodeAnalysis/osu.ruleset | 2 +- osu.Desktop/Updater/SquirrelUpdateManager.cs | 14 ++++++------ osu.Game/Beatmaps/BeatmapManager.cs | 2 +- osu.Game/Collections/CollectionManager.cs | 4 ++-- osu.Game/Database/ArchiveModelManager.cs | 15 ++++++------- .../DownloadableArchiveModelManager.cs | 2 +- osu.Game/Database/MemoryCachingComponent.cs | 2 +- osu.Game/Database/UserLookupCache.cs | 2 +- osu.Game/Graphics/ScreenshotManager.cs | 6 ++--- osu.Game/IO/Archives/ArchiveReader.cs | 2 +- osu.Game/IPC/ArchiveImportIPCChannel.cs | 4 ++-- osu.Game/Online/HubClientConnector.cs | 12 +++++----- .../Multiplayer/StatefulMultiplayerClient.cs | 22 +++++++++---------- osu.Game/OsuGame.cs | 8 +++---- osu.Game/OsuGameBase.cs | 4 ++-- osu.Game/Overlays/ChangelogOverlay.cs | 4 ++-- osu.Game/Scoring/ScorePerformanceCache.cs | 2 +- osu.Game/Screens/Import/FileImportScreen.cs | 2 +- .../Multiplayer/MultiplayerPlayer.cs | 6 ++--- .../OnlinePlay/Playlists/PlaylistsPlayer.cs | 4 ++-- osu.Game/Screens/Play/Player.cs | 4 ++-- osu.Game/Skinning/SkinManager.cs | 2 +- .../Multiplayer/TestMultiplayerClient.cs | 2 +- osu.Game/Updater/SimpleUpdateManager.cs | 2 +- osu.Game/Updater/UpdateManager.cs | 2 +- 25 files changed, 65 insertions(+), 66 deletions(-) diff --git a/CodeAnalysis/osu.ruleset b/CodeAnalysis/osu.ruleset index d497365f87..6a99e230d1 100644 --- a/CodeAnalysis/osu.ruleset +++ b/CodeAnalysis/osu.ruleset @@ -30,7 +30,7 @@ - + diff --git a/osu.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/SquirrelUpdateManager.cs index 71f9fafe57..47cd39dc5a 100644 --- a/osu.Desktop/Updater/SquirrelUpdateManager.cs +++ b/osu.Desktop/Updater/SquirrelUpdateManager.cs @@ -42,7 +42,7 @@ namespace osu.Desktop.Updater Splat.Locator.CurrentMutable.Register(() => new SquirrelLogger(), typeof(Splat.ILogger)); } - protected override async Task PerformUpdateCheck() => await checkForUpdateAsync(); + protected override async Task PerformUpdateCheck() => await checkForUpdateAsync().ConfigureAwait(false); private async Task checkForUpdateAsync(bool useDeltaPatching = true, UpdateProgressNotification notification = null) { @@ -51,9 +51,9 @@ namespace osu.Desktop.Updater try { - updateManager ??= await UpdateManager.GitHubUpdateManager(@"https://github.com/ppy/osu", @"osulazer", null, null, true); + updateManager ??= await UpdateManager.GitHubUpdateManager(@"https://github.com/ppy/osu", @"osulazer", null, null, true).ConfigureAwait(false); - var info = await updateManager.CheckForUpdate(!useDeltaPatching); + var info = await updateManager.CheckForUpdate(!useDeltaPatching).ConfigureAwait(false); if (info.ReleasesToApply.Count == 0) { @@ -79,12 +79,12 @@ namespace osu.Desktop.Updater try { - await updateManager.DownloadReleases(info.ReleasesToApply, p => notification.Progress = p / 100f); + await updateManager.DownloadReleases(info.ReleasesToApply, p => notification.Progress = p / 100f).ConfigureAwait(false); notification.Progress = 0; notification.Text = @"Installing update..."; - await updateManager.ApplyReleases(info, p => notification.Progress = p / 100f); + await updateManager.ApplyReleases(info, p => notification.Progress = p / 100f).ConfigureAwait(false); notification.State = ProgressNotificationState.Completed; updatePending = true; @@ -97,7 +97,7 @@ namespace osu.Desktop.Updater // could fail if deltas are unavailable for full update path (https://github.com/Squirrel/Squirrel.Windows/issues/959) // try again without deltas. - await checkForUpdateAsync(false, notification); + await checkForUpdateAsync(false, notification).ConfigureAwait(false); scheduleRecheck = false; } else @@ -116,7 +116,7 @@ namespace osu.Desktop.Updater if (scheduleRecheck) { // check again in 30 minutes. - Scheduler.AddDelayed(async () => await checkForUpdateAsync(), 60000 * 30); + Scheduler.AddDelayed(async () => await checkForUpdateAsync().ConfigureAwait(false), 60000 * 30); } } diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index d653e5386b..29b3f5d3a3 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -156,7 +156,7 @@ namespace osu.Game.Beatmaps bool hadOnlineBeatmapIDs = beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID > 0); if (onlineLookupQueue != null) - await onlineLookupQueue.UpdateAsync(beatmapSet, cancellationToken); + await onlineLookupQueue.UpdateAsync(beatmapSet, cancellationToken).ConfigureAwait(false); // ensure at least one beatmap was able to retrieve or keep an online ID, else drop the set ID. if (hadOnlineBeatmapIDs && !beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID > 0)) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index fb9c230c7a..9723409c79 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -124,7 +124,7 @@ namespace osu.Game.Collections return Task.Run(async () => { using (var stream = stable.GetStream(database_name)) - await Import(stream); + await Import(stream).ConfigureAwait(false); }); } @@ -139,7 +139,7 @@ namespace osu.Game.Collections PostNotification?.Invoke(notification); var collections = readCollections(stream, notification); - await importCollections(collections); + await importCollections(collections).ConfigureAwait(false); notification.CompletionText = $"Imported {collections.Count} collections"; notification.State = ProgressNotificationState.Completed; diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index daaba9098e..d809dbcb01 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -22,7 +22,6 @@ using osu.Game.IO.Archives; using osu.Game.IPC; using osu.Game.Overlays.Notifications; using SharpCompress.Archives.Zip; -using FileInfo = osu.Game.IO.FileInfo; namespace osu.Game.Database { @@ -163,7 +162,7 @@ namespace osu.Game.Database try { - var model = await Import(task, isLowPriorityImport, notification.CancellationToken); + var model = await Import(task, isLowPriorityImport, notification.CancellationToken).ConfigureAwait(false); lock (imported) { @@ -183,7 +182,7 @@ namespace osu.Game.Database { Logger.Error(e, $@"Could not import ({task})", LoggingTarget.Database); } - })); + })).ConfigureAwait(false); if (imported.Count == 0) { @@ -226,7 +225,7 @@ namespace osu.Game.Database TModel import; using (ArchiveReader reader = task.GetReader()) - import = await Import(reader, lowPriority, cancellationToken); + import = await Import(reader, lowPriority, cancellationToken).ConfigureAwait(false); // We may or may not want to delete the file depending on where it is stored. // e.g. reconstructing/repairing database with items from default storage. @@ -358,7 +357,7 @@ namespace osu.Game.Database item.Files = archive != null ? createFileInfos(archive, Files) : new List(); item.Hash = ComputeHash(item, archive); - await Populate(item, archive, cancellationToken); + await Populate(item, archive, cancellationToken).ConfigureAwait(false); using (var write = ContextFactory.GetForWrite()) // used to share a context for full import. keep in mind this will block all writes. { @@ -410,7 +409,7 @@ namespace osu.Game.Database flushEvents(true); return item; - }, cancellationToken, TaskCreationOptions.HideScheduler, lowPriority ? import_scheduler_low_priority : import_scheduler).Unwrap(); + }, cancellationToken, TaskCreationOptions.HideScheduler, lowPriority ? import_scheduler_low_priority : import_scheduler).Unwrap().ConfigureAwait(false); /// /// Exports an item to a legacy (.zip based) package. @@ -621,7 +620,7 @@ namespace osu.Game.Database } /// - /// Create all required s for the provided archive, adding them to the global file store. + /// Create all required s for the provided archive, adding them to the global file store. /// private List createFileInfos(ArchiveReader reader, FileStore files) { @@ -699,7 +698,7 @@ namespace osu.Game.Database return Task.CompletedTask; } - return Task.Run(async () => await Import(GetStableImportPaths(storage).ToArray())); + return Task.Run(async () => await Import(GetStableImportPaths(storage).ToArray()).ConfigureAwait(false)); } /// diff --git a/osu.Game/Database/DownloadableArchiveModelManager.cs b/osu.Game/Database/DownloadableArchiveModelManager.cs index 50b022f9ff..da3144e8d0 100644 --- a/osu.Game/Database/DownloadableArchiveModelManager.cs +++ b/osu.Game/Database/DownloadableArchiveModelManager.cs @@ -82,7 +82,7 @@ namespace osu.Game.Database Task.Factory.StartNew(async () => { // This gets scheduled back to the update thread, but we want the import to run in the background. - var imported = await Import(notification, new ImportTask(filename)); + var imported = await Import(notification, new ImportTask(filename)).ConfigureAwait(false); // for now a failed import will be marked as a failed download for simplicity. if (!imported.Any()) diff --git a/osu.Game/Database/MemoryCachingComponent.cs b/osu.Game/Database/MemoryCachingComponent.cs index d913e66428..a1a1279d71 100644 --- a/osu.Game/Database/MemoryCachingComponent.cs +++ b/osu.Game/Database/MemoryCachingComponent.cs @@ -29,7 +29,7 @@ namespace osu.Game.Database if (CheckExists(lookup, out TValue performance)) return performance; - var computed = await ComputeValueAsync(lookup, token); + var computed = await ComputeValueAsync(lookup, token).ConfigureAwait(false); if (computed != null || CacheNullValues) cache[lookup] = computed; diff --git a/osu.Game/Database/UserLookupCache.cs b/osu.Game/Database/UserLookupCache.cs index 568726199c..19cc211709 100644 --- a/osu.Game/Database/UserLookupCache.cs +++ b/osu.Game/Database/UserLookupCache.cs @@ -28,7 +28,7 @@ namespace osu.Game.Database public Task GetUserAsync(int userId, CancellationToken token = default) => GetAsync(userId, token); protected override async Task ComputeValueAsync(int lookup, CancellationToken token = default) - => await queryUser(lookup); + => await queryUser(lookup).ConfigureAwait(false); private readonly Queue<(int id, TaskCompletionSource)> pendingUserTasks = new Queue<(int, TaskCompletionSource)>(); private Task pendingRequestTask; diff --git a/osu.Game/Graphics/ScreenshotManager.cs b/osu.Game/Graphics/ScreenshotManager.cs index f7914cbbca..fb7fe4947b 100644 --- a/osu.Game/Graphics/ScreenshotManager.cs +++ b/osu.Game/Graphics/ScreenshotManager.cs @@ -103,7 +103,7 @@ namespace osu.Game.Graphics } } - using (var image = await host.TakeScreenshotAsync()) + using (var image = await host.TakeScreenshotAsync().ConfigureAwait(false)) { if (Interlocked.Decrement(ref screenShotTasks) == 0 && cursorVisibility.Value == false) cursorVisibility.Value = true; @@ -116,13 +116,13 @@ namespace osu.Game.Graphics switch (screenshotFormat.Value) { case ScreenshotFormat.Png: - await image.SaveAsPngAsync(stream); + await image.SaveAsPngAsync(stream).ConfigureAwait(false); break; case ScreenshotFormat.Jpg: const int jpeg_quality = 92; - await image.SaveAsJpegAsync(stream, new JpegEncoder { Quality = jpeg_quality }); + await image.SaveAsJpegAsync(stream, new JpegEncoder { Quality = jpeg_quality }).ConfigureAwait(false); break; default: diff --git a/osu.Game/IO/Archives/ArchiveReader.cs b/osu.Game/IO/Archives/ArchiveReader.cs index f74574e60c..679ab40402 100644 --- a/osu.Game/IO/Archives/ArchiveReader.cs +++ b/osu.Game/IO/Archives/ArchiveReader.cs @@ -41,7 +41,7 @@ namespace osu.Game.IO.Archives return null; byte[] buffer = new byte[input.Length]; - await input.ReadAsync(buffer); + await input.ReadAsync(buffer).ConfigureAwait(false); return buffer; } } diff --git a/osu.Game/IPC/ArchiveImportIPCChannel.cs b/osu.Game/IPC/ArchiveImportIPCChannel.cs index 029908ec9d..d9d0e4c0ea 100644 --- a/osu.Game/IPC/ArchiveImportIPCChannel.cs +++ b/osu.Game/IPC/ArchiveImportIPCChannel.cs @@ -33,12 +33,12 @@ namespace osu.Game.IPC if (importer == null) { // we want to contact a remote osu! to handle the import. - await SendMessageAsync(new ArchiveImportMessage { Path = path }); + await SendMessageAsync(new ArchiveImportMessage { Path = path }).ConfigureAwait(false); return; } if (importer.HandledExtensions.Contains(Path.GetExtension(path)?.ToLowerInvariant())) - await importer.Import(path); + await importer.Import(path).ConfigureAwait(false); } } diff --git a/osu.Game/Online/HubClientConnector.cs b/osu.Game/Online/HubClientConnector.cs index fdb21c5000..3839762e46 100644 --- a/osu.Game/Online/HubClientConnector.cs +++ b/osu.Game/Online/HubClientConnector.cs @@ -79,7 +79,7 @@ namespace osu.Game.Online { cancelExistingConnect(); - if (!await connectionLock.WaitAsync(10000)) + if (!await connectionLock.WaitAsync(10000).ConfigureAwait(false)) throw new TimeoutException("Could not obtain a lock to connect. A previous attempt is likely stuck."); try @@ -88,7 +88,7 @@ namespace osu.Game.Online { // ensure any previous connection was disposed. // this will also create a new cancellation token source. - await disconnect(false); + await disconnect(false).ConfigureAwait(false); // this token will be valid for the scope of this connection. // if cancelled, we can be sure that a disconnect or reconnect is handled elsewhere. @@ -103,7 +103,7 @@ namespace osu.Game.Online // importantly, rebuild the connection each attempt to get an updated access token. CurrentConnection = buildConnection(cancellationToken); - await CurrentConnection.StartAsync(cancellationToken); + await CurrentConnection.StartAsync(cancellationToken).ConfigureAwait(false); Logger.Log($"{clientName} connected!", LoggingTarget.Network); isConnected.Value = true; @@ -119,7 +119,7 @@ namespace osu.Game.Online Logger.Log($"{clientName} connection error: {e}", LoggingTarget.Network); // retry on any failure. - await Task.Delay(5000, cancellationToken); + await Task.Delay(5000, cancellationToken).ConfigureAwait(false); } } } @@ -174,14 +174,14 @@ namespace osu.Game.Online if (takeLock) { - if (!await connectionLock.WaitAsync(10000)) + if (!await connectionLock.WaitAsync(10000).ConfigureAwait(false)) throw new TimeoutException("Could not obtain a lock to disconnect. A previous attempt is likely stuck."); } try { if (CurrentConnection != null) - await CurrentConnection.DisposeAsync(); + await CurrentConnection.DisposeAsync().ConfigureAwait(false); } finally { diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index 73100be505..0f7050596f 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -131,12 +131,12 @@ namespace osu.Game.Online.Multiplayer Debug.Assert(room.RoomID.Value != null); // Join the server-side room. - var joinedRoom = await JoinRoom(room.RoomID.Value.Value); + var joinedRoom = await JoinRoom(room.RoomID.Value.Value).ConfigureAwait(false); Debug.Assert(joinedRoom != null); // Populate users. Debug.Assert(joinedRoom.Users != null); - await Task.WhenAll(joinedRoom.Users.Select(PopulateUser)); + await Task.WhenAll(joinedRoom.Users.Select(PopulateUser)).ConfigureAwait(false); // Update the stored room (must be done on update thread for thread-safety). await scheduleAsync(() => @@ -144,11 +144,11 @@ namespace osu.Game.Online.Multiplayer Room = joinedRoom; apiRoom = room; defaultPlaylistItemId = apiRoom.Playlist.FirstOrDefault()?.ID ?? 0; - }, cancellationSource.Token); + }, cancellationSource.Token).ConfigureAwait(false); // Update room settings. - await updateLocalRoomSettings(joinedRoom.Settings, cancellationSource.Token); - }, cancellationSource.Token); + await updateLocalRoomSettings(joinedRoom.Settings, cancellationSource.Token).ConfigureAwait(false); + }, cancellationSource.Token).ConfigureAwait(false); } /// @@ -178,8 +178,8 @@ namespace osu.Game.Online.Multiplayer return joinOrLeaveTaskChain.Add(async () => { - await scheduledReset; - await LeaveRoomInternal(); + await scheduledReset.ConfigureAwait(false); + await LeaveRoomInternal().ConfigureAwait(false); }); } @@ -237,11 +237,11 @@ namespace osu.Game.Online.Multiplayer switch (localUser.State) { case MultiplayerUserState.Idle: - await ChangeState(MultiplayerUserState.Ready); + await ChangeState(MultiplayerUserState.Ready).ConfigureAwait(false); return; case MultiplayerUserState.Ready: - await ChangeState(MultiplayerUserState.Idle); + await ChangeState(MultiplayerUserState.Idle).ConfigureAwait(false); return; default: @@ -307,7 +307,7 @@ namespace osu.Game.Online.Multiplayer if (Room == null) return; - await PopulateUser(user); + await PopulateUser(user).ConfigureAwait(false); Scheduler.Add(() => { @@ -486,7 +486,7 @@ namespace osu.Game.Online.Multiplayer /// Populates the for a given . /// /// The to populate. - protected async Task PopulateUser(MultiplayerRoomUser multiplayerUser) => multiplayerUser.User ??= await userLookupCache.GetUserAsync(multiplayerUser.UserID); + protected async Task PopulateUser(MultiplayerRoomUser multiplayerUser) => multiplayerUser.User ??= await userLookupCache.GetUserAsync(multiplayerUser.UserID).ConfigureAwait(false); /// /// Updates the local room settings with the given . diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 203cc458e0..b7398efdc2 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -440,7 +440,7 @@ namespace osu.Game public override Task Import(params ImportTask[] imports) { // encapsulate task as we don't want to begin the import process until in a ready state. - var importTask = new Task(async () => await base.Import(imports)); + var importTask = new Task(async () => await base.Import(imports).ConfigureAwait(false)); waitForReady(() => this, _ => importTask.Start()); @@ -831,7 +831,7 @@ namespace osu.Game asyncLoadStream = Task.Run(async () => { if (previousLoadStream != null) - await previousLoadStream; + await previousLoadStream.ConfigureAwait(false); try { @@ -845,7 +845,7 @@ namespace osu.Game // The delegate won't complete if OsuGame has been disposed in the meantime while (!IsDisposed && !del.Completed) - await Task.Delay(10); + await Task.Delay(10).ConfigureAwait(false); // Either we're disposed or the load process has started successfully if (IsDisposed) @@ -853,7 +853,7 @@ namespace osu.Game Debug.Assert(task != null); - await task; + await task.ConfigureAwait(false); Logger.Log($"Loaded {component}!", level: LogLevel.Debug); } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 3d24f245f9..e1c7b67a8c 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -434,7 +434,7 @@ namespace osu.Game foreach (var importer in fileImporters) { if (importer.HandledExtensions.Contains(extension)) - await importer.Import(paths); + await importer.Import(paths).ConfigureAwait(false); } } @@ -445,7 +445,7 @@ namespace osu.Game { var importer = fileImporters.FirstOrDefault(i => i.HandledExtensions.Contains(taskGroup.Key)); return importer?.Import(taskGroup.ToArray()) ?? Task.CompletedTask; - })); + })).ConfigureAwait(false); } public IEnumerable HandledExtensions => fileImporters.SelectMany(i => i.HandledExtensions); diff --git a/osu.Game/Overlays/ChangelogOverlay.cs b/osu.Game/Overlays/ChangelogOverlay.cs index 537dd00727..2da5be5e6c 100644 --- a/osu.Game/Overlays/ChangelogOverlay.cs +++ b/osu.Game/Overlays/ChangelogOverlay.cs @@ -160,9 +160,9 @@ namespace osu.Game.Overlays tcs.SetException(e); }; - await API.PerformAsync(req); + await API.PerformAsync(req).ConfigureAwait(false); - await tcs.Task; + return tcs.Task; }); } diff --git a/osu.Game/Scoring/ScorePerformanceCache.cs b/osu.Game/Scoring/ScorePerformanceCache.cs index 5f66c13d2f..bb15983de3 100644 --- a/osu.Game/Scoring/ScorePerformanceCache.cs +++ b/osu.Game/Scoring/ScorePerformanceCache.cs @@ -34,7 +34,7 @@ namespace osu.Game.Scoring { var score = lookup.ScoreInfo; - var attributes = await difficultyCache.GetDifficultyAsync(score.Beatmap, score.Ruleset, score.Mods, token); + var attributes = await difficultyCache.GetDifficultyAsync(score.Beatmap, score.Ruleset, score.Mods, token).ConfigureAwait(false); // Performance calculation requires the beatmap and ruleset to be locally available. If not, return a default value. if (attributes.Attributes == null) diff --git a/osu.Game/Screens/Import/FileImportScreen.cs b/osu.Game/Screens/Import/FileImportScreen.cs index 329623e03a..ee8ef6926d 100644 --- a/osu.Game/Screens/Import/FileImportScreen.cs +++ b/osu.Game/Screens/Import/FileImportScreen.cs @@ -154,7 +154,7 @@ namespace osu.Game.Screens.Import Task.Factory.StartNew(async () => { - await game.Import(path); + await game.Import(path).ConfigureAwait(false); // some files will be deleted after successful import, so we want to refresh the view. Schedule(() => diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs index ffcf248575..b3cd44d55a 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs @@ -137,13 +137,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override async Task SubmitScore(Score score) { - await base.SubmitScore(score); + await base.SubmitScore(score).ConfigureAwait(false); - await client.ChangeState(MultiplayerUserState.FinishedPlay); + await client.ChangeState(MultiplayerUserState.FinishedPlay).ConfigureAwait(false); // Await up to 60 seconds for results to become available (6 api request timeouts). // This is arbitrary just to not leave the player in an essentially deadlocked state if any connection issues occur. - await Task.WhenAny(resultsReady.Task, Task.Delay(TimeSpan.FromSeconds(60))); + await Task.WhenAny(resultsReady.Task, Task.Delay(TimeSpan.FromSeconds(60))).ConfigureAwait(false); } protected override ResultsScreen CreateResults(ScoreInfo score) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs index ddc88261f7..a75e4bdc07 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs @@ -108,7 +108,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists protected override async Task SubmitScore(Score score) { - await base.SubmitScore(score); + await base.SubmitScore(score).ConfigureAwait(false); Debug.Assert(Token != null); @@ -128,7 +128,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists }; api.Queue(request); - await tcs.Task; + await tcs.Task.ConfigureAwait(false); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index e81efdac78..0e221351aa 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -592,7 +592,7 @@ namespace osu.Game.Screens.Play try { - await SubmitScore(score); + await SubmitScore(score).ConfigureAwait(false); } catch (Exception ex) { @@ -601,7 +601,7 @@ namespace osu.Game.Screens.Play try { - await ImportScore(score); + await ImportScore(score).ConfigureAwait(false); } catch (Exception ex) { diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 2826c826a5..fcde9f041b 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -120,7 +120,7 @@ namespace osu.Game.Skinning protected override async Task Populate(SkinInfo model, ArchiveReader archive, CancellationToken cancellationToken = default) { - await base.Populate(model, archive, cancellationToken); + await base.Populate(model, archive, cancellationToken).ConfigureAwait(false); if (model.Name?.Contains(".osk") == true) populateMetadata(model); diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index c03364a391..09fcc1ff47 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -136,7 +136,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Debug.Assert(Room != null); - await ((IMultiplayerClient)this).SettingsChanged(settings); + await ((IMultiplayerClient)this).SettingsChanged(settings).ConfigureAwait(false); foreach (var user in Room.Users.Where(u => u.State == MultiplayerUserState.Ready)) ChangeUserState(user.UserID, MultiplayerUserState.Idle); diff --git a/osu.Game/Updater/SimpleUpdateManager.cs b/osu.Game/Updater/SimpleUpdateManager.cs index 4ebf2a7368..6eded7ce53 100644 --- a/osu.Game/Updater/SimpleUpdateManager.cs +++ b/osu.Game/Updater/SimpleUpdateManager.cs @@ -37,7 +37,7 @@ namespace osu.Game.Updater { var releases = new OsuJsonWebRequest("https://api.github.com/repos/ppy/osu/releases/latest"); - await releases.PerformAsync(); + await releases.PerformAsync().ConfigureAwait(false); var latest = releases.ResponseObject; diff --git a/osu.Game/Updater/UpdateManager.cs b/osu.Game/Updater/UpdateManager.cs index f772c6d282..9a0454bc95 100644 --- a/osu.Game/Updater/UpdateManager.cs +++ b/osu.Game/Updater/UpdateManager.cs @@ -69,7 +69,7 @@ namespace osu.Game.Updater lock (updateTaskLock) waitTask = (updateCheckTask ??= PerformUpdateCheck()); - bool hasUpdates = await waitTask; + bool hasUpdates = await waitTask.ConfigureAwait(false); lock (updateTaskLock) updateCheckTask = null; From d2bc48e57650d0ff2c1ec9cf840f4258f90b786b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 8 Mar 2021 12:57:30 +0900 Subject: [PATCH 174/390] Exclude tests from ConfigureAwait rule --- osu.Game.Tests/osu.Game.Tests.csproj | 3 +++ osu.Game.Tests/tests.ruleset | 6 ++++++ 2 files changed, 9 insertions(+) create mode 100644 osu.Game.Tests/tests.ruleset diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 32ccb5b699..e36b3cdc74 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -13,6 +13,9 @@ WinExe net5.0 + + tests.ruleset + diff --git a/osu.Game.Tests/tests.ruleset b/osu.Game.Tests/tests.ruleset new file mode 100644 index 0000000000..a0abb781d3 --- /dev/null +++ b/osu.Game.Tests/tests.ruleset @@ -0,0 +1,6 @@ + + + + + + From 6cb0db9c33c41312e9a380168d41327794a2288c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 8 Mar 2021 14:45:11 +0900 Subject: [PATCH 175/390] Apply override rules to iOS/Android test projects --- osu.Game.Tests.Android/osu.Game.Tests.Android.csproj | 5 ++++- osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj b/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj index 543f2f35a7..c3d9cb5875 100644 --- a/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj +++ b/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj @@ -20,6 +20,9 @@ + + $(NoWarn);CA2007 + %(RecursiveDir)%(Filename)%(Extension) @@ -74,4 +77,4 @@ - \ No newline at end of file + diff --git a/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj b/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj index e83bef4a95..97df9b2cd5 100644 --- a/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj +++ b/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj @@ -21,6 +21,9 @@ %(RecursiveDir)%(Filename)%(Extension) + + $(NoWarn);CA2007 + {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D} @@ -48,4 +51,4 @@ - \ No newline at end of file + From 02194a93cb324e9a3781e82a73ce47ae6ce40f45 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 8 Mar 2021 15:17:10 +0900 Subject: [PATCH 176/390] Apply missing additions to android project --- osu.Android/OsuGameActivity.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs index d087c6218d..cffcea22c2 100644 --- a/osu.Android/OsuGameActivity.cs +++ b/osu.Android/OsuGameActivity.cs @@ -100,15 +100,15 @@ namespace osu.Android // copy to an arbitrary-access memory stream to be able to proceed with the import. var copy = new MemoryStream(); using (var stream = ContentResolver.OpenInputStream(uri)) - await stream.CopyToAsync(copy); + await stream.CopyToAsync(copy).ConfigureAwait(false); lock (tasks) { tasks.Add(new ImportTask(copy, filename)); } - })); + })).ConfigureAwait(false); - await game.Import(tasks.ToArray()); + await game.Import(tasks.ToArray()).ConfigureAwait(false); }, TaskCreationOptions.LongRunning); } } From bb79da1aacfd45dc73d1f493bd5e0400327dc61f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 9 Mar 2021 00:33:43 +0300 Subject: [PATCH 177/390] Correct playfield shift counteract comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs index 406c19e76a..896c3f4a3e 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs @@ -152,7 +152,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy Origin = Anchor.Centre; Size = new Vector2(640, 480); - // counteracts the playfield shift from OsuPlayfieldAdjustmentContainer. + // stable applies this adjustment conditionally, locally in the spinner. + // in lazer this is handled at a higher level in OsuPlayfieldAdjustmentContainer, + // therefore it's safe to apply it unconditionally in this component. Position = new Vector2(0, -8f); } } From dc9028d24acd3df3152057f36f0aa0217b9c954a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Mar 2021 14:27:20 +0900 Subject: [PATCH 178/390] Update framework --- osu.Android.props | 2 +- osu.Game/Updater/SimpleUpdateManager.cs | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index c428cd2546..5b700224db 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/Updater/SimpleUpdateManager.cs b/osu.Game/Updater/SimpleUpdateManager.cs index 6eded7ce53..50572a7867 100644 --- a/osu.Game/Updater/SimpleUpdateManager.cs +++ b/osu.Game/Updater/SimpleUpdateManager.cs @@ -77,7 +77,7 @@ namespace osu.Game.Updater bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".exe", StringComparison.Ordinal)); break; - case RuntimeInfo.Platform.MacOsx: + case RuntimeInfo.Platform.macOS: bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".app.zip", StringComparison.Ordinal)); break; diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index c7aa6a8e11..90c8b98f42 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -29,7 +29,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 729d692e0e..ccd33bf88c 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 05493958696d488c4a8b76582ee0f92d5e7cf75b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 9 Mar 2021 08:55:32 +0300 Subject: [PATCH 179/390] Inline "legacy coordinates container" and add "spinner Y centre" const --- .../Skinning/Legacy/LegacyNewStyleSpinner.cs | 3 +- .../Skinning/Legacy/LegacyOldStyleSpinner.cs | 33 ++++---- .../Skinning/Legacy/LegacySpinner.cs | 82 ++++++++----------- 3 files changed, 50 insertions(+), 68 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs index efeca53969..22fb3aab86 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs @@ -37,9 +37,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy AddInternal(scaleContainer = new Container { Scale = new Vector2(SPRITE_SCALE), - Anchor = Anchor.Centre, + Anchor = Anchor.TopCentre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, + Y = SPINNER_Y_CENTRE, Children = new Drawable[] { glow = new Sprite diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyOldStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyOldStyleSpinner.cs index 5c25c38504..19cb55c16e 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyOldStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyOldStyleSpinner.cs @@ -37,35 +37,34 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { new Sprite { - Anchor = Anchor.Centre, + Anchor = Anchor.TopCentre, Origin = Anchor.Centre, Texture = source.GetTexture("spinner-background"), - Scale = new Vector2(SPRITE_SCALE) + Scale = new Vector2(SPRITE_SCALE), + Y = SPINNER_Y_CENTRE, }, disc = new Sprite { - Anchor = Anchor.Centre, + Anchor = Anchor.TopCentre, Origin = Anchor.Centre, Texture = source.GetTexture("spinner-circle"), - Scale = new Vector2(SPRITE_SCALE) + Scale = new Vector2(SPRITE_SCALE), + Y = SPINNER_Y_CENTRE, }, - new LegacyCoordinatesContainer + metre = new Container { - Child = metre = new Container + AutoSizeAxes = Axes.Both, + // this anchor makes no sense, but that's what stable uses. + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + Margin = new MarginPadding { Top = SPINNER_TOP_OFFSET }, + Masking = true, + Child = metreSprite = new Sprite { - AutoSizeAxes = Axes.Both, - // this anchor makes no sense, but that's what stable uses. + Texture = source.GetTexture("spinner-metre"), Anchor = Anchor.TopLeft, Origin = Anchor.TopLeft, - Margin = new MarginPadding { Top = SPINNER_TOP_OFFSET }, - Masking = true, - Child = metreSprite = new Sprite - { - Texture = source.GetTexture("spinner-metre"), - Anchor = Anchor.TopLeft, - Origin = Anchor.TopLeft, - Scale = new Vector2(SPRITE_SCALE) - } + Scale = new Vector2(SPRITE_SCALE) } } }); diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs index 896c3f4a3e..1738003390 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs @@ -16,12 +16,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { public abstract class LegacySpinner : CompositeDrawable { - /// - /// An offset that simulates stable's spinner top offset, can be used with - /// for positioning some legacy spinner components perfectly as in stable. - /// (e.g. 'spin' sprite, 'clear' sprite, metre in old-style spinners) - /// - public static readonly float SPINNER_TOP_OFFSET = MathF.Ceiling(45f * SPRITE_SCALE); + protected static readonly float SPINNER_TOP_OFFSET = MathF.Ceiling(45f * SPRITE_SCALE); + protected static readonly float SPINNER_Y_CENTRE = SPINNER_TOP_OFFSET + 219f; protected const float SPRITE_SCALE = 0.625f; @@ -33,33 +29,41 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy [BackgroundDependencyLoader] private void load(DrawableHitObject drawableHitObject, ISkinSource source) { - RelativeSizeAxes = Axes.Both; + // legacy spinners relied heavily on absolute screen-space coordinate values. + // wrap everything in a container simulating absolute coords to preserve alignment + // as there are skins that depend on it. + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + Size = new Vector2(640, 480); + + // stable applies this adjustment conditionally, locally in the spinner. + // in lazer this is handled at a higher level in OsuPlayfieldAdjustmentContainer, + // therefore it's safe to apply it unconditionally in this component. + Position = new Vector2(0, -8f); DrawableSpinner = (DrawableSpinner)drawableHitObject; - AddInternal(new LegacyCoordinatesContainer + AddRangeInternal(new[] { - Depth = float.MinValue, - Children = new Drawable[] + spin = new Sprite { - spin = new Sprite - { - Anchor = Anchor.TopCentre, - Origin = Anchor.Centre, - Texture = source.GetTexture("spinner-spin"), - Scale = new Vector2(SPRITE_SCALE), - Y = SPINNER_TOP_OFFSET + 335, - }, - clear = new Sprite - { - Alpha = 0, - Anchor = Anchor.TopCentre, - Origin = Anchor.Centre, - Texture = source.GetTexture("spinner-clear"), - Scale = new Vector2(SPRITE_SCALE), - Y = SPINNER_TOP_OFFSET + 115, - }, - } + Anchor = Anchor.TopCentre, + Origin = Anchor.Centre, + Depth = float.MinValue, + Texture = source.GetTexture("spinner-spin"), + Scale = new Vector2(SPRITE_SCALE), + Y = SPINNER_TOP_OFFSET + 335, + }, + clear = new Sprite + { + Alpha = 0, + Anchor = Anchor.TopCentre, + Origin = Anchor.Centre, + Depth = float.MinValue, + Texture = source.GetTexture("spinner-clear"), + Scale = new Vector2(SPRITE_SCALE), + Y = SPINNER_TOP_OFFSET + 115, + }, }); } @@ -136,27 +140,5 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy if (DrawableSpinner != null) DrawableSpinner.ApplyCustomUpdateState -= UpdateStateTransforms; } - - /// - /// A simulating osu!stable's absolute screen-space, - /// for perfect placements of legacy spinner components with legacy coordinates. - /// - protected class LegacyCoordinatesContainer : Container - { - public LegacyCoordinatesContainer() - { - // legacy spinners relied heavily on absolute screen-space coordinate values. - // wrap everything in a container simulating absolute coords to preserve alignment - // as there are skins that depend on it. - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - Size = new Vector2(640, 480); - - // stable applies this adjustment conditionally, locally in the spinner. - // in lazer this is handled at a higher level in OsuPlayfieldAdjustmentContainer, - // therefore it's safe to apply it unconditionally in this component. - Position = new Vector2(0, -8f); - } - } } } From a5b3ac7ef8101c867bec8c1188ec4595ccb1c919 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Mar 2021 15:45:03 +0900 Subject: [PATCH 180/390] Add failing test covering alpha commands proceeding non-alpha (but ignored) commands --- .../Visual/Gameplay/TestSceneLeadIn.cs | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs index 563d6be0da..dccde366c2 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs @@ -46,11 +46,12 @@ namespace osu.Game.Tests.Visual.Gameplay [TestCase(0, 0)] [TestCase(-1000, -1000)] [TestCase(-10000, -10000)] - public void TestStoryboardProducesCorrectStartTime(double firstStoryboardEvent, double expectedStartTime) + public void TestStoryboardProducesCorrectStartTimeSimpleAlpha(double firstStoryboardEvent, double expectedStartTime) { var storyboard = new Storyboard(); var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero); + sprite.TimelineGroup.Alpha.Add(Easing.None, firstStoryboardEvent, firstStoryboardEvent + 500, 0, 1); storyboard.GetLayer("Background").Add(sprite); @@ -64,6 +65,43 @@ namespace osu.Game.Tests.Visual.Gameplay }); } + [TestCase(1000, 0, false)] + [TestCase(0, 0, false)] + [TestCase(-1000, -1000, false)] + [TestCase(-10000, -10000, false)] + [TestCase(1000, 0, true)] + [TestCase(0, 0, true)] + [TestCase(-1000, -1000, true)] + [TestCase(-10000, -10000, true)] + public void TestStoryboardProducesCorrectStartTimeFadeInAfterOtherEvents(double firstStoryboardEvent, double expectedStartTime, bool addEventToLoop) + { + var storyboard = new Storyboard(); + + var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero); + + // these should be ignored as we have an alpha visibility blocker proceeding this command. + sprite.TimelineGroup.Scale.Add(Easing.None, -20000, -18000, 0, 1); + var loopGroup = sprite.AddLoop(-20000, 50); + loopGroup.Scale.Add(Easing.None, -20000, -18000, 0, 1); + + var target = addEventToLoop ? loopGroup : sprite.TimelineGroup; + target.Alpha.Add(Easing.None, firstStoryboardEvent, firstStoryboardEvent + 500, 0, 1); + + // these should be ignored due to being in the future. + sprite.TimelineGroup.Alpha.Add(Easing.None, 18000, 20000, 0, 1); + loopGroup.Alpha.Add(Easing.None, 18000, 20000, 0, 1); + + storyboard.GetLayer("Background").Add(sprite); + + loadPlayerWithBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo), storyboard); + + AddAssert($"first frame is {expectedStartTime}", () => + { + Debug.Assert(player.FirstFrameClockTime != null); + return Precision.AlmostEquals(player.FirstFrameClockTime.Value, expectedStartTime, lenience_ms); + }); + } + private void loadPlayerWithBeatmap(IBeatmap beatmap, Storyboard storyboard = null) { AddStep("create player", () => From 8aaba324314c4cfc628de35a3b6ab438227103a5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Mar 2021 15:55:05 +0900 Subject: [PATCH 181/390] Fix storyboard commands occurring before the earliest point of visibility delaying gameplay In osu-stable, storyboard intros start from the first command, but in the case of storyboard drawables which have an initial hidden state, all commands before the time at which they become visible (ie. the first command where `Alpha` increases to a non-zero value) are ignored. This brings lazer in line with that behaviour. It also removes several unnecessary LINQ calls. Note that the alpha check being done in its own pass is important, as it must be the "minimum present alpha across all command groups, including loops". This is what makes the implementation slightly complex. Closes #11981. --- osu.Game/Storyboards/CommandTimelineGroup.cs | 19 +++++++++ osu.Game/Storyboards/StoryboardSprite.cs | 45 +++++++++++++++++--- 2 files changed, 58 insertions(+), 6 deletions(-) diff --git a/osu.Game/Storyboards/CommandTimelineGroup.cs b/osu.Game/Storyboards/CommandTimelineGroup.cs index 6ce3b617e9..617455cf0b 100644 --- a/osu.Game/Storyboards/CommandTimelineGroup.cs +++ b/osu.Game/Storyboards/CommandTimelineGroup.cs @@ -45,11 +45,30 @@ namespace osu.Game.Storyboards }; } + /// + /// Returns the earliest visible time. Will be null unless this group has an command with a start value of zero. + /// + public double? EarliestDisplayedTime + { + get + { + var first = Alpha.Commands.FirstOrDefault(); + + return first?.StartValue == 0 ? first.StartTime : (double?)null; + } + } + [JsonIgnore] public double CommandsStartTime { get { + // if the first alpha command starts at zero it should be given priority over anything else. + // this is due to it creating a state where the target is not present before that time, causing any other events to not be visible. + var earliestDisplay = EarliestDisplayedTime; + if (earliestDisplay != null) + return earliestDisplay.Value; + double min = double.MaxValue; for (int i = 0; i < timelines.Length; i++) diff --git a/osu.Game/Storyboards/StoryboardSprite.cs b/osu.Game/Storyboards/StoryboardSprite.cs index f411ad04f3..fdaa59d7d9 100644 --- a/osu.Game/Storyboards/StoryboardSprite.cs +++ b/osu.Game/Storyboards/StoryboardSprite.cs @@ -24,13 +24,46 @@ namespace osu.Game.Storyboards public readonly CommandTimelineGroup TimelineGroup = new CommandTimelineGroup(); - public double StartTime => Math.Min( - TimelineGroup.HasCommands ? TimelineGroup.CommandsStartTime : double.MaxValue, - loops.Any(l => l.HasCommands) ? loops.Where(l => l.HasCommands).Min(l => l.StartTime) : double.MaxValue); + public double StartTime + { + get + { + // check for presence affecting commands as an initial pass. + double earliestStartTime = TimelineGroup.EarliestDisplayedTime ?? double.MaxValue; - public double EndTime => Math.Max( - TimelineGroup.HasCommands ? TimelineGroup.CommandsEndTime : double.MinValue, - loops.Any(l => l.HasCommands) ? loops.Where(l => l.HasCommands).Max(l => l.EndTime) : double.MinValue); + foreach (var l in loops) + { + if (!(l.EarliestDisplayedTime is double lEarliest)) + continue; + + earliestStartTime = Math.Min(earliestStartTime, lEarliest); + } + + if (earliestStartTime < double.MaxValue) + return earliestStartTime; + + // if an alpha-affecting command was not found, use the earliest of any command. + earliestStartTime = TimelineGroup.StartTime; + + foreach (var l in loops) + earliestStartTime = Math.Min(earliestStartTime, l.StartTime); + + return earliestStartTime; + } + } + + public double EndTime + { + get + { + double latestEndTime = TimelineGroup.EndTime; + + foreach (var l in loops) + latestEndTime = Math.Max(latestEndTime, l.EndTime); + + return latestEndTime; + } + } public bool HasCommands => TimelineGroup.HasCommands || loops.Any(l => l.HasCommands); From 5a6864eb7826502cc74132275206834bf81532fe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Mar 2021 16:43:44 +0900 Subject: [PATCH 182/390] Fix SPM counter immediately disappearing on completion of spinners --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs | 3 +++ osu.Game.Rulesets.Osu/Skinning/Default/SpinnerSpmCounter.cs | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index d02376b6c3..69095fd160 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -109,6 +109,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables base.OnFree(); spinningSample.Samples = null; + + // the counter handles its own fade in (when spinning begins) so we should only be responsible for resetting it here, for pooling. + SpmCounter.Hide(); } protected override void LoadSamples() diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerSpmCounter.cs b/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerSpmCounter.cs index 69355f624b..f3e013c759 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerSpmCounter.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerSpmCounter.cs @@ -21,6 +21,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default private readonly OsuSpriteText spmText; + public override void ApplyTransformsAt(double time, bool propagateChildren = false) + { + // handles own fade in state. + } + public SpinnerSpmCounter() { Children = new Drawable[] From 4e8bcc92659b47bb1223e43458f8159475e78209 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Mar 2021 16:15:44 +0900 Subject: [PATCH 183/390] Fix SPM counter decreasing after spinner has already been completed --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 69095fd160..e6940f0985 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -267,7 +267,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (!SpmCounter.IsPresent && RotationTracker.Tracking) SpmCounter.FadeIn(HitObject.TimeFadeIn); - SpmCounter.SetRotation(Result.RateAdjustedRotation); + // don't update after end time to avoid the rate display dropping during fade out. + // this shouldn't be limited to StartTime as it causes weirdness with the underlying calculation, which is expecting updates during that period. + if (Time.Current <= HitObject.EndTime) + SpmCounter.SetRotation(Result.RateAdjustedRotation); updateBonusScore(); } From 3f349816649689b519f3ed93942c311c3881a90e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 11 Mar 2021 05:40:18 +0300 Subject: [PATCH 184/390] Fix incorrect spinner top offset calculation with clarification --- osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs index 1738003390..ab7d265f67 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.UI; using osu.Game.Skinning; using osuTK; @@ -16,7 +17,13 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { public abstract class LegacySpinner : CompositeDrawable { - protected static readonly float SPINNER_TOP_OFFSET = MathF.Ceiling(45f * SPRITE_SCALE); + /// + /// osu!stable applies this adjustment conditionally, locally in the spinner. + /// in lazer this is handled at a higher level in , + /// therefore it's safe to apply it unconditionally in this component. + /// + protected static readonly float SPINNER_TOP_OFFSET = 45f - 16f; + protected static readonly float SPINNER_Y_CENTRE = SPINNER_TOP_OFFSET + 219f; protected const float SPRITE_SCALE = 0.625f; From efb4a366d42600b5217e574f40c5d53438a8d3c7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Mar 2021 12:15:59 +0900 Subject: [PATCH 185/390] Fix xmldoc explaining incorrect behaviour MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Storyboards/CommandTimelineGroup.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Storyboards/CommandTimelineGroup.cs b/osu.Game/Storyboards/CommandTimelineGroup.cs index 617455cf0b..c478b91c22 100644 --- a/osu.Game/Storyboards/CommandTimelineGroup.cs +++ b/osu.Game/Storyboards/CommandTimelineGroup.cs @@ -46,7 +46,7 @@ namespace osu.Game.Storyboards } /// - /// Returns the earliest visible time. Will be null unless this group has an command with a start value of zero. + /// Returns the earliest visible time. Will be null unless this group's first command has a start value of zero. /// public double? EarliestDisplayedTime { From 1591d593e26095201cedb9d2e1fde2f2761a09a6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Mar 2021 12:58:15 +0900 Subject: [PATCH 186/390] Move spin start time to inside result and switch to standard state handling --- .../Judgements/OsuSpinnerJudgementResult.cs | 5 +++++ .../Objects/Drawables/DrawableSpinner.cs | 21 +++++++++++++++---- .../Skinning/Default/SpinnerSpmCounter.cs | 5 ----- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Judgements/OsuSpinnerJudgementResult.cs b/osu.Game.Rulesets.Osu/Judgements/OsuSpinnerJudgementResult.cs index e58aacd86e..9f77175398 100644 --- a/osu.Game.Rulesets.Osu/Judgements/OsuSpinnerJudgementResult.cs +++ b/osu.Game.Rulesets.Osu/Judgements/OsuSpinnerJudgementResult.cs @@ -38,6 +38,11 @@ namespace osu.Game.Rulesets.Osu.Judgements /// public float RateAdjustedRotation; + /// + /// Time instant at which the spin was started (the first user input which caused an increase in spin). + /// + public double? TimeStarted; + /// /// Time instant at which the spinner has been completed (the user has executed all required spins). /// Will be null if all required spins haven't been completed. diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index e6940f0985..3d614c2dbd 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -109,9 +109,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables base.OnFree(); spinningSample.Samples = null; - - // the counter handles its own fade in (when spinning begins) so we should only be responsible for resetting it here, for pooling. - SpmCounter.Hide(); } protected override void LoadSamples() @@ -161,6 +158,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } + protected override void UpdateStartTimeStateTransforms() + { + base.UpdateStartTimeStateTransforms(); + + if (Result?.TimeStarted is double startTime) + { + using (BeginAbsoluteSequence(startTime)) + fadeInCounter(); + } + } + protected override void UpdateHitStateTransforms(ArmedState state) { base.UpdateHitStateTransforms(state); @@ -265,7 +273,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables base.UpdateAfterChildren(); if (!SpmCounter.IsPresent && RotationTracker.Tracking) - SpmCounter.FadeIn(HitObject.TimeFadeIn); + { + Result.TimeStarted ??= Time.Current; + fadeInCounter(); + } // don't update after end time to avoid the rate display dropping during fade out. // this shouldn't be limited to StartTime as it causes weirdness with the underlying calculation, which is expecting updates during that period. @@ -275,6 +286,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables updateBonusScore(); } + private void fadeInCounter() => SpmCounter.FadeIn(HitObject.TimeFadeIn); + private int wholeSpins; private void updateBonusScore() diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerSpmCounter.cs b/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerSpmCounter.cs index f3e013c759..69355f624b 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerSpmCounter.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerSpmCounter.cs @@ -21,11 +21,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default private readonly OsuSpriteText spmText; - public override void ApplyTransformsAt(double time, bool propagateChildren = false) - { - // handles own fade in state. - } - public SpinnerSpmCounter() { Children = new Drawable[] From 8bc494b224639f79ab4bb6f0a31e18d89da5cfcf Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 11 Mar 2021 20:57:00 +0900 Subject: [PATCH 187/390] Adjust explanatory comments --- .../Skinning/Legacy/LegacySpinner.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs index ab7d265f67..acaec9cbc0 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs @@ -18,13 +18,13 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy public abstract class LegacySpinner : CompositeDrawable { /// - /// osu!stable applies this adjustment conditionally, locally in the spinner. - /// in lazer this is handled at a higher level in , - /// therefore it's safe to apply it unconditionally in this component. + /// All constant spinner coordinates are in osu!stable's gamefield space, which is shifted 16px downwards. + /// This offset is negated in both osu!stable and osu!lazer to bring all constant coordinates into window-space. + /// Note: SPINNER_Y_CENTRE + SPINNER_TOP_OFFSET - Position.Y = 240 (=480/2, or half the window-space in osu!stable) /// - protected static readonly float SPINNER_TOP_OFFSET = 45f - 16f; + protected const float SPINNER_TOP_OFFSET = 45f - 16f; - protected static readonly float SPINNER_Y_CENTRE = SPINNER_TOP_OFFSET + 219f; + protected const float SPINNER_Y_CENTRE = SPINNER_TOP_OFFSET + 219f; protected const float SPRITE_SCALE = 0.625f; @@ -43,9 +43,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy Origin = Anchor.Centre; Size = new Vector2(640, 480); - // stable applies this adjustment conditionally, locally in the spinner. - // in lazer this is handled at a higher level in OsuPlayfieldAdjustmentContainer, - // therefore it's safe to apply it unconditionally in this component. + // osu!stable positions components of the spinner in window-space (as opposed to gamefield-space). + // in lazer, the gamefield-space transformation is applied in OsuPlayfieldAdjustmentContainer, which is inverted here to bring coordinates back into window-space. Position = new Vector2(0, -8f); DrawableSpinner = (DrawableSpinner)drawableHitObject; From b5bdf235cad7a638ad0fc8ce62fd7f6a31edf094 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 11 Mar 2021 21:21:44 +0900 Subject: [PATCH 188/390] Slightly improve comments more --- .../Skinning/Legacy/LegacySpinner.cs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs index acaec9cbc0..1cc25bf053 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs @@ -18,8 +18,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy public abstract class LegacySpinner : CompositeDrawable { /// - /// All constant spinner coordinates are in osu!stable's gamefield space, which is shifted 16px downwards. - /// This offset is negated in both osu!stable and osu!lazer to bring all constant coordinates into window-space. + /// All constants are in osu!stable's gamefield space, which is shifted 16px downwards. + /// This offset is negated in both osu!stable and osu!lazer to bring all constants into window-space. /// Note: SPINNER_Y_CENTRE + SPINNER_TOP_OFFSET - Position.Y = 240 (=480/2, or half the window-space in osu!stable) /// protected const float SPINNER_TOP_OFFSET = 45f - 16f; @@ -36,15 +36,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy [BackgroundDependencyLoader] private void load(DrawableHitObject drawableHitObject, ISkinSource source) { - // legacy spinners relied heavily on absolute screen-space coordinate values. - // wrap everything in a container simulating absolute coords to preserve alignment - // as there are skins that depend on it. Anchor = Anchor.Centre; Origin = Anchor.Centre; - Size = new Vector2(640, 480); - // osu!stable positions components of the spinner in window-space (as opposed to gamefield-space). - // in lazer, the gamefield-space transformation is applied in OsuPlayfieldAdjustmentContainer, which is inverted here to bring coordinates back into window-space. + // osu!stable positions spinner components in window-space (as opposed to gamefield-space). This is a 640x480 area taking up the entire screen. + // In lazer, the gamefield-space positional transformation is applied in OsuPlayfieldAdjustmentContainer, which is inverted here to make this area take up the entire window space. + Size = new Vector2(640, 480); Position = new Vector2(0, -8f); DrawableSpinner = (DrawableSpinner)drawableHitObject; From ea9b48d17d08444c2e7e458fd874cac580ec388c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 11 Mar 2021 21:21:48 +0900 Subject: [PATCH 189/390] Remove unused using --- osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs index 1cc25bf053..513888db53 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Rulesets.Osu.UI; using osu.Game.Skinning; using osuTK; From f1302d16006b567343c12a2624eeb544c7eae9bf Mon Sep 17 00:00:00 2001 From: Roman Kapustin Date: Thu, 11 Mar 2021 19:23:56 +0300 Subject: [PATCH 190/390] Update Microsoft.EntityFrameworkCore --- osu.Desktop/osu.Desktop.csproj | 7 +++++-- osu.Game/Database/OsuDbContext.cs | 3 --- osu.Game/osu.Game.csproj | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 3e0f0cb7f6..4af69c573d 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -27,8 +27,11 @@ - - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index 2aae62edea..d27da50448 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -111,9 +111,6 @@ namespace osu.Game.Database { base.OnConfiguring(optionsBuilder); optionsBuilder - // this is required for the time being due to the way we are querying in places like BeatmapStore. - // if we ever move to having consumers file their own .Includes, or get eager loading support, this could be re-enabled. - .ConfigureWarnings(warnings => warnings.Ignore(CoreEventId.IncludeIgnoredWarning)) .UseSqlite(connectionString, sqliteOptions => sqliteOptions.CommandTimeout(10)) .UseLoggerFactory(logger.Value); } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 90c8b98f42..fa1b0a95c3 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,10 +24,10 @@ - - + + - + From 47b80d2474f6c2a371dd91aa48fda003a60e808e Mon Sep 17 00:00:00 2001 From: Roman Kapustin Date: Thu, 11 Mar 2021 20:51:54 +0300 Subject: [PATCH 191/390] Workaround InvalidOperation exceptions --- osu.Game/Beatmaps/BeatmapManager.cs | 16 ++++++++++++++++ osu.Game/Database/ArchiveModelManager.cs | 8 +++++++- osu.Game/Skinning/SkinManager.cs | 12 ++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 29b3f5d3a3..3254f53574 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -174,6 +174,22 @@ namespace osu.Game.Beatmaps if (beatmapSet.Beatmaps.Any(b => b.BaseDifficulty == null)) throw new InvalidOperationException($"Cannot import {nameof(BeatmapInfo)} with null {nameof(BeatmapInfo.BaseDifficulty)}."); + var dbContext = ContextFactory.Get(); + + // Workaround System.InvalidOperationException + // The instance of entity type 'RulesetInfo' cannot be tracked because another instance with the same key value for {'ID'} is already being tracked. + foreach (var beatmap in beatmapSet.Beatmaps) + { + beatmap.Ruleset = dbContext.RulesetInfo.Find(beatmap.RulesetID); + } + + // Workaround System.InvalidOperationException + // The instance of entity type 'FileInfo' cannot be tracked because another instance with the same key value for {'ID'} is already being tracked. + foreach (var file in beatmapSet.Files) + { + file.FileInfo = dbContext.FileInfo.Find(file.FileInfoID); + } + // check if a set already exists with the same online id, delete if it does. if (beatmapSet.OnlineBeatmapSetID != null) { diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index d809dbcb01..fe2caaa0b7 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -462,6 +462,10 @@ namespace osu.Game.Database // Dereference the existing file info, since the file model will be removed. if (file.FileInfo != null) { + // Workaround System.InvalidOperationException + // The instance of entity type 'FileInfo' cannot be tracked because another instance with the same key value for {'ID'} is already being tracked. + file.FileInfo = usage.Context.FileInfo.Find(file.FileInfoID); + Files.Dereference(file.FileInfo); // This shouldn't be required, but here for safety in case the provided TModel is not being change tracked @@ -635,10 +639,12 @@ namespace osu.Game.Database { using (Stream s = reader.GetStream(file)) { + var fileInfo = files.Add(s); fileInfos.Add(new TFileModel { Filename = file.Substring(prefix.Length).ToStandardisedPath(), - FileInfo = files.Add(s) + FileInfo = fileInfo, + FileInfoID = fileInfo.ID }); } } diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index fcde9f041b..2bb27b60d6 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -142,6 +142,18 @@ namespace osu.Game.Skinning } } + protected override void PreImport(SkinInfo model) + { + var dbContext = ContextFactory.Get(); + + // Workaround System.InvalidOperationException + // The instance of entity type 'FileInfo' cannot be tracked because another instance with the same key value for {'ID'} is already being tracked. + foreach (var file in model.Files) + { + file.FileInfo = dbContext.FileInfo.Find(file.FileInfoID); + } + } + /// /// Retrieve a instance for the provided /// From c6c616f244eb08e664c04937234ffcd6dd84b9c0 Mon Sep 17 00:00:00 2001 From: Roman Kapustin Date: Thu, 11 Mar 2021 21:02:40 +0300 Subject: [PATCH 192/390] Actualize tests --- .../Multiplayer/TestSceneMultiplayerMatchSongSelect.cs | 1 + .../SongSelect/TestSceneBeatmapRecommendations.cs | 1 + .../Visual/SongSelect/TestScenePlaySongSelect.cs | 4 +++- osu.Game/Scoring/ScoreInfo.cs | 10 ++-------- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index faa5d9e6fc..4a9eaa1842 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -56,6 +56,7 @@ namespace osu.Game.Tests.Visual.Multiplayer beatmaps.Add(new BeatmapInfo { Ruleset = rulesets.GetRuleset(i % 4), + RulesetID = i % 4, OnlineBeatmapID = beatmapId, Length = length, BPM = bpm, diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs index 53a956c77c..223ace6ca5 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs @@ -186,6 +186,7 @@ namespace osu.Game.Tests.Visual.SongSelect Metadata = metadata, BaseDifficulty = new BeatmapDifficulty(), Ruleset = ruleset, + RulesetID = ruleset.ID.GetValueOrDefault(), StarDifficulty = difficultyIndex + 1, Version = $"SR{difficultyIndex + 1}" }).ToList() diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 35c6d62cb7..4b402d0c54 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -911,9 +911,11 @@ namespace osu.Game.Tests.Visual.SongSelect int length = RNG.Next(30000, 200000); double bpm = RNG.NextSingle(80, 200); + var ruleset = getRuleset(); beatmaps.Add(new BeatmapInfo { - Ruleset = getRuleset(), + Ruleset = ruleset, + RulesetID = ruleset.ID.GetValueOrDefault(), OnlineBeatmapID = beatmapId, Version = $"{beatmapId} (length {TimeSpan.FromMilliseconds(length):m\\:ss}, bpm {bpm:0.#})", Length = length, diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index f5192f3a40..c5ad43abba 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -73,7 +73,7 @@ namespace osu.Game.Scoring } set { - modsJson = null; + modsJson = JsonConvert.SerializeObject(value.Select(m => new DeserializedMod { Acronym = m.Acronym })); mods = value; } } @@ -88,13 +88,7 @@ namespace osu.Game.Scoring { get { - if (modsJson != null) - return modsJson; - - if (mods == null) - return null; - - return modsJson = JsonConvert.SerializeObject(mods.Select(m => new DeserializedMod { Acronym = m.Acronym })); + return modsJson; } set { From d2f943395d349e08ae8e5a72b1f2996ea0d1d539 Mon Sep 17 00:00:00 2001 From: Roman Kapustin Date: Thu, 11 Mar 2021 22:12:47 +0300 Subject: [PATCH 193/390] Hotfix importing scores from stable --- osu.Game/Scoring/ScoreManager.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 96ec9644b5..a97c516a1b 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -52,6 +52,23 @@ namespace osu.Game.Scoring this.configManager = configManager; } + protected override void PreImport(ScoreInfo model) + { + var dbContext = ContextFactory.Get(); + + // Workaround System.InvalidOperationException + // The instance of entity type 'FileInfo' cannot be tracked because another instance with the same key value for {'ID'} is already being tracked. + foreach (var file in model.Files) + { + file.FileInfo = dbContext.FileInfo.Find(file.FileInfoID); + } + + foreach (var file in model.Beatmap.BeatmapSet.Files) + { + file.FileInfo = dbContext.FileInfo.Find(file.FileInfoID); + } + } + protected override ScoreInfo CreateModel(ArchiveReader archive) { if (archive == null) From 5a4b0174b187e649b9fb739fafd076ea19817f23 Mon Sep 17 00:00:00 2001 From: Roman Kapustin Date: Thu, 11 Mar 2021 22:40:35 +0300 Subject: [PATCH 194/390] Ignore MultipleCollectionIncludeWarning --- osu.Game/Database/OsuDbContext.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index d27da50448..689f248de8 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -112,6 +112,7 @@ namespace osu.Game.Database base.OnConfiguring(optionsBuilder); optionsBuilder .UseSqlite(connectionString, sqliteOptions => sqliteOptions.CommandTimeout(10)) + .ConfigureWarnings(w => w.Ignore(RelationalEventId.MultipleCollectionIncludeWarning)) .UseLoggerFactory(logger.Value); } From a60ff80c04850c1f09ad9f741197378e691fc78d Mon Sep 17 00:00:00 2001 From: Roman Kapustin Date: Fri, 12 Mar 2021 00:02:29 +0300 Subject: [PATCH 195/390] Use expression body in ModsJson get accessor --- osu.Game/Scoring/ScoreInfo.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index c5ad43abba..78101991f6 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -86,10 +86,7 @@ namespace osu.Game.Scoring [Column("Mods")] public string ModsJson { - get - { - return modsJson; - } + get => modsJson; set { modsJson = value; From e7707eee94335149c2c284dfba421a93b11c0f69 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Mar 2021 15:23:11 +0900 Subject: [PATCH 196/390] Switch RestoreDefaultsValueButton to use HasPendingTasks to avoid tooltip always showing --- osu.Game/Overlays/Settings/SettingsItem.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index 8631b8ac7b..85765bf991 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -123,6 +123,8 @@ namespace osu.Game.Overlays.Settings protected internal class RestoreDefaultValueButton : Container, IHasTooltip { + public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks; + private Bindable bindable; public Bindable Bindable @@ -147,7 +149,6 @@ namespace osu.Game.Overlays.Settings RelativeSizeAxes = Axes.Y; Width = SettingsPanel.CONTENT_MARGINS; Alpha = 0f; - AlwaysPresent = true; } [BackgroundDependencyLoader] From b9b095ee75c960287a8636d06c07afddade6865f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Mar 2021 14:49:38 +0900 Subject: [PATCH 197/390] Local framework --- osu.Desktop.slnf | 6 ++++-- osu.Game/osu.Game.csproj | 4 +++- osu.iOS.props | 4 ++-- osu.sln | 42 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 51 insertions(+), 5 deletions(-) diff --git a/osu.Desktop.slnf b/osu.Desktop.slnf index d2c14d321a..1e41d0af0e 100644 --- a/osu.Desktop.slnf +++ b/osu.Desktop.slnf @@ -15,7 +15,9 @@ "osu.Game.Tests\\osu.Game.Tests.csproj", "osu.Game.Tournament.Tests\\osu.Game.Tournament.Tests.csproj", "osu.Game.Tournament\\osu.Game.Tournament.csproj", - "osu.Game\\osu.Game.csproj" + "osu.Game\\osu.Game.csproj", + "../osu-framework/osu.Framework/osu.Framework.csproj", + "../osu-framework/osu.Framework/osu.Framework.NativeLibs.csproj" ] } -} \ No newline at end of file +} diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 90c8b98f42..f2fc1726cd 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -29,11 +29,13 @@ - + + + diff --git a/osu.iOS.props b/osu.iOS.props index ccd33bf88c..30df8c423e 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + diff --git a/osu.sln b/osu.sln index c9453359b1..4d0b3656e7 100644 --- a/osu.sln +++ b/osu.sln @@ -66,6 +66,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Benchmarks", "osu.Game.Benchmarks\osu.Game.Benchmarks.csproj", "{93632F2D-2BB4-46C1-A7B8-F8CF2FB27118}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Framework", "..\osu-framework\osu.Framework\osu.Framework.csproj", "{7EBA330C-6DD9-4F30-9332-6542D86D5BE1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Framework.iOS", "..\osu-framework\osu.Framework.iOS\osu.Framework.iOS.csproj", "{7A6EEFF0-760C-4EE5-BB5E-101E7D013392}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Framework.NativeLibs", "..\osu-framework\osu.Framework.NativeLibs\osu.Framework.NativeLibs.csproj", "{500039B3-0706-40C3-B6E7-1FD9187644A5}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -412,6 +418,42 @@ Global {93632F2D-2BB4-46C1-A7B8-F8CF2FB27118}.Release|iPhone.Build.0 = Release|Any CPU {93632F2D-2BB4-46C1-A7B8-F8CF2FB27118}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {93632F2D-2BB4-46C1-A7B8-F8CF2FB27118}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {7EBA330C-6DD9-4F30-9332-6542D86D5BE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7EBA330C-6DD9-4F30-9332-6542D86D5BE1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7EBA330C-6DD9-4F30-9332-6542D86D5BE1}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {7EBA330C-6DD9-4F30-9332-6542D86D5BE1}.Debug|iPhone.Build.0 = Debug|Any CPU + {7EBA330C-6DD9-4F30-9332-6542D86D5BE1}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {7EBA330C-6DD9-4F30-9332-6542D86D5BE1}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {7EBA330C-6DD9-4F30-9332-6542D86D5BE1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7EBA330C-6DD9-4F30-9332-6542D86D5BE1}.Release|Any CPU.Build.0 = Release|Any CPU + {7EBA330C-6DD9-4F30-9332-6542D86D5BE1}.Release|iPhone.ActiveCfg = Release|Any CPU + {7EBA330C-6DD9-4F30-9332-6542D86D5BE1}.Release|iPhone.Build.0 = Release|Any CPU + {7EBA330C-6DD9-4F30-9332-6542D86D5BE1}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {7EBA330C-6DD9-4F30-9332-6542D86D5BE1}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {7A6EEFF0-760C-4EE5-BB5E-101E7D013392}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7A6EEFF0-760C-4EE5-BB5E-101E7D013392}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7A6EEFF0-760C-4EE5-BB5E-101E7D013392}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {7A6EEFF0-760C-4EE5-BB5E-101E7D013392}.Debug|iPhone.Build.0 = Debug|Any CPU + {7A6EEFF0-760C-4EE5-BB5E-101E7D013392}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {7A6EEFF0-760C-4EE5-BB5E-101E7D013392}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {7A6EEFF0-760C-4EE5-BB5E-101E7D013392}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7A6EEFF0-760C-4EE5-BB5E-101E7D013392}.Release|Any CPU.Build.0 = Release|Any CPU + {7A6EEFF0-760C-4EE5-BB5E-101E7D013392}.Release|iPhone.ActiveCfg = Release|Any CPU + {7A6EEFF0-760C-4EE5-BB5E-101E7D013392}.Release|iPhone.Build.0 = Release|Any CPU + {7A6EEFF0-760C-4EE5-BB5E-101E7D013392}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {7A6EEFF0-760C-4EE5-BB5E-101E7D013392}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {500039B3-0706-40C3-B6E7-1FD9187644A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {500039B3-0706-40C3-B6E7-1FD9187644A5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {500039B3-0706-40C3-B6E7-1FD9187644A5}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {500039B3-0706-40C3-B6E7-1FD9187644A5}.Debug|iPhone.Build.0 = Debug|Any CPU + {500039B3-0706-40C3-B6E7-1FD9187644A5}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {500039B3-0706-40C3-B6E7-1FD9187644A5}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {500039B3-0706-40C3-B6E7-1FD9187644A5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {500039B3-0706-40C3-B6E7-1FD9187644A5}.Release|Any CPU.Build.0 = Release|Any CPU + {500039B3-0706-40C3-B6E7-1FD9187644A5}.Release|iPhone.ActiveCfg = Release|Any CPU + {500039B3-0706-40C3-B6E7-1FD9187644A5}.Release|iPhone.Build.0 = Release|Any CPU + {500039B3-0706-40C3-B6E7-1FD9187644A5}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {500039B3-0706-40C3-B6E7-1FD9187644A5}.Release|iPhoneSimulator.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From a33ffd56b80878e02a0cf03cac93a22869af4787 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Mar 2021 12:43:04 +0900 Subject: [PATCH 198/390] Allow CreateSettingsControls to work with all bindables in target class --- .../Configuration/SettingSourceAttribute.cs | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index cfce615130..a8cebb97b4 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Reflection; using JetBrains.Annotations; using osu.Framework.Bindables; +using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; using osu.Framework.Localisation; using osu.Game.Overlays.Settings; @@ -61,9 +62,13 @@ namespace osu.Game.Configuration public static class SettingSourceExtensions { - public static IEnumerable CreateSettingsControls(this object obj) + public static IEnumerable CreateSettingsControls(this object obj) => createSettingsControls(obj, obj.GetOrderedSettingsSourceProperties()); + + public static IEnumerable CreateSettingsControlsFromAllBindables(this object obj) => createSettingsControls(obj, obj.GetSettingsSourcePropertiesFromBindables()); + + private static IEnumerable createSettingsControls(object obj, IEnumerable<(SettingSourceAttribute, PropertyInfo)> sourceAttribs) { - foreach (var (attr, property) in obj.GetOrderedSettingsSourceProperties()) + foreach (var (attr, property) in sourceAttribs) { object value = property.GetValue(obj); @@ -139,6 +144,30 @@ namespace osu.Game.Configuration } } + public static IEnumerable<(SettingSourceAttribute, PropertyInfo)> GetSettingsSourcePropertiesFromBindables(this object obj) + { + HashSet handledProperties = new HashSet(); + + // reverse and de-dupe properties to surface base class settings to the top of return order. + foreach (var type in obj.GetType().EnumerateBaseTypes().Reverse()) + { + foreach (var property in type.GetProperties(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance)) + { + if (handledProperties.Contains(property.Name)) + continue; + + handledProperties.Add(property.Name); + + if (typeof(IBindable).IsAssignableFrom(property.PropertyType)) + { + var val = property.GetValue(obj); + string description = (val as IHasDescription)?.Description ?? string.Empty; + yield return (new SettingSourceAttribute(property.Name, description), property); + } + } + } + } + public static IEnumerable<(SettingSourceAttribute, PropertyInfo)> GetSettingsSourceProperties(this object obj) { foreach (var property in obj.GetType().GetProperties(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance)) From 4374e7da81cbece7dfed94b3478e22b19b5b49ca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Mar 2021 15:26:18 +0900 Subject: [PATCH 199/390] Convert bindable names to human readable sentences --- osu.Game/Configuration/SettingSourceAttribute.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index a8cebb97b4..fe8886b52e 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Reflection; +using Humanizer; using JetBrains.Annotations; using osu.Framework.Bindables; using osu.Framework.Extensions.TypeExtensions; @@ -162,7 +163,7 @@ namespace osu.Game.Configuration { var val = property.GetValue(obj); string description = (val as IHasDescription)?.Description ?? string.Empty; - yield return (new SettingSourceAttribute(property.Name, description), property); + yield return (new SettingSourceAttribute(property.Name.Humanize(), description), property); } } } From 6eadae8aaf56831d4ecfc19fb0f4dd1c0fdab2ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Mar 2021 18:35:42 +0900 Subject: [PATCH 200/390] Remove remnants of OsuTK --- osu.Desktop/OsuGameDesktop.cs | 22 +++++----------------- osu.Desktop/Program.cs | 2 +- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 5909b82c8f..b2487568ce 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -136,24 +136,12 @@ namespace osu.Desktop var iconStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico"); - switch (host.Window) - { - // Legacy osuTK DesktopGameWindow - case OsuTKDesktopWindow desktopGameWindow: - desktopGameWindow.CursorState |= CursorState.Hidden; - desktopGameWindow.SetIconFromStream(iconStream); - desktopGameWindow.Title = Name; - desktopGameWindow.FileDrop += (_, e) => fileDrop(e.FileNames); - break; + var desktopWindow = (SDL2DesktopWindow)host.Window; - // SDL2 DesktopWindow - case SDL2DesktopWindow desktopWindow: - desktopWindow.CursorState |= CursorState.Hidden; - desktopWindow.SetIconFromStream(iconStream); - desktopWindow.Title = Name; - desktopWindow.DragDrop += f => fileDrop(new[] { f }); - break; - } + desktopWindow.CursorState |= CursorState.Hidden; + desktopWindow.SetIconFromStream(iconStream); + desktopWindow.Title = Name; + desktopWindow.DragDrop += f => fileDrop(new[] { f }); } private void fileDrop(string[] filePaths) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index 6ca7079654..0c527ba881 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -24,7 +24,7 @@ namespace osu.Desktop var cwd = Environment.CurrentDirectory; bool useOsuTK = args.Contains("--tk"); - using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true, useOsuTK: useOsuTK)) + using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true)) { host.ExceptionThrown += handleException; From 3c21c83cc88e8a0613aeda4f6cb04069172c6777 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Mar 2021 18:36:28 +0900 Subject: [PATCH 201/390] Rename KeyboardSection to BindingSection --- .../Input/{KeyboardSettings.cs => BindingSettings.cs} | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) rename osu.Game/Overlays/Settings/Sections/Input/{KeyboardSettings.cs => BindingSettings.cs} (70%) diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyboardSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/BindingSettings.cs similarity index 70% rename from osu.Game/Overlays/Settings/Sections/Input/KeyboardSettings.cs rename to osu.Game/Overlays/Settings/Sections/Input/BindingSettings.cs index db6f24a954..79c73863cf 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyboardSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/BindingSettings.cs @@ -5,17 +5,17 @@ using osu.Framework.Graphics; namespace osu.Game.Overlays.Settings.Sections.Input { - public class KeyboardSettings : SettingsSubsection + public class BindingSettings : SettingsSubsection { - protected override string Header => "Keyboard"; + protected override string Header => "Shortcut and gameplay bindings"; - public KeyboardSettings(KeyBindingPanel keyConfig) + public BindingSettings(KeyBindingPanel keyConfig) { Children = new Drawable[] { new SettingsButton { - Text = "Key configuration", + Text = "Configure", TooltipText = "change global shortcut keys and gameplay bindings", Action = keyConfig.ToggleVisibility }, From 8635abbc4a616771e7ed702140109737919a4dd5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Mar 2021 18:37:55 +0900 Subject: [PATCH 202/390] Add the ability to not get controls for disabled bindables --- osu.Game/Configuration/SettingSourceAttribute.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index fe8886b52e..39d7fba32b 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -63,16 +63,21 @@ namespace osu.Game.Configuration public static class SettingSourceExtensions { - public static IEnumerable CreateSettingsControls(this object obj) => createSettingsControls(obj, obj.GetOrderedSettingsSourceProperties()); + public static IReadOnlyList CreateSettingsControls(this object obj, bool includeDisabled = true) => + createSettingsControls(obj, obj.GetOrderedSettingsSourceProperties(), includeDisabled).ToArray(); - public static IEnumerable CreateSettingsControlsFromAllBindables(this object obj) => createSettingsControls(obj, obj.GetSettingsSourcePropertiesFromBindables()); + public static IReadOnlyList CreateSettingsControlsFromAllBindables(this object obj, bool includeDisabled = true) => + createSettingsControls(obj, obj.GetSettingsSourcePropertiesFromBindables(), includeDisabled).ToArray(); - private static IEnumerable createSettingsControls(object obj, IEnumerable<(SettingSourceAttribute, PropertyInfo)> sourceAttribs) + private static IEnumerable createSettingsControls(object obj, IEnumerable<(SettingSourceAttribute, PropertyInfo)> sourceAttribs, bool includeDisabled = true) { foreach (var (attr, property) in sourceAttribs) { object value = property.GetValue(obj); + if ((value as IBindable)?.Disabled == true) + continue; + switch (value) { case BindableNumber bNumber: From 03230edcb11760ff457ce3667b3377e6aea30660 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Mar 2021 18:38:16 +0900 Subject: [PATCH 203/390] Update bindings settings to handle the new structure and show all handlers --- .../Settings/Sections/Input/MouseSettings.cs | 60 ++++++----------- .../Settings/Sections/InputSection.cs | 64 ++++++++++++++++++- 2 files changed, 82 insertions(+), 42 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs index 3a78cff890..036c4edfba 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs @@ -1,11 +1,11 @@ // 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; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Configuration; using osu.Framework.Graphics; +using osu.Framework.Input.Handlers.Mouse; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Input; @@ -14,35 +14,39 @@ namespace osu.Game.Overlays.Settings.Sections.Input { public class MouseSettings : SettingsSubsection { + private readonly MouseHandler mouseHandler; + protected override string Header => "Mouse"; - private readonly BindableBool rawInputToggle = new BindableBool(); - - private Bindable configSensitivity; + private Bindable handlerSensitivity; private Bindable localSensitivity; - private Bindable ignoredInputHandlers; - private Bindable windowMode; private SettingsEnumDropdown confineMouseModeSetting; + private Bindable relativeMode; + + public MouseSettings(MouseHandler mouseHandler) + { + this.mouseHandler = mouseHandler; + } [BackgroundDependencyLoader] private void load(OsuConfigManager osuConfig, FrameworkConfigManager config) { // use local bindable to avoid changing enabled state of game host's bindable. - configSensitivity = config.GetBindable(FrameworkSetting.CursorSensitivity); - localSensitivity = configSensitivity.GetUnboundCopy(); + handlerSensitivity = mouseHandler.Sensitivity.GetBoundCopy(); + localSensitivity = handlerSensitivity.GetUnboundCopy(); + relativeMode = mouseHandler.UseRelativeMode.GetBoundCopy(); windowMode = config.GetBindable(FrameworkSetting.WindowMode); - ignoredInputHandlers = config.GetBindable(FrameworkSetting.IgnoredInputHandlers); Children = new Drawable[] { new SettingsCheckbox { - LabelText = "Raw input", - Current = rawInputToggle + LabelText = "High precision mouse", + Current = relativeMode }, new SensitivitySetting { @@ -76,7 +80,9 @@ namespace osu.Game.Overlays.Settings.Sections.Input { base.LoadComplete(); - configSensitivity.BindValueChanged(val => + relativeMode.BindValueChanged(relative => localSensitivity.Disabled = !relative.NewValue, true); + + handlerSensitivity.BindValueChanged(val => { var disabled = localSensitivity.Disabled; @@ -85,7 +91,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input localSensitivity.Disabled = disabled; }, true); - localSensitivity.BindValueChanged(val => configSensitivity.Value = val.NewValue); + localSensitivity.BindValueChanged(val => handlerSensitivity.Value = val.NewValue); windowMode.BindValueChanged(mode => { @@ -102,32 +108,6 @@ namespace osu.Game.Overlays.Settings.Sections.Input confineMouseModeSetting.TooltipText = string.Empty; } }, true); - - if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows) - { - rawInputToggle.Disabled = true; - localSensitivity.Disabled = true; - } - else - { - rawInputToggle.ValueChanged += enabled => - { - // this is temporary until we support per-handler settings. - const string raw_mouse_handler = @"OsuTKRawMouseHandler"; - const string standard_mouse_handlers = @"OsuTKMouseHandler MouseHandler"; - - ignoredInputHandlers.Value = enabled.NewValue ? standard_mouse_handlers : raw_mouse_handler; - }; - - ignoredInputHandlers.ValueChanged += handler => - { - bool raw = !handler.NewValue.Contains("Raw"); - rawInputToggle.Value = raw; - localSensitivity.Disabled = !raw; - }; - - ignoredInputHandlers.TriggerChange(); - } } private class SensitivitySetting : SettingsSlider @@ -141,7 +121,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input private class SensitivitySlider : OsuSliderBar { - public override string TooltipText => Current.Disabled ? "enable raw input to adjust sensitivity" : $"{base.TooltipText}x"; + public override string TooltipText => Current.Disabled ? "enable high precision mouse to adjust sensitivity" : $"{base.TooltipText}x"; } } } diff --git a/osu.Game/Overlays/Settings/Sections/InputSection.cs b/osu.Game/Overlays/Settings/Sections/InputSection.cs index b43453f53d..107e37909c 100644 --- a/osu.Game/Overlays/Settings/Sections/InputSection.cs +++ b/osu.Game/Overlays/Settings/Sections/InputSection.cs @@ -1,28 +1,88 @@ // 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.Framework.Graphics.Sprites; +using osu.Framework.Input.Handlers; +using osu.Framework.Input.Handlers.Mouse; +using osu.Framework.Platform; +using osu.Game.Configuration; using osu.Game.Overlays.Settings.Sections.Input; namespace osu.Game.Overlays.Settings.Sections { public class InputSection : SettingsSection { + private readonly KeyBindingPanel keyConfig; + public override string Header => "Input"; + [Resolved] + private GameHost host { get; set; } + public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.Solid.Keyboard }; public InputSection(KeyBindingPanel keyConfig) + { + this.keyConfig = keyConfig; + } + + [BackgroundDependencyLoader] + private void load() { Children = new Drawable[] { - new MouseSettings(), - new KeyboardSettings(keyConfig), + new BindingSettings(keyConfig), }; + + foreach (var handler in host.AvailableInputHandlers) + { + var handlerSection = createSectionFor(handler); + + if (handlerSection != null) + Add(handlerSection); + } + } + + private SettingsSubsection createSectionFor(InputHandler handler) + { + var settingsControls = handler.CreateSettingsControlsFromAllBindables(false); + + if (settingsControls.Count == 0) + return null; + + SettingsSubsection section; + + switch (handler) + { + case MouseHandler mh: + section = new MouseSettings(mh); + break; + + default: + section = new HandlerSection(handler); + break; + } + + section.AddRange(settingsControls); + + return section; + } + + private class HandlerSection : SettingsSubsection + { + private readonly InputHandler handler; + + public HandlerSection(InputHandler handler) + { + this.handler = handler; + } + + protected override string Header => handler.Description; } } } From 3458dcc33aa07615dad66ba2b2ee643f874b5f22 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Mar 2021 18:40:38 +0900 Subject: [PATCH 204/390] Use whitelist to avoid exposing settings to user that shouldn't be --- .../Settings/Sections/InputSection.cs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/InputSection.cs b/osu.Game/Overlays/Settings/Sections/InputSection.cs index 107e37909c..e6aaa1ade9 100644 --- a/osu.Game/Overlays/Settings/Sections/InputSection.cs +++ b/osu.Game/Overlays/Settings/Sections/InputSection.cs @@ -5,6 +5,8 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Handlers; +using osu.Framework.Input.Handlers.Joystick; +using osu.Framework.Input.Handlers.Midi; using osu.Framework.Input.Handlers.Mouse; using osu.Framework.Platform; using osu.Game.Configuration; @@ -50,11 +52,6 @@ namespace osu.Game.Overlays.Settings.Sections private SettingsSubsection createSectionFor(InputHandler handler) { - var settingsControls = handler.CreateSettingsControlsFromAllBindables(false); - - if (settingsControls.Count == 0) - return null; - SettingsSubsection section; switch (handler) @@ -63,11 +60,21 @@ namespace osu.Game.Overlays.Settings.Sections section = new MouseSettings(mh); break; - default: + // whitelist the handlers which should be displayed to avoid any weird cases of users touching settings they shouldn't. + case JoystickHandler _: + case MidiHandler _: section = new HandlerSection(handler); break; + + default: + return null; } + var settingsControls = handler.CreateSettingsControlsFromAllBindables(false); + + if (settingsControls.Count == 0) + return null; + section.AddRange(settingsControls); return section; From 86164c027a052af679caeb9d1937828bde61df8e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Mar 2021 18:44:10 +0900 Subject: [PATCH 205/390] Update the method we use to reset input settings --- osu.Game/OsuGame.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index b7398efdc2..4dd7f97a72 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -880,8 +880,7 @@ namespace osu.Game switch (action) { case GlobalAction.ResetInputSettings: - frameworkConfig.GetBindable(FrameworkSetting.IgnoredInputHandlers).SetDefault(); - frameworkConfig.GetBindable(FrameworkSetting.CursorSensitivity).SetDefault(); + Host.ResetInputHandlers(); frameworkConfig.GetBindable(FrameworkSetting.ConfineMouseMode).SetDefault(); return true; From d0644221ff74801457b2b17efa728de723ddea20 Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 9 Nov 2020 14:43:06 -0800 Subject: [PATCH 206/390] Add test showing toolbar behavior change --- .../Navigation/TestSceneScreenNavigation.cs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index fc49517cdf..f2bb518b2e 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -229,6 +229,35 @@ namespace osu.Game.Tests.Visual.Navigation AddUntilStep("settings displayed", () => Game.Settings.State.Value == Visibility.Visible); } + [Test] + public void TestToolbarHiddenByUser() + { + AddStep("Enter menu", () => InputManager.Key(Key.Enter)); + + AddUntilStep("Wait for toolbar to load", () => Game.Toolbar.IsLoaded); + + AddStep("Hide toolbar", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.Key(Key.T); + InputManager.ReleaseKey(Key.ControlLeft); + }); + + pushEscape(); + + AddStep("Enter menu", () => InputManager.Key(Key.Enter)); + + AddAssert("Toolbar is hidden", () => Game.Toolbar.State.Value == Visibility.Hidden); + + AddStep("Enter song select", () => + { + InputManager.Key(Key.Enter); + InputManager.Key(Key.Enter); + }); + + AddAssert("Toolbar is hidden", () => Game.Toolbar.State.Value == Visibility.Hidden); + } + private void pushEscape() => AddStep("Press escape", () => InputManager.Key(Key.Escape)); From 6c0734a09ff87754eb91917623f717476ee400e6 Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 9 Nov 2020 14:45:20 -0800 Subject: [PATCH 207/390] Handle global action in toolbar instead of osugame --- osu.Game/OsuGame.cs | 4 ---- osu.Game/Overlays/Toolbar/Toolbar.cs | 22 ++++++++++++++++++++-- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index b7398efdc2..dd775888a1 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -885,10 +885,6 @@ namespace osu.Game frameworkConfig.GetBindable(FrameworkSetting.ConfineMouseMode).SetDefault(); return true; - case GlobalAction.ToggleToolbar: - Toolbar.ToggleVisibility(); - return true; - case GlobalAction.ToggleGameplayMouseButtons: LocalConfig.Set(OsuSetting.MouseDisableButtons, !LocalConfig.Get(OsuSetting.MouseDisableButtons)); return true; diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index 0ccb22df3a..011f5a03c9 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -13,10 +13,12 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Input.Events; using osu.Game.Rulesets; +using osu.Framework.Input.Bindings; +using osu.Game.Input.Bindings; namespace osu.Game.Overlays.Toolbar { - public class Toolbar : VisibilityContainer + public class Toolbar : VisibilityContainer, IKeyBindingHandler { public const float HEIGHT = 40; public const float TOOLTIP_HEIGHT = 30; @@ -30,7 +32,7 @@ namespace osu.Game.Overlays.Toolbar protected readonly IBindable OverlayActivationMode = new Bindable(OverlayActivation.All); - // Toolbar components like RulesetSelector should receive keyboard input events even when the toolbar is hidden. + // Toolbar and its components need keyboard input even when hidden. public override bool PropagateNonPositionalInputSubTree => true; public Toolbar() @@ -164,5 +166,21 @@ namespace osu.Game.Overlays.Toolbar this.MoveToY(-DrawSize.Y, transition_time, Easing.OutQuint); this.FadeOut(transition_time, Easing.InQuint); } + + public bool OnPressed(GlobalAction action) + { + switch (action) + { + case GlobalAction.ToggleToolbar: + ToggleVisibility(); + return true; + } + + return false; + } + + public void OnReleased(GlobalAction action) + { + } } } From 62f2a823f6be7d27ec7c6db751193023927bb130 Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 9 Nov 2020 14:46:08 -0800 Subject: [PATCH 208/390] Hide toolbar forever when the user hides it --- osu.Game/OsuGame.cs | 2 +- osu.Game/Overlays/Toolbar/Toolbar.cs | 6 ++++++ osu.Game/Screens/Menu/ButtonSystem.cs | 3 ++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index dd775888a1..fa9a0d4eb5 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -983,7 +983,7 @@ namespace osu.Game if (newOsuScreen.HideOverlaysOnEnter) CloseAllOverlays(); - else + else if (!Toolbar.HiddenByUser) Toolbar.Show(); if (newOsuScreen.AllowBackButton) diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index 011f5a03c9..7f77e5add9 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -23,6 +23,8 @@ namespace osu.Game.Overlays.Toolbar public const float HEIGHT = 40; public const float TOOLTIP_HEIGHT = 30; + public bool HiddenByUser; + public Action OnHome; private ToolbarUserButton userButton; @@ -169,10 +171,14 @@ namespace osu.Game.Overlays.Toolbar public bool OnPressed(GlobalAction action) { + if (OverlayActivationMode.Value == OverlayActivation.Disabled) + return false; + switch (action) { case GlobalAction.ToggleToolbar: ToggleVisibility(); + HiddenByUser = State.Value == Visibility.Hidden; return true; } diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 81b1cb0bf1..8f1fd627f5 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -352,7 +352,8 @@ namespace osu.Game.Screens.Menu if (impact) logo.Impact(); - game?.Toolbar.Show(); + if (game?.Toolbar.HiddenByUser == false) + game.Toolbar.Show(); }, 200); break; From 020a03e01ee7284d37359a67704b3e72ed8ff50a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 13 Mar 2021 05:22:20 +0300 Subject: [PATCH 209/390] Use sensible "score per tick" constant --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 8534cd89d7..1ec3c877e9 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -289,6 +289,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private void fadeInCounter() => SpmCounter.FadeIn(HitObject.TimeFadeIn); + private static readonly int score_per_tick = new SpinnerBonusTick.OsuSpinnerBonusTickJudgement().MaxNumericResult; + private int wholeSpins; private void updateBonusScore() @@ -315,7 +317,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables tick.TriggerResult(true); if (tick is DrawableSpinnerBonusTick) - gainedBonus.Value = tick.Result.Judgement.MaxNumericResult * (spins - HitObject.SpinsRequired); + gainedBonus.Value = score_per_tick * (spins - HitObject.SpinsRequired); } wholeSpins++; From 8fdab5a7de4a881604ebb5da7547106a153bf9dc Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 12 Mar 2021 02:37:07 +0300 Subject: [PATCH 210/390] Revert legacy spinner presence changes and bonus counter component No longer necessary, after inlining legacy coordinates logic to `LegacySpinner` and limiting precisely-positioned legacy components there --- .../Objects/Drawables/DrawableSpinner.cs | 1 - osu.Game.Rulesets.Osu/OsuSkinComponents.cs | 1 - .../Legacy/OsuLegacySkinTransformer.cs | 39 ++----------------- 3 files changed, 4 insertions(+), 37 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 1ec3c877e9..a4919d5061 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -82,7 +82,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Y = 120, Alpha = 0 }, - new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SpinnerBonusCounter), _ => new DefaultSpinnerBonusCounter()), spinningSample = new PausableSkinnableSound { Volume = { Value = 0 }, diff --git a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs index 131645406e..fcb544fa5b 100644 --- a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs +++ b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs @@ -19,6 +19,5 @@ namespace osu.Game.Rulesets.Osu SliderBall, SliderBody, SpinnerBody, - SpinnerBonusCounter, } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index ed09031fc1..d74f885573 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -6,7 +6,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Skinning; using osuTK; -using static osu.Game.Skinning.LegacySkinConfiguration; namespace osu.Game.Rulesets.Osu.Skinning.Legacy { @@ -14,12 +13,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { private Lazy hasHitCircle; - private Lazy spinnerStyle; - - private bool hasSpinner => spinnerStyle.Value != SpinnerStyle.Modern; - - private bool hasScoreFont => this.HasFont(GetConfig(LegacySetting.ScorePrefix)?.Value ?? "score"); - /// /// On osu-stable, hitcircles have 5 pixels of transparent padding on each side to allow for shadows etc. /// Their hittable area is 128px, but the actual circle portion is 118px. @@ -37,19 +30,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private void sourceChanged() { hasHitCircle = new Lazy(() => Source.GetTexture("hitcircle") != null); - - spinnerStyle = new Lazy(() => - { - bool hasBackground = Source.GetTexture("spinner-background") != null; - - if (Source.GetTexture("spinner-top") != null && !hasBackground) - return SpinnerStyle.NewLegacy; - - if (hasBackground) - return SpinnerStyle.OldLegacy; - - return SpinnerStyle.Modern; - }); } public override Drawable GetDrawableComponent(ISkinComponent component) @@ -130,18 +110,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy }; case OsuSkinComponents.SpinnerBody: - if (spinnerStyle.Value == SpinnerStyle.NewLegacy) + bool hasBackground = Source.GetTexture("spinner-background") != null; + + if (Source.GetTexture("spinner-top") != null && !hasBackground) return new LegacyNewStyleSpinner(); - else if (spinnerStyle.Value == SpinnerStyle.OldLegacy) + else if (hasBackground) return new LegacyOldStyleSpinner(); return null; - - case OsuSkinComponents.SpinnerBonusCounter: - if (hasSpinner && hasScoreFont) - return new LegacySpinnerBonusCounter(); - - return null; } return null; @@ -175,12 +151,5 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy return Source.GetConfig(lookup); } - - private enum SpinnerStyle - { - NewLegacy, - OldLegacy, - Modern, - } } } From 774ebf50bca5d5c0022c824db4e0d5d50e60281b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 12 Mar 2021 02:38:25 +0300 Subject: [PATCH 211/390] Move legacy spinner bonus counter to `LegacySpinner` --- .../Skinning/Legacy/LegacySpinner.cs | 62 +++++++++++++------ .../Legacy/LegacySpinnerBonusCounter.cs | 56 ----------------- 2 files changed, 43 insertions(+), 75 deletions(-) delete mode 100644 osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinnerBonusCounter.cs diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs index 513888db53..b0b9cba2bd 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs @@ -32,6 +32,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private Sprite spin; private Sprite clear; + private LegacySpriteText bonusCounter; + [BackgroundDependencyLoader] private void load(DrawableHitObject drawableHitObject, ISkinSource source) { @@ -45,36 +47,58 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy DrawableSpinner = (DrawableSpinner)drawableHitObject; - AddRangeInternal(new[] + AddInternal(new Container { - spin = new Sprite + Depth = float.MinValue, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - Anchor = Anchor.TopCentre, - Origin = Anchor.Centre, - Depth = float.MinValue, - Texture = source.GetTexture("spinner-spin"), - Scale = new Vector2(SPRITE_SCALE), - Y = SPINNER_TOP_OFFSET + 335, - }, - clear = new Sprite - { - Alpha = 0, - Anchor = Anchor.TopCentre, - Origin = Anchor.Centre, - Depth = float.MinValue, - Texture = source.GetTexture("spinner-clear"), - Scale = new Vector2(SPRITE_SCALE), - Y = SPINNER_TOP_OFFSET + 115, - }, + spin = new Sprite + { + Anchor = Anchor.TopCentre, + Origin = Anchor.Centre, + Texture = source.GetTexture("spinner-spin"), + Scale = new Vector2(SPRITE_SCALE), + Y = SPINNER_TOP_OFFSET + 335, + }, + clear = new Sprite + { + Alpha = 0, + Anchor = Anchor.TopCentre, + Origin = Anchor.Centre, + Texture = source.GetTexture("spinner-clear"), + Scale = new Vector2(SPRITE_SCALE), + Y = SPINNER_TOP_OFFSET + 115, + }, + bonusCounter = ((LegacySpriteText)source.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreText))).With(s => + { + s.Alpha = 0f; + s.Anchor = Anchor.TopCentre; + s.Origin = Anchor.Centre; + s.Font = s.Font.With(fixedWidth: false); + s.Scale = new Vector2(SPRITE_SCALE); + s.Y = SPINNER_TOP_OFFSET + 299; + }), + } }); } + private IBindable gainedBonus; + private readonly Bindable completed = new Bindable(); protected override void LoadComplete() { base.LoadComplete(); + gainedBonus = DrawableSpinner.GainedBonus.GetBoundCopy(); + gainedBonus.BindValueChanged(bonus => + { + bonusCounter.Text = $"{bonus.NewValue}"; + bonusCounter.FadeOutFromOne(800, Easing.Out); + bonusCounter.ScaleTo(SPRITE_SCALE * 2f).Then().ScaleTo(SPRITE_SCALE * 1.28f, 800, Easing.Out); + }); + completed.BindValueChanged(onCompletedChanged, true); DrawableSpinner.ApplyCustomUpdateState += UpdateStateTransforms; diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinnerBonusCounter.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinnerBonusCounter.cs deleted file mode 100644 index 3c4a6be4dd..0000000000 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinnerBonusCounter.cs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Skinning; -using osuTK; -using static osu.Game.Rulesets.Osu.Skinning.Legacy.LegacySpinner; - -namespace osu.Game.Rulesets.Osu.Skinning.Legacy -{ - public class LegacySpinnerBonusCounter : CompositeDrawable - { - private LegacySpriteText bonusCounter; - - private DrawableSpinner drawableSpinner; - - private IBindable gainedBonus; - - [BackgroundDependencyLoader] - private void load(DrawableHitObject drawableHitObject, ISkinSource source) - { - drawableSpinner = (DrawableSpinner)drawableHitObject; - - InternalChild = new LegacyCoordinatesContainer - { - Child = bonusCounter = ((LegacySpriteText)source.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreText))).With(s => - { - s.Alpha = 0f; - s.Anchor = Anchor.TopCentre; - s.Origin = Anchor.Centre; - s.Font = s.Font.With(fixedWidth: false); - s.Scale = new Vector2(SPRITE_SCALE); - s.Y = LegacyCoordinatesContainer.SPINNER_TOP_OFFSET + 299; - }), - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - gainedBonus = drawableSpinner.GainedBonus.GetBoundCopy(); - gainedBonus.BindValueChanged(bonus => - { - bonusCounter.Text = $"{bonus.NewValue}"; - bonusCounter.FadeOutFromOne(800, Easing.Out); - bonusCounter.ScaleTo(SPRITE_SCALE * 2f).Then().ScaleTo(SPRITE_SCALE * 1.28f, 800, Easing.Out); - }); - } - } -} From 98f6e16113debd5a4890d7e6eb6aad4a70226bad Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 12 Mar 2021 02:38:40 +0300 Subject: [PATCH 212/390] Move default spinner bonus counter to new `DefaultSpinner` --- .../Objects/Drawables/DrawableSpinner.cs | 2 +- ...innerBonusCounter.cs => DefaultSpinner.cs} | 38 +++++++++++++------ .../Skinning/Default/DefaultSpinnerDisc.cs | 5 --- 3 files changed, 28 insertions(+), 17 deletions(-) rename osu.Game.Rulesets.Osu/Skinning/Default/{DefaultSpinnerBonusCounter.cs => DefaultSpinner.cs} (62%) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index a4919d5061..f995140123 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables RelativeSizeAxes = Axes.Y, Children = new Drawable[] { - new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SpinnerBody), _ => new DefaultSpinnerDisc()), + new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SpinnerBody), _ => new DefaultSpinner()), RotationTracker = new SpinnerRotationTracker(this) } }, diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinnerBonusCounter.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinner.cs similarity index 62% rename from osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinnerBonusCounter.cs rename to osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinner.cs index 633766290f..83676d3784 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinnerBonusCounter.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinner.cs @@ -12,29 +12,45 @@ using osu.Game.Rulesets.Osu.Objects.Drawables; namespace osu.Game.Rulesets.Osu.Skinning.Default { - public class DefaultSpinnerBonusCounter : CompositeDrawable + public class DefaultSpinner : CompositeDrawable { - private OsuSpriteText bonusCounter; - private DrawableSpinner drawableSpinner; - private IBindable gainedBonus; + private OsuSpriteText bonusCounter; + + public DefaultSpinner() + { + RelativeSizeAxes = Axes.Both; + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + } [BackgroundDependencyLoader] private void load(DrawableHitObject drawableHitObject) { drawableSpinner = (DrawableSpinner)drawableHitObject; - InternalChild = bonusCounter = new OsuSpriteText + AddRangeInternal(new Drawable[] { - Alpha = 0, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.Numeric.With(size: 24), - Y = -120, - }; + new DefaultSpinnerDisc + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + bonusCounter = new OsuSpriteText + { + Alpha = 0, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.Numeric.With(size: 24), + Y = -120, + } + }); } + private IBindable gainedBonus; + protected override void LoadComplete() { base.LoadComplete(); diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinnerDisc.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinnerDisc.cs index 667fee1495..542f3eff0d 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinnerDisc.cs @@ -40,14 +40,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default public DefaultSpinnerDisc() { - RelativeSizeAxes = Axes.Both; - // we are slightly bigger than our parent, to clip the top and bottom of the circle // this should probably be revisited when scaled spinners are a thing. Scale = new Vector2(initial_scale); - - Anchor = Anchor.Centre; - Origin = Anchor.Centre; } [BackgroundDependencyLoader] From 5999e4ba3361f54828eaa25f1539ca0e2b991279 Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 9 Nov 2020 15:16:35 -0800 Subject: [PATCH 213/390] Add xmldoc for hiddenbyuser bool --- osu.Game/Overlays/Toolbar/Toolbar.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index 7f77e5add9..483d82200b 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -23,6 +23,9 @@ namespace osu.Game.Overlays.Toolbar public const float HEIGHT = 40; public const float TOOLTIP_HEIGHT = 30; + /// + /// Whether the user hid this with . + /// public bool HiddenByUser; public Action OnHome; From 0ba5312a4063e3308c666e17ab4590bfe2f38267 Mon Sep 17 00:00:00 2001 From: Joehu Date: Sat, 13 Mar 2021 00:05:26 -0800 Subject: [PATCH 214/390] Move blocking show logic to UpdateState --- osu.Game/OsuGame.cs | 2 +- osu.Game/Overlays/Toolbar/Toolbar.cs | 12 +++++++++--- osu.Game/Screens/Menu/ButtonSystem.cs | 3 +-- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index fa9a0d4eb5..dd775888a1 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -983,7 +983,7 @@ namespace osu.Game if (newOsuScreen.HideOverlaysOnEnter) CloseAllOverlays(); - else if (!Toolbar.HiddenByUser) + else Toolbar.Show(); if (newOsuScreen.AllowBackButton) diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index 483d82200b..7497f4d210 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -26,7 +26,9 @@ namespace osu.Game.Overlays.Toolbar /// /// Whether the user hid this with . /// - public bool HiddenByUser; + private bool hiddenByUser; + + private bool userToggled; public Action OnHome; @@ -149,7 +151,9 @@ namespace osu.Game.Overlays.Toolbar protected override void UpdateState(ValueChangedEvent state) { - if (state.NewValue == Visibility.Visible && OverlayActivationMode.Value == OverlayActivation.Disabled) + var blockShow = !userToggled && hiddenByUser; + + if (state.NewValue == Visibility.Visible && (OverlayActivationMode.Value == OverlayActivation.Disabled || blockShow)) { State.Value = Visibility.Hidden; return; @@ -180,8 +184,10 @@ namespace osu.Game.Overlays.Toolbar switch (action) { case GlobalAction.ToggleToolbar: + userToggled = true; ToggleVisibility(); - HiddenByUser = State.Value == Visibility.Hidden; + hiddenByUser = State.Value == Visibility.Hidden; + userToggled = false; return true; } diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 8f1fd627f5..81b1cb0bf1 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -352,8 +352,7 @@ namespace osu.Game.Screens.Menu if (impact) logo.Impact(); - if (game?.Toolbar.HiddenByUser == false) - game.Toolbar.Show(); + game?.Toolbar.Show(); }, 200); break; From b13f193c8dd60573aa579bb47b06d1d02ed0b5ef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 13 Mar 2021 19:26:38 +0900 Subject: [PATCH 215/390] Fix incorrect task being returned for changelog continuations --- osu.Game/Overlays/ChangelogOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/ChangelogOverlay.cs b/osu.Game/Overlays/ChangelogOverlay.cs index 2da5be5e6c..eda7748367 100644 --- a/osu.Game/Overlays/ChangelogOverlay.cs +++ b/osu.Game/Overlays/ChangelogOverlay.cs @@ -163,7 +163,7 @@ namespace osu.Game.Overlays await API.PerformAsync(req).ConfigureAwait(false); return tcs.Task; - }); + }).Unwrap(); } private CancellationTokenSource loadContentCancellation; From 4afbccfcff2544fa4a4e80d765a2bccb270c1658 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 13 Mar 2021 19:30:40 +0900 Subject: [PATCH 216/390] Fix initial operation potentially running before DI is completed --- osu.Game/Overlays/ChangelogOverlay.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/ChangelogOverlay.cs b/osu.Game/Overlays/ChangelogOverlay.cs index eda7748367..e7d68853ad 100644 --- a/osu.Game/Overlays/ChangelogOverlay.cs +++ b/osu.Game/Overlays/ChangelogOverlay.cs @@ -21,6 +21,8 @@ namespace osu.Game.Overlays { public class ChangelogOverlay : OnlineOverlay { + public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks; + public readonly Bindable Current = new Bindable(); private Sample sampleBack; @@ -126,8 +128,11 @@ namespace osu.Game.Overlays private Task initialFetchTask; - private void performAfterFetch(Action action) => fetchListing()?.ContinueWith(_ => - Schedule(action), TaskContinuationOptions.OnlyOnRanToCompletion); + private void performAfterFetch(Action action) => Schedule(() => + { + fetchListing()?.ContinueWith(_ => + Schedule(action), TaskContinuationOptions.OnlyOnRanToCompletion); + }); private Task fetchListing() { From e70ba2d005c45bb485d3c985873fbecf99a1c71c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 13 Mar 2021 23:29:01 +0900 Subject: [PATCH 217/390] Remove unnecessary second variable --- osu.Game/Overlays/Toolbar/Toolbar.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index 7497f4d210..5e2280e2fc 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -28,8 +28,6 @@ namespace osu.Game.Overlays.Toolbar /// private bool hiddenByUser; - private bool userToggled; - public Action OnHome; private ToolbarUserButton userButton; @@ -151,9 +149,9 @@ namespace osu.Game.Overlays.Toolbar protected override void UpdateState(ValueChangedEvent state) { - var blockShow = !userToggled && hiddenByUser; + bool blockShow = hiddenByUser || OverlayActivationMode.Value == OverlayActivation.Disabled; - if (state.NewValue == Visibility.Visible && (OverlayActivationMode.Value == OverlayActivation.Disabled || blockShow)) + if (state.NewValue == Visibility.Visible && blockShow) { State.Value = Visibility.Hidden; return; @@ -184,10 +182,8 @@ namespace osu.Game.Overlays.Toolbar switch (action) { case GlobalAction.ToggleToolbar: - userToggled = true; + hiddenByUser = State.Value == Visibility.Visible; // set before toggling to allow the operation to always succeed. ToggleVisibility(); - hiddenByUser = State.Value == Visibility.Hidden; - userToggled = false; return true; } From a227b0a581103004c10e767f0511f01bb8af2fbc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 13 Mar 2021 23:29:47 +0900 Subject: [PATCH 218/390] Build on xmldoc with rationale --- osu.Game/Overlays/Toolbar/Toolbar.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index 5e2280e2fc..d049c2d3ec 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -25,6 +25,7 @@ namespace osu.Game.Overlays.Toolbar /// /// Whether the user hid this with . + /// In this state, automatic toggles should not occur, respecting the user's preference to have no toolbar. /// private bool hiddenByUser; From 779c55d768a6723c1735ef92a26fedaec1dd6760 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 13 Mar 2021 06:05:08 +0300 Subject: [PATCH 219/390] Fix potentially adding null legacy text to hierarchy --- .../Skinning/Legacy/LegacySpinner.cs | 41 +++++++++++-------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs index b0b9cba2bd..610eb54316 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs @@ -47,7 +47,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy DrawableSpinner = (DrawableSpinner)drawableHitObject; - AddInternal(new Container + Container overlayContainer; + + AddInternal(overlayContainer = new Container { Depth = float.MinValue, RelativeSizeAxes = Axes.Both, @@ -70,17 +72,21 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy Scale = new Vector2(SPRITE_SCALE), Y = SPINNER_TOP_OFFSET + 115, }, - bonusCounter = ((LegacySpriteText)source.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreText))).With(s => - { - s.Alpha = 0f; - s.Anchor = Anchor.TopCentre; - s.Origin = Anchor.Centre; - s.Font = s.Font.With(fixedWidth: false); - s.Scale = new Vector2(SPRITE_SCALE); - s.Y = SPINNER_TOP_OFFSET + 299; - }), } }); + + bonusCounter = (LegacySpriteText)source.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreText)); + + if (bonusCounter != null) + { + bonusCounter.Alpha = 0f; + bonusCounter.Anchor = Anchor.TopCentre; + bonusCounter.Origin = Anchor.Centre; + bonusCounter.Font = bonusCounter.Font.With(fixedWidth: false); + bonusCounter.Scale = new Vector2(SPRITE_SCALE); + bonusCounter.Y = SPINNER_TOP_OFFSET + 299; + overlayContainer.Add(bonusCounter); + } } private IBindable gainedBonus; @@ -91,13 +97,16 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { base.LoadComplete(); - gainedBonus = DrawableSpinner.GainedBonus.GetBoundCopy(); - gainedBonus.BindValueChanged(bonus => + if (bonusCounter != null) { - bonusCounter.Text = $"{bonus.NewValue}"; - bonusCounter.FadeOutFromOne(800, Easing.Out); - bonusCounter.ScaleTo(SPRITE_SCALE * 2f).Then().ScaleTo(SPRITE_SCALE * 1.28f, 800, Easing.Out); - }); + gainedBonus = DrawableSpinner.GainedBonus.GetBoundCopy(); + gainedBonus.BindValueChanged(bonus => + { + bonusCounter.Text = $"{bonus.NewValue}"; + bonusCounter.FadeOutFromOne(800, Easing.Out); + bonusCounter.ScaleTo(SPRITE_SCALE * 2f).Then().ScaleTo(SPRITE_SCALE * 1.28f, 800, Easing.Out); + }); + } completed.BindValueChanged(onCompletedChanged, true); From 8b74666cc339416b2b3f443c8dfdbb518c2f2148 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 14 Mar 2021 15:51:38 +0100 Subject: [PATCH 220/390] Add support for pooling explosions in taiko --- .../Skinning/TestSceneHitExplosion.cs | 4 +- .../Skinning/Legacy/LegacyHitExplosion.cs | 51 +++++------- .../UI/DefaultHitExplosion.cs | 41 +++++++--- osu.Game.Rulesets.Taiko/UI/HitExplosion.cs | 80 ++++++++++++++----- .../UI/HitExplosionPool.cs | 24 ++++++ osu.Game.Rulesets.Taiko/UI/IHitExplosion.cs | 23 ++++++ osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 11 ++- 7 files changed, 170 insertions(+), 64 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko/UI/HitExplosionPool.cs create mode 100644 osu.Game.Rulesets.Taiko/UI/IHitExplosion.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs index fecb5d4a74..ba6e04c92e 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs @@ -38,11 +38,11 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning // the hit needs to be added to hierarchy in order for nested objects to be created correctly. // setting zero alpha is supposed to prevent the test from looking broken. hit.With(h => h.Alpha = 0), - new HitExplosion(hit, hit.Type) + new HitExplosion(hit.Type) { Anchor = Anchor.Centre, Origin = Anchor.Centre, - } + }.With(explosion => explosion.Apply(hit)) } }; } diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyHitExplosion.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyHitExplosion.cs index 651cdd6438..9734e12413 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyHitExplosion.cs @@ -1,22 +1,24 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Linq; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Taiko.Objects.Drawables; +using osu.Game.Rulesets.Taiko.UI; namespace osu.Game.Rulesets.Taiko.Skinning.Legacy { - public class LegacyHitExplosion : CompositeDrawable + public class LegacyHitExplosion : CompositeDrawable, IHitExplosion { - private readonly Drawable sprite; - private readonly Drawable strongSprite; + public override bool RemoveWhenNotAlive => false; - private DrawableStrongNestedHit nestedStrongHit; - private bool switchedToStrongSprite; + private readonly Drawable sprite; + + [CanBeNull] + private readonly Drawable strongSprite; /// /// Creates a new legacy hit explosion. @@ -27,14 +29,14 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy /// /// The normal legacy explosion sprite. /// The strong legacy explosion sprite. - public LegacyHitExplosion(Drawable sprite, Drawable strongSprite = null) + public LegacyHitExplosion(Drawable sprite, [CanBeNull] Drawable strongSprite = null) { this.sprite = sprite; this.strongSprite = strongSprite; } [BackgroundDependencyLoader] - private void load(DrawableHitObject judgedObject) + private void load() { Anchor = Anchor.Centre; Origin = Anchor.Centre; @@ -56,17 +58,15 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy s.Origin = Anchor.Centre; })); } - - if (judgedObject is DrawableHit hit) - nestedStrongHit = hit.NestedHitObjects.SingleOrDefault() as DrawableStrongNestedHit; } - protected override void LoadComplete() + public void Animate(DrawableHitObject drawableHitObject) { - base.LoadComplete(); - const double animation_time = 120; + (sprite as IFramedAnimation)?.GotoFrame(0); + (strongSprite as IFramedAnimation)?.GotoFrame(0); + this.FadeInFromZero(animation_time).Then().FadeOut(animation_time * 1.5); this.ScaleTo(0.6f) @@ -77,24 +77,13 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy Expire(true); } - protected override void Update() + public void AnimateSecondHit() { - base.Update(); + if (strongSprite == null) + return; - if (shouldSwitchToStrongSprite() && !switchedToStrongSprite) - { - sprite.FadeOut(50, Easing.OutQuint); - strongSprite.FadeIn(50, Easing.OutQuint); - switchedToStrongSprite = true; - } - } - - private bool shouldSwitchToStrongSprite() - { - if (nestedStrongHit == null || strongSprite == null) - return false; - - return nestedStrongHit.IsHit; + sprite.FadeOut(50, Easing.OutQuint); + strongSprite.FadeIn(50, Easing.OutQuint); } } } diff --git a/osu.Game.Rulesets.Taiko/UI/DefaultHitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/DefaultHitExplosion.cs index 3bd20e4bb4..2519573ce9 100644 --- a/osu.Game.Rulesets.Taiko/UI/DefaultHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/UI/DefaultHitExplosion.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -13,19 +14,25 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Taiko.UI { - internal class DefaultHitExplosion : CircularContainer + internal class DefaultHitExplosion : CircularContainer, IHitExplosion { - private readonly DrawableHitObject judgedObject; + public override bool RemoveWhenNotAlive => false; + private readonly HitResult result; - public DefaultHitExplosion(DrawableHitObject judgedObject, HitResult result) + [CanBeNull] + private Box body; + + [Resolved] + private OsuColour colours { get; set; } + + public DefaultHitExplosion(HitResult result) { - this.judgedObject = judgedObject; this.result = result; } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load() { RelativeSizeAxes = Axes.Both; @@ -40,26 +47,38 @@ namespace osu.Game.Rulesets.Taiko.UI if (!result.IsHit()) return; - bool isRim = (judgedObject.HitObject as Hit)?.Type == HitType.Rim; - InternalChildren = new[] { - new Box + body = new Box { RelativeSizeAxes = Axes.Both, - Colour = isRim ? colours.BlueDarker : colours.PinkDarker, } }; + + updateColour(); } - protected override void LoadComplete() + private void updateColour([CanBeNull] DrawableHitObject judgedObject = null) { - base.LoadComplete(); + if (body == null) + return; + + bool isRim = (judgedObject?.HitObject as Hit)?.Type == HitType.Rim; + body.Colour = isRim ? colours.BlueDarker : colours.PinkDarker; + } + + public void Animate(DrawableHitObject drawableHitObject) + { + updateColour(drawableHitObject); this.ScaleTo(3f, 1000, Easing.OutQuint); this.FadeOut(500); Expire(true); } + + public void AnimateSecondHit() + { + } } } diff --git a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs index d1fb3348b9..d2ae36a03e 100644 --- a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs @@ -2,10 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System; +using JetBrains.Annotations; using osuTK; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Pooling; +using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects; @@ -16,31 +18,35 @@ namespace osu.Game.Rulesets.Taiko.UI /// /// A circle explodes from the hit target to indicate a hitobject has been hit. /// - internal class HitExplosion : CircularContainer + internal class HitExplosion : PoolableDrawable { public override bool RemoveWhenNotAlive => true; - - [Cached(typeof(DrawableHitObject))] - public readonly DrawableHitObject JudgedObject; + public override bool RemoveCompletedTransforms => false; private readonly HitResult result; + [CanBeNull] + public DrawableHitObject JudgedObject; + private SkinnableDrawable skinnable; - public override double LifetimeStart => skinnable.Drawable.LifetimeStart; - - public override double LifetimeEnd => skinnable.Drawable.LifetimeEnd; - - public HitExplosion(DrawableHitObject judgedObject, HitResult result) + /// + /// This constructor only exists to meet the new() type constraint of . + /// + public HitExplosion() + : this(HitResult.Great) + { + } + + public HitExplosion(HitResult result) { - JudgedObject = judgedObject; this.result = result; Anchor = Anchor.Centre; Origin = Anchor.Centre; - RelativeSizeAxes = Axes.Both; Size = new Vector2(TaikoHitObject.DEFAULT_SIZE); + RelativeSizeAxes = Axes.Both; RelativePositionAxes = Axes.Both; } @@ -48,7 +54,44 @@ namespace osu.Game.Rulesets.Taiko.UI [BackgroundDependencyLoader] private void load() { - Child = skinnable = new SkinnableDrawable(new TaikoSkinComponent(getComponentName(result)), _ => new DefaultHitExplosion(JudgedObject, result)); + InternalChild = skinnable = new SkinnableDrawable(new TaikoSkinComponent(getComponentName(result)), _ => new DefaultHitExplosion(result)); + skinnable.OnSkinChanged += runAnimation; + } + + public void Apply([CanBeNull] DrawableHitObject drawableHitObject) + { + JudgedObject = drawableHitObject; + } + + protected override void PrepareForUse() + { + base.PrepareForUse(); + runAnimation(); + } + + protected override void FreeAfterUse() + { + base.FreeAfterUse(); + + // clean up transforms on free instead of on prepare as is usually the case + // to avoid potentially overriding the effects of VisualiseSecondHit() in the case it is called before PrepareForUse(). + ApplyTransformsAt(double.MinValue, true); + ClearTransforms(true); + } + + private void runAnimation() + { + if (JudgedObject?.Result == null) + return; + + double resultTime = JudgedObject.Result.TimeAbsolute; + + LifetimeStart = resultTime; + + using (BeginAbsoluteSequence(resultTime)) + (skinnable.Drawable as IHitExplosion)?.Animate(JudgedObject); + + LifetimeEnd = skinnable.Drawable.LatestTransformEndTime; } private static TaikoSkinComponents getComponentName(HitResult result) @@ -68,12 +111,13 @@ namespace osu.Game.Rulesets.Taiko.UI throw new ArgumentOutOfRangeException(nameof(result), $"Invalid result type: {result}"); } - /// - /// Transforms this hit explosion to visualise a secondary hit. - /// - public void VisualiseSecondHit() + public void VisualiseSecondHit(JudgementResult judgementResult) { - this.ResizeTo(new Vector2(TaikoStrongableHitObject.DEFAULT_STRONG_SIZE), 50); + using (BeginAbsoluteSequence(judgementResult.TimeAbsolute)) + { + this.ResizeTo(new Vector2(TaikoStrongableHitObject.DEFAULT_STRONG_SIZE), 50); + (skinnable.Drawable as IHitExplosion)?.AnimateSecondHit(); + } } } } diff --git a/osu.Game.Rulesets.Taiko/UI/HitExplosionPool.cs b/osu.Game.Rulesets.Taiko/UI/HitExplosionPool.cs new file mode 100644 index 0000000000..badf34554c --- /dev/null +++ b/osu.Game.Rulesets.Taiko/UI/HitExplosionPool.cs @@ -0,0 +1,24 @@ +// 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.Graphics.Pooling; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Taiko.UI +{ + /// + /// Pool for hit explosions of a specific type. + /// + internal class HitExplosionPool : DrawablePool + { + private readonly HitResult hitResult; + + public HitExplosionPool(HitResult hitResult) + : base(15) + { + this.hitResult = hitResult; + } + + protected override HitExplosion CreateNewDrawable() => new HitExplosion(hitResult); + } +} diff --git a/osu.Game.Rulesets.Taiko/UI/IHitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/IHitExplosion.cs new file mode 100644 index 0000000000..7af941d1ba --- /dev/null +++ b/osu.Game.Rulesets.Taiko/UI/IHitExplosion.cs @@ -0,0 +1,23 @@ +// 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.Rulesets.Objects.Drawables; + +namespace osu.Game.Rulesets.Taiko.UI +{ + /// + /// Interface for hit explosions shown on the playfield's hit target in taiko. + /// + public interface IHitExplosion + { + /// + /// Shows the hit explosion for the supplied . + /// + void Animate(DrawableHitObject drawableHitObject); + + /// + /// Transforms the hit explosion to visualise a secondary hit. + /// + void AnimateSecondHit(); + } +} diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index d2e7b604bb..46dafc3a30 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -42,6 +42,7 @@ namespace osu.Game.Rulesets.Taiko.UI private SkinnableDrawable mascot; private readonly IDictionary> judgementPools = new Dictionary>(); + private readonly IDictionary explosionPools = new Dictionary(); private ProxyContainer topLevelHitContainer; private Container rightArea; @@ -166,10 +167,15 @@ namespace osu.Game.Rulesets.Taiko.UI RegisterPool(100); var hitWindows = new TaikoHitWindows(); + foreach (var result in Enum.GetValues(typeof(HitResult)).OfType().Where(r => hitWindows.IsHitResultAllowed(r))) + { judgementPools.Add(result, new DrawablePool(15)); + explosionPools.Add(result, new HitExplosionPool(result)); + } AddRangeInternal(judgementPools.Values); + AddRangeInternal(explosionPools.Values); } protected override void LoadComplete() @@ -281,7 +287,7 @@ namespace osu.Game.Rulesets.Taiko.UI { case TaikoStrongJudgement _: if (result.IsHit) - hitExplosionContainer.Children.FirstOrDefault(e => e.JudgedObject == ((DrawableStrongNestedHit)judgedObject).ParentHitObject)?.VisualiseSecondHit(); + hitExplosionContainer.Children.FirstOrDefault(e => e.JudgedObject == ((DrawableStrongNestedHit)judgedObject).ParentHitObject)?.VisualiseSecondHit(result); break; case TaikoDrumRollTickJudgement _: @@ -315,7 +321,8 @@ namespace osu.Game.Rulesets.Taiko.UI private void addExplosion(DrawableHitObject drawableObject, HitResult result, HitType type) { - hitExplosionContainer.Add(new HitExplosion(drawableObject, result)); + hitExplosionContainer.Add(explosionPools[result] + .Get(explosion => explosion.Apply(drawableObject))); if (drawableObject.HitObject.Kiai) kiaiExplosionContainer.Add(new KiaiHitExplosion(drawableObject, type)); } From 00306c007529f176d92666f445a8ef7219787cea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 14 Mar 2021 15:56:34 +0100 Subject: [PATCH 221/390] Adjust test code after explosion pooling changes --- .../Skinning/TestSceneHitExplosion.cs | 19 ++++++++++++++----- .../TestSceneHits.cs | 10 +++++----- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs index ba6e04c92e..61ea8b664d 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs @@ -2,8 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.UI; @@ -13,6 +17,8 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning [TestFixture] public class TestSceneHitExplosion : TaikoSkinnableTestScene { + protected override double TimePerAction => 100; + [Test] public void TestNormalHit() { @@ -21,11 +27,14 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning AddStep("Miss", () => SetContents(() => getContentFor(createHit(HitResult.Miss)))); } - [Test] - public void TestStrongHit([Values(false, true)] bool hitBoth) + [TestCase(HitResult.Great)] + [TestCase(HitResult.Ok)] + public void TestStrongHit(HitResult type) { - AddStep("Great", () => SetContents(() => getContentFor(createStrongHit(HitResult.Great, hitBoth)))); - AddStep("Good", () => SetContents(() => getContentFor(createStrongHit(HitResult.Ok, hitBoth)))); + AddStep("create hit", () => SetContents(() => getContentFor(createStrongHit(type)))); + AddStep("visualise second hit", + () => this.ChildrenOfType() + .ForEach(e => e.VisualiseSecondHit(new JudgementResult(new HitObject { StartTime = Time.Current }, new Judgement())))); } private Drawable getContentFor(DrawableTestHit hit) @@ -49,6 +58,6 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning private DrawableTestHit createHit(HitResult type) => new DrawableTestHit(new Hit { StartTime = Time.Current }, type); - private DrawableTestHit createStrongHit(HitResult type, bool hitBoth) => new DrawableTestStrongHit(Time.Current, type, hitBoth); + private DrawableTestHit createStrongHit(HitResult type) => new DrawableTestStrongHit(Time.Current, type); } } diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs index 7695ca067b..87c936d386 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs @@ -11,7 +11,6 @@ using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Judgements; using osu.Game.Rulesets.Taiko.Objects; @@ -108,12 +107,12 @@ namespace osu.Game.Rulesets.Taiko.Tests { HitResult hitResult = RNG.Next(2) == 0 ? HitResult.Ok : HitResult.Great; - Hit hit = new Hit(); + Hit hit = new Hit { StartTime = DrawableRuleset.Playfield.Time.Current }; var h = new DrawableTestHit(hit, kiai: kiai) { X = RNG.NextSingle(hitResult == HitResult.Ok ? -0.1f : -0.05f, hitResult == HitResult.Ok ? 0.1f : 0.05f) }; DrawableRuleset.Playfield.Add(h); - ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = hitResult }); + ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(hit, new TaikoJudgement()) { Type = hitResult }); } private void addStrongHitJudgement(bool kiai) @@ -122,6 +121,7 @@ namespace osu.Game.Rulesets.Taiko.Tests Hit hit = new Hit { + StartTime = DrawableRuleset.Playfield.Time.Current, IsStrong = true, Samples = createSamples(strong: true) }; @@ -129,8 +129,8 @@ namespace osu.Game.Rulesets.Taiko.Tests DrawableRuleset.Playfield.Add(h); - ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = hitResult }); - ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h.NestedHitObjects.Single(), new JudgementResult(new HitObject(), new TaikoStrongJudgement()) { Type = HitResult.Great }); + ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(hit, new TaikoJudgement()) { Type = hitResult }); + ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h.NestedHitObjects.Single(), new JudgementResult(hit.NestedHitObjects.Single(), new TaikoStrongJudgement()) { Type = HitResult.Great }); } private void addMissJudgement() From 0a1e325fc774f371785ba95b618f71bd0637bb2e Mon Sep 17 00:00:00 2001 From: Roman Kapustin Date: Sun, 14 Mar 2021 19:34:53 +0300 Subject: [PATCH 222/390] Extract requerying of navigational properties from DbContext --- osu.Game/Beatmaps/BeatmapManager.cs | 16 +---------- osu.Game/Database/Extensions.cs | 44 +++++++++++++++++++++++++++++ osu.Game/Scoring/ScoreManager.cs | 14 +-------- osu.Game/Skinning/SkinManager.cs | 9 +----- 4 files changed, 47 insertions(+), 36 deletions(-) create mode 100644 osu.Game/Database/Extensions.cs diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 3254f53574..f42fba79cb 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -174,21 +174,7 @@ namespace osu.Game.Beatmaps if (beatmapSet.Beatmaps.Any(b => b.BaseDifficulty == null)) throw new InvalidOperationException($"Cannot import {nameof(BeatmapInfo)} with null {nameof(BeatmapInfo.BaseDifficulty)}."); - var dbContext = ContextFactory.Get(); - - // Workaround System.InvalidOperationException - // The instance of entity type 'RulesetInfo' cannot be tracked because another instance with the same key value for {'ID'} is already being tracked. - foreach (var beatmap in beatmapSet.Beatmaps) - { - beatmap.Ruleset = dbContext.RulesetInfo.Find(beatmap.RulesetID); - } - - // Workaround System.InvalidOperationException - // The instance of entity type 'FileInfo' cannot be tracked because another instance with the same key value for {'ID'} is already being tracked. - foreach (var file in beatmapSet.Files) - { - file.FileInfo = dbContext.FileInfo.Find(file.FileInfoID); - } + beatmapSet.Requery(ContextFactory); // check if a set already exists with the same online id, delete if it does. if (beatmapSet.OnlineBeatmapSetID != null) diff --git a/osu.Game/Database/Extensions.cs b/osu.Game/Database/Extensions.cs new file mode 100644 index 0000000000..3af26c348e --- /dev/null +++ b/osu.Game/Database/Extensions.cs @@ -0,0 +1,44 @@ +// 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 osu.Game.Beatmaps; +using osu.Game.Scoring; + +namespace osu.Game.Database +{ + public static class Extensions + { + public static void Requery(this BeatmapSetInfo beatmapSetInfo, IDatabaseContextFactory databaseContextFactory) + { + var dbContext = databaseContextFactory.Get(); + + foreach (var beatmap in beatmapSetInfo.Beatmaps) + { + // Workaround System.InvalidOperationException + // The instance of entity type 'RulesetInfo' cannot be tracked because another instance with the same key value for {'ID'} is already being tracked. + beatmap.Ruleset = dbContext.RulesetInfo.Find(beatmap.RulesetID); + } + + beatmapSetInfo.Files.Requery(databaseContextFactory); + } + + public static void Requery(this ScoreInfo scoreInfo, IDatabaseContextFactory databaseContextFactory) + { + scoreInfo.Files.Requery(databaseContextFactory); + scoreInfo.Beatmap.BeatmapSet.Files.Requery(databaseContextFactory); + } + + public static void Requery(this List files, IDatabaseContextFactory databaseContextFactory) where T : class, INamedFileInfo + { + var dbContext = databaseContextFactory.Get(); + + foreach (var file in files) + { + // Workaround System.InvalidOperationException + // The instance of entity type 'FileInfo' cannot be tracked because another instance with the same key value for {'ID'} is already being tracked. + file.FileInfo = dbContext.FileInfo.Find(file.FileInfoID); + } + } + } +} diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index a97c516a1b..1e90ee1ac7 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -54,19 +54,7 @@ namespace osu.Game.Scoring protected override void PreImport(ScoreInfo model) { - var dbContext = ContextFactory.Get(); - - // Workaround System.InvalidOperationException - // The instance of entity type 'FileInfo' cannot be tracked because another instance with the same key value for {'ID'} is already being tracked. - foreach (var file in model.Files) - { - file.FileInfo = dbContext.FileInfo.Find(file.FileInfoID); - } - - foreach (var file in model.Beatmap.BeatmapSet.Files) - { - file.FileInfo = dbContext.FileInfo.Find(file.FileInfoID); - } + model.Requery(ContextFactory); } protected override ScoreInfo CreateModel(ArchiveReader archive) diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 2bb27b60d6..c25f00eccb 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -144,14 +144,7 @@ namespace osu.Game.Skinning protected override void PreImport(SkinInfo model) { - var dbContext = ContextFactory.Get(); - - // Workaround System.InvalidOperationException - // The instance of entity type 'FileInfo' cannot be tracked because another instance with the same key value for {'ID'} is already being tracked. - foreach (var file in model.Files) - { - file.FileInfo = dbContext.FileInfo.Find(file.FileInfoID); - } + model.Files.Requery(ContextFactory); } /// From 61d5a6cc57941cab31e2abb0acee00c8fad3f80f Mon Sep 17 00:00:00 2001 From: Roman Kapustin Date: Sun, 14 Mar 2021 19:47:14 +0300 Subject: [PATCH 223/390] Simplify Microsoft.EntityFrameworkCore.Design PackageReference --- osu.Desktop/osu.Desktop.csproj | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 4af69c573d..d9d23dea6b 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -28,10 +28,7 @@ - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + From 28ef64b62a5d873110a163c4275fb48c9c47b262 Mon Sep 17 00:00:00 2001 From: Roman Kapustin Date: Sun, 14 Mar 2021 21:43:27 +0300 Subject: [PATCH 224/390] Explicitly specify SingleQuery behavior --- osu.Game/Database/OsuDbContext.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index 689f248de8..e5ae530018 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -3,7 +3,6 @@ using System; using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.Extensions.Logging; using osu.Framework.Logging; using osu.Framework.Statistics; @@ -111,8 +110,7 @@ namespace osu.Game.Database { base.OnConfiguring(optionsBuilder); optionsBuilder - .UseSqlite(connectionString, sqliteOptions => sqliteOptions.CommandTimeout(10)) - .ConfigureWarnings(w => w.Ignore(RelationalEventId.MultipleCollectionIncludeWarning)) + .UseSqlite(connectionString, sqliteOptions => sqliteOptions.CommandTimeout(10).UseQuerySplittingBehavior(QuerySplittingBehavior.SingleQuery)) .UseLoggerFactory(logger.Value); } From 900da7b891c3a74faa3ebbe0f030e9cf3350b273 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Mar 2021 12:42:41 +0900 Subject: [PATCH 225/390] Rename and refactor extenion methods to be easier to read --- .../Database/DatabaseWorkaroundExtensions.cs | 48 +++++++++++++++++++ osu.Game/Database/Extensions.cs | 44 ----------------- 2 files changed, 48 insertions(+), 44 deletions(-) create mode 100644 osu.Game/Database/DatabaseWorkaroundExtensions.cs delete mode 100644 osu.Game/Database/Extensions.cs diff --git a/osu.Game/Database/DatabaseWorkaroundExtensions.cs b/osu.Game/Database/DatabaseWorkaroundExtensions.cs new file mode 100644 index 0000000000..07ce7e8529 --- /dev/null +++ b/osu.Game/Database/DatabaseWorkaroundExtensions.cs @@ -0,0 +1,48 @@ +// 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 osu.Game.Beatmaps; +using osu.Game.Scoring; + +namespace osu.Game.Database +{ + public static class DatabaseWorkaroundExtensions + { + public static void Requery(this IHasPrimaryKey model, IDatabaseContextFactory contextFactory) + { + switch (model) + { + case ScoreInfo scoreInfo: + scoreInfo.Beatmap.BeatmapSet.Requery(contextFactory); + scoreInfo.Files.RequeryFiles(contextFactory); + break; + + case BeatmapSetInfo beatmapSetInfo: + var context = contextFactory.Get(); + + foreach (var beatmap in beatmapSetInfo.Beatmaps) + { + // Workaround System.InvalidOperationException + // The instance of entity type 'RulesetInfo' cannot be tracked because another instance with the same key value for {'ID'} is already being tracked. + beatmap.Ruleset = context.RulesetInfo.Find(beatmap.RulesetID); + } + + requeryFiles(beatmapSetInfo.Files, contextFactory); + break; + } + } + + public static void RequeryFiles(this List files, IDatabaseContextFactory databaseContextFactory) where T : class, INamedFileInfo + { + var dbContext = databaseContextFactory.Get(); + + foreach (var file in files) + { + // Workaround System.InvalidOperationException + // The instance of entity type 'FileInfo' cannot be tracked because another instance with the same key value for {'ID'} is already being tracked. + file.FileInfo = dbContext.FileInfo.Find(file.FileInfoID); + } + } + } +} diff --git a/osu.Game/Database/Extensions.cs b/osu.Game/Database/Extensions.cs deleted file mode 100644 index 3af26c348e..0000000000 --- a/osu.Game/Database/Extensions.cs +++ /dev/null @@ -1,44 +0,0 @@ -// 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 osu.Game.Beatmaps; -using osu.Game.Scoring; - -namespace osu.Game.Database -{ - public static class Extensions - { - public static void Requery(this BeatmapSetInfo beatmapSetInfo, IDatabaseContextFactory databaseContextFactory) - { - var dbContext = databaseContextFactory.Get(); - - foreach (var beatmap in beatmapSetInfo.Beatmaps) - { - // Workaround System.InvalidOperationException - // The instance of entity type 'RulesetInfo' cannot be tracked because another instance with the same key value for {'ID'} is already being tracked. - beatmap.Ruleset = dbContext.RulesetInfo.Find(beatmap.RulesetID); - } - - beatmapSetInfo.Files.Requery(databaseContextFactory); - } - - public static void Requery(this ScoreInfo scoreInfo, IDatabaseContextFactory databaseContextFactory) - { - scoreInfo.Files.Requery(databaseContextFactory); - scoreInfo.Beatmap.BeatmapSet.Files.Requery(databaseContextFactory); - } - - public static void Requery(this List files, IDatabaseContextFactory databaseContextFactory) where T : class, INamedFileInfo - { - var dbContext = databaseContextFactory.Get(); - - foreach (var file in files) - { - // Workaround System.InvalidOperationException - // The instance of entity type 'FileInfo' cannot be tracked because another instance with the same key value for {'ID'} is already being tracked. - file.FileInfo = dbContext.FileInfo.Find(file.FileInfoID); - } - } - } -} From 2bdffd10044984ea0a5639754b8ecff8f4fc979b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Mar 2021 12:47:58 +0900 Subject: [PATCH 226/390] Move skin requery logic into extension methods --- .../Database/DatabaseWorkaroundExtensions.cs | 19 +++++++++++++++++-- osu.Game/Skinning/SkinManager.cs | 2 +- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/DatabaseWorkaroundExtensions.cs b/osu.Game/Database/DatabaseWorkaroundExtensions.cs index 07ce7e8529..39bf358071 100644 --- a/osu.Game/Database/DatabaseWorkaroundExtensions.cs +++ b/osu.Game/Database/DatabaseWorkaroundExtensions.cs @@ -1,21 +1,36 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Scoring; +using osu.Game.Skinning; namespace osu.Game.Database { + /// + /// Extension methods which contain workarounds to make EFcore 5.x work with our existing (incorrect) thread safety. + /// The intention is to avoid blocking package updates while we consider the future of the database backend, with a potential backend switch imminent. + /// public static class DatabaseWorkaroundExtensions { + /// + /// Re-query the provided model to ensure it is in a sane state. This method requires explicit implementation per model type. + /// + /// + /// public static void Requery(this IHasPrimaryKey model, IDatabaseContextFactory contextFactory) { switch (model) { + case SkinInfo skinInfo: + requeryFiles(skinInfo.Files, contextFactory); + break; + case ScoreInfo scoreInfo: scoreInfo.Beatmap.BeatmapSet.Requery(contextFactory); - scoreInfo.Files.RequeryFiles(contextFactory); + requeryFiles(scoreInfo.Files, contextFactory); break; case BeatmapSetInfo beatmapSetInfo: @@ -33,7 +48,7 @@ namespace osu.Game.Database } } - public static void RequeryFiles(this List files, IDatabaseContextFactory databaseContextFactory) where T : class, INamedFileInfo + private static void requeryFiles(List files, IDatabaseContextFactory databaseContextFactory) where T : class, INamedFileInfo { var dbContext = databaseContextFactory.Get(); diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index c25f00eccb..601b77e782 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -144,7 +144,7 @@ namespace osu.Game.Skinning protected override void PreImport(SkinInfo model) { - model.Files.Requery(ContextFactory); + model.Requery(ContextFactory); } /// From 8a3553388972349a6833afcf915f44a7dae19995 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Mar 2021 12:48:23 +0900 Subject: [PATCH 227/390] Add fall-through case to catch a potential requery for unsupported model type --- osu.Game/Database/DatabaseWorkaroundExtensions.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Database/DatabaseWorkaroundExtensions.cs b/osu.Game/Database/DatabaseWorkaroundExtensions.cs index 39bf358071..1d5c98ed8d 100644 --- a/osu.Game/Database/DatabaseWorkaroundExtensions.cs +++ b/osu.Game/Database/DatabaseWorkaroundExtensions.cs @@ -45,6 +45,9 @@ namespace osu.Game.Database requeryFiles(beatmapSetInfo.Files, contextFactory); break; + + default: + throw new ArgumentException($"{nameof(Requery)} does not have support for the provided model type", nameof(model)); } } From 1573298e682329d133059a74dd56c8c849b86bba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Mar 2021 13:12:10 +0900 Subject: [PATCH 228/390] Update remaining package references to point to efcore5 --- .../osu.Game.Rulesets.Catch.Tests.csproj | 2 +- .../osu.Game.Rulesets.Mania.Tests.csproj | 2 +- osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj | 2 +- .../osu.Game.Rulesets.Taiko.Tests.csproj | 2 +- osu.Game.Tests/osu.Game.Tests.csproj | 2 +- osu.iOS.props | 2 -- 6 files changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj index 728af5124e..42f70151ac 100644 --- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj +++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj @@ -5,7 +5,7 @@ - + WinExe diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj index af16f39563..e51b20c9fe 100644 --- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj +++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj @@ -5,7 +5,7 @@ - + WinExe diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj index 3d2d1f3fec..f1f75148ef 100644 --- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj +++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj @@ -5,7 +5,7 @@ - + WinExe diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj index fa00922706..c9a320bdd5 100644 --- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj +++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj @@ -5,7 +5,7 @@ - + WinExe diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index e36b3cdc74..6f8e0fac6f 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -6,7 +6,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index ccd33bf88c..71fcdd45f3 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -90,8 +90,6 @@ - - From 79d3379f55b2b23dbfdd613c58adc8a8b3259768 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Mar 2021 13:20:22 +0900 Subject: [PATCH 229/390] Reformat application of configuration --- osu.Game/Database/OsuDbContext.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index e5ae530018..2342ab07d4 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -110,7 +110,10 @@ namespace osu.Game.Database { base.OnConfiguring(optionsBuilder); optionsBuilder - .UseSqlite(connectionString, sqliteOptions => sqliteOptions.CommandTimeout(10).UseQuerySplittingBehavior(QuerySplittingBehavior.SingleQuery)) + .UseSqlite(connectionString, + sqliteOptions => sqliteOptions + .CommandTimeout(10) + .UseQuerySplittingBehavior(QuerySplittingBehavior.SingleQuery)) .UseLoggerFactory(logger.Value); } From 2904f479c65bf9f2cd76a3164ab51d13ff53dc96 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Mar 2021 13:26:14 +0900 Subject: [PATCH 230/390] Share file lookup workaround in ArchiveModelManager with workaround extensions class --- osu.Game/Database/ArchiveModelManager.cs | 4 +--- .../Database/DatabaseWorkaroundExtensions.cs | 23 +++++++++++-------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index fe2caaa0b7..31c365b478 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -462,9 +462,7 @@ namespace osu.Game.Database // Dereference the existing file info, since the file model will be removed. if (file.FileInfo != null) { - // Workaround System.InvalidOperationException - // The instance of entity type 'FileInfo' cannot be tracked because another instance with the same key value for {'ID'} is already being tracked. - file.FileInfo = usage.Context.FileInfo.Find(file.FileInfoID); + file.Requery(usage.Context); Files.Dereference(file.FileInfo); diff --git a/osu.Game/Database/DatabaseWorkaroundExtensions.cs b/osu.Game/Database/DatabaseWorkaroundExtensions.cs index 1d5c98ed8d..8ac05f78e0 100644 --- a/osu.Game/Database/DatabaseWorkaroundExtensions.cs +++ b/osu.Game/Database/DatabaseWorkaroundExtensions.cs @@ -49,18 +49,23 @@ namespace osu.Game.Database default: throw new ArgumentException($"{nameof(Requery)} does not have support for the provided model type", nameof(model)); } + + void requeryFiles(List files, IDatabaseContextFactory databaseContextFactory) where T : class, INamedFileInfo + { + var dbContext = databaseContextFactory.Get(); + + foreach (var file in files) + { + Requery(file, dbContext); + } + } } - private static void requeryFiles(List files, IDatabaseContextFactory databaseContextFactory) where T : class, INamedFileInfo + public static void Requery(this INamedFileInfo file, OsuDbContext dbContext) { - var dbContext = databaseContextFactory.Get(); - - foreach (var file in files) - { - // Workaround System.InvalidOperationException - // The instance of entity type 'FileInfo' cannot be tracked because another instance with the same key value for {'ID'} is already being tracked. - file.FileInfo = dbContext.FileInfo.Find(file.FileInfoID); - } + // Workaround System.InvalidOperationException + // The instance of entity type 'FileInfo' cannot be tracked because another instance with the same key value for {'ID'} is already being tracked. + file.FileInfo = dbContext.FileInfo.Find(file.FileInfoID); } } } From fce21f23d687ab72be391c23987ad1edff8740f0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Mar 2021 13:29:26 +0900 Subject: [PATCH 231/390] Add comments marking workarounds required for EFcore 5 --- .../Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs | 2 +- .../Visual/SongSelect/TestSceneBeatmapRecommendations.cs | 2 +- osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs | 2 +- osu.Game/Database/ArchiveModelManager.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index 4a9eaa1842..8cfe5d8af2 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -56,7 +56,7 @@ namespace osu.Game.Tests.Visual.Multiplayer beatmaps.Add(new BeatmapInfo { Ruleset = rulesets.GetRuleset(i % 4), - RulesetID = i % 4, + RulesetID = i % 4, // workaround for efcore 5 compatibility. OnlineBeatmapID = beatmapId, Length = length, BPM = bpm, diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs index 223ace6ca5..9b8b74e6f6 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs @@ -186,7 +186,7 @@ namespace osu.Game.Tests.Visual.SongSelect Metadata = metadata, BaseDifficulty = new BeatmapDifficulty(), Ruleset = ruleset, - RulesetID = ruleset.ID.GetValueOrDefault(), + RulesetID = ruleset.ID.GetValueOrDefault(), // workaround for efcore 5 compatibility. StarDifficulty = difficultyIndex + 1, Version = $"SR{difficultyIndex + 1}" }).ToList() diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 4b402d0c54..2d192ae207 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -915,7 +915,7 @@ namespace osu.Game.Tests.Visual.SongSelect beatmaps.Add(new BeatmapInfo { Ruleset = ruleset, - RulesetID = ruleset.ID.GetValueOrDefault(), + RulesetID = ruleset.ID.GetValueOrDefault(), // workaround for efcore 5 compatibility. OnlineBeatmapID = beatmapId, Version = $"{beatmapId} (length {TimeSpan.FromMilliseconds(length):m\\:ss}, bpm {bpm:0.#})", Length = length, diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 31c365b478..64428882ac 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -642,7 +642,7 @@ namespace osu.Game.Database { Filename = file.Substring(prefix.Length).ToStandardisedPath(), FileInfo = fileInfo, - FileInfoID = fileInfo.ID + FileInfoID = fileInfo.ID // workaround for efcore 5 compatibility. }); } } From 6d4c1ba2aee43c9fdab49f83ba16c0bcdedcce78 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Mar 2021 13:35:08 +0900 Subject: [PATCH 232/390] Fix a couple of new inspections introduced in Rider EAPs --- osu.Game/Beatmaps/BeatmapConverter.cs | 10 +++++----- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 3 --- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs index cb0b3a8d09..b291edd19d 100644 --- a/osu.Game/Beatmaps/BeatmapConverter.cs +++ b/osu.Game/Beatmaps/BeatmapConverter.cs @@ -17,12 +17,12 @@ namespace osu.Game.Beatmaps public abstract class BeatmapConverter : IBeatmapConverter where T : HitObject { - private event Action> ObjectConverted; + private event Action> objectConverted; event Action> IBeatmapConverter.ObjectConverted { - add => ObjectConverted += value; - remove => ObjectConverted -= value; + add => objectConverted += value; + remove => objectConverted -= value; } public IBeatmap Beatmap { get; } @@ -92,10 +92,10 @@ namespace osu.Game.Beatmaps var converted = ConvertHitObject(obj, beatmap, cancellationToken); - if (ObjectConverted != null) + if (objectConverted != null) { converted = converted.ToList(); - ObjectConverted.Invoke(obj, converted); + objectConverted.Invoke(obj, converted); } foreach (var c in converted) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index df940e8c8e..d06478b9de 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -471,9 +471,6 @@ namespace osu.Game.Beatmaps.Formats private string toLegacyCustomSampleBank(HitSampleInfo hitSampleInfo) { - if (hitSampleInfo == null) - return "0"; - if (hitSampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacy) return legacy.CustomSampleBank.ToString(CultureInfo.InvariantCulture); From 1e519f0d31125a3bb508be2dd97777556f69f0b1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Mar 2021 14:20:59 +0900 Subject: [PATCH 233/390] Fix seemingly innocent logic change causing breakage in score imports --- osu.Game/Database/DatabaseWorkaroundExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/DatabaseWorkaroundExtensions.cs b/osu.Game/Database/DatabaseWorkaroundExtensions.cs index 8ac05f78e0..a3a982f232 100644 --- a/osu.Game/Database/DatabaseWorkaroundExtensions.cs +++ b/osu.Game/Database/DatabaseWorkaroundExtensions.cs @@ -29,7 +29,7 @@ namespace osu.Game.Database break; case ScoreInfo scoreInfo: - scoreInfo.Beatmap.BeatmapSet.Requery(contextFactory); + requeryFiles(scoreInfo.Beatmap.BeatmapSet.Files, contextFactory); requeryFiles(scoreInfo.Files, contextFactory); break; From 3dd72d6f7d229bd7cb8311e707ace797fed31d3e Mon Sep 17 00:00:00 2001 From: Joehu Date: Sun, 14 Mar 2021 22:47:05 -0700 Subject: [PATCH 234/390] Fix disable mouse buttons setting not showing default indicator when using keybind --- osu.Game/OsuGame.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index dd775888a1..eb34ba4a37 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -886,7 +886,9 @@ namespace osu.Game return true; case GlobalAction.ToggleGameplayMouseButtons: - LocalConfig.Set(OsuSetting.MouseDisableButtons, !LocalConfig.Get(OsuSetting.MouseDisableButtons)); + var mouseDisableButtons = LocalConfig.GetBindable(OsuSetting.MouseDisableButtons); + + mouseDisableButtons.Value = !mouseDisableButtons.Value; return true; case GlobalAction.RandomSkin: From 848adddd9285c3b22c93e6fa06f12c2e9f825c48 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 15 Mar 2021 11:05:29 +0300 Subject: [PATCH 235/390] Use `double.ToString(InvariantInfo)` instead --- osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinner.cs | 3 ++- osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinner.cs index 83676d3784..891821fe2f 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinner.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Globalization; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -58,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default gainedBonus = drawableSpinner.GainedBonus.GetBoundCopy(); gainedBonus.BindValueChanged(bonus => { - bonusCounter.Text = $"{bonus.NewValue}"; + bonusCounter.Text = bonus.NewValue.ToString(NumberFormatInfo.InvariantInfo); bonusCounter.FadeOutFromOne(1500); bonusCounter.ScaleTo(1.5f).Then().ScaleTo(1f, 1000, Easing.OutQuint); }); diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs index 610eb54316..6d4fbd7445 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Globalization; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -102,7 +103,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy gainedBonus = DrawableSpinner.GainedBonus.GetBoundCopy(); gainedBonus.BindValueChanged(bonus => { - bonusCounter.Text = $"{bonus.NewValue}"; + bonusCounter.Text = bonus.NewValue.ToString(NumberFormatInfo.InvariantInfo); bonusCounter.FadeOutFromOne(800, Easing.Out); bonusCounter.ScaleTo(SPRITE_SCALE * 2f).Then().ScaleTo(SPRITE_SCALE * 1.28f, 800, Easing.Out); }); From 393f1fbd3f7f4169fefb1df16b689e66314e6fdf Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 15 Mar 2021 10:07:50 -0700 Subject: [PATCH 236/390] Remove skype --- osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs | 1 - osu.Game/Users/User.cs | 3 --- 2 files changed, 4 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs index 2925107766..662f55317b 100644 --- a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs @@ -138,7 +138,6 @@ namespace osu.Game.Overlays.Profile.Header if (!string.IsNullOrEmpty(user.Twitter)) anyInfoAdded |= tryAddInfo(FontAwesome.Brands.Twitter, "@" + user.Twitter, $@"https://twitter.com/{user.Twitter}"); anyInfoAdded |= tryAddInfo(FontAwesome.Brands.Discord, user.Discord); - anyInfoAdded |= tryAddInfo(FontAwesome.Brands.Skype, user.Skype, @"skype:" + user.Skype + @"?chat"); anyInfoAdded |= tryAddInfo(FontAwesome.Solid.Link, websiteWithoutProtocol, user.Website); // If no information was added to the bottomLinkContainer, hide it to avoid unwanted padding diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs index 4d537b91bd..6c45417db0 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -111,9 +111,6 @@ namespace osu.Game.Users [JsonProperty(@"twitter")] public string Twitter; - [JsonProperty(@"skype")] - public string Skype; - [JsonProperty(@"discord")] public string Discord; From 58220481dbd71edf491a56494bafa302f8250915 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 15 Mar 2021 20:38:11 +0100 Subject: [PATCH 237/390] Rename `I{-> Animatable}HitExplosion` --- osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyHitExplosion.cs | 2 +- osu.Game.Rulesets.Taiko/UI/DefaultHitExplosion.cs | 2 +- osu.Game.Rulesets.Taiko/UI/HitExplosion.cs | 4 ++-- .../UI/{IHitExplosion.cs => IAnimatableHitExplosion.cs} | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) rename osu.Game.Rulesets.Taiko/UI/{IHitExplosion.cs => IAnimatableHitExplosion.cs} (79%) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyHitExplosion.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyHitExplosion.cs index 9734e12413..aad9f53b93 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyHitExplosion.cs @@ -11,7 +11,7 @@ using osu.Game.Rulesets.Taiko.UI; namespace osu.Game.Rulesets.Taiko.Skinning.Legacy { - public class LegacyHitExplosion : CompositeDrawable, IHitExplosion + public class LegacyHitExplosion : CompositeDrawable, IAnimatableHitExplosion { public override bool RemoveWhenNotAlive => false; diff --git a/osu.Game.Rulesets.Taiko/UI/DefaultHitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/DefaultHitExplosion.cs index 2519573ce9..5bb463353d 100644 --- a/osu.Game.Rulesets.Taiko/UI/DefaultHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/UI/DefaultHitExplosion.cs @@ -14,7 +14,7 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Taiko.UI { - internal class DefaultHitExplosion : CircularContainer, IHitExplosion + internal class DefaultHitExplosion : CircularContainer, IAnimatableHitExplosion { public override bool RemoveWhenNotAlive => false; diff --git a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs index d2ae36a03e..bdebe9da17 100644 --- a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs @@ -89,7 +89,7 @@ namespace osu.Game.Rulesets.Taiko.UI LifetimeStart = resultTime; using (BeginAbsoluteSequence(resultTime)) - (skinnable.Drawable as IHitExplosion)?.Animate(JudgedObject); + (skinnable.Drawable as IAnimatableHitExplosion)?.Animate(JudgedObject); LifetimeEnd = skinnable.Drawable.LatestTransformEndTime; } @@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Taiko.UI using (BeginAbsoluteSequence(judgementResult.TimeAbsolute)) { this.ResizeTo(new Vector2(TaikoStrongableHitObject.DEFAULT_STRONG_SIZE), 50); - (skinnable.Drawable as IHitExplosion)?.AnimateSecondHit(); + (skinnable.Drawable as IAnimatableHitExplosion)?.AnimateSecondHit(); } } } diff --git a/osu.Game.Rulesets.Taiko/UI/IHitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/IAnimatableHitExplosion.cs similarity index 79% rename from osu.Game.Rulesets.Taiko/UI/IHitExplosion.cs rename to osu.Game.Rulesets.Taiko/UI/IAnimatableHitExplosion.cs index 7af941d1ba..cf0f5f9fb6 100644 --- a/osu.Game.Rulesets.Taiko/UI/IHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/UI/IAnimatableHitExplosion.cs @@ -6,9 +6,9 @@ using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.Taiko.UI { /// - /// Interface for hit explosions shown on the playfield's hit target in taiko. + /// A skinnable element of a hit explosion that supports playing an animation from the current point in time. /// - public interface IHitExplosion + public interface IAnimatableHitExplosion { /// /// Shows the hit explosion for the supplied . From f4e508b57051e887a00a8f4649e4616b762a8c8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 15 Mar 2021 20:43:30 +0100 Subject: [PATCH 238/390] Remove unnecessary overrides --- osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs | 2 -- osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyHitExplosion.cs | 2 -- 2 files changed, 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs b/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs index 69b81d6d5c..1b5d576c1e 100644 --- a/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs +++ b/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs @@ -21,8 +21,6 @@ namespace osu.Game.Rulesets.Mania.UI { private const float default_large_faint_size = 0.8f; - public override bool RemoveWhenNotAlive => true; - [Resolved] private Column column { get; set; } diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyHitExplosion.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyHitExplosion.cs index aad9f53b93..bef9279bac 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyHitExplosion.cs @@ -13,8 +13,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy { public class LegacyHitExplosion : CompositeDrawable, IAnimatableHitExplosion { - public override bool RemoveWhenNotAlive => false; - private readonly Drawable sprite; [CanBeNull] From 72c18fbdfe8f91798a1adf16ca1f92ecbc593ae4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 15 Mar 2021 20:48:19 +0100 Subject: [PATCH 239/390] Restructure explosion animation to avoid resetting transforms on free --- osu.Game.Rulesets.Taiko/UI/HitExplosion.cs | 32 ++++++++++++---------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs index bdebe9da17..8f5e9e54ab 100644 --- a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs @@ -25,6 +25,8 @@ namespace osu.Game.Rulesets.Taiko.UI private readonly HitResult result; + private double? secondHitTime; + [CanBeNull] public DrawableHitObject JudgedObject; @@ -61,6 +63,7 @@ namespace osu.Game.Rulesets.Taiko.UI public void Apply([CanBeNull] DrawableHitObject drawableHitObject) { JudgedObject = drawableHitObject; + secondHitTime = null; } protected override void PrepareForUse() @@ -69,16 +72,6 @@ namespace osu.Game.Rulesets.Taiko.UI runAnimation(); } - protected override void FreeAfterUse() - { - base.FreeAfterUse(); - - // clean up transforms on free instead of on prepare as is usually the case - // to avoid potentially overriding the effects of VisualiseSecondHit() in the case it is called before PrepareForUse(). - ApplyTransformsAt(double.MinValue, true); - ClearTransforms(true); - } - private void runAnimation() { if (JudgedObject?.Result == null) @@ -88,9 +81,21 @@ namespace osu.Game.Rulesets.Taiko.UI LifetimeStart = resultTime; + ApplyTransformsAt(double.MinValue, true); + ClearTransforms(true); + using (BeginAbsoluteSequence(resultTime)) (skinnable.Drawable as IAnimatableHitExplosion)?.Animate(JudgedObject); + if (secondHitTime != null) + { + using (BeginAbsoluteSequence(secondHitTime.Value)) + { + this.ResizeTo(new Vector2(TaikoStrongableHitObject.DEFAULT_STRONG_SIZE), 50); + (skinnable.Drawable as IAnimatableHitExplosion)?.AnimateSecondHit(); + } + } + LifetimeEnd = skinnable.Drawable.LatestTransformEndTime; } @@ -113,11 +118,8 @@ namespace osu.Game.Rulesets.Taiko.UI public void VisualiseSecondHit(JudgementResult judgementResult) { - using (BeginAbsoluteSequence(judgementResult.TimeAbsolute)) - { - this.ResizeTo(new Vector2(TaikoStrongableHitObject.DEFAULT_STRONG_SIZE), 50); - (skinnable.Drawable as IAnimatableHitExplosion)?.AnimateSecondHit(); - } + secondHitTime = judgementResult.TimeAbsolute; + runAnimation(); } } } From da3dc61aae29202b7a0ebb498c274852c1d955ab Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 16 Mar 2021 10:58:42 +0900 Subject: [PATCH 240/390] Remove newline --- osu.Game/OsuGame.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index eb34ba4a37..7d11029a9c 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -887,7 +887,6 @@ namespace osu.Game case GlobalAction.ToggleGameplayMouseButtons: var mouseDisableButtons = LocalConfig.GetBindable(OsuSetting.MouseDisableButtons); - mouseDisableButtons.Value = !mouseDisableButtons.Value; return true; From c7740d1181a165466b9960ed54f10c7e14e85075 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Mar 2021 15:52:24 +0900 Subject: [PATCH 241/390] Fix opening the editor occasionally causing a hard crash due to incorrect threading logic Setting one of the global screen `Bindable`s (in this case, `Beatmap`) is not valid from anywhere but the update thread. This changes the order in which things happen during the editor startup process to ensure correctness. Closes #11968. --- osu.Game/Screens/Edit/Editor.cs | 67 +++++++++++++++++++++------------ 1 file changed, 42 insertions(+), 25 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 0ba202b082..3a4c3491ff 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -106,26 +106,29 @@ namespace osu.Game.Screens.Edit [BackgroundDependencyLoader] private void load(OsuColour colours, GameHost host, OsuConfigManager config) { - if (Beatmap.Value is DummyWorkingBeatmap) + var loadableBeatmap = Beatmap.Value; + + if (loadableBeatmap is DummyWorkingBeatmap) { isNewBeatmap = true; - var newBeatmap = beatmapManager.CreateNew(Ruleset.Value, api.LocalUser.Value); + loadableBeatmap = beatmapManager.CreateNew(Ruleset.Value, api.LocalUser.Value); + + // required so we can get the track length in EditorClock. + // this is safe as nothing has yet got a reference to this new beatmap. + loadableBeatmap.LoadTrack(); // this is a bit haphazard, but guards against setting the lease Beatmap bindable if // the editor has already been exited. if (!ValidForPush) return; - - // this probably shouldn't be set in the asynchronous load method, but everything following relies on it. - Beatmap.Value = newBeatmap; } - beatDivisor.Value = Beatmap.Value.BeatmapInfo.BeatDivisor; - beatDivisor.BindValueChanged(divisor => Beatmap.Value.BeatmapInfo.BeatDivisor = divisor.NewValue); + beatDivisor.Value = loadableBeatmap.BeatmapInfo.BeatDivisor; + beatDivisor.BindValueChanged(divisor => loadableBeatmap.BeatmapInfo.BeatDivisor = divisor.NewValue); // Todo: should probably be done at a DrawableRuleset level to share logic with Player. - clock = new EditorClock(Beatmap.Value, beatDivisor) { IsCoupled = false }; + clock = new EditorClock(loadableBeatmap, beatDivisor) { IsCoupled = false }; UpdateClockSource(); @@ -139,7 +142,7 @@ namespace osu.Game.Screens.Edit try { - playableBeatmap = Beatmap.Value.GetPlayableBeatmap(Beatmap.Value.BeatmapInfo.Ruleset); + playableBeatmap = loadableBeatmap.GetPlayableBeatmap(loadableBeatmap.BeatmapInfo.Ruleset); // clone these locally for now to avoid incurring overhead on GetPlayableBeatmap usages. // eventually we will want to improve how/where this is done as there are issues with *not* cloning it in all cases. @@ -153,13 +156,21 @@ namespace osu.Game.Screens.Edit return; } - AddInternal(editorBeatmap = new EditorBeatmap(playableBeatmap, Beatmap.Value.Skin)); + AddInternal(editorBeatmap = new EditorBeatmap(playableBeatmap, loadableBeatmap.Skin)); dependencies.CacheAs(editorBeatmap); changeHandler = new EditorChangeHandler(editorBeatmap); dependencies.CacheAs(changeHandler); updateLastSavedHash(); + Schedule(() => + { + // we need to avoid changing the beatmap from an asynchronous load thread. it can potentially cause weirdness including crashes. + // this assumes that nothing during the rest of this load() method is accessing Beatmap.Value (loadableBeatmap should be preferred). + // generally this is quite safe, as the actual load of editor content comes after menuBar.Mode.ValueChanged is fired in its own LoadComplete. + Beatmap.Value = loadableBeatmap; + }); + OsuMenuItem undoMenuItem; OsuMenuItem redoMenuItem; @@ -167,17 +178,6 @@ namespace osu.Game.Screens.Edit EditorMenuItem copyMenuItem; EditorMenuItem pasteMenuItem; - var fileMenuItems = new List - { - new EditorMenuItem("Save", MenuItemType.Standard, Save) - }; - - if (RuntimeInfo.IsDesktop) - fileMenuItems.Add(new EditorMenuItem("Export package", MenuItemType.Standard, exportBeatmap)); - - fileMenuItems.Add(new EditorMenuItemSpacer()); - fileMenuItems.Add(new EditorMenuItem("Exit", MenuItemType.Standard, this.Exit)); - AddInternal(new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, @@ -209,7 +209,7 @@ namespace osu.Game.Screens.Edit { new MenuItem("File") { - Items = fileMenuItems + Items = createFileMenuItems() }, new MenuItem("Edit") { @@ -242,7 +242,11 @@ namespace osu.Game.Screens.Edit Height = 60, Children = new Drawable[] { - bottomBackground = new Box { RelativeSizeAxes = Axes.Both }, + bottomBackground = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.Gray2 + }, new Container { RelativeSizeAxes = Axes.Both, @@ -299,8 +303,6 @@ namespace osu.Game.Screens.Edit clipboard.BindValueChanged(content => pasteMenuItem.Action.Disabled = string.IsNullOrEmpty(content.NewValue)); menuBar.Mode.ValueChanged += onModeChanged; - - bottomBackground.Colour = colours.Gray2; } /// @@ -681,6 +683,21 @@ namespace osu.Game.Screens.Edit lastSavedHash = changeHandler.CurrentStateHash; } + private List createFileMenuItems() + { + var fileMenuItems = new List + { + new EditorMenuItem("Save", MenuItemType.Standard, Save) + }; + + if (RuntimeInfo.IsDesktop) + fileMenuItems.Add(new EditorMenuItem("Export package", MenuItemType.Standard, exportBeatmap)); + + fileMenuItems.Add(new EditorMenuItemSpacer()); + fileMenuItems.Add(new EditorMenuItem("Exit", MenuItemType.Standard, this.Exit)); + return fileMenuItems; + } + public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime); public double GetBeatLengthAtTime(double referenceTime) => editorBeatmap.GetBeatLengthAtTime(referenceTime); From 7fa5fd56475124fa78c84cbb18e3c19838d98e5a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 17 Mar 2021 16:10:16 +0900 Subject: [PATCH 242/390] Update usages of config with framework changes --- .../TestSceneCatchModHidden.cs | 2 +- .../TestSceneCatcher.cs | 4 +- .../ManiaRulesetConfigManager.cs | 4 +- .../TestSceneDrawableJudgement.cs | 4 +- .../TestSceneGameplayCursor.cs | 12 +- .../TestSceneSkinFallbacks.cs | 20 +-- .../Configuration/OsuRulesetConfigManager.cs | 8 +- .../Input/ConfineMouseTrackerTest.cs | 2 +- .../NonVisual/CustomDataDirectoryTest.cs | 4 +- .../TestSceneSeasonalBackgroundLoader.cs | 4 +- .../Visual/Gameplay/TestSceneFailingLayer.cs | 12 +- .../Visual/Gameplay/TestSceneHUDOverlay.cs | 8 +- .../TestSceneStoryboardSamplePlayback.cs | 2 +- .../Visual/Navigation/OsuGameTestScene.cs | 4 +- .../Navigation/TestSettingsMigration.cs | 6 +- .../SongSelect/TestScenePlaySongSelect.cs | 24 +-- .../TestSceneBeatmapListingSearchControl.cs | 4 +- .../UserInterface/TestSceneOnScreenDisplay.cs | 12 +- .../NonVisual/CustomTourneyDirectoryTest.cs | 2 +- osu.Game.Tournament/IO/TournamentStorage.cs | 2 +- .../Components/DrawingsConfigManager.cs | 4 +- osu.Game/Configuration/OsuConfigManager.cs | 142 +++++++++--------- osu.Game/Configuration/SessionStatics.cs | 8 +- .../Configuration/StorageConfigManager.cs | 2 +- osu.Game/IO/OsuStorage.cs | 4 +- osu.Game/Online/API/APIAccess.cs | 4 +- osu.Game/Updater/UpdateManager.cs | 2 +- 27 files changed, 153 insertions(+), 153 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs index f15da29993..1248409b2a 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.Tests [BackgroundDependencyLoader] private void load() { - LocalConfig.Set(OsuSetting.IncreaseFirstObjectVisibility, false); + LocalConfig.SetValue(OsuSetting.IncreaseFirstObjectVisibility, false); } [Test] diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs index e8bb57cdf3..48efd73222 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs @@ -202,7 +202,7 @@ namespace osu.Game.Rulesets.Catch.Tests public void TestHitLightingColour() { var fruitColour = SkinConfiguration.DefaultComboColours[1]; - AddStep("enable hit lighting", () => config.Set(OsuSetting.HitLighting, true)); + AddStep("enable hit lighting", () => config.SetValue(OsuSetting.HitLighting, true)); AddStep("catch fruit", () => attemptCatch(new Fruit())); AddAssert("correct hit lighting colour", () => catcher.ChildrenOfType().First()?.ObjectColour == fruitColour); @@ -211,7 +211,7 @@ namespace osu.Game.Rulesets.Catch.Tests [Test] public void TestHitLightingDisabled() { - AddStep("disable hit lighting", () => config.Set(OsuSetting.HitLighting, false)); + AddStep("disable hit lighting", () => config.SetValue(OsuSetting.HitLighting, false)); AddStep("catch fruit", () => attemptCatch(new Fruit())); AddAssert("no hit lighting", () => !catcher.ChildrenOfType().Any()); } diff --git a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs index 756f2b7b2f..39d0f4bae4 100644 --- a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs +++ b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs @@ -20,8 +20,8 @@ namespace osu.Game.Rulesets.Mania.Configuration { base.InitialiseDefaults(); - Set(ManiaRulesetSetting.ScrollTime, 1500.0, DrawableManiaRuleset.MIN_TIME_RANGE, DrawableManiaRuleset.MAX_TIME_RANGE, 5); - Set(ManiaRulesetSetting.ScrollDirection, ManiaScrollingDirection.Down); + SetDefault(ManiaRulesetSetting.ScrollTime, 1500.0, DrawableManiaRuleset.MIN_TIME_RANGE, DrawableManiaRuleset.MAX_TIME_RANGE, 5); + SetDefault(ManiaRulesetSetting.ScrollDirection, ManiaScrollingDirection.Down); } public override TrackedSettings CreateTrackedSettings() => new TrackedSettings diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs index e4158d8f07..4395ca6281 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Tests [Test] public void TestHitLightingDisabled() { - AddStep("hit lighting disabled", () => config.Set(OsuSetting.HitLighting, false)); + AddStep("hit lighting disabled", () => config.SetValue(OsuSetting.HitLighting, false)); showResult(HitResult.Great); @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Tests [Test] public void TestHitLightingEnabled() { - AddStep("hit lighting enabled", () => config.Set(OsuSetting.HitLighting, true)); + AddStep("hit lighting enabled", () => config.SetValue(OsuSetting.HitLighting, true)); showResult(HitResult.Great); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs index 461779b185..e3ccf83715 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Tests AddSliderStep("circle size", 0f, 10f, 0f, val => { - config.Set(OsuSetting.AutoCursorSize, true); + config.SetValue(OsuSetting.AutoCursorSize, true); gameplayBeatmap.BeatmapInfo.BaseDifficulty.CircleSize = val; Scheduler.AddOnce(recreate); }); @@ -64,21 +64,21 @@ namespace osu.Game.Rulesets.Osu.Tests [TestCase(10, 1.5f)] public void TestSizing(int circleSize, float userScale) { - AddStep($"set user scale to {userScale}", () => config.Set(OsuSetting.GameplayCursorSize, userScale)); + AddStep($"set user scale to {userScale}", () => config.SetValue(OsuSetting.GameplayCursorSize, userScale)); AddStep($"adjust cs to {circleSize}", () => gameplayBeatmap.BeatmapInfo.BaseDifficulty.CircleSize = circleSize); - AddStep("turn on autosizing", () => config.Set(OsuSetting.AutoCursorSize, true)); + AddStep("turn on autosizing", () => config.SetValue(OsuSetting.AutoCursorSize, true)); AddStep("load content", loadContent); AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == OsuCursorContainer.GetScaleForCircleSize(circleSize) * userScale); - AddStep("set user scale to 1", () => config.Set(OsuSetting.GameplayCursorSize, 1f)); + AddStep("set user scale to 1", () => config.SetValue(OsuSetting.GameplayCursorSize, 1f)); AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == OsuCursorContainer.GetScaleForCircleSize(circleSize)); - AddStep("turn off autosizing", () => config.Set(OsuSetting.AutoCursorSize, false)); + AddStep("turn off autosizing", () => config.SetValue(OsuSetting.AutoCursorSize, false)); AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == 1); - AddStep($"set user scale to {userScale}", () => config.Set(OsuSetting.GameplayCursorSize, userScale)); + AddStep($"set user scale to {userScale}", () => config.SetValue(OsuSetting.GameplayCursorSize, userScale)); AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == userScale); } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs index 8dbb48c048..56f6fb85fa 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs @@ -42,10 +42,10 @@ namespace osu.Game.Rulesets.Osu.Tests { AddStep("enable user provider", () => testUserSkin.Enabled = true); - AddStep("enable beatmap skin", () => LocalConfig.Set(OsuSetting.BeatmapSkins, true)); + AddStep("enable beatmap skin", () => LocalConfig.SetValue(OsuSetting.BeatmapSkins, true)); checkNextHitObject("beatmap"); - AddStep("disable beatmap skin", () => LocalConfig.Set(OsuSetting.BeatmapSkins, false)); + AddStep("disable beatmap skin", () => LocalConfig.SetValue(OsuSetting.BeatmapSkins, false)); checkNextHitObject("user"); AddStep("disable user provider", () => testUserSkin.Enabled = false); @@ -57,20 +57,20 @@ namespace osu.Game.Rulesets.Osu.Tests { AddStep("enable user provider", () => testUserSkin.Enabled = true); - AddStep("enable beatmap skin", () => LocalConfig.Set(OsuSetting.BeatmapSkins, true)); - AddStep("enable beatmap colours", () => LocalConfig.Set(OsuSetting.BeatmapColours, true)); + AddStep("enable beatmap skin", () => LocalConfig.SetValue(OsuSetting.BeatmapSkins, true)); + AddStep("enable beatmap colours", () => LocalConfig.SetValue(OsuSetting.BeatmapColours, true)); checkNextHitObject("beatmap"); - AddStep("enable beatmap skin", () => LocalConfig.Set(OsuSetting.BeatmapSkins, true)); - AddStep("disable beatmap colours", () => LocalConfig.Set(OsuSetting.BeatmapColours, false)); + AddStep("enable beatmap skin", () => LocalConfig.SetValue(OsuSetting.BeatmapSkins, true)); + AddStep("disable beatmap colours", () => LocalConfig.SetValue(OsuSetting.BeatmapColours, false)); checkNextHitObject("beatmap"); - AddStep("disable beatmap skin", () => LocalConfig.Set(OsuSetting.BeatmapSkins, false)); - AddStep("enable beatmap colours", () => LocalConfig.Set(OsuSetting.BeatmapColours, true)); + AddStep("disable beatmap skin", () => LocalConfig.SetValue(OsuSetting.BeatmapSkins, false)); + AddStep("enable beatmap colours", () => LocalConfig.SetValue(OsuSetting.BeatmapColours, true)); checkNextHitObject("user"); - AddStep("disable beatmap skin", () => LocalConfig.Set(OsuSetting.BeatmapSkins, false)); - AddStep("disable beatmap colours", () => LocalConfig.Set(OsuSetting.BeatmapColours, false)); + AddStep("disable beatmap skin", () => LocalConfig.SetValue(OsuSetting.BeatmapSkins, false)); + AddStep("disable beatmap colours", () => LocalConfig.SetValue(OsuSetting.BeatmapColours, false)); checkNextHitObject("user"); AddStep("disable user provider", () => testUserSkin.Enabled = false); diff --git a/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs index e8272057f3..9589fd576f 100644 --- a/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs +++ b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs @@ -17,10 +17,10 @@ namespace osu.Game.Rulesets.Osu.Configuration protected override void InitialiseDefaults() { base.InitialiseDefaults(); - Set(OsuRulesetSetting.SnakingInSliders, true); - Set(OsuRulesetSetting.SnakingOutSliders, true); - Set(OsuRulesetSetting.ShowCursorTrail, true); - Set(OsuRulesetSetting.PlayfieldBorderStyle, PlayfieldBorderStyle.None); + SetDefault(OsuRulesetSetting.SnakingInSliders, true); + SetDefault(OsuRulesetSetting.SnakingOutSliders, true); + SetDefault(OsuRulesetSetting.ShowCursorTrail, true); + SetDefault(OsuRulesetSetting.PlayfieldBorderStyle, PlayfieldBorderStyle.None); } } diff --git a/osu.Game.Tests/Input/ConfineMouseTrackerTest.cs b/osu.Game.Tests/Input/ConfineMouseTrackerTest.cs index b90382488f..27cece42e8 100644 --- a/osu.Game.Tests/Input/ConfineMouseTrackerTest.cs +++ b/osu.Game.Tests/Input/ConfineMouseTrackerTest.cs @@ -88,7 +88,7 @@ namespace osu.Game.Tests.Input => AddStep($"make window {mode}", () => frameworkConfigManager.GetBindable(FrameworkSetting.WindowMode).Value = mode); private void setGameSideModeTo(OsuConfineMouseMode mode) - => AddStep($"set {mode} game-side", () => Game.LocalConfig.Set(OsuSetting.ConfineMouseMode, mode)); + => AddStep($"set {mode} game-side", () => Game.LocalConfig.SetValue(OsuSetting.ConfineMouseMode, mode)); private void setLocalUserPlayingTo(bool playing) => AddStep($"local user {(playing ? "playing" : "not playing")}", () => Game.LocalUserPlaying.Value = playing); diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index 045246e5ed..a763544c37 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -47,7 +47,7 @@ namespace osu.Game.Tests.NonVisual using (var host = new CustomTestHeadlessGameHost()) { using (var storageConfig = new StorageConfigManager(host.InitialStorage)) - storageConfig.Set(StorageConfig.FullPath, customPath); + storageConfig.SetValue(StorageConfig.FullPath, customPath); try { @@ -73,7 +73,7 @@ namespace osu.Game.Tests.NonVisual using (var host = new CustomTestHeadlessGameHost()) { using (var storageConfig = new StorageConfigManager(host.InitialStorage)) - storageConfig.Set(StorageConfig.FullPath, customPath); + storageConfig.SetValue(StorageConfig.FullPath, customPath); try { diff --git a/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs b/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs index fba0d92d4b..e7cf830db0 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs @@ -58,7 +58,7 @@ namespace osu.Game.Tests.Visual.Background public void SetUp() => Schedule(() => { // reset API response in statics to avoid test crosstalk. - statics.Set(Static.SeasonalBackgrounds, null); + statics.SetValue(Static.SeasonalBackgrounds, null); textureStore.PerformedLookups.Clear(); dummyAPI.SetState(APIState.Online); @@ -146,7 +146,7 @@ namespace osu.Game.Tests.Visual.Background }); private void setSeasonalBackgroundMode(SeasonalBackgroundMode mode) - => AddStep($"set seasonal mode to {mode}", () => config.Set(OsuSetting.SeasonalBackgroundMode, mode)); + => AddStep($"set seasonal mode to {mode}", () => config.SetValue(OsuSetting.SeasonalBackgroundMode, mode)); private void createLoader() => AddStep("create loader", () => diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs index 1c55595c97..5a1a9d3d87 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs @@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual.Gameplay }); AddStep("show health", () => showHealth.Value = true); - AddStep("enable layer", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, true)); + AddStep("enable layer", () => config.SetValue(OsuSetting.FadePlayfieldWhenHealthLow, true)); AddUntilStep("layer is visible", () => layer.IsPresent); } @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestLayerDisabledViaConfig() { - AddStep("disable layer", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, false)); + AddStep("disable layer", () => config.SetValue(OsuSetting.FadePlayfieldWhenHealthLow, false)); AddStep("set health to 0.10", () => layer.Current.Value = 0.1); AddUntilStep("layer is not visible", () => !layer.IsPresent); } @@ -81,19 +81,19 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("set health to 0.10", () => layer.Current.Value = 0.1); AddStep("don't show health", () => showHealth.Value = false); - AddStep("disable FadePlayfieldWhenHealthLow", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, false)); + AddStep("disable FadePlayfieldWhenHealthLow", () => config.SetValue(OsuSetting.FadePlayfieldWhenHealthLow, false)); AddUntilStep("layer fade is invisible", () => !layer.IsPresent); AddStep("don't show health", () => showHealth.Value = false); - AddStep("enable FadePlayfieldWhenHealthLow", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, true)); + AddStep("enable FadePlayfieldWhenHealthLow", () => config.SetValue(OsuSetting.FadePlayfieldWhenHealthLow, true)); AddUntilStep("layer fade is invisible", () => !layer.IsPresent); AddStep("show health", () => showHealth.Value = true); - AddStep("disable FadePlayfieldWhenHealthLow", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, false)); + AddStep("disable FadePlayfieldWhenHealthLow", () => config.SetValue(OsuSetting.FadePlayfieldWhenHealthLow, false)); AddUntilStep("layer fade is invisible", () => !layer.IsPresent); AddStep("show health", () => showHealth.Value = true); - AddStep("enable FadePlayfieldWhenHealthLow", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, true)); + AddStep("enable FadePlayfieldWhenHealthLow", () => config.SetValue(OsuSetting.FadePlayfieldWhenHealthLow, true)); AddUntilStep("layer fade is visible", () => layer.IsPresent); } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index f9914e0193..3cefb8623f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -81,7 +81,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("get original config value", () => originalConfigValue = config.Get(OsuSetting.HUDVisibilityMode)); - AddStep("set hud to never show", () => config.Set(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Never)); + AddStep("set hud to never show", () => config.SetValue(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Never)); AddUntilStep("wait for fade", () => !hideTarget.IsPresent); @@ -91,7 +91,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("stop trigering", () => InputManager.ReleaseKey(Key.ControlLeft)); AddUntilStep("wait for fade", () => !hideTarget.IsPresent); - AddStep("set original config value", () => config.Set(OsuSetting.HUDVisibilityMode, originalConfigValue)); + AddStep("set original config value", () => config.SetValue(OsuSetting.HUDVisibilityMode, originalConfigValue)); } [Test] @@ -120,7 +120,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("set keycounter visible false", () => { - config.Set(OsuSetting.KeyOverlay, false); + config.SetValue(OsuSetting.KeyOverlay, false); hudOverlay.KeyCounter.AlwaysVisible.Value = false; }); @@ -132,7 +132,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("hidetarget is visible", () => hideTarget.IsPresent); AddAssert("key counters still hidden", () => !keyCounterFlow.IsPresent); - AddStep("return value", () => config.Set(OsuSetting.KeyOverlay, keyCounterVisibleValue)); + AddStep("return value", () => config.SetValue(OsuSetting.KeyOverlay, keyCounterVisibleValue)); } private void createNew(Action action = null) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs index 1544f8fd35..a718a98aa6 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs @@ -22,7 +22,7 @@ namespace osu.Game.Tests.Visual.Gameplay [BackgroundDependencyLoader] private void load(OsuConfigManager config) { - config.Set(OsuSetting.ShowStoryboard, true); + config.SetValue(OsuSetting.ShowStoryboard, true); storyboard = new Storyboard(); var backgroundLayer = storyboard.GetLayer("Background"); diff --git a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs index 96393cc4c3..6ca7707906 100644 --- a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs +++ b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs @@ -82,7 +82,7 @@ namespace osu.Game.Tests.Visual.Navigation // todo: this can be removed once we can run audio tracks without a device present // see https://github.com/ppy/osu/issues/1302 - Game.LocalConfig.Set(OsuSetting.IntroSequence, IntroSequence.Circles); + Game.LocalConfig.SetValue(OsuSetting.IntroSequence, IntroSequence.Circles); Add(Game); } @@ -136,7 +136,7 @@ namespace osu.Game.Tests.Visual.Navigation base.LoadComplete(); API.Login("Rhythm Champion", "osu!"); - Dependencies.Get().Set(Static.MutedAudioNotificationShownOnce, true); + Dependencies.Get().SetValue(Static.MutedAudioNotificationShownOnce, true); } } diff --git a/osu.Game.Tests/Visual/Navigation/TestSettingsMigration.cs b/osu.Game.Tests/Visual/Navigation/TestSettingsMigration.cs index c0b77b580e..768f2057a2 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSettingsMigration.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSettingsMigration.cs @@ -15,8 +15,8 @@ namespace osu.Game.Tests.Visual.Navigation using (var config = new OsuConfigManager(LocalStorage)) { - config.Set(OsuSetting.Version, "2020.101.0"); - config.Set(OsuSetting.DisplayStarsMaximum, 10.0); + config.SetValue(OsuSetting.Version, "2020.101.0"); + config.SetValue(OsuSetting.DisplayStarsMaximum, 10.0); } } @@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.Navigation { AddAssert("config has migrated value", () => Precision.AlmostEquals(Game.LocalConfig.Get(OsuSetting.DisplayStarsMaximum), 10.1)); - AddStep("set value again", () => Game.LocalConfig.Set(OsuSetting.DisplayStarsMaximum, 10)); + AddStep("set value again", () => Game.LocalConfig.SetValue(OsuSetting.DisplayStarsMaximum, 10)); AddStep("force save config", () => Game.LocalConfig.Save()); diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 2d192ae207..057b539e44 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -207,14 +207,14 @@ namespace osu.Game.Tests.Visual.SongSelect addRulesetImportStep(0); addRulesetImportStep(0); - AddStep("change convert setting", () => config.Set(OsuSetting.ShowConvertedBeatmaps, false)); + AddStep("change convert setting", () => config.SetValue(OsuSetting.ShowConvertedBeatmaps, false)); createSongSelect(); AddStep("push child screen", () => Stack.Push(new TestSceneOsuScreenStack.TestScreen("test child"))); AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen()); - AddStep("change convert setting", () => config.Set(OsuSetting.ShowConvertedBeatmaps, true)); + AddStep("change convert setting", () => config.SetValue(OsuSetting.ShowConvertedBeatmaps, true)); AddStep("return", () => songSelect.MakeCurrent()); AddUntilStep("wait for current", () => songSelect.IsCurrentScreen()); @@ -297,13 +297,13 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("random map selected", () => songSelect.CurrentBeatmap != defaultBeatmap); - AddStep(@"Sort by Artist", () => config.Set(OsuSetting.SongSelectSortingMode, SortMode.Artist)); - AddStep(@"Sort by Title", () => config.Set(OsuSetting.SongSelectSortingMode, SortMode.Title)); - AddStep(@"Sort by Author", () => config.Set(OsuSetting.SongSelectSortingMode, SortMode.Author)); - AddStep(@"Sort by DateAdded", () => config.Set(OsuSetting.SongSelectSortingMode, SortMode.DateAdded)); - AddStep(@"Sort by BPM", () => config.Set(OsuSetting.SongSelectSortingMode, SortMode.BPM)); - AddStep(@"Sort by Length", () => config.Set(OsuSetting.SongSelectSortingMode, SortMode.Length)); - AddStep(@"Sort by Difficulty", () => config.Set(OsuSetting.SongSelectSortingMode, SortMode.Difficulty)); + AddStep(@"Sort by Artist", () => config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.Artist)); + AddStep(@"Sort by Title", () => config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.Title)); + AddStep(@"Sort by Author", () => config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.Author)); + AddStep(@"Sort by DateAdded", () => config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.DateAdded)); + AddStep(@"Sort by BPM", () => config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.BPM)); + AddStep(@"Sort by Length", () => config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.Length)); + AddStep(@"Sort by Difficulty", () => config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.Difficulty)); } [Test] @@ -470,7 +470,7 @@ namespace osu.Game.Tests.Visual.SongSelect changeRuleset(0); // used for filter check below - AddStep("allow convert display", () => config.Set(OsuSetting.ShowConvertedBeatmaps, true)); + AddStep("allow convert display", () => config.SetValue(OsuSetting.ShowConvertedBeatmaps, true)); AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap != null); @@ -648,7 +648,7 @@ namespace osu.Game.Tests.Visual.SongSelect { int changeCount = 0; - AddStep("change convert setting", () => config.Set(OsuSetting.ShowConvertedBeatmaps, false)); + AddStep("change convert setting", () => config.SetValue(OsuSetting.ShowConvertedBeatmaps, false)); AddStep("bind beatmap changed", () => { Beatmap.ValueChanged += onChange; @@ -686,7 +686,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("selection changed only fired twice", () => changeCount == 2); AddStep("unbind beatmap changed", () => Beatmap.ValueChanged -= onChange); - AddStep("change convert setting", () => config.Set(OsuSetting.ShowConvertedBeatmaps, true)); + AddStep("change convert setting", () => config.SetValue(OsuSetting.ShowConvertedBeatmaps, true)); // ReSharper disable once AccessToModifiedClosure void onChange(ValueChangedEvent valueChangedEvent) => changeCount++; diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs index a9747e73f9..9602758ffc 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs @@ -92,10 +92,10 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestExplicitConfig() { - AddStep("configure explicit content to allowed", () => localConfig.Set(OsuSetting.ShowOnlineExplicitContent, true)); + AddStep("configure explicit content to allowed", () => localConfig.SetValue(OsuSetting.ShowOnlineExplicitContent, true)); AddAssert("explicit control set to show", () => control.ExplicitContent.Value == SearchExplicit.Show); - AddStep("configure explicit content to disallowed", () => localConfig.Set(OsuSetting.ShowOnlineExplicitContent, false)); + AddStep("configure explicit content to disallowed", () => localConfig.SetValue(OsuSetting.ShowOnlineExplicitContent, false)); AddAssert("explicit control set to hide", () => control.ExplicitContent.Value == SearchExplicit.Hide); } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs index 45720548c8..493e2f54e5 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs @@ -44,22 +44,22 @@ namespace osu.Game.Tests.Visual.UserInterface protected override void InitialiseDefaults() { - Set(TestConfigSetting.ToggleSettingNoKeybind, false); - Set(TestConfigSetting.EnumSettingNoKeybind, EnumSetting.Setting1); - Set(TestConfigSetting.ToggleSettingWithKeybind, false); - Set(TestConfigSetting.EnumSettingWithKeybind, EnumSetting.Setting1); + SetDefault(TestConfigSetting.ToggleSettingNoKeybind, false); + SetDefault(TestConfigSetting.EnumSettingNoKeybind, EnumSetting.Setting1); + SetDefault(TestConfigSetting.ToggleSettingWithKeybind, false); + SetDefault(TestConfigSetting.EnumSettingWithKeybind, EnumSetting.Setting1); base.InitialiseDefaults(); } - public void ToggleSetting(TestConfigSetting setting) => Set(setting, !Get(setting)); + public void ToggleSetting(TestConfigSetting setting) => SetValue(setting, !Get(setting)); public void IncrementEnumSetting(TestConfigSetting setting) { var nextValue = Get(setting) + 1; if (nextValue > EnumSetting.Setting4) nextValue = EnumSetting.Setting1; - Set(setting, nextValue); + SetValue(setting, nextValue); } public override TrackedSettings CreateTrackedSettings() => new TrackedSettings diff --git a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs index 567d9f0d62..46c3b8bc3b 100644 --- a/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/CustomTourneyDirectoryTest.cs @@ -50,7 +50,7 @@ namespace osu.Game.Tournament.Tests.NonVisual storage.DeleteDirectory(string.Empty); using (var storageConfig = new TournamentStorageManager(storage)) - storageConfig.Set(StorageConfig.CurrentTournament, custom_tournament); + storageConfig.SetValue(StorageConfig.CurrentTournament, custom_tournament); try { diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index 2ba1b6be8f..5d9fed6288 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -70,7 +70,7 @@ namespace osu.Game.Tournament.IO moveFileIfExists("drawings.ini", destination); ChangeTargetStorage(newStorage); - storageConfig.Set(StorageConfig.CurrentTournament, default_tournament); + storageConfig.SetValue(StorageConfig.CurrentTournament, default_tournament); storageConfig.Save(); } diff --git a/osu.Game.Tournament/Screens/Drawings/Components/DrawingsConfigManager.cs b/osu.Game.Tournament/Screens/Drawings/Components/DrawingsConfigManager.cs index d197c0f5d9..1a2f5a1ff4 100644 --- a/osu.Game.Tournament/Screens/Drawings/Components/DrawingsConfigManager.cs +++ b/osu.Game.Tournament/Screens/Drawings/Components/DrawingsConfigManager.cs @@ -12,8 +12,8 @@ namespace osu.Game.Tournament.Screens.Drawings.Components protected override void InitialiseDefaults() { - Set(DrawingsConfig.Groups, 8, 1, 8); - Set(DrawingsConfig.TeamsPerGroup, 8, 1, 8); + SetDefault(DrawingsConfig.Groups, 8, 1, 8); + SetDefault(DrawingsConfig.TeamsPerGroup, 8, 1, 8); } public DrawingsConfigManager(Storage storage) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index d0fa45bb7a..387cfbb193 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -24,126 +24,126 @@ namespace osu.Game.Configuration protected override void InitialiseDefaults() { // UI/selection defaults - Set(OsuSetting.Ruleset, 0, 0, int.MaxValue); - Set(OsuSetting.Skin, 0, -1, int.MaxValue); + SetDefault(OsuSetting.Ruleset, 0, 0, int.MaxValue); + SetDefault(OsuSetting.Skin, 0, -1, int.MaxValue); - Set(OsuSetting.BeatmapDetailTab, PlayBeatmapDetailArea.TabType.Details); - Set(OsuSetting.BeatmapDetailModsFilter, false); + SetDefault(OsuSetting.BeatmapDetailTab, PlayBeatmapDetailArea.TabType.Details); + SetDefault(OsuSetting.BeatmapDetailModsFilter, false); - Set(OsuSetting.ShowConvertedBeatmaps, true); - Set(OsuSetting.DisplayStarsMinimum, 0.0, 0, 10, 0.1); - Set(OsuSetting.DisplayStarsMaximum, 10.1, 0, 10.1, 0.1); + SetDefault(OsuSetting.ShowConvertedBeatmaps, true); + SetDefault(OsuSetting.DisplayStarsMinimum, 0.0, 0, 10, 0.1); + SetDefault(OsuSetting.DisplayStarsMaximum, 10.1, 0, 10.1, 0.1); - Set(OsuSetting.SongSelectGroupingMode, GroupMode.All); - Set(OsuSetting.SongSelectSortingMode, SortMode.Title); + SetDefault(OsuSetting.SongSelectGroupingMode, GroupMode.All); + SetDefault(OsuSetting.SongSelectSortingMode, SortMode.Title); - Set(OsuSetting.RandomSelectAlgorithm, RandomSelectAlgorithm.RandomPermutation); + SetDefault(OsuSetting.RandomSelectAlgorithm, RandomSelectAlgorithm.RandomPermutation); - Set(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2f, 1f); + SetDefault(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2f, 1f); // Online settings - Set(OsuSetting.Username, string.Empty); - Set(OsuSetting.Token, string.Empty); + SetDefault(OsuSetting.Username, string.Empty); + SetDefault(OsuSetting.Token, string.Empty); - Set(OsuSetting.AutomaticallyDownloadWhenSpectating, false); + SetDefault(OsuSetting.AutomaticallyDownloadWhenSpectating, false); - Set(OsuSetting.SavePassword, false).ValueChanged += enabled => + SetDefault(OsuSetting.SavePassword, false).ValueChanged += enabled => { - if (enabled.NewValue) Set(OsuSetting.SaveUsername, true); + if (enabled.NewValue) SetValue(OsuSetting.SaveUsername, true); }; - Set(OsuSetting.SaveUsername, true).ValueChanged += enabled => + SetDefault(OsuSetting.SaveUsername, true).ValueChanged += enabled => { - if (!enabled.NewValue) Set(OsuSetting.SavePassword, false); + if (!enabled.NewValue) SetValue(OsuSetting.SavePassword, false); }; - Set(OsuSetting.ExternalLinkWarning, true); - Set(OsuSetting.PreferNoVideo, false); + SetDefault(OsuSetting.ExternalLinkWarning, true); + SetDefault(OsuSetting.PreferNoVideo, false); - Set(OsuSetting.ShowOnlineExplicitContent, false); + SetDefault(OsuSetting.ShowOnlineExplicitContent, false); // Audio - Set(OsuSetting.VolumeInactive, 0.25, 0, 1, 0.01); + SetDefault(OsuSetting.VolumeInactive, 0.25, 0, 1, 0.01); - Set(OsuSetting.MenuVoice, true); - Set(OsuSetting.MenuMusic, true); + SetDefault(OsuSetting.MenuVoice, true); + SetDefault(OsuSetting.MenuMusic, true); - Set(OsuSetting.AudioOffset, 0, -500.0, 500.0, 1); + SetDefault(OsuSetting.AudioOffset, 0, -500.0, 500.0, 1); // Input - Set(OsuSetting.MenuCursorSize, 1.0f, 0.5f, 2f, 0.01f); - Set(OsuSetting.GameplayCursorSize, 1.0f, 0.1f, 2f, 0.01f); - Set(OsuSetting.AutoCursorSize, false); + SetDefault(OsuSetting.MenuCursorSize, 1.0f, 0.5f, 2f, 0.01f); + SetDefault(OsuSetting.GameplayCursorSize, 1.0f, 0.1f, 2f, 0.01f); + SetDefault(OsuSetting.AutoCursorSize, false); - Set(OsuSetting.MouseDisableButtons, false); - Set(OsuSetting.MouseDisableWheel, false); - Set(OsuSetting.ConfineMouseMode, OsuConfineMouseMode.DuringGameplay); + SetDefault(OsuSetting.MouseDisableButtons, false); + SetDefault(OsuSetting.MouseDisableWheel, false); + SetDefault(OsuSetting.ConfineMouseMode, OsuConfineMouseMode.DuringGameplay); // Graphics - Set(OsuSetting.ShowFpsDisplay, false); + SetDefault(OsuSetting.ShowFpsDisplay, false); - Set(OsuSetting.ShowStoryboard, true); - Set(OsuSetting.BeatmapSkins, true); - Set(OsuSetting.BeatmapColours, true); - Set(OsuSetting.BeatmapHitsounds, true); + SetDefault(OsuSetting.ShowStoryboard, true); + SetDefault(OsuSetting.BeatmapSkins, true); + SetDefault(OsuSetting.BeatmapColours, true); + SetDefault(OsuSetting.BeatmapHitsounds, true); - Set(OsuSetting.CursorRotation, true); + SetDefault(OsuSetting.CursorRotation, true); - Set(OsuSetting.MenuParallax, true); + SetDefault(OsuSetting.MenuParallax, true); // Gameplay - Set(OsuSetting.DimLevel, 0.8, 0, 1, 0.01); - Set(OsuSetting.BlurLevel, 0, 0, 1, 0.01); - Set(OsuSetting.LightenDuringBreaks, true); + SetDefault(OsuSetting.DimLevel, 0.8, 0, 1, 0.01); + SetDefault(OsuSetting.BlurLevel, 0, 0, 1, 0.01); + SetDefault(OsuSetting.LightenDuringBreaks, true); - Set(OsuSetting.HitLighting, true); + SetDefault(OsuSetting.HitLighting, true); - Set(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Always); - Set(OsuSetting.ShowProgressGraph, true); - Set(OsuSetting.ShowHealthDisplayWhenCantFail, true); - Set(OsuSetting.FadePlayfieldWhenHealthLow, true); - Set(OsuSetting.KeyOverlay, false); - Set(OsuSetting.PositionalHitSounds, true); - Set(OsuSetting.AlwaysPlayFirstComboBreak, true); - Set(OsuSetting.ScoreMeter, ScoreMeterType.HitErrorBoth); + SetDefault(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Always); + SetDefault(OsuSetting.ShowProgressGraph, true); + SetDefault(OsuSetting.ShowHealthDisplayWhenCantFail, true); + SetDefault(OsuSetting.FadePlayfieldWhenHealthLow, true); + SetDefault(OsuSetting.KeyOverlay, false); + SetDefault(OsuSetting.PositionalHitSounds, true); + SetDefault(OsuSetting.AlwaysPlayFirstComboBreak, true); + SetDefault(OsuSetting.ScoreMeter, ScoreMeterType.HitErrorBoth); - Set(OsuSetting.FloatingComments, false); + SetDefault(OsuSetting.FloatingComments, false); - Set(OsuSetting.ScoreDisplayMode, ScoringMode.Standardised); + SetDefault(OsuSetting.ScoreDisplayMode, ScoringMode.Standardised); - Set(OsuSetting.IncreaseFirstObjectVisibility, true); - Set(OsuSetting.GameplayDisableWinKey, true); + SetDefault(OsuSetting.IncreaseFirstObjectVisibility, true); + SetDefault(OsuSetting.GameplayDisableWinKey, true); // Update - Set(OsuSetting.ReleaseStream, ReleaseStream.Lazer); + SetDefault(OsuSetting.ReleaseStream, ReleaseStream.Lazer); - Set(OsuSetting.Version, string.Empty); + SetDefault(OsuSetting.Version, string.Empty); - Set(OsuSetting.ScreenshotFormat, ScreenshotFormat.Jpg); - Set(OsuSetting.ScreenshotCaptureMenuCursor, false); + SetDefault(OsuSetting.ScreenshotFormat, ScreenshotFormat.Jpg); + SetDefault(OsuSetting.ScreenshotCaptureMenuCursor, false); - Set(OsuSetting.SongSelectRightMouseScroll, false); + SetDefault(OsuSetting.SongSelectRightMouseScroll, false); - Set(OsuSetting.Scaling, ScalingMode.Off); + SetDefault(OsuSetting.Scaling, ScalingMode.Off); - Set(OsuSetting.ScalingSizeX, 0.8f, 0.2f, 1f); - Set(OsuSetting.ScalingSizeY, 0.8f, 0.2f, 1f); + SetDefault(OsuSetting.ScalingSizeX, 0.8f, 0.2f, 1f); + SetDefault(OsuSetting.ScalingSizeY, 0.8f, 0.2f, 1f); - Set(OsuSetting.ScalingPositionX, 0.5f, 0f, 1f); - Set(OsuSetting.ScalingPositionY, 0.5f, 0f, 1f); + SetDefault(OsuSetting.ScalingPositionX, 0.5f, 0f, 1f); + SetDefault(OsuSetting.ScalingPositionY, 0.5f, 0f, 1f); - Set(OsuSetting.UIScale, 1f, 0.8f, 1.6f, 0.01f); + SetDefault(OsuSetting.UIScale, 1f, 0.8f, 1.6f, 0.01f); - Set(OsuSetting.UIHoldActivationDelay, 200f, 0f, 500f, 50f); + SetDefault(OsuSetting.UIHoldActivationDelay, 200f, 0f, 500f, 50f); - Set(OsuSetting.IntroSequence, IntroSequence.Triangles); + SetDefault(OsuSetting.IntroSequence, IntroSequence.Triangles); - Set(OsuSetting.MenuBackgroundSource, BackgroundSource.Skin); - Set(OsuSetting.SeasonalBackgroundMode, SeasonalBackgroundMode.Sometimes); + SetDefault(OsuSetting.MenuBackgroundSource, BackgroundSource.Skin); + SetDefault(OsuSetting.SeasonalBackgroundMode, SeasonalBackgroundMode.Sometimes); - Set(OsuSetting.DiscordRichPresence, DiscordRichPresenceMode.Full); + SetDefault(OsuSetting.DiscordRichPresence, DiscordRichPresenceMode.Full); - Set(OsuSetting.EditorWaveformOpacity, 1f); + SetDefault(OsuSetting.EditorWaveformOpacity, 1f); } public OsuConfigManager(Storage storage) diff --git a/osu.Game/Configuration/SessionStatics.cs b/osu.Game/Configuration/SessionStatics.cs index fd401119ff..36eb6964dd 100644 --- a/osu.Game/Configuration/SessionStatics.cs +++ b/osu.Game/Configuration/SessionStatics.cs @@ -14,10 +14,10 @@ namespace osu.Game.Configuration { protected override void InitialiseDefaults() { - Set(Static.LoginOverlayDisplayed, false); - Set(Static.MutedAudioNotificationShownOnce, false); - Set(Static.LastHoverSoundPlaybackTime, (double?)null); - Set(Static.SeasonalBackgrounds, null); + SetDefault(Static.LoginOverlayDisplayed, false); + SetDefault(Static.MutedAudioNotificationShownOnce, false); + SetDefault(Static.LastHoverSoundPlaybackTime, (double?)null); + SetDefault(Static.SeasonalBackgrounds, null); } } diff --git a/osu.Game/Configuration/StorageConfigManager.cs b/osu.Game/Configuration/StorageConfigManager.cs index 929f8f22ad..90ea42b638 100644 --- a/osu.Game/Configuration/StorageConfigManager.cs +++ b/osu.Game/Configuration/StorageConfigManager.cs @@ -19,7 +19,7 @@ namespace osu.Game.Configuration { base.InitialiseDefaults(); - Set(StorageConfig.FullPath, string.Empty); + SetDefault(StorageConfig.FullPath, string.Empty); } } diff --git a/osu.Game/IO/OsuStorage.cs b/osu.Game/IO/OsuStorage.cs index 8097f61ea4..7df5d820ee 100644 --- a/osu.Game/IO/OsuStorage.cs +++ b/osu.Game/IO/OsuStorage.cs @@ -58,7 +58,7 @@ namespace osu.Game.IO /// public void ResetCustomStoragePath() { - storageConfig.Set(StorageConfig.FullPath, string.Empty); + storageConfig.SetValue(StorageConfig.FullPath, string.Empty); storageConfig.Save(); ChangeTargetStorage(defaultStorage); @@ -103,7 +103,7 @@ namespace osu.Game.IO public override void Migrate(Storage newStorage) { base.Migrate(newStorage); - storageConfig.Set(StorageConfig.FullPath, newStorage.GetFullPath(".")); + storageConfig.SetValue(StorageConfig.FullPath, newStorage.GetFullPath(".")); storageConfig.Save(); } } diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index ede64c0340..944525c119 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -89,7 +89,7 @@ namespace osu.Game.Online.API thread.Start(); } - private void onTokenChanged(ValueChangedEvent e) => config.Set(OsuSetting.Token, config.Get(OsuSetting.SavePassword) ? authentication.TokenString : string.Empty); + private void onTokenChanged(ValueChangedEvent e) => config.SetValue(OsuSetting.Token, config.Get(OsuSetting.SavePassword) ? authentication.TokenString : string.Empty); internal new void Schedule(Action action) => base.Schedule(action); @@ -134,7 +134,7 @@ namespace osu.Game.Online.API state.Value = APIState.Connecting; // save the username at this point, if the user requested for it to be. - config.Set(OsuSetting.Username, config.Get(OsuSetting.SaveUsername) ? ProvidedUsername : string.Empty); + config.SetValue(OsuSetting.Username, config.Get(OsuSetting.SaveUsername) ? ProvidedUsername : string.Empty); if (!authentication.HasValidAccessToken && !authentication.AuthenticateWithLogin(ProvidedUsername, password)) { diff --git a/osu.Game/Updater/UpdateManager.cs b/osu.Game/Updater/UpdateManager.cs index 9a0454bc95..1c72f3ebe2 100644 --- a/osu.Game/Updater/UpdateManager.cs +++ b/osu.Game/Updater/UpdateManager.cs @@ -52,7 +52,7 @@ namespace osu.Game.Updater // debug / local compilations will reset to a non-release string. // can be useful to check when an install has transitioned between release and otherwise (see OsuConfigManager's migrations). - config.Set(OsuSetting.Version, version); + config.SetValue(OsuSetting.Version, version); } private readonly object updateTaskLock = new object(); From eda891223c469cdb23a07fa8faeac7331db2aa33 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Mar 2021 16:47:12 +0900 Subject: [PATCH 243/390] Start the editor with empty artist/creator/difficulty name fields --- osu.Game/Beatmaps/BeatmapManager.cs | 3 --- osu.Game/Screens/Edit/Setup/MetadataSection.cs | 8 ++++---- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index f42fba79cb..115d1b33bb 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -113,8 +113,6 @@ namespace osu.Game.Beatmaps { var metadata = new BeatmapMetadata { - Artist = "artist", - Title = "title", Author = user, }; @@ -128,7 +126,6 @@ namespace osu.Game.Beatmaps BaseDifficulty = new BeatmapDifficulty(), Ruleset = ruleset, Metadata = metadata, - Version = "difficulty" } } }; diff --git a/osu.Game/Screens/Edit/Setup/MetadataSection.cs b/osu.Game/Screens/Edit/Setup/MetadataSection.cs index e812c042fb..2b10be0423 100644 --- a/osu.Game/Screens/Edit/Setup/MetadataSection.cs +++ b/osu.Game/Screens/Edit/Setup/MetadataSection.cs @@ -28,25 +28,25 @@ namespace osu.Game.Screens.Edit.Setup }, artistTextBox = new LabelledTextBox { - Label = "Artist", + PlaceholderText = "Artist", Current = { Value = Beatmap.Metadata.Artist }, TabbableContentContainer = this }, titleTextBox = new LabelledTextBox { - Label = "Title", + PlaceholderText = "Title", Current = { Value = Beatmap.Metadata.Title }, TabbableContentContainer = this }, creatorTextBox = new LabelledTextBox { - Label = "Creator", + PlaceholderText = "Creator", Current = { Value = Beatmap.Metadata.AuthorString }, TabbableContentContainer = this }, difficultyTextBox = new LabelledTextBox { - Label = "Difficulty Name", + PlaceholderText = "Difficulty Name", Current = { Value = Beatmap.BeatmapInfo.Version }, TabbableContentContainer = this }, From 26d6f96c4e2decdba1836a98280a2ba6381c379d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Mar 2021 16:56:58 +0900 Subject: [PATCH 244/390] Fix LabelledTextBox not correctly forwarding focus to its underlying TextBox component --- osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs index 4aeda74be8..266eb11319 100644 --- a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterface; namespace osu.Game.Graphics.UserInterfaceV2 @@ -53,6 +54,14 @@ namespace osu.Game.Graphics.UserInterfaceV2 CornerRadius = CORNER_RADIUS, }; + public override bool AcceptsFocus => true; + + protected override void OnFocus(FocusEvent e) + { + base.OnFocus(e); + GetContainingInputManager().ChangeFocus(Component); + } + protected override OsuTextBox CreateComponent() => CreateTextBox().With(t => { t.OnCommit += (sender, newText) => OnCommit?.Invoke(sender, newText); From 5adc675862afb015ad737db47ebda917fc24c2f2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Mar 2021 16:57:14 +0900 Subject: [PATCH 245/390] Focus artist textbox on entering song setup if fields are empty --- osu.Game/Screens/Edit/Setup/MetadataSection.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Screens/Edit/Setup/MetadataSection.cs b/osu.Game/Screens/Edit/Setup/MetadataSection.cs index 2b10be0423..c5a2b77ab4 100644 --- a/osu.Game/Screens/Edit/Setup/MetadataSection.cs +++ b/osu.Game/Screens/Edit/Setup/MetadataSection.cs @@ -56,6 +56,14 @@ namespace osu.Game.Screens.Edit.Setup item.OnCommit += onCommit; } + protected override void LoadComplete() + { + base.LoadComplete(); + + if (string.IsNullOrEmpty(artistTextBox.Current.Value)) + GetContainingInputManager().ChangeFocus(artistTextBox); + } + private void onCommit(TextBox sender, bool newText) { if (!newText) return; From 3b6a1180b68515b82a9e970f84a3e51d5f908f4e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Mar 2021 17:02:11 +0900 Subject: [PATCH 246/390] Remove non-accessed field --- osu.Game/Screens/Edit/Editor.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 3a4c3491ff..0c24eb6a4d 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -74,7 +74,6 @@ namespace osu.Game.Screens.Edit private string lastSavedHash; - private Box bottomBackground; private Container screenContainer; private EditorScreen currentScreen; @@ -242,7 +241,7 @@ namespace osu.Game.Screens.Edit Height = 60, Children = new Drawable[] { - bottomBackground = new Box + new Box { RelativeSizeAxes = Axes.Both, Colour = colours.Gray2 From d0e61e5b4d609c342e55e2b8a2ed86c66977d11e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Mar 2021 17:14:04 +0900 Subject: [PATCH 247/390] Put back the label --- osu.Game/Screens/Edit/Setup/MetadataSection.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/MetadataSection.cs b/osu.Game/Screens/Edit/Setup/MetadataSection.cs index c5a2b77ab4..f429164ece 100644 --- a/osu.Game/Screens/Edit/Setup/MetadataSection.cs +++ b/osu.Game/Screens/Edit/Setup/MetadataSection.cs @@ -28,25 +28,25 @@ namespace osu.Game.Screens.Edit.Setup }, artistTextBox = new LabelledTextBox { - PlaceholderText = "Artist", + Label = "Artist", Current = { Value = Beatmap.Metadata.Artist }, TabbableContentContainer = this }, titleTextBox = new LabelledTextBox { - PlaceholderText = "Title", + Label = "Title", Current = { Value = Beatmap.Metadata.Title }, TabbableContentContainer = this }, creatorTextBox = new LabelledTextBox { - PlaceholderText = "Creator", + Label = "Creator", Current = { Value = Beatmap.Metadata.AuthorString }, TabbableContentContainer = this }, difficultyTextBox = new LabelledTextBox { - PlaceholderText = "Difficulty Name", + Label = "Difficulty Name", Current = { Value = Beatmap.BeatmapInfo.Version }, TabbableContentContainer = this }, From a1a0074c3203e4f259fde9f87f9516acca239cf2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Mar 2021 18:05:11 +0900 Subject: [PATCH 248/390] Revert "Local framework" This reverts commit b9b095ee75c960287a8636d06c07afddade6865f. --- osu.Desktop.slnf | 6 ++---- osu.Game/osu.Game.csproj | 4 +--- osu.iOS.props | 4 ++-- osu.sln | 42 ---------------------------------------- 4 files changed, 5 insertions(+), 51 deletions(-) diff --git a/osu.Desktop.slnf b/osu.Desktop.slnf index 1e41d0af0e..d2c14d321a 100644 --- a/osu.Desktop.slnf +++ b/osu.Desktop.slnf @@ -15,9 +15,7 @@ "osu.Game.Tests\\osu.Game.Tests.csproj", "osu.Game.Tournament.Tests\\osu.Game.Tournament.Tests.csproj", "osu.Game.Tournament\\osu.Game.Tournament.csproj", - "osu.Game\\osu.Game.csproj", - "../osu-framework/osu.Framework/osu.Framework.csproj", - "../osu-framework/osu.Framework/osu.Framework.NativeLibs.csproj" + "osu.Game\\osu.Game.csproj" ] } -} +} \ No newline at end of file diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index f2fc1726cd..90c8b98f42 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -29,13 +29,11 @@ + - - - diff --git a/osu.iOS.props b/osu.iOS.props index 30df8c423e..ccd33bf88c 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + diff --git a/osu.sln b/osu.sln index 4d0b3656e7..c9453359b1 100644 --- a/osu.sln +++ b/osu.sln @@ -66,12 +66,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Benchmarks", "osu.Game.Benchmarks\osu.Game.Benchmarks.csproj", "{93632F2D-2BB4-46C1-A7B8-F8CF2FB27118}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Framework", "..\osu-framework\osu.Framework\osu.Framework.csproj", "{7EBA330C-6DD9-4F30-9332-6542D86D5BE1}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Framework.iOS", "..\osu-framework\osu.Framework.iOS\osu.Framework.iOS.csproj", "{7A6EEFF0-760C-4EE5-BB5E-101E7D013392}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Framework.NativeLibs", "..\osu-framework\osu.Framework.NativeLibs\osu.Framework.NativeLibs.csproj", "{500039B3-0706-40C3-B6E7-1FD9187644A5}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -418,42 +412,6 @@ Global {93632F2D-2BB4-46C1-A7B8-F8CF2FB27118}.Release|iPhone.Build.0 = Release|Any CPU {93632F2D-2BB4-46C1-A7B8-F8CF2FB27118}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {93632F2D-2BB4-46C1-A7B8-F8CF2FB27118}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {7EBA330C-6DD9-4F30-9332-6542D86D5BE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7EBA330C-6DD9-4F30-9332-6542D86D5BE1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7EBA330C-6DD9-4F30-9332-6542D86D5BE1}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {7EBA330C-6DD9-4F30-9332-6542D86D5BE1}.Debug|iPhone.Build.0 = Debug|Any CPU - {7EBA330C-6DD9-4F30-9332-6542D86D5BE1}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {7EBA330C-6DD9-4F30-9332-6542D86D5BE1}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {7EBA330C-6DD9-4F30-9332-6542D86D5BE1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7EBA330C-6DD9-4F30-9332-6542D86D5BE1}.Release|Any CPU.Build.0 = Release|Any CPU - {7EBA330C-6DD9-4F30-9332-6542D86D5BE1}.Release|iPhone.ActiveCfg = Release|Any CPU - {7EBA330C-6DD9-4F30-9332-6542D86D5BE1}.Release|iPhone.Build.0 = Release|Any CPU - {7EBA330C-6DD9-4F30-9332-6542D86D5BE1}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {7EBA330C-6DD9-4F30-9332-6542D86D5BE1}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {7A6EEFF0-760C-4EE5-BB5E-101E7D013392}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7A6EEFF0-760C-4EE5-BB5E-101E7D013392}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7A6EEFF0-760C-4EE5-BB5E-101E7D013392}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {7A6EEFF0-760C-4EE5-BB5E-101E7D013392}.Debug|iPhone.Build.0 = Debug|Any CPU - {7A6EEFF0-760C-4EE5-BB5E-101E7D013392}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {7A6EEFF0-760C-4EE5-BB5E-101E7D013392}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {7A6EEFF0-760C-4EE5-BB5E-101E7D013392}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7A6EEFF0-760C-4EE5-BB5E-101E7D013392}.Release|Any CPU.Build.0 = Release|Any CPU - {7A6EEFF0-760C-4EE5-BB5E-101E7D013392}.Release|iPhone.ActiveCfg = Release|Any CPU - {7A6EEFF0-760C-4EE5-BB5E-101E7D013392}.Release|iPhone.Build.0 = Release|Any CPU - {7A6EEFF0-760C-4EE5-BB5E-101E7D013392}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {7A6EEFF0-760C-4EE5-BB5E-101E7D013392}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {500039B3-0706-40C3-B6E7-1FD9187644A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {500039B3-0706-40C3-B6E7-1FD9187644A5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {500039B3-0706-40C3-B6E7-1FD9187644A5}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {500039B3-0706-40C3-B6E7-1FD9187644A5}.Debug|iPhone.Build.0 = Debug|Any CPU - {500039B3-0706-40C3-B6E7-1FD9187644A5}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {500039B3-0706-40C3-B6E7-1FD9187644A5}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {500039B3-0706-40C3-B6E7-1FD9187644A5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {500039B3-0706-40C3-B6E7-1FD9187644A5}.Release|Any CPU.Build.0 = Release|Any CPU - {500039B3-0706-40C3-B6E7-1FD9187644A5}.Release|iPhone.ActiveCfg = Release|Any CPU - {500039B3-0706-40C3-B6E7-1FD9187644A5}.Release|iPhone.Build.0 = Release|Any CPU - {500039B3-0706-40C3-B6E7-1FD9187644A5}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {500039B3-0706-40C3-B6E7-1FD9187644A5}.Release|iPhoneSimulator.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 577d40d8d155daf4af76278b7dce81df355eef59 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Mar 2021 18:05:18 +0900 Subject: [PATCH 249/390] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 5b700224db..e0392bd687 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index fa1b0a95c3..360c522193 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -29,7 +29,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 71fcdd45f3..b763a91dfb 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -91,7 +91,7 @@ - + From 79041c1c4b7a386781a4c01cd2d56a1e74f601eb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Mar 2021 18:07:42 +0900 Subject: [PATCH 250/390] Remove osuTK reference --- osu.Desktop/Program.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index 0c527ba881..d06c4b6746 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -22,7 +22,6 @@ namespace osu.Desktop { // Back up the cwd before DesktopGameHost changes it var cwd = Environment.CurrentDirectory; - bool useOsuTK = args.Contains("--tk"); using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true)) { From fccd495f27e3955f33b4a2f3fd5258fffd7b5faa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Mar 2021 18:07:51 +0900 Subject: [PATCH 251/390] Remove obsoleted setting for now --- osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs index 036c4edfba..fb908a7669 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs @@ -53,11 +53,6 @@ namespace osu.Game.Overlays.Settings.Sections.Input LabelText = "Cursor sensitivity", Current = localSensitivity }, - new SettingsCheckbox - { - LabelText = "Map absolute input to window", - Current = config.GetBindable(FrameworkSetting.MapAbsoluteInputToWindow) - }, confineMouseModeSetting = new SettingsEnumDropdown { LabelText = "Confine mouse cursor to window", From 4bf57ad86080133e9d437a0079eb3255c8e9c789 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Mar 2021 18:24:24 +0900 Subject: [PATCH 252/390] Remove remaining reference to obsolete value --- osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs index 6ca7707906..bf5338d81a 100644 --- a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs +++ b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Platform; @@ -62,10 +61,6 @@ namespace osu.Game.Tests.Visual.Navigation RecycleLocalStorage(); - // see MouseSettings - var frameworkConfig = host.Dependencies.Get(); - frameworkConfig.GetBindable(FrameworkSetting.CursorSensitivity).Disabled = false; - CreateGame(); }); From f7ec79c5f42cd90f3201ae8855bc4d53fc5594fc Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 17 Mar 2021 19:02:25 +0900 Subject: [PATCH 253/390] Fix incorrect generic type --- osu.Game.Tests/Visual/Navigation/TestSettingsMigration.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSettingsMigration.cs b/osu.Game.Tests/Visual/Navigation/TestSettingsMigration.cs index 768f2057a2..c1c968e862 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSettingsMigration.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSettingsMigration.cs @@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.Navigation { AddAssert("config has migrated value", () => Precision.AlmostEquals(Game.LocalConfig.Get(OsuSetting.DisplayStarsMaximum), 10.1)); - AddStep("set value again", () => Game.LocalConfig.SetValue(OsuSetting.DisplayStarsMaximum, 10)); + AddStep("set value again", () => Game.LocalConfig.SetValue(OsuSetting.DisplayStarsMaximum, 10.0)); AddStep("force save config", () => Game.LocalConfig.Save()); From e59b8b4ce66a6b30057add268d1620d5af8be231 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 17 Mar 2021 19:07:29 +0900 Subject: [PATCH 254/390] Fix test checking nullable string --- .../Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs index 2f558a6379..591095252f 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs @@ -49,7 +49,7 @@ namespace osu.Game.Tests.Visual.Ranking })); AddAssert("mapped by text not present", () => - this.ChildrenOfType().All(spriteText => !containsAny(spriteText.Current.Value, "mapped", "by"))); + this.ChildrenOfType().All(spriteText => !containsAny(spriteText.Text.ToString(), "mapped", "by"))); } private void showPanel(ScoreInfo score) => Child = new ExpandedPanelMiddleContentContainer(score); From 3bfde7341f25b9fb06213426fed89a9e5accda0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 17 Mar 2021 17:14:53 +0100 Subject: [PATCH 255/390] Revert "Remove unnecessary overrides" This reverts commit f4e508b57051e887a00a8f4649e4616b762a8c8c. --- osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs | 2 ++ osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyHitExplosion.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs b/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs index 1b5d576c1e..69b81d6d5c 100644 --- a/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs +++ b/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs @@ -21,6 +21,8 @@ namespace osu.Game.Rulesets.Mania.UI { private const float default_large_faint_size = 0.8f; + public override bool RemoveWhenNotAlive => true; + [Resolved] private Column column { get; set; } diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyHitExplosion.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyHitExplosion.cs index bef9279bac..aad9f53b93 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyHitExplosion.cs @@ -13,6 +13,8 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy { public class LegacyHitExplosion : CompositeDrawable, IAnimatableHitExplosion { + public override bool RemoveWhenNotAlive => false; + private readonly Drawable sprite; [CanBeNull] From f1e66cc420ce69e133f08068fdc2c1affd3e02cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 17 Mar 2021 18:37:11 +0100 Subject: [PATCH 256/390] Adjust test namespace --- osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs index a824696022..aafd0ee32b 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs @@ -7,7 +7,7 @@ using osu.Framework.Graphics.Containers; using osu.Game.Screens.Edit.Components; using osuTK; -namespace osu.Game.Tests.Visual.Editor +namespace osu.Game.Tests.Visual.Editing { [TestFixture] public class TestSceneEditorClock : EditorClockTestScene From 3b55eeb416c01f1f2ba5d2fa0a8ce0d7a25d6bd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 17 Mar 2021 18:39:48 +0100 Subject: [PATCH 257/390] Fix test failure by setting beatmap Post-merge, it was failing because somewhere along the way `EditorClockTestScene` started expecting inheritors to set the beatmap themselves explicitly in BDL. --- osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs index aafd0ee32b..63d7dbc2b5 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs @@ -2,8 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit.Components; using osuTK; @@ -35,6 +37,12 @@ namespace osu.Game.Tests.Visual.Editing }); } + [BackgroundDependencyLoader] + private void load() + { + Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); + } + [Test] public void TestStopAtTrackEnd() { From 21e18c9f6eae256c894ec81ff00dc529dc7762fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 17 Mar 2021 18:44:21 +0100 Subject: [PATCH 258/390] Fix test hangs in browser due to changing tracks via music controller --- osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs index 63d7dbc2b5..58375f295b 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs @@ -41,6 +41,9 @@ namespace osu.Game.Tests.Visual.Editing private void load() { Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); + // ensure that music controller does not change this beatmap due to it + // completing naturally as part of the test. + Beatmap.Disabled = true; } [Test] From 6cea74f0fada8f90bf27e3005cccd2ca5dc42706 Mon Sep 17 00:00:00 2001 From: Joehu Date: Wed, 17 Mar 2021 13:13:13 -0700 Subject: [PATCH 259/390] Remove available kudosu section from user profile overlay in line with web --- .../Profile/Sections/Kudosu/KudosuInfo.cs | 34 ++----------------- 1 file changed, 3 insertions(+), 31 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs index d4d0976724..e5b4193f3b 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs @@ -23,44 +23,17 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu { this.user.BindTo(user); CountSection total; - CountSection avaliable; RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; Masking = true; CornerRadius = 3; - Children = new Drawable[] - { - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(5, 0), - Children = new[] - { - total = new CountTotal(), - avaliable = new CountAvailable() - } - } - }; - this.user.ValueChanged += u => - { - total.Count = u.NewValue?.Kudosu.Total ?? 0; - avaliable.Count = u.NewValue?.Kudosu.Available ?? 0; - }; + Child = total = new CountTotal(); + + this.user.ValueChanged += u => total.Count = u.NewValue?.Kudosu.Total ?? 0; } protected override bool OnClick(ClickEvent e) => true; - private class CountAvailable : CountSection - { - public CountAvailable() - : base("Kudosu Avaliable") - { - DescriptionText.Text = "Kudosu can be traded for kudosu stars, which will help your beatmap get more attention. This is the number of kudosu you haven't traded in yet."; - } - } - private class CountTotal : CountSection { public CountTotal() @@ -86,7 +59,6 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu public CountSection(string header) { RelativeSizeAxes = Axes.X; - Width = 0.5f; AutoSizeAxes = Axes.Y; Padding = new MarginPadding { Top = 10, Bottom = 20 }; Child = new FillFlowContainer From 599c55fca5d08f0f2a30cde6be78273b6ad24404 Mon Sep 17 00:00:00 2001 From: Joehu Date: Wed, 17 Mar 2021 13:14:18 -0700 Subject: [PATCH 260/390] Update total kudosu earned description text in line with web --- osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs index e5b4193f3b..87622939e2 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs @@ -40,7 +40,7 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu : base("Total Kudosu Earned") { DescriptionText.AddText("Based on how much of a contribution the user has made to beatmap moderation. See "); - DescriptionText.AddLink("this link", "https://osu.ppy.sh/wiki/Kudosu"); + DescriptionText.AddLink("this page", "https://osu.ppy.sh/wiki/Kudosu"); DescriptionText.AddText(" for more information."); } } From 2e63c2ce20ef4c707150f2eaf3c5a640a9c963c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 17 Mar 2021 21:50:45 +0100 Subject: [PATCH 261/390] Fix selection box operation hotkeys not registering in change handler Could lead to crashes after reversing a note cluster and playing it back. The root cause of the crash was that the hotkey operations were not ran inside of an editor change handler operation. This, in turn, caused the autoplay replay to not be regenerated after flipping an object cluster, therefore finally manifesting as a hard crash due to negative time offsets appearing in judgement results, which interfered with the default implementation of note lock. Note that this incidentally also fixes the fact that selection box hotkey operations (reverse and flip) did not handle undo/redo. --- .../Edit/Compose/Components/SelectionBox.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs index 2f4721f63e..9d6b44e207 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs @@ -113,16 +113,25 @@ namespace osu.Game.Screens.Edit.Compose.Components if (e.Repeat || !e.ControlPressed) return false; + bool runOperationFromHotkey(Func operation) + { + operationStarted(); + bool result = operation?.Invoke() ?? false; + operationEnded(); + + return result; + } + switch (e.Key) { case Key.G: - return CanReverse && OnReverse?.Invoke() == true; + return CanReverse && runOperationFromHotkey(OnReverse); case Key.H: - return CanScaleX && OnFlip?.Invoke(Direction.Horizontal) == true; + return CanScaleX && runOperationFromHotkey(() => OnFlip?.Invoke(Direction.Horizontal) ?? false); case Key.J: - return CanScaleY && OnFlip?.Invoke(Direction.Vertical) == true; + return CanScaleY && runOperationFromHotkey(() => OnFlip?.Invoke(Direction.Vertical) ?? false); } return base.OnKeyDown(e); From 08ffe425f9b9a1cd9eba6c3cd903264680244090 Mon Sep 17 00:00:00 2001 From: Joehu Date: Wed, 17 Mar 2021 14:46:23 -0700 Subject: [PATCH 262/390] Update kudosu description color in line with web --- osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs index 87622939e2..115d705766 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs @@ -103,7 +103,6 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu private void load(OverlayColourProvider colourProvider) { lineBackground.Colour = colourProvider.Highlight1; - DescriptionText.Colour = colourProvider.Foreground1; } } } From f95ce90c95495691e1d7c57e10a8c456ffbfc82c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 17 Mar 2021 23:32:08 +0100 Subject: [PATCH 263/390] Adjust kudosu count formatting --- osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs index 115d705766..cdb24b784c 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs @@ -53,7 +53,7 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu public new int Count { - set => valueText.Text = value.ToString(); + set => valueText.Text = value.ToString("N0"); } public CountSection(string header) From df6570ebf544dd6157e14376055ec271fd9c132c Mon Sep 17 00:00:00 2001 From: voidedWarranties Date: Wed, 17 Mar 2021 15:31:16 -0700 Subject: [PATCH 264/390] Improve logic and add previously failing test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- .../Visual/Editing/TestSceneEditorClock.cs | 35 ++++++++++++++----- osu.Game/Screens/Edit/EditorClock.cs | 26 +++++++------- 2 files changed, 39 insertions(+), 22 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs index 58375f295b..390198be04 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs @@ -49,14 +49,33 @@ namespace osu.Game.Tests.Visual.Editing [Test] public void TestStopAtTrackEnd() { - AddStep("Reset clock", () => Clock.Seek(0)); - AddStep("Start clock", Clock.Start); - AddAssert("Clock running", () => Clock.IsRunning); - AddStep("Seek near end", () => Clock.Seek(Clock.TrackLength - 250)); - AddUntilStep("Clock stops", () => !Clock.IsRunning); - AddAssert("Clock stopped at end", () => Clock.CurrentTime == Clock.TrackLength); - AddStep("Start clock again", Clock.Start); - AddAssert("Clock looped to start", () => Clock.IsRunning && Clock.CurrentTime < 500); + AddStep("reset clock", () => Clock.Seek(0)); + + AddStep("start clock", Clock.Start); + AddAssert("clock running", () => Clock.IsRunning); + + AddStep("seek near end", () => Clock.Seek(Clock.TrackLength - 250)); + AddUntilStep("clock stops", () => !Clock.IsRunning); + + AddAssert("clock stopped at end", () => Clock.CurrentTime == Clock.TrackLength); + + AddStep("start clock again", Clock.Start); + AddAssert("clock looped to start", () => Clock.IsRunning && Clock.CurrentTime < 500); + } + + [Test] + public void TestWrapWhenStoppedAtTrackEnd() + { + AddStep("reset clock", () => Clock.Seek(0)); + + AddStep("stop clock", Clock.Stop); + AddAssert("clock stopped", () => !Clock.IsRunning); + + AddStep("seek exactly to end", () => Clock.Seek(Clock.TrackLength)); + AddAssert("clock stopped at end", () => Clock.CurrentTime == Clock.TrackLength); + + AddStep("start clock again", Clock.Start); + AddAssert("clock looped to start", () => Clock.IsRunning && Clock.CurrentTime < 500); } } } diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index 94bb4f5228..e227bd29bf 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -172,6 +172,10 @@ namespace osu.Game.Screens.Edit public void Start() { ClearTransforms(); + + if (playbackFinished) + underlyingClock.Seek(0); + underlyingClock.Start(); } @@ -222,21 +226,15 @@ namespace osu.Game.Screens.Edit { underlyingClock.ProcessFrame(); - if (IsRunning) - { - var playbackAlreadyStopped = playbackFinished; - playbackFinished = CurrentTime >= TrackLength; + var playbackAlreadyStopped = playbackFinished; + playbackFinished = CurrentTime >= TrackLength; - if (playbackFinished) - { - if (!playbackAlreadyStopped) - { - underlyingClock.Stop(); - underlyingClock.Seek(TrackLength); - } - else - underlyingClock.Seek(0); - } + if (playbackFinished && !playbackAlreadyStopped) + { + if (IsRunning) + underlyingClock.Stop(); + + underlyingClock.Seek(TrackLength); } } From bb3c3f302aa4b0cd2f0e5e8a9d80235fc7d810fd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Mar 2021 15:36:07 +0900 Subject: [PATCH 265/390] Fix skin parser not stripping whitespace before parsing --- osu.Game.Tests/Resources/skin-with-space.ini | 2 ++ osu.Game.Tests/Skins/LegacySkinDecoderTest.cs | 9 +++++++++ osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 2 ++ 3 files changed, 13 insertions(+) create mode 100644 osu.Game.Tests/Resources/skin-with-space.ini diff --git a/osu.Game.Tests/Resources/skin-with-space.ini b/osu.Game.Tests/Resources/skin-with-space.ini new file mode 100644 index 0000000000..3e64257a3e --- /dev/null +++ b/osu.Game.Tests/Resources/skin-with-space.ini @@ -0,0 +1,2 @@ +[General] +Version: 2 \ No newline at end of file diff --git a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs index aedf26ee75..dcb866c99f 100644 --- a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs +++ b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs @@ -91,6 +91,15 @@ namespace osu.Game.Tests.Skins Assert.AreEqual(2.0m, decoder.Decode(stream).LegacyVersion); } + [Test] + public void TestStripWhitespace() + { + var decoder = new LegacySkinDecoder(); + using (var resStream = TestResources.OpenResource("skin-with-space.ini")) + using (var stream = new LineBufferedReader(resStream)) + Assert.AreEqual(2.0m, decoder.Decode(stream).LegacyVersion); + } + [Test] public void TestDecodeLatestVersion() { diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 2fb24c24e0..bd1b6627b4 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -36,6 +36,8 @@ namespace osu.Game.Beatmaps.Formats if (ShouldSkipLine(line)) continue; + line = line.Trim(); + if (line.StartsWith('[') && line.EndsWith(']')) { if (!Enum.TryParse(line[1..^1], out section)) From 5b0d75ee56690dbb3a121d741768515417f51ee8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Mar 2021 16:30:30 +0900 Subject: [PATCH 266/390] Only trim trailing spaces to avoid breakage in storyboard parsing --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 14 ++++++-------- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 4 +--- .../Beatmaps/Formats/LegacyStoryboardDecoder.cs | 2 -- osu.Game/Skinning/LegacyManiaSkinDecoder.cs | 2 -- 4 files changed, 7 insertions(+), 15 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 99dffa7041..40bc75e847 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -67,16 +67,14 @@ namespace osu.Game.Beatmaps.Formats protected override void ParseLine(Beatmap beatmap, Section section, string line) { - var strippedLine = StripComments(line); - switch (section) { case Section.General: - handleGeneral(strippedLine); + handleGeneral(line); return; case Section.Editor: - handleEditor(strippedLine); + handleEditor(line); return; case Section.Metadata: @@ -84,19 +82,19 @@ namespace osu.Game.Beatmaps.Formats return; case Section.Difficulty: - handleDifficulty(strippedLine); + handleDifficulty(line); return; case Section.Events: - handleEvent(strippedLine); + handleEvent(line); return; case Section.TimingPoints: - handleTimingPoint(strippedLine); + handleTimingPoint(line); return; case Section.HitObjects: - handleHitObject(strippedLine); + handleHitObject(line); return; } diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index bd1b6627b4..10a716963e 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -36,7 +36,7 @@ namespace osu.Game.Beatmaps.Formats if (ShouldSkipLine(line)) continue; - line = line.Trim(); + line = StripComments(line).TrimEnd(); if (line.StartsWith('[') && line.EndsWith(']')) { @@ -73,8 +73,6 @@ namespace osu.Game.Beatmaps.Formats protected virtual void ParseLine(T output, Section section, string line) { - line = StripComments(line); - switch (section) { case Section.Colours: diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index b9bf6823b5..6301c42deb 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -45,8 +45,6 @@ namespace osu.Game.Beatmaps.Formats protected override void ParseLine(Storyboard storyboard, Section section, string line) { - line = StripComments(line); - switch (section) { case Section.General: diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs index 0a1de461ea..5308640bdd 100644 --- a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs +++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs @@ -31,8 +31,6 @@ namespace osu.Game.Skinning protected override void ParseLine(List output, Section section, string line) { - line = StripComments(line); - switch (section) { case Section.Mania: From b68dc686ee9bf7c978fd746248403a6decbf18a4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 18 Mar 2021 19:19:53 +0900 Subject: [PATCH 267/390] Fix converted mania scores not accounting for GREATs --- osu.Game/Scoring/ScoreManager.cs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 1e90ee1ac7..5fa971ce80 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -20,6 +20,7 @@ using osu.Game.IO.Archives; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Rulesets; +using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring.Legacy; @@ -157,9 +158,19 @@ namespace osu.Game.Scoring } int beatmapMaxCombo; + double accuracy = score.Accuracy; if (score.IsLegacyScore) { + if (score.RulesetID == 3) + { + // Recalculate mania's accuracy based on hit statistics. + double maxBaseScore = score.Statistics.Select(kvp => kvp.Value).Sum() * Judgement.ToNumericResult(HitResult.Perfect); + double baseScore = score.Statistics.Select(kvp => Judgement.ToNumericResult(kvp.Key) * kvp.Value).Sum(); + if (maxBaseScore > 0) + accuracy = baseScore / maxBaseScore; + } + // This score is guaranteed to be an osu!stable score. // The combo must be determined through either the beatmap's max combo value or the difficulty calculator, as lazer's scoring has changed and the score statistics cannot be used. if (score.Beatmap.MaxCombo == null) @@ -176,7 +187,7 @@ namespace osu.Game.Scoring difficultyBindable.BindValueChanged(d => { if (d.NewValue is StarDifficulty diff) - updateScore(diff.MaxCombo); + updateScore(diff.MaxCombo, accuracy); }, true); return; @@ -191,10 +202,10 @@ namespace osu.Game.Scoring beatmapMaxCombo = Enum.GetValues(typeof(HitResult)).OfType().Where(r => r.AffectsCombo()).Select(r => score.Statistics.GetOrDefault(r)).Sum(); } - updateScore(beatmapMaxCombo); + updateScore(beatmapMaxCombo, accuracy); } - private void updateScore(int beatmapMaxCombo) + private void updateScore(int beatmapMaxCombo, double accuracy) { if (beatmapMaxCombo == 0) { @@ -207,7 +218,7 @@ namespace osu.Game.Scoring scoreProcessor.Mods.Value = score.Mods; - Value = (long)Math.Round(scoreProcessor.GetScore(ScoringMode.Value, beatmapMaxCombo, score.Accuracy, (double)score.MaxCombo / beatmapMaxCombo, score.Statistics)); + Value = (long)Math.Round(scoreProcessor.GetScore(ScoringMode.Value, beatmapMaxCombo, accuracy, (double)score.MaxCombo / beatmapMaxCombo, score.Statistics)); } } From 917717686a6bef391d86cb4a92683e9442ac8c78 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 18 Mar 2021 19:26:29 +0900 Subject: [PATCH 268/390] Expand explanatory comment --- osu.Game/Scoring/ScoreManager.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 5fa971ce80..7d0abc5996 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -164,7 +164,9 @@ namespace osu.Game.Scoring { if (score.RulesetID == 3) { - // Recalculate mania's accuracy based on hit statistics. + // In osu!stable, a full-GREAT score has 100% accuracy in mania. Along with a full combo, the score becomes indistinguishable from a full-PERFECT score. + // To get around this, recalculate accuracy based on the hit statistics. + // Note: This cannot be applied universally to all legacy scores, as some rulesets (e.g. catch) group multiple judgements together. double maxBaseScore = score.Statistics.Select(kvp => kvp.Value).Sum() * Judgement.ToNumericResult(HitResult.Perfect); double baseScore = score.Statistics.Select(kvp => Judgement.ToNumericResult(kvp.Key) * kvp.Value).Sum(); if (maxBaseScore > 0) From 0c3c8141dac75fbc7ccd5c8d79746dc413c8df21 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 18 Mar 2021 19:39:42 +0900 Subject: [PATCH 269/390] Remove Expires and RemoveWhenNotAlive override --- osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyHitExplosion.cs | 4 ---- osu.Game.Rulesets.Taiko/UI/DefaultHitExplosion.cs | 4 ---- 2 files changed, 8 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyHitExplosion.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyHitExplosion.cs index aad9f53b93..21bd35ad22 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyHitExplosion.cs @@ -13,8 +13,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy { public class LegacyHitExplosion : CompositeDrawable, IAnimatableHitExplosion { - public override bool RemoveWhenNotAlive => false; - private readonly Drawable sprite; [CanBeNull] @@ -73,8 +71,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy .Then().ScaleTo(1.1f, animation_time * 0.8) .Then().ScaleTo(0.9f, animation_time * 0.4) .Then().ScaleTo(1f, animation_time * 0.2); - - Expire(true); } public void AnimateSecondHit() diff --git a/osu.Game.Rulesets.Taiko/UI/DefaultHitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/DefaultHitExplosion.cs index 5bb463353d..91e844187a 100644 --- a/osu.Game.Rulesets.Taiko/UI/DefaultHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/UI/DefaultHitExplosion.cs @@ -16,8 +16,6 @@ namespace osu.Game.Rulesets.Taiko.UI { internal class DefaultHitExplosion : CircularContainer, IAnimatableHitExplosion { - public override bool RemoveWhenNotAlive => false; - private readonly HitResult result; [CanBeNull] @@ -73,8 +71,6 @@ namespace osu.Game.Rulesets.Taiko.UI this.ScaleTo(3f, 1000, Easing.OutQuint); this.FadeOut(500); - - Expire(true); } public void AnimateSecondHit() From c694deb7d605759a3d7c6b7877d85bf422be4425 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Mar 2021 21:16:50 +0900 Subject: [PATCH 270/390] Revert changes to SettingsSourceAttribute class --- .../Configuration/SettingSourceAttribute.cs | 39 +------------------ 1 file changed, 2 insertions(+), 37 deletions(-) diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index 39d7fba32b..cfce615130 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -5,10 +5,8 @@ using System; using System.Collections.Generic; using System.Linq; using System.Reflection; -using Humanizer; using JetBrains.Annotations; using osu.Framework.Bindables; -using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; using osu.Framework.Localisation; using osu.Game.Overlays.Settings; @@ -63,21 +61,12 @@ namespace osu.Game.Configuration public static class SettingSourceExtensions { - public static IReadOnlyList CreateSettingsControls(this object obj, bool includeDisabled = true) => - createSettingsControls(obj, obj.GetOrderedSettingsSourceProperties(), includeDisabled).ToArray(); - - public static IReadOnlyList CreateSettingsControlsFromAllBindables(this object obj, bool includeDisabled = true) => - createSettingsControls(obj, obj.GetSettingsSourcePropertiesFromBindables(), includeDisabled).ToArray(); - - private static IEnumerable createSettingsControls(object obj, IEnumerable<(SettingSourceAttribute, PropertyInfo)> sourceAttribs, bool includeDisabled = true) + public static IEnumerable CreateSettingsControls(this object obj) { - foreach (var (attr, property) in sourceAttribs) + foreach (var (attr, property) in obj.GetOrderedSettingsSourceProperties()) { object value = property.GetValue(obj); - if ((value as IBindable)?.Disabled == true) - continue; - switch (value) { case BindableNumber bNumber: @@ -150,30 +139,6 @@ namespace osu.Game.Configuration } } - public static IEnumerable<(SettingSourceAttribute, PropertyInfo)> GetSettingsSourcePropertiesFromBindables(this object obj) - { - HashSet handledProperties = new HashSet(); - - // reverse and de-dupe properties to surface base class settings to the top of return order. - foreach (var type in obj.GetType().EnumerateBaseTypes().Reverse()) - { - foreach (var property in type.GetProperties(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance)) - { - if (handledProperties.Contains(property.Name)) - continue; - - handledProperties.Add(property.Name); - - if (typeof(IBindable).IsAssignableFrom(property.PropertyType)) - { - var val = property.GetValue(obj); - string description = (val as IHasDescription)?.Description ?? string.Empty; - yield return (new SettingSourceAttribute(property.Name.Humanize(), description), property); - } - } - } - } - public static IEnumerable<(SettingSourceAttribute, PropertyInfo)> GetSettingsSourceProperties(this object obj) { foreach (var property in obj.GetType().GetProperties(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance)) From a8cc3a3b4468c885504c69ef9fbdb0dc62df059c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Mar 2021 21:17:04 +0900 Subject: [PATCH 271/390] Implement enable state changes locally for InputHandlers which should be toggleable --- .../Settings/Sections/InputSection.cs | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/InputSection.cs b/osu.Game/Overlays/Settings/Sections/InputSection.cs index e6aaa1ade9..8d5944f5bf 100644 --- a/osu.Game/Overlays/Settings/Sections/InputSection.cs +++ b/osu.Game/Overlays/Settings/Sections/InputSection.cs @@ -9,7 +9,6 @@ using osu.Framework.Input.Handlers.Joystick; using osu.Framework.Input.Handlers.Midi; using osu.Framework.Input.Handlers.Mouse; using osu.Framework.Platform; -using osu.Game.Configuration; using osu.Game.Overlays.Settings.Sections.Input; namespace osu.Game.Overlays.Settings.Sections @@ -70,13 +69,6 @@ namespace osu.Game.Overlays.Settings.Sections return null; } - var settingsControls = handler.CreateSettingsControlsFromAllBindables(false); - - if (settingsControls.Count == 0) - return null; - - section.AddRange(settingsControls); - return section; } @@ -89,6 +81,19 @@ namespace osu.Game.Overlays.Settings.Sections this.handler = handler; } + [BackgroundDependencyLoader] + private void load() + { + Children = new Drawable[] + { + new SettingsCheckbox + { + LabelText = "Enabled", + Current = handler.Enabled + }, + }; + } + protected override string Header => handler.Description; } } From b419d2c2e27762af7701c1823ae9e7786a730e0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 18 Mar 2021 19:52:38 +0100 Subject: [PATCH 272/390] Fix invalid xmldoc indent --- osu.Game/Skinning/LegacySkin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 571d65e28b..b69e99773c 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -506,7 +506,7 @@ namespace osu.Game.Skinning Samples?.Dispose(); } - /// + /// /// A sample wrapper which keeps a reference to the contained skin to avoid finalizer garbage collection of the managing SampleStore. /// private class LegacySkinSample : ISample From b9761c819629f64d647d6178e7bbd7c9987b2b28 Mon Sep 17 00:00:00 2001 From: voidedWarranties Date: Thu, 18 Mar 2021 16:20:31 -0700 Subject: [PATCH 273/390] Further simplify logic --- osu.Game/Screens/Edit/EditorClock.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index e227bd29bf..d0197ce1ec 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -226,15 +226,15 @@ namespace osu.Game.Screens.Edit { underlyingClock.ProcessFrame(); - var playbackAlreadyStopped = playbackFinished; playbackFinished = CurrentTime >= TrackLength; - if (playbackFinished && !playbackAlreadyStopped) + if (playbackFinished) { if (IsRunning) underlyingClock.Stop(); - underlyingClock.Seek(TrackLength); + if (CurrentTime > TrackLength) + underlyingClock.Seek(TrackLength); } } From 5f31304d05fa71b356651643608aa34f8207e323 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Mar 2021 14:00:26 +0900 Subject: [PATCH 274/390] Give each type of slider path type a unique colour to help visually distinguish them --- .../Components/PathControlPointPiece.cs | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index e9838de63d..311ab8ee62 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -12,6 +12,7 @@ using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit; using osuTK; @@ -195,7 +196,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components markerRing.Alpha = IsSelected.Value ? 1 : 0; - Color4 colour = ControlPoint.Type.Value != null ? colours.Red : colours.Yellow; + Color4 colour = getColourFromNodeType(); if (IsHovered || IsSelected.Value) colour = colour.Lighten(1); @@ -203,5 +204,26 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components marker.Colour = colour; marker.Scale = new Vector2(slider.Scale); } + + private Color4 getColourFromNodeType() + { + if (!(ControlPoint.Type.Value is PathType pathType)) + return colours.Yellow; + + switch (pathType) + { + case PathType.Catmull: + return colours.Seafoam; + + case PathType.Bezier: + return colours.Pink; + + case PathType.PerfectCurve: + return colours.PurpleDark; + + default: + return colours.Red; + } + } } } From 0e821e857e34f810e1ede0c9f28df6678602caf3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Mar 2021 15:23:31 +0900 Subject: [PATCH 275/390] Remove unnecessary duplicated skin changed handling For some reason we were handling this both in `DrawableSkinnableSound` and `PoolableSkinnableSample` in very similar ways. Only one seems required. --- osu.Game/Skinning/SkinnableSound.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index edd3a2cdd3..e447f9c44c 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -139,12 +139,6 @@ namespace osu.Game.Skinning samplesContainer.ForEach(c => c.Stop()); } - protected override void SkinChanged(ISkinSource skin, bool allowFallback) - { - base.SkinChanged(skin, allowFallback); - updateSamples(); - } - private void updateSamples() { bool wasPlaying = IsPlaying; From bf4317d3f06ecd3ba29c2f43c33561e5557c3b24 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Mar 2021 15:34:19 +0900 Subject: [PATCH 276/390] Ensure looping is disabled on old samples when switching skins --- osu.Game/Skinning/PoolableSkinnableSample.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Skinning/PoolableSkinnableSample.cs b/osu.Game/Skinning/PoolableSkinnableSample.cs index 9103a6a960..09e087d0f2 100644 --- a/osu.Game/Skinning/PoolableSkinnableSample.cs +++ b/osu.Game/Skinning/PoolableSkinnableSample.cs @@ -83,6 +83,14 @@ namespace osu.Game.Skinning bool wasPlaying = Playing; + if (activeChannel != null) + { + // when switching away from previous samples, we don't want to call Stop() on them as it sounds better to let them play out. + // this may change in the future if we use PoolableSkinSample in more locations than gameplay. + // we *do* want to turn off looping, else we end up with an infinite looping sample running in the background. + activeChannel.Looping = false; + } + sampleContainer.Clear(); Sample = null; From 9491e6394a65de287bb05c96188db69ca7837158 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Mar 2021 15:46:43 +0900 Subject: [PATCH 277/390] Include the bundled skins when selecting a random skin --- osu.Game/Skinning/SkinManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 601b77e782..752c742a45 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -86,7 +86,7 @@ namespace osu.Game.Skinning public void SelectRandomSkin() { // choose from only user skins, removing the current selection to ensure a new one is chosen. - var randomChoices = GetAllUsableSkins().Where(s => s.ID > 0 && s.ID != CurrentSkinInfo.Value.ID).ToArray(); + var randomChoices = GetAllUsableSkins().Where(s => s.ID != CurrentSkinInfo.Value.ID).ToArray(); if (randomChoices.Length == 0) { From a9c4fa442a983534897b6d91e3fe9dd60d97dead Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Mar 2021 16:47:39 +0900 Subject: [PATCH 278/390] Avoid potential crash if an overlay is toggled before it has been loaded --- osu.Game/OsuGame.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 66b9141ce8..e5e1f6946e 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -756,6 +756,10 @@ namespace osu.Game private void showOverlayAboveOthers(OverlayContainer overlay, OverlayContainer[] otherOverlays) { + // generally shouldn't ever hit this state, but protects against a crash on attempting to change ChildDepth. + if (overlay.LoadState < LoadState.Ready) + return; + otherOverlays.Where(o => o != overlay).ForEach(o => o.Hide()); // show above others if not visible at all, else leave at current depth. From 27c38db14dee71d4f5a40300acbe90eb84d8bf3e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Mar 2021 16:58:08 +0900 Subject: [PATCH 279/390] Add tooltips for slider path nodes which aren't inheriting --- .../Blueprints/Sliders/Components/PathControlPointPiece.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index 311ab8ee62..1390675a1a 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -7,6 +7,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Graphics; @@ -24,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components /// /// A visualisation of a single in a . /// - public class PathControlPointPiece : BlueprintPiece + public class PathControlPointPiece : BlueprintPiece, IHasTooltip { public Action RequestSelection; @@ -225,5 +226,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components return colours.Red; } } + + public string TooltipText => ControlPoint.Type.Value.ToString() ?? string.Empty; } } From 0195d654cacb2e496ce7cc2b3c3b3991111eabb3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Mar 2021 17:09:49 +0900 Subject: [PATCH 280/390] Increase the precision of speed multiplier to match osu-stable --- osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs index 0bc5605051..73337ab6f5 100644 --- a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs @@ -19,7 +19,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// public readonly BindableDouble SpeedMultiplierBindable = new BindableDouble(1) { - Precision = 0.1, + Precision = 0.01, Default = 1, MinValue = 0.1, MaxValue = 10 From 32c571fc94a0ca6521b25226670f79238dc9bf98 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Mar 2021 17:13:30 +0900 Subject: [PATCH 281/390] Adjust keyboard step to be something sensible --- osu.Game/Screens/Edit/Timing/DifficultySection.cs | 3 ++- osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/DifficultySection.cs b/osu.Game/Screens/Edit/Timing/DifficultySection.cs index b87b8961f8..9d80ca0b14 100644 --- a/osu.Game/Screens/Edit/Timing/DifficultySection.cs +++ b/osu.Game/Screens/Edit/Timing/DifficultySection.cs @@ -18,7 +18,8 @@ namespace osu.Game.Screens.Edit.Timing { multiplierSlider = new SliderWithTextBoxInput("Speed Multiplier") { - Current = new DifficultyControlPoint().SpeedMultiplierBindable + Current = new DifficultyControlPoint().SpeedMultiplierBindable, + KeyboardStep = 0.1f } }); } diff --git a/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs b/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs index f2f9f76143..10a5771520 100644 --- a/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs +++ b/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs @@ -69,6 +69,15 @@ namespace osu.Game.Screens.Edit.Timing }, true); } + /// + /// A custom step value for each key press which actuates a change on this control. + /// + public float KeyboardStep + { + get => slider.KeyboardStep; + set => slider.KeyboardStep = value; + } + public Bindable Current { get => slider.Current; From 1c865682ae3b0d4116fa8483b1e173c67ab079a3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Mar 2021 16:25:50 +0900 Subject: [PATCH 282/390] Add tablet configuration tests --- .../Settings/TestSceneTabletAreaSelection.cs | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 osu.Game.Tests/Visual/Settings/TestSceneTabletAreaSelection.cs diff --git a/osu.Game.Tests/Visual/Settings/TestSceneTabletAreaSelection.cs b/osu.Game.Tests/Visual/Settings/TestSceneTabletAreaSelection.cs new file mode 100644 index 0000000000..30e265baaa --- /dev/null +++ b/osu.Game.Tests/Visual/Settings/TestSceneTabletAreaSelection.cs @@ -0,0 +1,104 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using OpenTabletDriver.Plugin.Tablet; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.Settings +{ + [TestFixture] + public class TestSceneTabletAreaSelection : OsuTestScene + { + private TabletAreaSelection areaSelection; + + [BackgroundDependencyLoader] + private void load() + { + DigitizerIdentifier testTablet = new DigitizerIdentifier + { + // size specifications in millimetres. + Width = 160, + Height = 100, + }; + + AddRange(new[] + { + areaSelection = new TabletAreaSelection(testTablet) + { + State = { Value = Visibility.Visible } + } + }); + } + } + + public class TabletAreaSelection : OsuFocusedOverlayContainer + { + private readonly DigitizerIdentifier tablet; + + private readonly Container tabletContainer; + private readonly Container usableAreaContainer; + + public TabletAreaSelection(DigitizerIdentifier tablet) + { + RelativeSizeAxes = Axes.Both; + + this.tablet = tablet; + + InternalChildren = new Drawable[] + { + tabletContainer = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(3), + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + }, + usableAreaContainer = new Container + { + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Yellow, + }, + new OsuSpriteText + { + Text = "usable area", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = Color4.Black, + Font = OsuFont.Default.With(size: 12) + } + } + }, + } + } + }; + } + + [BackgroundDependencyLoader] + private void load() + { + // TODO: handle tablet device changes etc. + tabletContainer.Size = new Vector2(tablet.Width, tablet.Height); + + usableAreaContainer.Position = new Vector2(10, 30); + usableAreaContainer.Size = new Vector2(80, 60); + } + } +} From d026c8da851a5fbd399206b8efff28b76c09c643 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Mar 2021 18:37:46 +0900 Subject: [PATCH 283/390] Initial pass of configuration interface --- .../Settings/TestSceneTabletSettings.cs | 40 +++++++ .../Sections/Input/TabletAreaSelection.cs | 79 ++++++------- .../Settings/Sections/Input/TabletSettings.cs | 111 ++++++++++++++++++ .../Settings/Sections/InputSection.cs | 6 + 4 files changed, 194 insertions(+), 42 deletions(-) create mode 100644 osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs rename osu.Game.Tests/Visual/Settings/TestSceneTabletAreaSelection.cs => osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs (55%) create mode 100644 osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs diff --git a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs new file mode 100644 index 0000000000..be5b355e06 --- /dev/null +++ b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs @@ -0,0 +1,40 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Drawing; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Input.Handlers.Tablet; +using osu.Framework.Platform; +using osu.Game.Overlays.Settings.Sections.Input; + +namespace osu.Game.Tests.Visual.Settings +{ + [TestFixture] + public class TestSceneTabletSettings : OsuTestScene + { + [BackgroundDependencyLoader] + private void load(GameHost host) + { + var tabletHandler = host.AvailableInputHandlers.OfType().FirstOrDefault(); + + if (tabletHandler == null) + return; + + tabletHandler.AreaOffset.MinValue = new Size(0, 0); + tabletHandler.AreaOffset.MaxValue = new Size(160, 100); + tabletHandler.AreaOffset.Value = new Size(10, 10); + + tabletHandler.AreaSize.MinValue = new Size(0, 0); + tabletHandler.AreaSize.MaxValue = new Size(160, 100); + tabletHandler.AreaSize.Value = new Size(100, 80); + + AddRange(new Drawable[] + { + new TabletSettings(tabletHandler), + }); + } + } +} diff --git a/osu.Game.Tests/Visual/Settings/TestSceneTabletAreaSelection.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs similarity index 55% rename from osu.Game.Tests/Visual/Settings/TestSceneTabletAreaSelection.cs rename to osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs index 30e265baaa..31a2768735 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneTabletAreaSelection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs @@ -1,65 +1,46 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using NUnit.Framework; -using OpenTabletDriver.Plugin.Tablet; +using System.Drawing; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Handlers.Tablet; using osu.Game.Graphics; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osuTK; + using osuTK.Graphics; -namespace osu.Game.Tests.Visual.Settings +namespace osu.Game.Overlays.Settings.Sections.Input { - [TestFixture] - public class TestSceneTabletAreaSelection : OsuTestScene + public class TabletAreaSelection : CompositeDrawable { - private TabletAreaSelection areaSelection; - - [BackgroundDependencyLoader] - private void load() - { - DigitizerIdentifier testTablet = new DigitizerIdentifier - { - // size specifications in millimetres. - Width = 160, - Height = 100, - }; - - AddRange(new[] - { - areaSelection = new TabletAreaSelection(testTablet) - { - State = { Value = Visibility.Visible } - } - }); - } - } - - public class TabletAreaSelection : OsuFocusedOverlayContainer - { - private readonly DigitizerIdentifier tablet; + private readonly ITabletHandler handler; private readonly Container tabletContainer; private readonly Container usableAreaContainer; - public TabletAreaSelection(DigitizerIdentifier tablet) - { - RelativeSizeAxes = Axes.Both; + private readonly Bindable areaOffset = new BindableSize(); + private readonly Bindable areaSize = new BindableSize(); + private readonly Bindable tabletSize = new BindableSize(); - this.tablet = tablet; + public TabletAreaSelection(ITabletHandler handler) + { + this.handler = handler; + + Padding = new MarginPadding(5); InternalChildren = new Drawable[] { tabletContainer = new Container { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(3), + Masking = true, + CornerRadius = 5, + BorderThickness = 2, + BorderColour = Color4.Black, Children = new Drawable[] { new Box @@ -69,6 +50,8 @@ namespace osu.Game.Tests.Visual.Settings }, usableAreaContainer = new Container { + Masking = true, + CornerRadius = 5, Children = new Drawable[] { new Box @@ -94,11 +77,23 @@ namespace osu.Game.Tests.Visual.Settings [BackgroundDependencyLoader] private void load() { - // TODO: handle tablet device changes etc. - tabletContainer.Size = new Vector2(tablet.Width, tablet.Height); + areaOffset.BindTo(handler.AreaOffset); + areaOffset.BindValueChanged(val => + { + usableAreaContainer.MoveTo(new Vector2(val.NewValue.Width, val.NewValue.Height), 100, Easing.OutQuint); + }, true); - usableAreaContainer.Position = new Vector2(10, 30); - usableAreaContainer.Size = new Vector2(80, 60); + areaSize.BindTo(handler.AreaSize); + areaSize.BindValueChanged(val => + { + usableAreaContainer.ResizeTo(new Vector2(val.NewValue.Width, val.NewValue.Height), 100, Easing.OutQuint); + }, true); + + ((IBindable)tabletSize).BindTo(handler.TabletSize); + tabletSize.BindValueChanged(val => + { + tabletContainer.ResizeTo(new Vector2(tabletSize.Value.Width, tabletSize.Value.Height), 100, Easing.OutQuint); + }, true); } } } diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs new file mode 100644 index 0000000000..5df9c879eb --- /dev/null +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -0,0 +1,111 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Drawing; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Input.Handlers.Tablet; +using osu.Game.Configuration; + +namespace osu.Game.Overlays.Settings.Sections.Input +{ + public class TabletSettings : SettingsSubsection + { + private readonly ITabletHandler tabletHandler; + + private readonly BindableSize areaOffset = new BindableSize(); + private readonly BindableSize areaSize = new BindableSize(); + private readonly BindableSize tabletSize = new BindableSize(); + + private readonly BindableNumber offsetX = new BindableNumber { MinValue = 0 }; + private readonly BindableNumber offsetY = new BindableNumber { MinValue = 0 }; + + private readonly BindableNumber sizeX = new BindableNumber { MinValue = 0 }; + private readonly BindableNumber sizeY = new BindableNumber { MinValue = 0 }; + + protected override string Header => "Tablet"; + + public TabletSettings(ITabletHandler tabletHandler) + { + this.tabletHandler = tabletHandler; + } + + [BackgroundDependencyLoader] + private void load(OsuConfigManager osuConfig, FrameworkConfigManager config) + { + // TODO: this should all eventually be replaced with a control that handles BindableSize. + areaOffset.BindTo(tabletHandler.AreaOffset); + areaOffset.BindValueChanged(val => + { + offsetX.Value = val.NewValue.Width; + offsetY.Value = val.NewValue.Height; + }, true); + + offsetX.BindValueChanged(val => areaOffset.Value = new Size(val.NewValue, areaOffset.Value.Height)); + offsetY.BindValueChanged(val => areaOffset.Value = new Size(areaOffset.Value.Width, val.NewValue)); + + areaSize.BindTo(tabletHandler.AreaSize); + areaSize.BindValueChanged(val => + { + sizeX.Value = val.NewValue.Width; + sizeY.Value = val.NewValue.Height; + }, true); + + sizeX.BindValueChanged(val => areaSize.Value = new Size(val.NewValue, areaSize.Value.Height)); + sizeY.BindValueChanged(val => areaSize.Value = new Size(areaSize.Value.Width, val.NewValue)); + + ((IBindable)tabletSize).BindTo(tabletHandler.TabletSize); + tabletSize.BindValueChanged(val => + { + // todo: these should propagate from a TabletChanged event or similar. + offsetX.MaxValue = val.NewValue.Width; + sizeX.Default = sizeX.MaxValue = val.NewValue.Width; + + offsetY.MaxValue = val.NewValue.Height; + sizeY.Default = sizeY.MaxValue = val.NewValue.Height; + + updateDisplay(); + }, true); + } + + private void updateDisplay() + { + if (tabletSize.Value == System.Drawing.Size.Empty) + { + Clear(); + return; + } + + Children = new Drawable[] + { + new SettingsSlider + { + LabelText = "Offset X", + Current = offsetX + }, + new SettingsSlider + { + LabelText = "Offset Y", + Current = offsetY + }, + new SettingsSlider + { + LabelText = "Size X", + Current = sizeX + }, + new SettingsSlider + { + LabelText = "Size Y", + Current = sizeY + }, + new TabletAreaSelection(tabletHandler) + { + RelativeSizeAxes = Axes.X, + Height = 100, + } + }; + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/InputSection.cs b/osu.Game/Overlays/Settings/Sections/InputSection.cs index 8d5944f5bf..6e99891794 100644 --- a/osu.Game/Overlays/Settings/Sections/InputSection.cs +++ b/osu.Game/Overlays/Settings/Sections/InputSection.cs @@ -8,6 +8,7 @@ using osu.Framework.Input.Handlers; using osu.Framework.Input.Handlers.Joystick; using osu.Framework.Input.Handlers.Midi; using osu.Framework.Input.Handlers.Mouse; +using osu.Framework.Input.Handlers.Tablet; using osu.Framework.Platform; using osu.Game.Overlays.Settings.Sections.Input; @@ -55,6 +56,11 @@ namespace osu.Game.Overlays.Settings.Sections switch (handler) { + // ReSharper disable once SuspiciousTypeConversion.Global (net standard fuckery) + case ITabletHandler th: + section = new TabletSettings(th); + break; + case MouseHandler mh: section = new MouseSettings(mh); break; From 3b7edf13337a26a9d358b2dd70eef5773ba73332 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Mar 2021 14:45:21 +0900 Subject: [PATCH 284/390] Make tablet display always fit to size of settings area --- .../Settings/TestSceneTabletSettings.cs | 26 ++++- .../Sections/Input/TabletAreaSelection.cs | 105 ++++++++++-------- 2 files changed, 78 insertions(+), 53 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs index be5b355e06..6455f51ab9 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs @@ -2,9 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using System.Drawing; -using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input.Handlers.Tablet; using osu.Framework.Platform; @@ -18,16 +18,13 @@ namespace osu.Game.Tests.Visual.Settings [BackgroundDependencyLoader] private void load(GameHost host) { - var tabletHandler = host.AvailableInputHandlers.OfType().FirstOrDefault(); - - if (tabletHandler == null) - return; + var tabletHandler = new TestTabletHandler(); tabletHandler.AreaOffset.MinValue = new Size(0, 0); tabletHandler.AreaOffset.MaxValue = new Size(160, 100); tabletHandler.AreaOffset.Value = new Size(10, 10); - tabletHandler.AreaSize.MinValue = new Size(0, 0); + tabletHandler.AreaSize.MinValue = new Size(10, 10); tabletHandler.AreaSize.MaxValue = new Size(160, 100); tabletHandler.AreaSize.Value = new Size(100, 80); @@ -35,6 +32,23 @@ namespace osu.Game.Tests.Visual.Settings { new TabletSettings(tabletHandler), }); + + AddStep("Test with wide tablet", () => tabletHandler.SetTabletSize(new Size(160, 100))); + AddStep("Test with square tablet", () => tabletHandler.SetTabletSize(new Size(300, 300))); + AddStep("Test with tall tablet", () => tabletHandler.SetTabletSize(new Size(100, 300))); + AddStep("Test with very tall tablet", () => tabletHandler.SetTabletSize(new Size(100, 700))); + } + + public class TestTabletHandler : ITabletHandler + { + private readonly Bindable tabletSize = new Bindable(); + + public BindableSize AreaOffset { get; } = new BindableSize(); + public BindableSize AreaSize { get; } = new BindableSize(); + public IBindable TabletSize => tabletSize; + public BindableBool Enabled { get; } = new BindableBool(true); + + public void SetTabletSize(Size size) => tabletSize.Value = size; } } } diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs index 31a2768735..775aceb5f9 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Drawing; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -11,7 +12,6 @@ using osu.Framework.Input.Handlers.Tablet; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osuTK; - using osuTK.Graphics; namespace osu.Game.Overlays.Settings.Sections.Input @@ -20,8 +20,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input { private readonly ITabletHandler handler; - private readonly Container tabletContainer; - private readonly Container usableAreaContainer; + private Container tabletContainer; + private Container usableAreaContainer; private readonly Bindable areaOffset = new BindableSize(); private readonly Bindable areaSize = new BindableSize(); @@ -30,53 +30,50 @@ namespace osu.Game.Overlays.Settings.Sections.Input public TabletAreaSelection(ITabletHandler handler) { this.handler = handler; - - Padding = new MarginPadding(5); - - InternalChildren = new Drawable[] - { - tabletContainer = new Container - { - Masking = true, - CornerRadius = 5, - BorderThickness = 2, - BorderColour = Color4.Black, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.White, - }, - usableAreaContainer = new Container - { - Masking = true, - CornerRadius = 5, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Yellow, - }, - new OsuSpriteText - { - Text = "usable area", - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Colour = Color4.Black, - Font = OsuFont.Default.With(size: 12) - } - } - }, - } - } - }; } [BackgroundDependencyLoader] private void load() { + Padding = new MarginPadding(5); + + InternalChild = tabletContainer = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Masking = true, + CornerRadius = 5, + BorderThickness = 2, + BorderColour = Color4.Black, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + }, + usableAreaContainer = new Container + { + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Yellow, + }, + new OsuSpriteText + { + Text = "usable area", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = Color4.Black, + Font = OsuFont.Default.With(size: 12) + } + } + }, + } + }; + areaOffset.BindTo(handler.AreaOffset); areaOffset.BindValueChanged(val => { @@ -92,8 +89,22 @@ namespace osu.Game.Overlays.Settings.Sections.Input ((IBindable)tabletSize).BindTo(handler.TabletSize); tabletSize.BindValueChanged(val => { - tabletContainer.ResizeTo(new Vector2(tabletSize.Value.Width, tabletSize.Value.Height), 100, Easing.OutQuint); - }, true); + tabletContainer.Size = new Vector2(val.NewValue.Width, val.NewValue.Height); + }); + } + + protected override void Update() + { + base.Update(); + + var size = tabletSize.Value; + + float fitX = size.Width / DrawWidth; + float fitY = size.Height / DrawHeight; + + float adjust = MathF.Max(fitX, fitY); + + tabletContainer.Scale = new Vector2(1 / adjust); } } } From 926e40925ef0ba15bb6a3994f71e291dfd7196c5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Mar 2021 15:57:06 +0900 Subject: [PATCH 285/390] Add exclude rule to fix dynamic compilations issues with settings sections --- osu.Game/Overlays/Settings/SettingsSubsection.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/Settings/SettingsSubsection.cs b/osu.Game/Overlays/Settings/SettingsSubsection.cs index 1b82d973e9..6abf6283b9 100644 --- a/osu.Game/Overlays/Settings/SettingsSubsection.cs +++ b/osu.Game/Overlays/Settings/SettingsSubsection.cs @@ -8,10 +8,12 @@ using osu.Game.Graphics.Sprites; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Testing; using osu.Game.Graphics; namespace osu.Game.Overlays.Settings { + [ExcludeFromDynamicCompile] public abstract class SettingsSubsection : FillFlowContainer, IHasFilterableChildren { protected override Container Content => FlowContent; From 0a6525baee8afa45b44432e8acc0e13fb0ca1be7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Mar 2021 15:57:29 +0900 Subject: [PATCH 286/390] Fix slider bars reloading each time the tablet size is changed --- .../Settings/Sections/Input/TabletSettings.cs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index 5df9c879eb..ac4a42e984 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -4,10 +4,8 @@ using System.Drawing; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Input.Handlers.Tablet; -using osu.Game.Configuration; namespace osu.Game.Overlays.Settings.Sections.Input { @@ -33,9 +31,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input } [BackgroundDependencyLoader] - private void load(OsuConfigManager osuConfig, FrameworkConfigManager config) + private void load() { - // TODO: this should all eventually be replaced with a control that handles BindableSize. areaOffset.BindTo(tabletHandler.AreaOffset); areaOffset.BindValueChanged(val => { @@ -59,6 +56,9 @@ namespace osu.Game.Overlays.Settings.Sections.Input ((IBindable)tabletSize).BindTo(tabletHandler.TabletSize); tabletSize.BindValueChanged(val => { + if (tabletSize.Value == System.Drawing.Size.Empty) + return; + // todo: these should propagate from a TabletChanged event or similar. offsetX.MaxValue = val.NewValue.Width; sizeX.Default = sizeX.MaxValue = val.NewValue.Width; @@ -72,11 +72,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input private void updateDisplay() { - if (tabletSize.Value == System.Drawing.Size.Empty) - { - Clear(); + if (Children.Count > 0) return; - } Children = new Drawable[] { @@ -103,7 +100,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input new TabletAreaSelection(tabletHandler) { RelativeSizeAxes = Axes.X, - Height = 100, + Height = 300, } }; } From 94f184d113200bdca2c7c89496948a9e2e3d9990 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Mar 2021 16:02:39 +0900 Subject: [PATCH 287/390] Add feedback when area extends beyond tablet size --- .../Sections/Input/TabletAreaSelection.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs index 775aceb5f9..77b16a970d 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs @@ -59,7 +59,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input new Box { RelativeSizeAxes = Axes.Both, - Colour = Color4.Yellow, + Colour = Color4.White, }, new OsuSpriteText { @@ -78,21 +78,37 @@ namespace osu.Game.Overlays.Settings.Sections.Input areaOffset.BindValueChanged(val => { usableAreaContainer.MoveTo(new Vector2(val.NewValue.Width, val.NewValue.Height), 100, Easing.OutQuint); + checkBounds(); }, true); areaSize.BindTo(handler.AreaSize); areaSize.BindValueChanged(val => { usableAreaContainer.ResizeTo(new Vector2(val.NewValue.Width, val.NewValue.Height), 100, Easing.OutQuint); + checkBounds(); }, true); ((IBindable)tabletSize).BindTo(handler.TabletSize); tabletSize.BindValueChanged(val => { tabletContainer.Size = new Vector2(val.NewValue.Width, val.NewValue.Height); + checkBounds(); }); } + [Resolved] + private OsuColour colour { get; set; } + + private void checkBounds() + { + Size areaExtent = areaOffset.Value + areaSize.Value; + + bool isWithinBounds = areaExtent.Width < tabletSize.Value.Width + && areaExtent.Height < tabletSize.Value.Height; + + usableAreaContainer.FadeColour(isWithinBounds ? colour.Blue : colour.RedLight, 100); + } + protected override void Update() { base.Update(); From 464702182d6064023064720824403e8277bd19a1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Mar 2021 16:23:46 +0900 Subject: [PATCH 288/390] Consume device name --- .../Visual/Settings/TestSceneTabletSettings.cs | 1 + .../Settings/Sections/Input/TabletAreaSelection.cs | 14 +++++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs index 6455f51ab9..1c9cd6c2ba 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs @@ -46,6 +46,7 @@ namespace osu.Game.Tests.Visual.Settings public BindableSize AreaOffset { get; } = new BindableSize(); public BindableSize AreaSize { get; } = new BindableSize(); public IBindable TabletSize => tabletSize; + public string DeviceName => "test tablet T-421"; public BindableBool Enabled { get; } = new BindableBool(true); public void SetTabletSize(Size size) => tabletSize.Value = size; diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs index 77b16a970d..54a14cd822 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs @@ -27,6 +27,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input private readonly Bindable areaSize = new BindableSize(); private readonly Bindable tabletSize = new BindableSize(); + private OsuSpriteText tabletName; + public TabletAreaSelection(ITabletHandler handler) { this.handler = handler; @@ -44,13 +46,13 @@ namespace osu.Game.Overlays.Settings.Sections.Input Masking = true, CornerRadius = 5, BorderThickness = 2, - BorderColour = Color4.Black, + BorderColour = colour.Gray3, Children = new Drawable[] { new Box { RelativeSizeAxes = Axes.Both, - Colour = Color4.White, + Colour = colour.Gray1, }, usableAreaContainer = new Container { @@ -59,7 +61,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input new Box { RelativeSizeAxes = Axes.Both, - Colour = Color4.White, + Alpha = 0.6f, }, new OsuSpriteText { @@ -71,6 +73,11 @@ namespace osu.Game.Overlays.Settings.Sections.Input } } }, + tabletName = new OsuSpriteText + { + Padding = new MarginPadding(3), + Font = OsuFont.Default.With(size: 8) + }, } }; @@ -92,6 +99,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input tabletSize.BindValueChanged(val => { tabletContainer.Size = new Vector2(val.NewValue.Width, val.NewValue.Height); + tabletName.Text = handler.DeviceName; checkBounds(); }); } From 382109c7a221f83b02b00a70e002551e7244157f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Mar 2021 16:24:08 +0900 Subject: [PATCH 289/390] Make test scene feel more like settings (width-wise) --- .../Visual/Settings/TestSceneTabletSettings.cs | 9 ++++++++- osu.Game/Overlays/SettingsPanel.cs | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs index 1c9cd6c2ba..3d65db9420 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs @@ -8,6 +8,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input.Handlers.Tablet; using osu.Framework.Platform; +using osu.Game.Overlays; using osu.Game.Overlays.Settings.Sections.Input; namespace osu.Game.Tests.Visual.Settings @@ -30,7 +31,13 @@ namespace osu.Game.Tests.Visual.Settings AddRange(new Drawable[] { - new TabletSettings(tabletHandler), + new TabletSettings(tabletHandler) + { + RelativeSizeAxes = Axes.None, + Width = SettingsPanel.WIDTH, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + } }); AddStep("Test with wide tablet", () => tabletHandler.SetTabletSize(new Size(160, 100))); diff --git a/osu.Game/Overlays/SettingsPanel.cs b/osu.Game/Overlays/SettingsPanel.cs index f1270f750e..8f3274b2b5 100644 --- a/osu.Game/Overlays/SettingsPanel.cs +++ b/osu.Game/Overlays/SettingsPanel.cs @@ -27,7 +27,7 @@ namespace osu.Game.Overlays private const float sidebar_width = Sidebar.DEFAULT_WIDTH; - protected const float WIDTH = 400; + public const float WIDTH = 400; protected Container ContentContainer; From 2dc2cb04c317de94a762dbdb74dfede0a91b33ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Mar 2021 16:24:20 +0900 Subject: [PATCH 290/390] Fix bounds check becoming false when using full area --- .../Overlays/Settings/Sections/Input/TabletAreaSelection.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs index 54a14cd822..6a3cc46e2b 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs @@ -111,8 +111,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input { Size areaExtent = areaOffset.Value + areaSize.Value; - bool isWithinBounds = areaExtent.Width < tabletSize.Value.Width - && areaExtent.Height < tabletSize.Value.Height; + bool isWithinBounds = areaExtent.Width <= tabletSize.Value.Width + && areaExtent.Height <= tabletSize.Value.Height; usableAreaContainer.FadeColour(isWithinBounds ? colour.Blue : colour.RedLight, 100); } From 9b70f0ee1fca97f9d8d686c131dd66f5a8be20ef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Mar 2021 16:24:51 +0900 Subject: [PATCH 291/390] Tidy up visual appearance of settings and add a reset button --- .../Settings/Sections/Input/TabletSettings.cs | 57 ++++++++++++------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index ac4a42e984..ca0a0349ab 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -66,6 +66,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input offsetY.MaxValue = val.NewValue.Height; sizeY.Default = sizeY.MaxValue = val.NewValue.Height; + areaSize.Default = new Size(sizeX.Default, sizeY.Default); + updateDisplay(); }, true); } @@ -77,31 +79,44 @@ namespace osu.Game.Overlays.Settings.Sections.Input Children = new Drawable[] { - new SettingsSlider - { - LabelText = "Offset X", - Current = offsetX - }, - new SettingsSlider - { - LabelText = "Offset Y", - Current = offsetY - }, - new SettingsSlider - { - LabelText = "Size X", - Current = sizeX - }, - new SettingsSlider - { - LabelText = "Size Y", - Current = sizeY - }, new TabletAreaSelection(tabletHandler) { RelativeSizeAxes = Axes.X, Height = 300, - } + }, + new SettingsButton + { + Text = "Reset to full area", + Action = () => + { + areaOffset.SetDefault(); + areaSize.SetDefault(); + }, + }, + new SettingsCheckbox + { + LabelText = "Lock aspect ratio", + }, + new SettingsSlider + { + LabelText = "X Offset", + Current = offsetX + }, + new SettingsSlider + { + LabelText = "Y Offset", + Current = offsetY + }, + new SettingsSlider + { + LabelText = "Width", + Current = sizeX + }, + new SettingsSlider + { + LabelText = "Height", + Current = sizeY + }, }; } } From 43359553c1a3f6326ce13c0080440667ff13548e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Mar 2021 17:04:22 +0900 Subject: [PATCH 292/390] Add aspect ratio display and limiting --- .../Settings/Sections/Input/TabletSettings.cs | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index ca0a0349ab..5d85ecf138 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Drawing; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -23,6 +24,15 @@ namespace osu.Game.Overlays.Settings.Sections.Input private readonly BindableNumber sizeX = new BindableNumber { MinValue = 0 }; private readonly BindableNumber sizeY = new BindableNumber { MinValue = 0 }; + private SettingsButton aspectResetButton; + + private readonly BindableNumber aspectRatio = new BindableFloat(1) + { + MinValue = 0.5f, + MaxValue = 2, + Precision = 0.01f, + }; + protected override string Header => "Tablet"; public TabletSettings(ITabletHandler tabletHandler) @@ -48,6 +58,33 @@ namespace osu.Game.Overlays.Settings.Sections.Input { sizeX.Value = val.NewValue.Width; sizeY.Value = val.NewValue.Height; + + float proposedAspectRatio = (float)sizeX.Value / sizeY.Value; + + aspectRatio.Value = proposedAspectRatio; + + if (proposedAspectRatio < aspectRatio.MinValue || proposedAspectRatio > aspectRatio.MaxValue) + { + // apply aspect ratio restrictions to keep things in a usable state. + + // correction is always going to be below 1. + float correction = proposedAspectRatio > aspectRatio.Value + ? aspectRatio.Value / proposedAspectRatio + : proposedAspectRatio / aspectRatio.Value; + + if (val.NewValue.Width != val.OldValue.Width) + { + if (val.NewValue.Width > val.OldValue.Width) + correction = 1 / correction; + areaSize.Value = new Size(areaSize.Value.Width, (int)(val.NewValue.Height * correction)); + } + else + { + if (val.NewValue.Height > val.OldValue.Height) + correction = 1 / correction; + areaSize.Value = new Size((int)(val.NewValue.Width * correction), areaSize.Value.Height); + } + } }, true); sizeX.BindValueChanged(val => areaSize.Value = new Size(val.NewValue, areaSize.Value.Height)); @@ -97,6 +134,15 @@ namespace osu.Game.Overlays.Settings.Sections.Input { LabelText = "Lock aspect ratio", }, + aspectResetButton = new SettingsButton + { + Text = "Take aspect ratio from screen size", + }, + new SettingsSlider + { + LabelText = "Aspect Ratio", + Current = aspectRatio + }, new SettingsSlider { LabelText = "X Offset", From e3bed4c97dff26b9af05609fa100c2d748956b3c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Mar 2021 17:57:50 +0900 Subject: [PATCH 293/390] Simplify aspect ratio application, add window conforming and direct adjustment --- .../Settings/Sections/Input/TabletSettings.cs | 120 ++++++++++++------ 1 file changed, 81 insertions(+), 39 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index 5d85ecf138..9fa74eda18 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -1,12 +1,14 @@ // 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.ComponentModel; using System.Drawing; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input.Handlers.Tablet; +using osu.Framework.Platform; +using osu.Framework.Threading; namespace osu.Game.Overlays.Settings.Sections.Input { @@ -21,18 +23,28 @@ namespace osu.Game.Overlays.Settings.Sections.Input private readonly BindableNumber offsetX = new BindableNumber { MinValue = 0 }; private readonly BindableNumber offsetY = new BindableNumber { MinValue = 0 }; - private readonly BindableNumber sizeX = new BindableNumber { MinValue = 0 }; - private readonly BindableNumber sizeY = new BindableNumber { MinValue = 0 }; + private readonly BindableNumber sizeX = new BindableNumber { MinValue = 10 }; + private readonly BindableNumber sizeY = new BindableNumber { MinValue = 10 }; - private SettingsButton aspectResetButton; + [Resolved] + private GameHost host { get; set; } + + /// + /// Based on the longest available smartphone. + /// + private const float largest_feasible_aspect_ratio = 20f / 9; private readonly BindableNumber aspectRatio = new BindableFloat(1) { - MinValue = 0.5f, - MaxValue = 2, + MinValue = 1 / largest_feasible_aspect_ratio, + MaxValue = largest_feasible_aspect_ratio, Precision = 0.01f, }; + private readonly BindableBool aspectLock = new BindableBool(); + + private ScheduledDelegate aspectRatioApplication; + protected override string Header => "Tablet"; public TabletSettings(ITabletHandler tabletHandler) @@ -59,37 +71,18 @@ namespace osu.Game.Overlays.Settings.Sections.Input sizeX.Value = val.NewValue.Width; sizeY.Value = val.NewValue.Height; - float proposedAspectRatio = (float)sizeX.Value / sizeY.Value; - - aspectRatio.Value = proposedAspectRatio; - - if (proposedAspectRatio < aspectRatio.MinValue || proposedAspectRatio > aspectRatio.MaxValue) - { - // apply aspect ratio restrictions to keep things in a usable state. - - // correction is always going to be below 1. - float correction = proposedAspectRatio > aspectRatio.Value - ? aspectRatio.Value / proposedAspectRatio - : proposedAspectRatio / aspectRatio.Value; - - if (val.NewValue.Width != val.OldValue.Width) - { - if (val.NewValue.Width > val.OldValue.Width) - correction = 1 / correction; - areaSize.Value = new Size(areaSize.Value.Width, (int)(val.NewValue.Height * correction)); - } - else - { - if (val.NewValue.Height > val.OldValue.Height) - correction = 1 / correction; - areaSize.Value = new Size((int)(val.NewValue.Width * correction), areaSize.Value.Height); - } - } + aspectRatioApplication?.Cancel(); + aspectRatioApplication = Schedule(() => applyAspectRatio(val)); }, true); sizeX.BindValueChanged(val => areaSize.Value = new Size(val.NewValue, areaSize.Value.Height)); sizeY.BindValueChanged(val => areaSize.Value = new Size(areaSize.Value.Width, val.NewValue)); + aspectRatio.BindValueChanged(aspect => + { + forceAspectRatio(aspect.NewValue); + }); + ((IBindable)tabletSize).BindTo(tabletHandler.TabletSize); tabletSize.BindValueChanged(val => { @@ -109,6 +102,33 @@ namespace osu.Game.Overlays.Settings.Sections.Input }, true); } + private void applyAspectRatio(ValueChangedEvent sizeChanged) + { + float proposedAspectRatio = (float)sizeX.Value / sizeY.Value; + + if (!aspectLock.Value) + { + aspectRatio.Value = proposedAspectRatio; + + // aspect ratio was in a valid range. + if (proposedAspectRatio >= aspectRatio.MinValue && proposedAspectRatio <= aspectRatio.MaxValue) + return; + } + + if (sizeChanged.NewValue.Width != sizeChanged.OldValue.Width) + { + areaSize.Value = new Size(areaSize.Value.Width, (int)(areaSize.Value.Width / aspectRatio.Value)); + } + else + { + areaSize.Value = new Size((int)(areaSize.Value.Height * aspectRatio.Value), areaSize.Value.Height); + } + + // cancel any event which may have fired while updating variables as a result of aspect ratio limitations. + // this avoids a potential feedback loop. + aspectRatioApplication?.Cancel(); + } + private void updateDisplay() { if (Children.Count > 0) @@ -121,22 +141,24 @@ namespace osu.Game.Overlays.Settings.Sections.Input RelativeSizeAxes = Axes.X, Height = 300, }, - new SettingsButton + new DangerousSettingsButton { Text = "Reset to full area", Action = () => { + aspectLock.Value = false; + areaOffset.SetDefault(); areaSize.SetDefault(); }, }, - new SettingsCheckbox + new SettingsButton { - LabelText = "Lock aspect ratio", - }, - aspectResetButton = new SettingsButton - { - Text = "Take aspect ratio from screen size", + Text = "Conform to current game aspect ratio", + Action = () => + { + forceAspectRatio((float)host.Window.ClientSize.Width / host.Window.ClientSize.Height); + } }, new SettingsSlider { @@ -153,6 +175,11 @@ namespace osu.Game.Overlays.Settings.Sections.Input LabelText = "Y Offset", Current = offsetY }, + new SettingsCheckbox + { + LabelText = "Lock aspect ratio", + Current = aspectLock + }, new SettingsSlider { LabelText = "Width", @@ -165,5 +192,20 @@ namespace osu.Game.Overlays.Settings.Sections.Input }, }; } + + private void forceAspectRatio(float aspectRatio) + { + aspectLock.Value = false; + + int proposedHeight = (int)(sizeX.Value / aspectRatio); + + if (proposedHeight < sizeY.MaxValue) + sizeY.Value = proposedHeight; + else + sizeX.Value = (int)(sizeY.Value * aspectRatio); + + aspectRatioApplication?.Cancel(); + aspectLock.Value = true; + } } } From 932745e5c4049e8cf15074f5ecca437364d18fd9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Mar 2021 18:14:29 +0900 Subject: [PATCH 294/390] Fix remaining feedback loops --- .../Settings/Sections/Input/TabletSettings.cs | 51 ++++++++++++------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index 9fa74eda18..e94df7dc1b 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.ComponentModel; using System.Drawing; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -80,7 +79,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input aspectRatio.BindValueChanged(aspect => { - forceAspectRatio(aspect.NewValue); + aspectRatioApplication?.Cancel(); + aspectRatioApplication = Schedule(() => forceAspectRatio(aspect.NewValue)); }); ((IBindable)tabletSize).BindTo(tabletHandler.TabletSize); @@ -102,31 +102,44 @@ namespace osu.Game.Overlays.Settings.Sections.Input }, true); } + private float curentAspectRatio => (float)sizeX.Value / sizeY.Value; + private void applyAspectRatio(ValueChangedEvent sizeChanged) { - float proposedAspectRatio = (float)sizeX.Value / sizeY.Value; + float proposedAspectRatio = curentAspectRatio; - if (!aspectLock.Value) + try { - aspectRatio.Value = proposedAspectRatio; + if (!aspectLock.Value) + { + // aspect ratio was in a valid range. + if (proposedAspectRatio >= aspectRatio.MinValue && proposedAspectRatio <= aspectRatio.MaxValue) + { + updateAspectRatio(); + return; + } + } - // aspect ratio was in a valid range. - if (proposedAspectRatio >= aspectRatio.MinValue && proposedAspectRatio <= aspectRatio.MaxValue) - return; + if (sizeChanged.NewValue.Width != sizeChanged.OldValue.Width) + { + areaSize.Value = new Size(areaSize.Value.Width, (int)(areaSize.Value.Width / aspectRatio.Value)); + } + else + { + areaSize.Value = new Size((int)(areaSize.Value.Height * aspectRatio.Value), areaSize.Value.Height); + } } - - if (sizeChanged.NewValue.Width != sizeChanged.OldValue.Width) + finally { - areaSize.Value = new Size(areaSize.Value.Width, (int)(areaSize.Value.Width / aspectRatio.Value)); - } - else - { - areaSize.Value = new Size((int)(areaSize.Value.Height * aspectRatio.Value), areaSize.Value.Height); + // cancel any event which may have fired while updating variables as a result of aspect ratio limitations. + // this avoids a potential feedback loop. + aspectRatioApplication?.Cancel(); } + } - // cancel any event which may have fired while updating variables as a result of aspect ratio limitations. - // this avoids a potential feedback loop. - aspectRatioApplication?.Cancel(); + private void updateAspectRatio() + { + aspectRatio.Value = curentAspectRatio; } private void updateDisplay() @@ -204,6 +217,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input else sizeX.Value = (int)(sizeY.Value * aspectRatio); + updateAspectRatio(); + aspectRatioApplication?.Cancel(); aspectLock.Value = true; } From bba25a0182660443283b91c01356742cae676e56 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Mar 2021 18:40:21 +0900 Subject: [PATCH 295/390] Tidy up draw hierarchy and bindable logic --- .../Sections/Input/TabletAreaSelection.cs | 7 +- .../Settings/Sections/Input/TabletSettings.cs | 184 +++++++++--------- 2 files changed, 95 insertions(+), 96 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs index 6a3cc46e2b..3a278820f0 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs @@ -25,7 +25,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input private readonly Bindable areaOffset = new BindableSize(); private readonly Bindable areaSize = new BindableSize(); - private readonly Bindable tabletSize = new BindableSize(); + private readonly IBindable tabletSize = new BindableSize(); private OsuSpriteText tabletName; @@ -95,7 +95,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input checkBounds(); }, true); - ((IBindable)tabletSize).BindTo(handler.TabletSize); + tabletSize.BindTo(handler.TabletSize); tabletSize.BindValueChanged(val => { tabletContainer.Size = new Vector2(val.NewValue.Width, val.NewValue.Height); @@ -123,6 +123,9 @@ namespace osu.Game.Overlays.Settings.Sections.Input var size = tabletSize.Value; + if (size == System.Drawing.Size.Empty) + return; + float fitX = size.Width / DrawWidth; float fitY = size.Height / DrawHeight; diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index e94df7dc1b..3f8723025f 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -17,7 +17,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input private readonly BindableSize areaOffset = new BindableSize(); private readonly BindableSize areaSize = new BindableSize(); - private readonly BindableSize tabletSize = new BindableSize(); + private readonly IBindable tabletSize = new BindableSize(); private readonly BindableNumber offsetX = new BindableNumber { MinValue = 0 }; private readonly BindableNumber offsetY = new BindableNumber { MinValue = 0 }; @@ -54,99 +54,6 @@ namespace osu.Game.Overlays.Settings.Sections.Input [BackgroundDependencyLoader] private void load() { - areaOffset.BindTo(tabletHandler.AreaOffset); - areaOffset.BindValueChanged(val => - { - offsetX.Value = val.NewValue.Width; - offsetY.Value = val.NewValue.Height; - }, true); - - offsetX.BindValueChanged(val => areaOffset.Value = new Size(val.NewValue, areaOffset.Value.Height)); - offsetY.BindValueChanged(val => areaOffset.Value = new Size(areaOffset.Value.Width, val.NewValue)); - - areaSize.BindTo(tabletHandler.AreaSize); - areaSize.BindValueChanged(val => - { - sizeX.Value = val.NewValue.Width; - sizeY.Value = val.NewValue.Height; - - aspectRatioApplication?.Cancel(); - aspectRatioApplication = Schedule(() => applyAspectRatio(val)); - }, true); - - sizeX.BindValueChanged(val => areaSize.Value = new Size(val.NewValue, areaSize.Value.Height)); - sizeY.BindValueChanged(val => areaSize.Value = new Size(areaSize.Value.Width, val.NewValue)); - - aspectRatio.BindValueChanged(aspect => - { - aspectRatioApplication?.Cancel(); - aspectRatioApplication = Schedule(() => forceAspectRatio(aspect.NewValue)); - }); - - ((IBindable)tabletSize).BindTo(tabletHandler.TabletSize); - tabletSize.BindValueChanged(val => - { - if (tabletSize.Value == System.Drawing.Size.Empty) - return; - - // todo: these should propagate from a TabletChanged event or similar. - offsetX.MaxValue = val.NewValue.Width; - sizeX.Default = sizeX.MaxValue = val.NewValue.Width; - - offsetY.MaxValue = val.NewValue.Height; - sizeY.Default = sizeY.MaxValue = val.NewValue.Height; - - areaSize.Default = new Size(sizeX.Default, sizeY.Default); - - updateDisplay(); - }, true); - } - - private float curentAspectRatio => (float)sizeX.Value / sizeY.Value; - - private void applyAspectRatio(ValueChangedEvent sizeChanged) - { - float proposedAspectRatio = curentAspectRatio; - - try - { - if (!aspectLock.Value) - { - // aspect ratio was in a valid range. - if (proposedAspectRatio >= aspectRatio.MinValue && proposedAspectRatio <= aspectRatio.MaxValue) - { - updateAspectRatio(); - return; - } - } - - if (sizeChanged.NewValue.Width != sizeChanged.OldValue.Width) - { - areaSize.Value = new Size(areaSize.Value.Width, (int)(areaSize.Value.Width / aspectRatio.Value)); - } - else - { - areaSize.Value = new Size((int)(areaSize.Value.Height * aspectRatio.Value), areaSize.Value.Height); - } - } - finally - { - // cancel any event which may have fired while updating variables as a result of aspect ratio limitations. - // this avoids a potential feedback loop. - aspectRatioApplication?.Cancel(); - } - } - - private void updateAspectRatio() - { - aspectRatio.Value = curentAspectRatio; - } - - private void updateDisplay() - { - if (Children.Count > 0) - return; - Children = new Drawable[] { new TabletAreaSelection(tabletHandler) @@ -204,6 +111,91 @@ namespace osu.Game.Overlays.Settings.Sections.Input Current = sizeY }, }; + + areaOffset.BindTo(tabletHandler.AreaOffset); + areaOffset.BindValueChanged(val => + { + offsetX.Value = val.NewValue.Width; + offsetY.Value = val.NewValue.Height; + }, true); + + offsetX.BindValueChanged(val => areaOffset.Value = new Size(val.NewValue, areaOffset.Value.Height)); + offsetY.BindValueChanged(val => areaOffset.Value = new Size(areaOffset.Value.Width, val.NewValue)); + + areaSize.BindTo(tabletHandler.AreaSize); + areaSize.BindValueChanged(val => + { + sizeX.Value = val.NewValue.Width; + sizeY.Value = val.NewValue.Height; + }, true); + + sizeX.BindValueChanged(val => + { + areaSize.Value = new Size(val.NewValue, areaSize.Value.Height); + + aspectRatioApplication?.Cancel(); + aspectRatioApplication = Schedule(() => applyAspectRatio(sizeX)); + }); + + sizeY.BindValueChanged(val => + { + areaSize.Value = new Size(areaSize.Value.Width, val.NewValue); + + aspectRatioApplication?.Cancel(); + aspectRatioApplication = Schedule(() => applyAspectRatio(sizeY)); + }); + + aspectRatio.BindValueChanged(aspect => + { + aspectRatioApplication?.Cancel(); + aspectRatioApplication = Schedule(() => forceAspectRatio(aspect.NewValue)); + }); + + tabletSize.BindTo(tabletHandler.TabletSize); + tabletSize.BindValueChanged(val => + { + if (tabletSize.Value == System.Drawing.Size.Empty) + return; + + // todo: these should propagate from a TabletChanged event or similar. + offsetX.MaxValue = val.NewValue.Width; + sizeX.Default = sizeX.MaxValue = val.NewValue.Width; + + offsetY.MaxValue = val.NewValue.Height; + sizeY.Default = sizeY.MaxValue = val.NewValue.Height; + + areaSize.Default = new Size(sizeX.Default, sizeY.Default); + }, true); + } + + private void applyAspectRatio(BindableNumber sizeChanged) + { + try + { + if (!aspectLock.Value) + { + float proposedAspectRatio = curentAspectRatio; + + if (proposedAspectRatio >= aspectRatio.MinValue && proposedAspectRatio <= aspectRatio.MaxValue) + { + // aspect ratio was in a valid range. + updateAspectRatio(); + return; + } + } + + // if lock is applied (or the specified values were out of range) aim to adjust the axis the user was not adjusting to conform. + if (sizeChanged == sizeX) + sizeY.Value = (int)(areaSize.Value.Width / aspectRatio.Value); + else + sizeX.Value = (int)(areaSize.Value.Height * aspectRatio.Value); + } + finally + { + // cancel any event which may have fired while updating variables as a result of aspect ratio limitations. + // this avoids a potential feedback loop. + aspectRatioApplication?.Cancel(); + } } private void forceAspectRatio(float aspectRatio) @@ -222,5 +214,9 @@ namespace osu.Game.Overlays.Settings.Sections.Input aspectRatioApplication?.Cancel(); aspectLock.Value = true; } + + private void updateAspectRatio() => aspectRatio.Value = curentAspectRatio; + + private float curentAspectRatio => (float)sizeX.Value / sizeY.Value; } } From a8e319a320a1aae74a29432b0794a16ce869330a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Mar 2021 18:51:43 +0900 Subject: [PATCH 296/390] Remove min/max from test scene to fix weirdness when switching test sizings --- osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs index 3d65db9420..aaf2f13953 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs @@ -21,12 +21,7 @@ namespace osu.Game.Tests.Visual.Settings { var tabletHandler = new TestTabletHandler(); - tabletHandler.AreaOffset.MinValue = new Size(0, 0); - tabletHandler.AreaOffset.MaxValue = new Size(160, 100); tabletHandler.AreaOffset.Value = new Size(10, 10); - - tabletHandler.AreaSize.MinValue = new Size(10, 10); - tabletHandler.AreaSize.MaxValue = new Size(160, 100); tabletHandler.AreaSize.Value = new Size(100, 80); AddRange(new Drawable[] From 9a6a0f3df5e8a2d4a67f4bc567ba3e5db8a76a2c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Mar 2021 23:47:08 +0900 Subject: [PATCH 297/390] Add test coverage and better UI handling of no tablet connected scenario --- .../Settings/TestSceneTabletSettings.cs | 10 +- .../Settings/Sections/Input/TabletSettings.cs | 130 +++++++++++------- 2 files changed, 89 insertions(+), 51 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs index aaf2f13953..2baeadddc0 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs @@ -8,6 +8,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input.Handlers.Tablet; using osu.Framework.Platform; +using osu.Framework.Utils; using osu.Game.Overlays; using osu.Game.Overlays.Settings.Sections.Input; @@ -39,6 +40,7 @@ namespace osu.Game.Tests.Visual.Settings AddStep("Test with square tablet", () => tabletHandler.SetTabletSize(new Size(300, 300))); AddStep("Test with tall tablet", () => tabletHandler.SetTabletSize(new Size(100, 300))); AddStep("Test with very tall tablet", () => tabletHandler.SetTabletSize(new Size(100, 700))); + AddStep("Test no tablet present", () => tabletHandler.SetTabletSize(System.Drawing.Size.Empty)); } public class TestTabletHandler : ITabletHandler @@ -48,10 +50,14 @@ namespace osu.Game.Tests.Visual.Settings public BindableSize AreaOffset { get; } = new BindableSize(); public BindableSize AreaSize { get; } = new BindableSize(); public IBindable TabletSize => tabletSize; - public string DeviceName => "test tablet T-421"; + public string DeviceName { get; private set; } public BindableBool Enabled { get; } = new BindableBool(true); - public void SetTabletSize(Size size) => tabletSize.Value = size; + public void SetTabletSize(Size size) + { + DeviceName = size != System.Drawing.Size.Empty ? $"test tablet T-{RNG.Next(999):000}" : string.Empty; + tabletSize.Value = size; + } } } } diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index 3f8723025f..7da61cf192 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -5,9 +5,11 @@ using System.Drawing; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Input.Handlers.Tablet; using osu.Framework.Platform; using osu.Framework.Threading; +using osu.Game.Graphics.Sprites; namespace osu.Game.Overlays.Settings.Sections.Input { @@ -44,6 +46,10 @@ namespace osu.Game.Overlays.Settings.Sections.Input private ScheduledDelegate aspectRatioApplication; + private FillFlowContainer mainSettings; + + private OsuSpriteText noTabletMessage; + protected override string Header => "Tablet"; public TabletSettings(ITabletHandler tabletHandler) @@ -56,60 +62,77 @@ namespace osu.Game.Overlays.Settings.Sections.Input { Children = new Drawable[] { - new TabletAreaSelection(tabletHandler) + noTabletMessage = new OsuSpriteText { + Text = "No tablet detected!", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + mainSettings = new FillFlowContainer + { + Alpha = 0, RelativeSizeAxes = Axes.X, - Height = 300, - }, - new DangerousSettingsButton - { - Text = "Reset to full area", - Action = () => + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] { - aspectLock.Value = false; + new TabletAreaSelection(tabletHandler) + { + RelativeSizeAxes = Axes.X, + Height = 300, + Margin = new MarginPadding(10) + }, + new DangerousSettingsButton + { + Text = "Reset to full area", + Action = () => + { + aspectLock.Value = false; - areaOffset.SetDefault(); - areaSize.SetDefault(); - }, - }, - new SettingsButton - { - Text = "Conform to current game aspect ratio", - Action = () => - { - forceAspectRatio((float)host.Window.ClientSize.Width / host.Window.ClientSize.Height); + areaOffset.SetDefault(); + areaSize.SetDefault(); + }, + }, + new SettingsButton + { + Text = "Conform to current game aspect ratio", + Action = () => + { + forceAspectRatio((float)host.Window.ClientSize.Width / host.Window.ClientSize.Height); + } + }, + new SettingsSlider + { + LabelText = "Aspect Ratio", + Current = aspectRatio + }, + new SettingsSlider + { + LabelText = "X Offset", + Current = offsetX + }, + new SettingsSlider + { + LabelText = "Y Offset", + Current = offsetY + }, + new SettingsCheckbox + { + LabelText = "Lock aspect ratio", + Current = aspectLock + }, + new SettingsSlider + { + LabelText = "Width", + Current = sizeX + }, + new SettingsSlider + { + LabelText = "Height", + Current = sizeY + }, } }, - new SettingsSlider - { - LabelText = "Aspect Ratio", - Current = aspectRatio - }, - new SettingsSlider - { - LabelText = "X Offset", - Current = offsetX - }, - new SettingsSlider - { - LabelText = "Y Offset", - Current = offsetY - }, - new SettingsCheckbox - { - LabelText = "Lock aspect ratio", - Current = aspectLock - }, - new SettingsSlider - { - LabelText = "Width", - Current = sizeX - }, - new SettingsSlider - { - LabelText = "Height", - Current = sizeY - }, }; areaOffset.BindTo(tabletHandler.AreaOffset); @@ -154,8 +177,17 @@ namespace osu.Game.Overlays.Settings.Sections.Input tabletSize.BindTo(tabletHandler.TabletSize); tabletSize.BindValueChanged(val => { - if (tabletSize.Value == System.Drawing.Size.Empty) + bool tabletFound = tabletSize.Value != System.Drawing.Size.Empty; + + if (!tabletFound) + { + mainSettings.Hide(); + noTabletMessage.Show(); return; + } + + mainSettings.Show(); + noTabletMessage.Hide(); // todo: these should propagate from a TabletChanged event or similar. offsetX.MaxValue = val.NewValue.Width; From d422a6590036dc26a881fa2519da4d09111ac479 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Mar 2021 23:47:18 +0900 Subject: [PATCH 298/390] Fix initial tablet size not being initialised --- .../Settings/Sections/Input/TabletAreaSelection.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs index 3a278820f0..af144c8102 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs @@ -80,6 +80,11 @@ namespace osu.Game.Overlays.Settings.Sections.Input }, } }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); areaOffset.BindTo(handler.AreaOffset); areaOffset.BindValueChanged(val => @@ -101,7 +106,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input tabletContainer.Size = new Vector2(val.NewValue.Width, val.NewValue.Height); tabletName.Text = handler.DeviceName; checkBounds(); - }); + }, true); } [Resolved] From 9d0c8902a6e6dcc1edc369b8ebf768089f22c8a6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Mar 2021 23:57:05 +0900 Subject: [PATCH 299/390] Fix margins and spacing between sub flowed items --- .../Overlays/Settings/Sections/Input/TabletAreaSelection.cs | 6 +++--- osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs index af144c8102..3b1bae7cf0 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs @@ -32,13 +32,13 @@ namespace osu.Game.Overlays.Settings.Sections.Input public TabletAreaSelection(ITabletHandler handler) { this.handler = handler; + + Padding = new MarginPadding { Horizontal = SettingsPanel.CONTENT_MARGINS }; } [BackgroundDependencyLoader] private void load() { - Padding = new MarginPadding(5); - InternalChild = tabletContainer = new Container { Anchor = Anchor.Centre, @@ -131,7 +131,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input if (size == System.Drawing.Size.Empty) return; - float fitX = size.Width / DrawWidth; + float fitX = size.Width / (DrawWidth - Padding.Left - Padding.Right); float fitY = size.Height / DrawHeight; float adjust = MathF.Max(fitX, fitY); diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index 7da61cf192..b17cfced95 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -10,6 +10,7 @@ using osu.Framework.Input.Handlers.Tablet; using osu.Framework.Platform; using osu.Framework.Threading; using osu.Game.Graphics.Sprites; +using osuTK; namespace osu.Game.Overlays.Settings.Sections.Input { @@ -67,12 +68,14 @@ namespace osu.Game.Overlays.Settings.Sections.Input Text = "No tablet detected!", Anchor = Anchor.Centre, Origin = Anchor.Centre, + Padding = new MarginPadding { Horizontal = SettingsPanel.CONTENT_MARGINS } }, mainSettings = new FillFlowContainer { Alpha = 0, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, + Spacing = new Vector2(0, 8), Direction = FillDirection.Vertical, Children = new Drawable[] { @@ -80,7 +83,6 @@ namespace osu.Game.Overlays.Settings.Sections.Input { RelativeSizeAxes = Axes.X, Height = 300, - Margin = new MarginPadding(10) }, new DangerousSettingsButton { From 196f95ae545853c00e3da66ba9bc20e4871c75a2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Mar 2021 12:50:02 +0900 Subject: [PATCH 300/390] Update to use new bindables and centered area offset --- .../Settings/TestSceneTabletSettings.cs | 44 +++++++----- .../Sections/Input/TabletAreaSelection.cs | 49 ++++++++------ .../Settings/Sections/Input/TabletSettings.cs | 67 ++++++++++--------- 3 files changed, 89 insertions(+), 71 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs index 2baeadddc0..a7f6c8c0d3 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Drawing; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -11,6 +10,7 @@ using osu.Framework.Platform; using osu.Framework.Utils; using osu.Game.Overlays; using osu.Game.Overlays.Settings.Sections.Input; +using osuTK; namespace osu.Game.Tests.Visual.Settings { @@ -22,9 +22,6 @@ namespace osu.Game.Tests.Visual.Settings { var tabletHandler = new TestTabletHandler(); - tabletHandler.AreaOffset.Value = new Size(10, 10); - tabletHandler.AreaSize.Value = new Size(100, 80); - AddRange(new Drawable[] { new TabletSettings(tabletHandler) @@ -36,27 +33,40 @@ namespace osu.Game.Tests.Visual.Settings } }); - AddStep("Test with wide tablet", () => tabletHandler.SetTabletSize(new Size(160, 100))); - AddStep("Test with square tablet", () => tabletHandler.SetTabletSize(new Size(300, 300))); - AddStep("Test with tall tablet", () => tabletHandler.SetTabletSize(new Size(100, 300))); - AddStep("Test with very tall tablet", () => tabletHandler.SetTabletSize(new Size(100, 700))); - AddStep("Test no tablet present", () => tabletHandler.SetTabletSize(System.Drawing.Size.Empty)); + AddStep("Test with wide tablet", () => tabletHandler.SetTabletSize(new Vector2(160, 100))); + AddStep("Test with square tablet", () => tabletHandler.SetTabletSize(new Vector2(300, 300))); + AddStep("Test with tall tablet", () => tabletHandler.SetTabletSize(new Vector2(100, 300))); + AddStep("Test with very tall tablet", () => tabletHandler.SetTabletSize(new Vector2(100, 700))); + AddStep("Test no tablet present", () => tabletHandler.SetTabletSize(Vector2.Zero)); } public class TestTabletHandler : ITabletHandler { - private readonly Bindable tabletSize = new Bindable(); + public Bindable AreaOffset { get; } = new Bindable(); + public Bindable AreaSize { get; } = new Bindable(); + + public IBindable Tablet => tablet; + + private readonly Bindable tablet = new Bindable(); - public BindableSize AreaOffset { get; } = new BindableSize(); - public BindableSize AreaSize { get; } = new BindableSize(); - public IBindable TabletSize => tabletSize; - public string DeviceName { get; private set; } public BindableBool Enabled { get; } = new BindableBool(true); - public void SetTabletSize(Size size) + public void SetTabletSize(Vector2 size) { - DeviceName = size != System.Drawing.Size.Empty ? $"test tablet T-{RNG.Next(999):000}" : string.Empty; - tabletSize.Value = size; + tablet.Value = size != Vector2.Zero ? new TabletInfo($"test tablet T-{RNG.Next(999):000}", size) : null; + + AreaSize.Default = new Vector2(size.X, size.Y); + + // if it's clear the user has not configured the area, take the full area from the tablet that was just found. + if (AreaSize.Value == Vector2.Zero) + AreaSize.SetDefault(); + + AreaOffset.Default = new Vector2(size.X / 2, size.Y / 2); + + // likewise with the position, use the centre point if it has not been configured. + // it's safe to assume no user would set their centre point to 0,0 for now. + if (AreaOffset.Value == Vector2.Zero) + AreaOffset.SetDefault(); } } } diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs index 3b1bae7cf0..c0412fb99d 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Drawing; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -23,9 +22,10 @@ namespace osu.Game.Overlays.Settings.Sections.Input private Container tabletContainer; private Container usableAreaContainer; - private readonly Bindable areaOffset = new BindableSize(); - private readonly Bindable areaSize = new BindableSize(); - private readonly IBindable tabletSize = new BindableSize(); + private readonly Bindable areaOffset = new Bindable(); + private readonly Bindable areaSize = new Bindable(); + + private readonly IBindable tablet = new Bindable(); private OsuSpriteText tabletName; @@ -56,6 +56,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input }, usableAreaContainer = new Container { + Origin = Anchor.Centre, Children = new Drawable[] { new Box @@ -89,24 +90,27 @@ namespace osu.Game.Overlays.Settings.Sections.Input areaOffset.BindTo(handler.AreaOffset); areaOffset.BindValueChanged(val => { - usableAreaContainer.MoveTo(new Vector2(val.NewValue.Width, val.NewValue.Height), 100, Easing.OutQuint); - checkBounds(); + usableAreaContainer.MoveTo(val.NewValue, 100, Easing.OutQuint) + .OnComplete(_ => checkBounds()); // required as we are using SSDQ. }, true); areaSize.BindTo(handler.AreaSize); areaSize.BindValueChanged(val => { - usableAreaContainer.ResizeTo(new Vector2(val.NewValue.Width, val.NewValue.Height), 100, Easing.OutQuint); + usableAreaContainer.ResizeTo(val.NewValue, 100, Easing.OutQuint) + .OnComplete(_ => checkBounds()); // required as we are using SSDQ. + }, true); + + tablet.BindTo(handler.Tablet); + tablet.BindValueChanged(val => + { + tabletContainer.Size = val.NewValue?.Size ?? Vector2.Zero; + tabletName.Text = val.NewValue?.Name ?? string.Empty; checkBounds(); }, true); - tabletSize.BindTo(handler.TabletSize); - tabletSize.BindValueChanged(val => - { - tabletContainer.Size = new Vector2(val.NewValue.Width, val.NewValue.Height); - tabletName.Text = handler.DeviceName; - checkBounds(); - }, true); + // initial animation should be instant. + FinishTransforms(true); } [Resolved] @@ -114,10 +118,13 @@ namespace osu.Game.Overlays.Settings.Sections.Input private void checkBounds() { - Size areaExtent = areaOffset.Value + areaSize.Value; + if (tablet.Value == null) + return; - bool isWithinBounds = areaExtent.Width <= tabletSize.Value.Width - && areaExtent.Height <= tabletSize.Value.Height; + var usableSsdq = usableAreaContainer.ScreenSpaceDrawQuad; + + bool isWithinBounds = tabletContainer.ScreenSpaceDrawQuad.Contains(usableSsdq.TopLeft) && + tabletContainer.ScreenSpaceDrawQuad.Contains(usableSsdq.BottomRight); usableAreaContainer.FadeColour(isWithinBounds ? colour.Blue : colour.RedLight, 100); } @@ -126,13 +133,11 @@ namespace osu.Game.Overlays.Settings.Sections.Input { base.Update(); - var size = tabletSize.Value; - - if (size == System.Drawing.Size.Empty) + if (!(tablet.Value?.Size is Vector2 size)) return; - float fitX = size.Width / (DrawWidth - Padding.Left - Padding.Right); - float fitY = size.Height / DrawHeight; + float fitX = size.X / (DrawWidth - Padding.Left - Padding.Right); + float fitY = size.Y / DrawHeight; float adjust = MathF.Max(fitX, fitY); diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index b17cfced95..9f81391434 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Drawing; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -18,15 +17,15 @@ namespace osu.Game.Overlays.Settings.Sections.Input { private readonly ITabletHandler tabletHandler; - private readonly BindableSize areaOffset = new BindableSize(); - private readonly BindableSize areaSize = new BindableSize(); - private readonly IBindable tabletSize = new BindableSize(); + private readonly Bindable areaOffset = new Bindable(); + private readonly Bindable areaSize = new Bindable(); + private readonly IBindable tablet = new Bindable(); - private readonly BindableNumber offsetX = new BindableNumber { MinValue = 0 }; - private readonly BindableNumber offsetY = new BindableNumber { MinValue = 0 }; + private readonly BindableNumber offsetX = new BindableNumber { MinValue = 0 }; + private readonly BindableNumber offsetY = new BindableNumber { MinValue = 0 }; - private readonly BindableNumber sizeX = new BindableNumber { MinValue = 10 }; - private readonly BindableNumber sizeY = new BindableNumber { MinValue = 10 }; + private readonly BindableNumber sizeX = new BindableNumber { MinValue = 10 }; + private readonly BindableNumber sizeY = new BindableNumber { MinValue = 10 }; [Resolved] private GameHost host { get; set; } @@ -108,12 +107,12 @@ namespace osu.Game.Overlays.Settings.Sections.Input LabelText = "Aspect Ratio", Current = aspectRatio }, - new SettingsSlider + new SettingsSlider { LabelText = "X Offset", Current = offsetX }, - new SettingsSlider + new SettingsSlider { LabelText = "Y Offset", Current = offsetY @@ -123,12 +122,12 @@ namespace osu.Game.Overlays.Settings.Sections.Input LabelText = "Lock aspect ratio", Current = aspectLock }, - new SettingsSlider + new SettingsSlider { LabelText = "Width", Current = sizeX }, - new SettingsSlider + new SettingsSlider { LabelText = "Height", Current = sizeY @@ -140,23 +139,23 @@ namespace osu.Game.Overlays.Settings.Sections.Input areaOffset.BindTo(tabletHandler.AreaOffset); areaOffset.BindValueChanged(val => { - offsetX.Value = val.NewValue.Width; - offsetY.Value = val.NewValue.Height; + offsetX.Value = val.NewValue.X; + offsetY.Value = val.NewValue.Y; }, true); - offsetX.BindValueChanged(val => areaOffset.Value = new Size(val.NewValue, areaOffset.Value.Height)); - offsetY.BindValueChanged(val => areaOffset.Value = new Size(areaOffset.Value.Width, val.NewValue)); + offsetX.BindValueChanged(val => areaOffset.Value = new Vector2(val.NewValue, areaOffset.Value.Y)); + offsetY.BindValueChanged(val => areaOffset.Value = new Vector2(areaOffset.Value.X, val.NewValue)); areaSize.BindTo(tabletHandler.AreaSize); areaSize.BindValueChanged(val => { - sizeX.Value = val.NewValue.Width; - sizeY.Value = val.NewValue.Height; + sizeX.Value = val.NewValue.X; + sizeY.Value = val.NewValue.Y; }, true); sizeX.BindValueChanged(val => { - areaSize.Value = new Size(val.NewValue, areaSize.Value.Height); + areaSize.Value = new Vector2(val.NewValue, areaSize.Value.Y); aspectRatioApplication?.Cancel(); aspectRatioApplication = Schedule(() => applyAspectRatio(sizeX)); @@ -164,7 +163,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input sizeY.BindValueChanged(val => { - areaSize.Value = new Size(areaSize.Value.Width, val.NewValue); + areaSize.Value = new Vector2(areaSize.Value.X, val.NewValue); aspectRatioApplication?.Cancel(); aspectRatioApplication = Schedule(() => applyAspectRatio(sizeY)); @@ -176,10 +175,12 @@ namespace osu.Game.Overlays.Settings.Sections.Input aspectRatioApplication = Schedule(() => forceAspectRatio(aspect.NewValue)); }); - tabletSize.BindTo(tabletHandler.TabletSize); - tabletSize.BindValueChanged(val => + tablet.BindTo(tabletHandler.Tablet); + tablet.BindValueChanged(val => { - bool tabletFound = tabletSize.Value != System.Drawing.Size.Empty; + var tab = val.NewValue; + + bool tabletFound = tab != null; if (!tabletFound) { @@ -192,17 +193,19 @@ namespace osu.Game.Overlays.Settings.Sections.Input noTabletMessage.Hide(); // todo: these should propagate from a TabletChanged event or similar. - offsetX.MaxValue = val.NewValue.Width; - sizeX.Default = sizeX.MaxValue = val.NewValue.Width; + offsetX.MaxValue = tab.Size.X; + offsetX.Default = tab.Size.X / 2; + sizeX.Default = sizeX.MaxValue = tab.Size.X; - offsetY.MaxValue = val.NewValue.Height; - sizeY.Default = sizeY.MaxValue = val.NewValue.Height; + offsetY.MaxValue = tab.Size.Y; + offsetY.Default = tab.Size.Y / 2; + sizeY.Default = sizeY.MaxValue = tab.Size.Y; - areaSize.Default = new Size(sizeX.Default, sizeY.Default); + areaSize.Default = new Vector2(sizeX.Default, sizeY.Default); }, true); } - private void applyAspectRatio(BindableNumber sizeChanged) + private void applyAspectRatio(BindableNumber sizeChanged) { try { @@ -220,9 +223,9 @@ namespace osu.Game.Overlays.Settings.Sections.Input // if lock is applied (or the specified values were out of range) aim to adjust the axis the user was not adjusting to conform. if (sizeChanged == sizeX) - sizeY.Value = (int)(areaSize.Value.Width / aspectRatio.Value); + sizeY.Value = (int)(areaSize.Value.X / aspectRatio.Value); else - sizeX.Value = (int)(areaSize.Value.Height * aspectRatio.Value); + sizeX.Value = (int)(areaSize.Value.Y * aspectRatio.Value); } finally { @@ -251,6 +254,6 @@ namespace osu.Game.Overlays.Settings.Sections.Input private void updateAspectRatio() => aspectRatio.Value = curentAspectRatio; - private float curentAspectRatio => (float)sizeX.Value / sizeY.Value; + private float curentAspectRatio => sizeX.Value / sizeY.Value; } } From fb7d095e4a0a934664db0a5cbc7585eea55b46da Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Mar 2021 13:03:06 +0900 Subject: [PATCH 301/390] Show aspect ratio for current usable area --- .../Sections/Input/TabletAreaSelection.cs | 30 +++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs index c0412fb99d..ba219cfe7d 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs @@ -29,6 +29,9 @@ namespace osu.Game.Overlays.Settings.Sections.Input private OsuSpriteText tabletName; + private Box usableFill; + private OsuSpriteText usableAreaText; + public TabletAreaSelection(ITabletHandler handler) { this.handler = handler; @@ -59,17 +62,16 @@ namespace osu.Game.Overlays.Settings.Sections.Input Origin = Anchor.Centre, Children = new Drawable[] { - new Box + usableFill = new Box { RelativeSizeAxes = Axes.Both, Alpha = 0.6f, }, - new OsuSpriteText + usableAreaText = new OsuSpriteText { - Text = "usable area", Anchor = Anchor.Centre, Origin = Anchor.Centre, - Colour = Color4.Black, + Colour = Color4.White, Font = OsuFont.Default.With(size: 12) } } @@ -99,6 +101,12 @@ namespace osu.Game.Overlays.Settings.Sections.Input { usableAreaContainer.ResizeTo(val.NewValue, 100, Easing.OutQuint) .OnComplete(_ => checkBounds()); // required as we are using SSDQ. + + int x = (int)val.NewValue.X; + int y = (int)val.NewValue.Y; + int commonDivider = greatestCommonDivider(x, y); + + usableAreaText.Text = $"{(float)x / commonDivider}:{(float)y / commonDivider}"; }, true); tablet.BindTo(handler.Tablet); @@ -113,6 +121,18 @@ namespace osu.Game.Overlays.Settings.Sections.Input FinishTransforms(true); } + private static int greatestCommonDivider(int a, int b) + { + while (b != 0) + { + int remainder = a % b; + a = b; + b = remainder; + } + + return a; + } + [Resolved] private OsuColour colour { get; set; } @@ -126,7 +146,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input bool isWithinBounds = tabletContainer.ScreenSpaceDrawQuad.Contains(usableSsdq.TopLeft) && tabletContainer.ScreenSpaceDrawQuad.Contains(usableSsdq.BottomRight); - usableAreaContainer.FadeColour(isWithinBounds ? colour.Blue : colour.RedLight, 100); + usableFill.FadeColour(isWithinBounds ? colour.Blue : colour.RedLight, 100); } protected override void Update() From e8c20bdcb12a3a1f513a6fa63f72511630799afe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Mar 2021 13:05:43 +0900 Subject: [PATCH 302/390] Add centre crosshair --- .../Sections/Input/TabletAreaSelection.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs index ba219cfe7d..0a44b1a44d 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs @@ -67,12 +67,27 @@ namespace osu.Game.Overlays.Settings.Sections.Input RelativeSizeAxes = Axes.Both, Alpha = 0.6f, }, + new Box + { + Colour = Color4.White, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Height = 5, + }, + new Box + { + Colour = Color4.White, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 5, + }, usableAreaText = new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, Colour = Color4.White, - Font = OsuFont.Default.With(size: 12) + Font = OsuFont.Default.With(size: 12), + Y = 10 } } }, From 6285dcd1a1d9989dd9daeeb685102b48472e4ba6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Mar 2021 13:09:15 +0900 Subject: [PATCH 303/390] Add arbitrary value to fix FP contains check failures --- .../Overlays/Settings/Sections/Input/TabletAreaSelection.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs index 0a44b1a44d..df25668411 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs @@ -158,8 +158,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input var usableSsdq = usableAreaContainer.ScreenSpaceDrawQuad; - bool isWithinBounds = tabletContainer.ScreenSpaceDrawQuad.Contains(usableSsdq.TopLeft) && - tabletContainer.ScreenSpaceDrawQuad.Contains(usableSsdq.BottomRight); + bool isWithinBounds = tabletContainer.ScreenSpaceDrawQuad.Contains(usableSsdq.TopLeft + new Vector2(1)) && + tabletContainer.ScreenSpaceDrawQuad.Contains(usableSsdq.BottomRight - new Vector2(1)); usableFill.FadeColour(isWithinBounds ? colour.Blue : colour.RedLight, 100); } From c624aa939774cc3f783eb2a588d96c8afefc37f2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Mar 2021 13:23:23 +0900 Subject: [PATCH 304/390] Only update tablet values on commit --- osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index 9f81391434..b2d37e345f 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -104,16 +104,19 @@ namespace osu.Game.Overlays.Settings.Sections.Input }, new SettingsSlider { + TransferValueOnCommit = true, LabelText = "Aspect Ratio", Current = aspectRatio }, new SettingsSlider { + TransferValueOnCommit = true, LabelText = "X Offset", Current = offsetX }, new SettingsSlider { + TransferValueOnCommit = true, LabelText = "Y Offset", Current = offsetY }, @@ -124,11 +127,13 @@ namespace osu.Game.Overlays.Settings.Sections.Input }, new SettingsSlider { + TransferValueOnCommit = true, LabelText = "Width", Current = sizeX }, new SettingsSlider { + TransferValueOnCommit = true, LabelText = "Height", Current = sizeY }, From b1c4ac9f42842943d451f13b57da44231b34431d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Mar 2021 14:13:53 +0900 Subject: [PATCH 305/390] Remove local implementation of Vector2Converter This has been moved to framework in https://github.com/ppy/osu-framework/pull/4285. --- .../Converters/Vector2Converter.cs | 34 ------------------- .../IO/Serialization/IJsonSerializable.cs | 2 -- 2 files changed, 36 deletions(-) delete mode 100644 osu.Game/IO/Serialization/Converters/Vector2Converter.cs diff --git a/osu.Game/IO/Serialization/Converters/Vector2Converter.cs b/osu.Game/IO/Serialization/Converters/Vector2Converter.cs deleted file mode 100644 index 46447b607b..0000000000 --- a/osu.Game/IO/Serialization/Converters/Vector2Converter.cs +++ /dev/null @@ -1,34 +0,0 @@ -// 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 Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using osuTK; - -namespace osu.Game.IO.Serialization.Converters -{ - /// - /// A type of that serializes only the X and Y coordinates of a . - /// - public class Vector2Converter : JsonConverter - { - public override Vector2 ReadJson(JsonReader reader, Type objectType, Vector2 existingValue, bool hasExistingValue, JsonSerializer serializer) - { - var obj = JObject.Load(reader); - return new Vector2((float)obj["x"], (float)obj["y"]); - } - - public override void WriteJson(JsonWriter writer, Vector2 value, JsonSerializer serializer) - { - writer.WriteStartObject(); - - writer.WritePropertyName("x"); - writer.WriteValue(value.X); - writer.WritePropertyName("y"); - writer.WriteValue(value.Y); - - writer.WriteEndObject(); - } - } -} diff --git a/osu.Game/IO/Serialization/IJsonSerializable.cs b/osu.Game/IO/Serialization/IJsonSerializable.cs index ac95d47c4b..30430e6f7f 100644 --- a/osu.Game/IO/Serialization/IJsonSerializable.cs +++ b/osu.Game/IO/Serialization/IJsonSerializable.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using Newtonsoft.Json; -using osu.Game.IO.Serialization.Converters; namespace osu.Game.IO.Serialization { @@ -28,7 +27,6 @@ namespace osu.Game.IO.Serialization Formatting = Formatting.Indented, ObjectCreationHandling = ObjectCreationHandling.Replace, DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate, - Converters = new JsonConverter[] { new Vector2Converter() }, ContractResolver = new KeyContractResolver() }; } From 1e82033c840725d4facd32a95be7d7ebe682e908 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Mar 2021 14:18:56 +0900 Subject: [PATCH 306/390] Move bindings to LoadComplete to avoid cross-thread issues --- osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index b2d37e345f..19a3c5b6f2 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -140,6 +140,11 @@ namespace osu.Game.Overlays.Settings.Sections.Input } }, }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); areaOffset.BindTo(tabletHandler.AreaOffset); areaOffset.BindValueChanged(val => From fefb0078056fcfada6602ca1396318b618767bde Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Mar 2021 14:19:15 +0900 Subject: [PATCH 307/390] Remove no longer relevant comment --- osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index 19a3c5b6f2..d0e3ddbd91 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -202,7 +202,6 @@ namespace osu.Game.Overlays.Settings.Sections.Input mainSettings.Show(); noTabletMessage.Hide(); - // todo: these should propagate from a TabletChanged event or similar. offsetX.MaxValue = tab.Size.X; offsetX.Default = tab.Size.X / 2; sizeX.Default = sizeX.MaxValue = tab.Size.X; From bd1e2da1c2260457234bf40d16af2f130f68765a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Mar 2021 19:09:39 +0900 Subject: [PATCH 308/390] Always hide other overlays, even if the new one is not loaded --- osu.Game/OsuGame.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index e5e1f6946e..e2f0f0c05b 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -756,12 +756,12 @@ namespace osu.Game private void showOverlayAboveOthers(OverlayContainer overlay, OverlayContainer[] otherOverlays) { + otherOverlays.Where(o => o != overlay).ForEach(o => o.Hide()); + // generally shouldn't ever hit this state, but protects against a crash on attempting to change ChildDepth. if (overlay.LoadState < LoadState.Ready) return; - otherOverlays.Where(o => o != overlay).ForEach(o => o.Hide()); - // show above others if not visible at all, else leave at current depth. if (!overlay.IsPresent) overlayContent.ChangeChildDepth(overlay, (float)-Clock.CurrentTime); From c0c8b3e46c49290709a8c9948693c72857404704 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Mar 2021 19:22:47 +0900 Subject: [PATCH 309/390] Fix regression meaning `SkinnableSound` initialisation may never happen --- osu.Game/Skinning/SkinnableSound.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index e447f9c44c..9c6a4f7970 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -131,6 +131,14 @@ namespace osu.Game.Skinning }); } + protected override void LoadAsyncComplete() + { + base.LoadAsyncComplete(); + + if (!samplesContainer.Any()) + updateSamples(); + } + /// /// Stops the samples. /// From 9be7981e0d981cea5ed9b605ab9423e77877b7f7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Mar 2021 19:45:00 +0900 Subject: [PATCH 310/390] Adjust timeline ticks to be more visible --- osu.Game/Screens/Edit/BindableBeatDivisor.cs | 3 +-- .../Edit/Compose/Components/DistanceSnapGrid.cs | 2 +- .../Components/Timeline/TimelineTickDisplay.cs | 13 +++++++++---- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/BindableBeatDivisor.cs b/osu.Game/Screens/Edit/BindableBeatDivisor.cs index d9477dd4bc..ff33f0c70d 100644 --- a/osu.Game/Screens/Edit/BindableBeatDivisor.cs +++ b/osu.Game/Screens/Edit/BindableBeatDivisor.cs @@ -4,7 +4,6 @@ using System; using System.Linq; using osu.Framework.Bindables; -using osu.Framework.Graphics.Colour; using osu.Game.Graphics; using osuTK.Graphics; @@ -48,7 +47,7 @@ namespace osu.Game.Screens.Edit /// The beat divisor. /// The set of colours. /// The applicable colour from for . - public static ColourInfo GetColourFor(int beatDivisor, OsuColour colours) + public static Color4 GetColourFor(int beatDivisor, OsuColour colours) { switch (beatDivisor) { diff --git a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs index 8a92a2011d..59f88ac641 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs @@ -132,7 +132,7 @@ namespace osu.Game.Screens.Edit.Compose.Components var colour = BindableBeatDivisor.GetColourFor(BindableBeatDivisor.GetDivisorForBeatIndex(beatIndex + placementIndex + 1, beatDivisor.Value), Colours); int repeatIndex = placementIndex / beatDivisor.Value; - return colour.MultiplyAlpha(0.5f / (repeatIndex + 1)); + return ColourInfo.SingleColour(colour).MultiplyAlpha(0.5f / (repeatIndex + 1)); } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs index fb11b859a7..c070c833f8 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs @@ -6,7 +6,9 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Caching; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts; @@ -124,25 +126,28 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (beat == 0 && i == 0) nextMinTick = float.MinValue; - var indexInBar = beat % ((int)point.TimeSignature * beatDivisor.Value); + int indexInBar = beat % ((int)point.TimeSignature * beatDivisor.Value); var divisor = BindableBeatDivisor.GetDivisorForBeatIndex(beat, beatDivisor.Value); var colour = BindableBeatDivisor.GetColourFor(divisor, colours); + bool isMainBeat = indexInBar == 0; + // even though "bar lines" take up the full vertical space, we render them in two pieces because it allows for less anchor/origin churn. - var height = indexInBar == 0 ? 0.5f : 0.1f - (float)divisor / highestDivisor * 0.08f; + float height = isMainBeat ? 0.5f : 0.4f - (float)divisor / highestDivisor * 0.2f; + float gradientOpacity = isMainBeat ? 1 : 0; var topPoint = getNextUsablePoint(); topPoint.X = xPos; - topPoint.Colour = colour; topPoint.Height = height; + topPoint.Colour = ColourInfo.GradientVertical(colour, colour.Opacity(gradientOpacity)); topPoint.Anchor = Anchor.TopLeft; topPoint.Origin = Anchor.TopCentre; var bottomPoint = getNextUsablePoint(); bottomPoint.X = xPos; - bottomPoint.Colour = colour; bottomPoint.Anchor = Anchor.BottomLeft; + bottomPoint.Colour = ColourInfo.GradientVertical(colour.Opacity(gradientOpacity), colour); bottomPoint.Origin = Anchor.BottomCentre; bottomPoint.Height = height; } From 8955071703d40ed9f0210a45def45546bf1c19cb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Mar 2021 20:01:45 +0900 Subject: [PATCH 311/390] Change editor speed adjust to adjust frequency --- osu.Game/Screens/Edit/Components/PlaybackControl.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/PlaybackControl.cs b/osu.Game/Screens/Edit/Components/PlaybackControl.cs index 9739f2876a..bdc6e238c8 100644 --- a/osu.Game/Screens/Edit/Components/PlaybackControl.cs +++ b/osu.Game/Screens/Edit/Components/PlaybackControl.cs @@ -27,7 +27,7 @@ namespace osu.Game.Screens.Edit.Components [Resolved] private EditorClock editorClock { get; set; } - private readonly BindableNumber tempo = new BindableDouble(1); + private readonly BindableNumber freqAdjust = new BindableDouble(1); [BackgroundDependencyLoader] private void load() @@ -58,16 +58,16 @@ namespace osu.Game.Screens.Edit.Components RelativeSizeAxes = Axes.Both, Height = 0.5f, Padding = new MarginPadding { Left = 45 }, - Child = new PlaybackTabControl { Current = tempo }, + Child = new PlaybackTabControl { Current = freqAdjust }, } }; - Track.BindValueChanged(tr => tr.NewValue?.AddAdjustment(AdjustableProperty.Tempo, tempo), true); + Track.BindValueChanged(tr => tr.NewValue?.AddAdjustment(AdjustableProperty.Frequency, freqAdjust), true); } protected override void Dispose(bool isDisposing) { - Track.Value?.RemoveAdjustment(AdjustableProperty.Tempo, tempo); + Track.Value?.RemoveAdjustment(AdjustableProperty.Frequency, freqAdjust); base.Dispose(isDisposing); } @@ -101,7 +101,7 @@ namespace osu.Game.Screens.Edit.Components private class PlaybackTabControl : OsuTabControl { - private static readonly double[] tempo_values = { 0.5, 0.75, 1 }; + private static readonly double[] tempo_values = { 0.25, 0.5, 0.75, 1 }; protected override TabItem CreateTabItem(double value) => new PlaybackTabItem(value); From 86b229b1c94a1e0e3379b4e013206fd5ac996771 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Mar 2021 20:05:18 +0900 Subject: [PATCH 312/390] Increase maximum usable aspect ratio to account for ultrawide monitors --- osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index d0e3ddbd91..73ac8e4f35 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -31,9 +31,9 @@ namespace osu.Game.Overlays.Settings.Sections.Input private GameHost host { get; set; } /// - /// Based on the longest available smartphone. + /// Based on ultrawide monitor configurations. /// - private const float largest_feasible_aspect_ratio = 20f / 9; + private const float largest_feasible_aspect_ratio = 21f / 9; private readonly BindableNumber aspectRatio = new BindableFloat(1) { From 4795170c6044bc7092b4c78b687f5a5bdc880088 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Mar 2021 20:07:13 +0900 Subject: [PATCH 313/390] Add back the default json converter locally to ensure it's actually used --- osu.Game/IO/Serialization/IJsonSerializable.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/IO/Serialization/IJsonSerializable.cs b/osu.Game/IO/Serialization/IJsonSerializable.cs index 30430e6f7f..ba188963ea 100644 --- a/osu.Game/IO/Serialization/IJsonSerializable.cs +++ b/osu.Game/IO/Serialization/IJsonSerializable.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 System.Collections.Generic; using Newtonsoft.Json; +using osu.Framework.IO.Serialization; namespace osu.Game.IO.Serialization { @@ -27,6 +29,7 @@ namespace osu.Game.IO.Serialization Formatting = Formatting.Indented, ObjectCreationHandling = ObjectCreationHandling.Replace, DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate, + Converters = new List { new Vector2Converter() }, ContractResolver = new KeyContractResolver() }; } From 095b7f86685799249f5427ba7010f55a6a7e6646 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Mar 2021 20:09:12 +0900 Subject: [PATCH 314/390] Rewrite code to account for non-loaded edge case --- osu.Game/OsuGame.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index e2f0f0c05b..be919d60ca 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -758,13 +758,15 @@ namespace osu.Game { otherOverlays.Where(o => o != overlay).ForEach(o => o.Hide()); - // generally shouldn't ever hit this state, but protects against a crash on attempting to change ChildDepth. - if (overlay.LoadState < LoadState.Ready) + // Partially visible so leave it at the current depth. + if (overlay.IsPresent) return; - // show above others if not visible at all, else leave at current depth. - if (!overlay.IsPresent) + // Show above all other overlays. + if (overlay.IsLoaded) overlayContent.ChangeChildDepth(overlay, (float)-Clock.CurrentTime); + else + overlay.Depth = (float)-Clock.CurrentTime; } private void forwardLoggedErrorsToNotifications() From 6f32c302eb692f2a8c7bf6122149a28d96672446 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Mar 2021 20:13:51 +0900 Subject: [PATCH 315/390] Add checkbox to optionally disable tablet handling --- .../Settings/Sections/Input/TabletSettings.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index 73ac8e4f35..893fe575cd 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -62,11 +62,18 @@ namespace osu.Game.Overlays.Settings.Sections.Input { Children = new Drawable[] { + new SettingsCheckbox + { + LabelText = "Enabled", + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Current = tabletHandler.Enabled + }, noTabletMessage = new OsuSpriteText { Text = "No tablet detected!", - Anchor = Anchor.Centre, - Origin = Anchor.Centre, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, Padding = new MarginPadding { Horizontal = SettingsPanel.CONTENT_MARGINS } }, mainSettings = new FillFlowContainer From 63cbac3bd059778a8b94ad0d5e5b47beab637f33 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Mar 2021 20:15:29 +0900 Subject: [PATCH 316/390] Ensure aspect ratio slider gets an initial value --- osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index 893fe575cd..b06b148984 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -186,6 +186,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input aspectRatioApplication = Schedule(() => applyAspectRatio(sizeY)); }); + updateAspectRatio(); aspectRatio.BindValueChanged(aspect => { aspectRatioApplication?.Cancel(); From b2d8db3a92d65c2db7a26d08f276a30fef82c7c7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Mar 2021 20:25:21 +0900 Subject: [PATCH 317/390] Rename incorrect variable --- osu.Game/Skinning/PoolableSkinnableSample.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Skinning/PoolableSkinnableSample.cs b/osu.Game/Skinning/PoolableSkinnableSample.cs index 09e087d0f2..c01a6d20cc 100644 --- a/osu.Game/Skinning/PoolableSkinnableSample.cs +++ b/osu.Game/Skinning/PoolableSkinnableSample.cs @@ -94,21 +94,21 @@ namespace osu.Game.Skinning sampleContainer.Clear(); Sample = null; - var ch = CurrentSkin.GetSample(sampleInfo); + var sample = CurrentSkin.GetSample(sampleInfo); - if (ch == null && AllowDefaultFallback) + if (sample == null && AllowDefaultFallback) { foreach (var lookup in sampleInfo.LookupNames) { - if ((ch = sampleStore.Get(lookup)) != null) + if ((sample = sampleStore.Get(lookup)) != null) break; } } - if (ch == null) + if (sample == null) return; - sampleContainer.Add(Sample = new DrawableSample(ch)); + sampleContainer.Add(Sample = new DrawableSample(sample)); // Start playback internally for the new sample if the previous one was playing beforehand. if (wasPlaying && Looping) From 68aaf90702458f288913e191c4d27c342aa7a358 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Mar 2021 20:30:52 +0900 Subject: [PATCH 318/390] Fix disposal rather than performing some weird hack --- osu.Game/Skinning/LegacySkin.cs | 4 +++- osu.Game/Skinning/PoolableSkinnableSample.cs | 8 -------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index b69e99773c..ec49d43c67 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -509,7 +509,7 @@ namespace osu.Game.Skinning /// /// A sample wrapper which keeps a reference to the contained skin to avoid finalizer garbage collection of the managing SampleStore. /// - private class LegacySkinSample : ISample + private class LegacySkinSample : ISample, IDisposable { private readonly Sample sample; @@ -575,6 +575,8 @@ namespace osu.Game.Skinning public IBindable AggregateFrequency => sample.AggregateFrequency; public IBindable AggregateTempo => sample.AggregateTempo; + + public void Dispose() => sample.Dispose(); } } } diff --git a/osu.Game/Skinning/PoolableSkinnableSample.cs b/osu.Game/Skinning/PoolableSkinnableSample.cs index c01a6d20cc..b04158a58f 100644 --- a/osu.Game/Skinning/PoolableSkinnableSample.cs +++ b/osu.Game/Skinning/PoolableSkinnableSample.cs @@ -83,14 +83,6 @@ namespace osu.Game.Skinning bool wasPlaying = Playing; - if (activeChannel != null) - { - // when switching away from previous samples, we don't want to call Stop() on them as it sounds better to let them play out. - // this may change in the future if we use PoolableSkinSample in more locations than gameplay. - // we *do* want to turn off looping, else we end up with an infinite looping sample running in the background. - activeChannel.Looping = false; - } - sampleContainer.Clear(); Sample = null; From 9634560d4b761a2a3aa90d78ffb1933e2c96c7c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 19 Mar 2021 21:36:28 +0100 Subject: [PATCH 319/390] Fix control point visualiser crashing after deselections `SliderSelectionBlueprint.OnDeselected()` would expire the `ControlPointVisualiser` on deselection, leading to its removal from the blueprint and eventual disposal, but still kept a separate reference to said visualiser in another field. This could lead to that stale reference to a disposed child getting read in `ReceivePositionalInputAt()`, crashing quite a ways down over at the framework side on futilely trying to compute the bounding box of a drawable with no parent. --- .../Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 3d3dff653a..befe3c6695 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -114,6 +114,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders // throw away frame buffers on deselection. ControlPointVisualiser?.Expire(); + ControlPointVisualiser = null; + BodyPiece.RecyclePath(); } From e67c759eef36a409e4b57ab1c7643eb307280cd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 19 Mar 2021 22:44:31 +0100 Subject: [PATCH 320/390] Mark control point visualiser as possibly-null --- .../Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index befe3c6695..ba9bb3c485 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -28,6 +29,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders protected SliderBodyPiece BodyPiece { get; private set; } protected SliderCircleSelectionBlueprint HeadBlueprint { get; private set; } protected SliderCircleSelectionBlueprint TailBlueprint { get; private set; } + + [CanBeNull] protected PathControlPointVisualiser ControlPointVisualiser { get; private set; } private readonly DrawableSlider slider; From 8e0536e1e2d2eb66a7572dcf27f19d65cb4f5e7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 19 Mar 2021 22:20:26 +0100 Subject: [PATCH 321/390] Add failing test scene --- .../Editing/TestSceneBlueprintSelection.cs | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 osu.Game.Tests/Visual/Editing/TestSceneBlueprintSelection.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneBlueprintSelection.cs b/osu.Game.Tests/Visual/Editing/TestSceneBlueprintSelection.cs new file mode 100644 index 0000000000..fd9c09fd5f --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestSceneBlueprintSelection.cs @@ -0,0 +1,70 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Screens.Edit.Compose.Components; +using osu.Game.Tests.Beatmaps; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Editing +{ + public class TestSceneBlueprintSelection : EditorTestScene + { + protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false); + + private BlueprintContainer blueprintContainer + => Editor.ChildrenOfType().First(); + + [Test] + public void TestSelectedObjectHasPriorityWhenOverlapping() + { + var firstSlider = new Slider + { + Path = new SliderPath(new[] + { + new PathControlPoint(new Vector2()), + new PathControlPoint(new Vector2(150, -50)), + new PathControlPoint(new Vector2(300, 0)) + }), + Position = new Vector2(0, 100) + }; + var secondSlider = new Slider + { + Path = new SliderPath(new[] + { + new PathControlPoint(new Vector2()), + new PathControlPoint(new Vector2(-50, 50)), + new PathControlPoint(new Vector2(-100, 100)) + }), + Position = new Vector2(200, 0) + }; + + AddStep("add overlapping sliders", () => + { + EditorBeatmap.Add(firstSlider); + EditorBeatmap.Add(secondSlider); + }); + AddStep("select first slider", () => EditorBeatmap.SelectedHitObjects.Add(firstSlider)); + + AddStep("move mouse to common point", () => + { + var pos = blueprintContainer.ChildrenOfType().ElementAt(1).ScreenSpaceDrawQuad.Centre; + InputManager.MoveMouseTo(pos); + }); + AddStep("right click", () => InputManager.Click(MouseButton.Right)); + + AddAssert("selection is unchanged", () => EditorBeatmap.SelectedHitObjects.Single() == firstSlider); + } + } +} From dd48b68f8ad6fc513d17f9847d0863df2c90dc1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 19 Mar 2021 22:20:40 +0100 Subject: [PATCH 322/390] Ensure selected blueprints are given selection priority --- osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 051d0766bf..7def7e1d16 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -338,7 +338,8 @@ namespace osu.Game.Screens.Edit.Compose.Components private bool beginClickSelection(MouseButtonEvent e) { // Iterate from the top of the input stack (blueprints closest to the front of the screen first). - foreach (SelectionBlueprint blueprint in SelectionBlueprints.AliveChildren.Reverse()) + // Priority is given to already-selected blueprints. + foreach (SelectionBlueprint blueprint in SelectionBlueprints.AliveChildren.Reverse().OrderByDescending(b => b.IsSelected)) { if (!blueprint.IsHovered) continue; From ca943a897a387e7a7f7d8a934aee7125f262f698 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 20 Mar 2021 10:51:58 +0900 Subject: [PATCH 323/390] Fix back to front initialisation order --- osu.Game/Skinning/SkinnableSound.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index 9c6a4f7970..f935adf7a5 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -133,10 +133,11 @@ namespace osu.Game.Skinning protected override void LoadAsyncComplete() { - base.LoadAsyncComplete(); - + // ensure samples are constructed before SkinChanged() is called via base.LoadAsyncComplete(). if (!samplesContainer.Any()) updateSamples(); + + base.LoadAsyncComplete(); } /// From d28bed6ed29f9aeea901ccbec2c075367007fb47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 20 Mar 2021 12:29:24 +0100 Subject: [PATCH 324/390] Schedule adding transforms on tablet changes Fixes `InvalidThreadForMutationException`s that pop up when disconnecting/reconnecting tablets during the game's operation. In those cases the value change callback executes from an OpenTabletDriver thread. --- .../Sections/Input/TabletAreaSelection.cs | 15 ++++++----- .../Settings/Sections/Input/TabletSettings.cs | 25 +++++++++++++------ 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs index df25668411..ecb8acce54 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs @@ -125,17 +125,20 @@ namespace osu.Game.Overlays.Settings.Sections.Input }, true); tablet.BindTo(handler.Tablet); - tablet.BindValueChanged(val => - { - tabletContainer.Size = val.NewValue?.Size ?? Vector2.Zero; - tabletName.Text = val.NewValue?.Name ?? string.Empty; - checkBounds(); - }, true); + tablet.BindValueChanged(_ => Scheduler.AddOnce(updateTabletDetails)); + updateTabletDetails(); // initial animation should be instant. FinishTransforms(true); } + private void updateTabletDetails() + { + tabletContainer.Size = tablet.Value?.Size ?? Vector2.Zero; + tabletName.Text = tablet.Value?.Name ?? string.Empty; + checkBounds(); + } + private static int greatestCommonDivider(int a, int b) { while (b != 0) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index b06b148984..4baf43783d 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -196,19 +196,13 @@ namespace osu.Game.Overlays.Settings.Sections.Input tablet.BindTo(tabletHandler.Tablet); tablet.BindValueChanged(val => { + Scheduler.AddOnce(toggleVisibility); + var tab = val.NewValue; bool tabletFound = tab != null; - if (!tabletFound) - { - mainSettings.Hide(); - noTabletMessage.Show(); return; - } - - mainSettings.Show(); - noTabletMessage.Hide(); offsetX.MaxValue = tab.Size.X; offsetX.Default = tab.Size.X / 2; @@ -222,6 +216,21 @@ namespace osu.Game.Overlays.Settings.Sections.Input }, true); } + private void toggleVisibility() + { + bool tabletFound = tablet.Value != null; + + if (!tabletFound) + { + mainSettings.Hide(); + noTabletMessage.Show(); + return; + } + + mainSettings.Show(); + noTabletMessage.Hide(); + } + private void applyAspectRatio(BindableNumber sizeChanged) { try From 86b569f5f7a13700b7ef728fafb6fe1c014ea8d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 20 Mar 2021 12:34:41 +0100 Subject: [PATCH 325/390] Fix typo in identifier --- osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index 4baf43783d..bd0f7ddc4c 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -237,7 +237,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input { if (!aspectLock.Value) { - float proposedAspectRatio = curentAspectRatio; + float proposedAspectRatio = currentAspectRatio; if (proposedAspectRatio >= aspectRatio.MinValue && proposedAspectRatio <= aspectRatio.MaxValue) { @@ -278,8 +278,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input aspectLock.Value = true; } - private void updateAspectRatio() => aspectRatio.Value = curentAspectRatio; + private void updateAspectRatio() => aspectRatio.Value = currentAspectRatio; - private float curentAspectRatio => sizeX.Value / sizeY.Value; + private float currentAspectRatio => sizeX.Value / sizeY.Value; } } From a16c0641b27ab69f59c56a4cf96f388e941affa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 21 Mar 2021 11:01:06 +0100 Subject: [PATCH 326/390] Revert EF Core to version 2.2 This reverts commit f3faad74d587bbbd104395f5072723203c9d54aa, reversing changes made to 712e7bc7bfaa94dd8c7248d9e800e7bc916476c0. Several issues arose after migrating to 5.0, including, but possibly not limited to, performance regressions in song select, as well as failures when attempting to save beatmaps after metadata changes in the editor. --- osu.Desktop/osu.Desktop.csproj | 4 +- .../osu.Game.Rulesets.Catch.Tests.csproj | 2 +- .../osu.Game.Rulesets.Mania.Tests.csproj | 2 +- .../osu.Game.Rulesets.Osu.Tests.csproj | 2 +- .../osu.Game.Rulesets.Taiko.Tests.csproj | 2 +- .../TestSceneMultiplayerMatchSongSelect.cs | 1 - .../TestSceneBeatmapRecommendations.cs | 1 - .../SongSelect/TestScenePlaySongSelect.cs | 4 +- osu.Game.Tests/osu.Game.Tests.csproj | 2 +- osu.Game/Beatmaps/BeatmapManager.cs | 2 - osu.Game/Database/ArchiveModelManager.cs | 6 +- .../Database/DatabaseWorkaroundExtensions.cs | 71 ------------------- osu.Game/Database/OsuDbContext.cs | 9 +-- osu.Game/Scoring/ScoreInfo.cs | 13 +++- osu.Game/Scoring/ScoreManager.cs | 5 -- osu.Game/Skinning/SkinManager.cs | 5 -- osu.Game/osu.Game.csproj | 6 +- osu.iOS.props | 2 + 18 files changed, 30 insertions(+), 109 deletions(-) delete mode 100644 osu.Game/Database/DatabaseWorkaroundExtensions.cs diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index d9d23dea6b..3e0f0cb7f6 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -27,8 +27,8 @@ - - + + diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj index 42f70151ac..728af5124e 100644 --- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj +++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj @@ -5,7 +5,7 @@ - + WinExe diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj index e51b20c9fe..af16f39563 100644 --- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj +++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj @@ -5,7 +5,7 @@ - + WinExe diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj index f1f75148ef..3d2d1f3fec 100644 --- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj +++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj @@ -5,7 +5,7 @@ - + WinExe diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj index c9a320bdd5..fa00922706 100644 --- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj +++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj @@ -5,7 +5,7 @@ - + WinExe diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index 8cfe5d8af2..faa5d9e6fc 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -56,7 +56,6 @@ namespace osu.Game.Tests.Visual.Multiplayer beatmaps.Add(new BeatmapInfo { Ruleset = rulesets.GetRuleset(i % 4), - RulesetID = i % 4, // workaround for efcore 5 compatibility. OnlineBeatmapID = beatmapId, Length = length, BPM = bpm, diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs index 9b8b74e6f6..53a956c77c 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs @@ -186,7 +186,6 @@ namespace osu.Game.Tests.Visual.SongSelect Metadata = metadata, BaseDifficulty = new BeatmapDifficulty(), Ruleset = ruleset, - RulesetID = ruleset.ID.GetValueOrDefault(), // workaround for efcore 5 compatibility. StarDifficulty = difficultyIndex + 1, Version = $"SR{difficultyIndex + 1}" }).ToList() diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 057b539e44..5731b1ac2c 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -911,11 +911,9 @@ namespace osu.Game.Tests.Visual.SongSelect int length = RNG.Next(30000, 200000); double bpm = RNG.NextSingle(80, 200); - var ruleset = getRuleset(); beatmaps.Add(new BeatmapInfo { - Ruleset = ruleset, - RulesetID = ruleset.ID.GetValueOrDefault(), // workaround for efcore 5 compatibility. + Ruleset = getRuleset(), OnlineBeatmapID = beatmapId, Version = $"{beatmapId} (length {TimeSpan.FromMilliseconds(length):m\\:ss}, bpm {bpm:0.#})", Length = length, diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 6f8e0fac6f..e36b3cdc74 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -6,7 +6,7 @@ - + diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 115d1b33bb..b4ea898b7d 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -171,8 +171,6 @@ namespace osu.Game.Beatmaps if (beatmapSet.Beatmaps.Any(b => b.BaseDifficulty == null)) throw new InvalidOperationException($"Cannot import {nameof(BeatmapInfo)} with null {nameof(BeatmapInfo.BaseDifficulty)}."); - beatmapSet.Requery(ContextFactory); - // check if a set already exists with the same online id, delete if it does. if (beatmapSet.OnlineBeatmapSetID != null) { diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 64428882ac..d809dbcb01 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -462,8 +462,6 @@ namespace osu.Game.Database // Dereference the existing file info, since the file model will be removed. if (file.FileInfo != null) { - file.Requery(usage.Context); - Files.Dereference(file.FileInfo); // This shouldn't be required, but here for safety in case the provided TModel is not being change tracked @@ -637,12 +635,10 @@ namespace osu.Game.Database { using (Stream s = reader.GetStream(file)) { - var fileInfo = files.Add(s); fileInfos.Add(new TFileModel { Filename = file.Substring(prefix.Length).ToStandardisedPath(), - FileInfo = fileInfo, - FileInfoID = fileInfo.ID // workaround for efcore 5 compatibility. + FileInfo = files.Add(s) }); } } diff --git a/osu.Game/Database/DatabaseWorkaroundExtensions.cs b/osu.Game/Database/DatabaseWorkaroundExtensions.cs deleted file mode 100644 index a3a982f232..0000000000 --- a/osu.Game/Database/DatabaseWorkaroundExtensions.cs +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using System.Collections.Generic; -using osu.Game.Beatmaps; -using osu.Game.Scoring; -using osu.Game.Skinning; - -namespace osu.Game.Database -{ - /// - /// Extension methods which contain workarounds to make EFcore 5.x work with our existing (incorrect) thread safety. - /// The intention is to avoid blocking package updates while we consider the future of the database backend, with a potential backend switch imminent. - /// - public static class DatabaseWorkaroundExtensions - { - /// - /// Re-query the provided model to ensure it is in a sane state. This method requires explicit implementation per model type. - /// - /// - /// - public static void Requery(this IHasPrimaryKey model, IDatabaseContextFactory contextFactory) - { - switch (model) - { - case SkinInfo skinInfo: - requeryFiles(skinInfo.Files, contextFactory); - break; - - case ScoreInfo scoreInfo: - requeryFiles(scoreInfo.Beatmap.BeatmapSet.Files, contextFactory); - requeryFiles(scoreInfo.Files, contextFactory); - break; - - case BeatmapSetInfo beatmapSetInfo: - var context = contextFactory.Get(); - - foreach (var beatmap in beatmapSetInfo.Beatmaps) - { - // Workaround System.InvalidOperationException - // The instance of entity type 'RulesetInfo' cannot be tracked because another instance with the same key value for {'ID'} is already being tracked. - beatmap.Ruleset = context.RulesetInfo.Find(beatmap.RulesetID); - } - - requeryFiles(beatmapSetInfo.Files, contextFactory); - break; - - default: - throw new ArgumentException($"{nameof(Requery)} does not have support for the provided model type", nameof(model)); - } - - void requeryFiles(List files, IDatabaseContextFactory databaseContextFactory) where T : class, INamedFileInfo - { - var dbContext = databaseContextFactory.Get(); - - foreach (var file in files) - { - Requery(file, dbContext); - } - } - } - - public static void Requery(this INamedFileInfo file, OsuDbContext dbContext) - { - // Workaround System.InvalidOperationException - // The instance of entity type 'FileInfo' cannot be tracked because another instance with the same key value for {'ID'} is already being tracked. - file.FileInfo = dbContext.FileInfo.Find(file.FileInfoID); - } - } -} diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index 2342ab07d4..2aae62edea 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -3,6 +3,7 @@ using System; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.Extensions.Logging; using osu.Framework.Logging; using osu.Framework.Statistics; @@ -110,10 +111,10 @@ namespace osu.Game.Database { base.OnConfiguring(optionsBuilder); optionsBuilder - .UseSqlite(connectionString, - sqliteOptions => sqliteOptions - .CommandTimeout(10) - .UseQuerySplittingBehavior(QuerySplittingBehavior.SingleQuery)) + // this is required for the time being due to the way we are querying in places like BeatmapStore. + // if we ever move to having consumers file their own .Includes, or get eager loading support, this could be re-enabled. + .ConfigureWarnings(warnings => warnings.Ignore(CoreEventId.IncludeIgnoredWarning)) + .UseSqlite(connectionString, sqliteOptions => sqliteOptions.CommandTimeout(10)) .UseLoggerFactory(logger.Value); } diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 78101991f6..f5192f3a40 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -73,7 +73,7 @@ namespace osu.Game.Scoring } set { - modsJson = JsonConvert.SerializeObject(value.Select(m => new DeserializedMod { Acronym = m.Acronym })); + modsJson = null; mods = value; } } @@ -86,7 +86,16 @@ namespace osu.Game.Scoring [Column("Mods")] public string ModsJson { - get => modsJson; + get + { + if (modsJson != null) + return modsJson; + + if (mods == null) + return null; + + return modsJson = JsonConvert.SerializeObject(mods.Select(m => new DeserializedMod { Acronym = m.Acronym })); + } set { modsJson = value; diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 7d0abc5996..c7ee26c248 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -53,11 +53,6 @@ namespace osu.Game.Scoring this.configManager = configManager; } - protected override void PreImport(ScoreInfo model) - { - model.Requery(ContextFactory); - } - protected override ScoreInfo CreateModel(ArchiveReader archive) { if (archive == null) diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 894a068b7f..9257636301 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -142,11 +142,6 @@ namespace osu.Game.Skinning } } - protected override void PreImport(SkinInfo model) - { - model.Requery(ContextFactory); - } - /// /// Retrieve a instance for the provided /// diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 360c522193..9731c1d5ea 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,10 +24,10 @@ - - + + - + diff --git a/osu.iOS.props b/osu.iOS.props index b763a91dfb..11677d345e 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -90,6 +90,8 @@ + + From c4f3714385c411dca8092f3163aad2817ff7f62b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 21 Mar 2021 18:39:52 +0100 Subject: [PATCH 327/390] Make hold note input tests fail due to head hiding --- .../TestSceneHoldNoteInput.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs index 596430f9e5..7ae69bf7d7 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs @@ -5,11 +5,13 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Screens; +using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Replays; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Replays; using osu.Game.Rulesets.Mania.Scoring; using osu.Game.Rulesets.Objects; @@ -345,6 +347,14 @@ namespace osu.Game.Rulesets.Mania.Tests AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0); AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen()); + + AddUntilStep("wait for head", () => currentPlayer.GameplayClockContainer.GameplayClock.CurrentTime >= time_head); + AddAssert("head is visible", + () => currentPlayer.ChildrenOfType() + .Single(note => note.HitObject == beatmap.HitObjects[0]) + .Head + .Alpha == 1); + AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value); } @@ -352,6 +362,8 @@ namespace osu.Game.Rulesets.Mania.Tests { public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; + public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer; + protected override bool PauseOnFocusLost => false; public ScoreAccessibleReplayPlayer(Score score) From 9a330c4c56aeeda049cfccdd6a15f10c1966758c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 21 Mar 2021 18:34:31 +0100 Subject: [PATCH 328/390] Fix mania hold note heads hiding when frozen This was an insidious regression from a3dc1d5. Prior to that commit, `DrawableHoldNoteHead` had `UpdateStateTransforms()` overridden, to set the hold note head's lifetime. When that method was split into `UpdateInitialStateTransforms()` and `UpdateHitStateTransforms()`, the lifetime set was moved to the former. Unfortunately, that override served two purposes: both to set the lifetime, and to suppress hit animations which would normally be added by the base `DrawableManiaHitObject`. That fact being missed led to `UpdateHitStateTransforms()` hiding the hold note head immediately on hit and with a slight delay on miss. To resolve, explicitly override `UpdateHitStateTransforms()` and suppress the base call, with an explanatory comment. --- .../Objects/Drawables/DrawableHoldNoteHead.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs index 75dcf0e55e..35ba2465fa 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs @@ -1,6 +1,8 @@ // 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.Rulesets.Objects.Drawables; + namespace osu.Game.Rulesets.Mania.Objects.Drawables { /// @@ -25,6 +27,14 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables LifetimeEnd = LifetimeStart + 30000; } + protected override void UpdateHitStateTransforms(ArmedState state) + { + // suppress the base call explicitly. + // the hold note head should never change its visual state on its own due to the "freezing" mechanic + // (when hit, it remains visible in place at the judgement line; when dropped, it will scroll past the line). + // it will be hidden along with its parenting hold note when required. + } + public override bool OnPressed(ManiaAction action) => false; // Handled by the hold note public override void OnReleased(ManiaAction action) From e31d583a7f468853844bb1e8aa38f2d70225f122 Mon Sep 17 00:00:00 2001 From: Joehu Date: Sun, 21 Mar 2021 11:16:59 -0700 Subject: [PATCH 329/390] Add comments count to user profile overlay --- osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs | 6 ++++++ osu.Game/Users/User.cs | 3 +++ 2 files changed, 9 insertions(+) diff --git a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs index 662f55317b..e73579fad0 100644 --- a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using Humanizer; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; @@ -115,6 +116,11 @@ namespace osu.Game.Overlays.Profile.Header topLinkContainer.AddText("Contributed "); topLinkContainer.AddLink($@"{user.PostCount:#,##0} forum posts", $"{api.WebsiteRootUrl}/users/{user.Id}/posts", creationParameters: embolden); + addSpacer(topLinkContainer); + + topLinkContainer.AddText("Posted "); + topLinkContainer.AddLink("comment".ToQuantity(user.CommentsCount, "#,##0"), $"{api.WebsiteRootUrl}/comments?user_id={user.Id}", creationParameters: embolden); + string websiteWithoutProtocol = user.Website; if (!string.IsNullOrEmpty(websiteWithoutProtocol)) diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs index 6c45417db0..74ffb7c457 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -120,6 +120,9 @@ namespace osu.Game.Users [JsonProperty(@"post_count")] public int PostCount; + [JsonProperty(@"comments_count")] + public int CommentsCount; + [JsonProperty(@"follower_count")] public int FollowerCount; From 9bc6cdf042317265f7e37b3feaf58f10770d6fac Mon Sep 17 00:00:00 2001 From: Joehu Date: Sun, 21 Mar 2021 11:19:07 -0700 Subject: [PATCH 330/390] Fix singular format regression on forum post text --- osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs index e73579fad0..fe61e532e1 100644 --- a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs @@ -114,7 +114,7 @@ namespace osu.Game.Overlays.Profile.Header } topLinkContainer.AddText("Contributed "); - topLinkContainer.AddLink($@"{user.PostCount:#,##0} forum posts", $"{api.WebsiteRootUrl}/users/{user.Id}/posts", creationParameters: embolden); + topLinkContainer.AddLink("forum post".ToQuantity(user.PostCount, "#,##0"), $"{api.WebsiteRootUrl}/users/{user.Id}/posts", creationParameters: embolden); addSpacer(topLinkContainer); From f7bf23dbe9b51fa6f2a8168efdf4ce1b081e8874 Mon Sep 17 00:00:00 2001 From: owen-young Date: Sun, 21 Mar 2021 21:50:19 -0500 Subject: [PATCH 331/390] first attempt at changing windowMode to be fullscreen on default --- osu.Game/OsuGame.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index dd775888a1..2fd6331c86 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -126,6 +126,8 @@ namespace osu.Game private Bindable configSkin; + private Bindable windowMode; + private readonly string[] args; private readonly List overlays = new List(); @@ -631,6 +633,12 @@ namespace osu.Game loadComponentSingleFile(volume = new VolumeOverlay(), leftFloatingOverlayContent.Add, true); + frameworkConfig.GetBindable(FrameworkSetting.WindowMode); + windowMode.BindValueChanged(mode => ScheduleAfterChildren(() => + { + windowMode.Value = WindowMode.Windowed; + }), true); + var onScreenDisplay = new OnScreenDisplay(); onScreenDisplay.BeginTracking(this, frameworkConfig); From 073dba5330663073433b38798626ae0d6781d971 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Mar 2021 14:05:37 +0900 Subject: [PATCH 332/390] Remove local workarounds to attempt to avoid crashes on skin change --- osu.Game/Skinning/LegacySkin.cs | 76 +-------------------------------- 1 file changed, 1 insertion(+), 75 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index ec49d43c67..12abc4d867 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -7,7 +7,6 @@ using System.Diagnostics; using System.IO; using System.Linq; using JetBrains.Annotations; -using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -462,7 +461,7 @@ namespace osu.Game.Skinning var sample = Samples?.Get(lookup); if (sample != null) - return new LegacySkinSample(sample, this); + return sample; } return null; @@ -505,78 +504,5 @@ namespace osu.Game.Skinning Textures?.Dispose(); Samples?.Dispose(); } - - /// - /// A sample wrapper which keeps a reference to the contained skin to avoid finalizer garbage collection of the managing SampleStore. - /// - private class LegacySkinSample : ISample, IDisposable - { - private readonly Sample sample; - - [UsedImplicitly] - private readonly LegacySkin skin; - - public LegacySkinSample(Sample sample, LegacySkin skin) - { - this.sample = sample; - this.skin = skin; - } - - public SampleChannel Play() - { - return sample.Play(); - } - - public SampleChannel GetChannel() - { - return sample.GetChannel(); - } - - public double Length => sample.Length; - - public Bindable PlaybackConcurrency => sample.PlaybackConcurrency; - public BindableNumber Volume => sample.Volume; - - public BindableNumber Balance => sample.Balance; - - public BindableNumber Frequency => sample.Frequency; - - public BindableNumber Tempo => sample.Tempo; - - public void BindAdjustments(IAggregateAudioAdjustment component) - { - sample.BindAdjustments(component); - } - - public void UnbindAdjustments(IAggregateAudioAdjustment component) - { - sample.UnbindAdjustments(component); - } - - public void AddAdjustment(AdjustableProperty type, IBindable adjustBindable) - { - sample.AddAdjustment(type, adjustBindable); - } - - public void RemoveAdjustment(AdjustableProperty type, IBindable adjustBindable) - { - sample.RemoveAdjustment(type, adjustBindable); - } - - public void RemoveAllAdjustments(AdjustableProperty type) - { - sample.RemoveAllAdjustments(type); - } - - public IBindable AggregateVolume => sample.AggregateVolume; - - public IBindable AggregateBalance => sample.AggregateBalance; - - public IBindable AggregateFrequency => sample.AggregateFrequency; - - public IBindable AggregateTempo => sample.AggregateTempo; - - public void Dispose() => sample.Dispose(); - } } } From db64fac8241710308f02399909ec7f6f05be6251 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Mar 2021 15:26:22 +0900 Subject: [PATCH 333/390] Delay key fade in legacy mania skins --- osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyKeyArea.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyKeyArea.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyKeyArea.cs index 78ccb83a8c..174324f5f6 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyKeyArea.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyKeyArea.cs @@ -101,8 +101,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy { if (action == column.Action.Value) { - upSprite.FadeTo(1); - downSprite.FadeTo(0); + upSprite.Delay(80).FadeTo(1); + downSprite.Delay(80).FadeTo(0); } } } From fc632fd48aae56b1045a1955b035d8ac574609d8 Mon Sep 17 00:00:00 2001 From: Owen Young Date: Mon, 22 Mar 2021 01:30:20 -0500 Subject: [PATCH 334/390] Added WindowSetting setting to OsuSetting enum so that it can be set by default at startup. Modified LayoutSettings.cs so that when it is changed in the settings, it is written to the local settings as well. --- osu.Game/Configuration/OsuConfigManager.cs | 3 +++ osu.Game/OsuGame.cs | 9 +++------ .../Settings/Sections/Graphics/LayoutSettings.cs | 7 ++++++- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index d0fa45bb7a..cd74fe25f4 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -91,6 +91,8 @@ namespace osu.Game.Configuration Set(OsuSetting.MenuParallax, true); + Set(OsuSetting.WindowSetting, WindowMode.Fullscreen); + // Gameplay Set(OsuSetting.DimLevel, 0.8, 0, 1, 0.01); Set(OsuSetting.BlurLevel, 0, 0, 1, 0.01); @@ -233,6 +235,7 @@ namespace osu.Game.Configuration MenuVoice, CursorRotation, MenuParallax, + WindowSetting, BeatmapDetailTab, BeatmapDetailModsFilter, Username, diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 2fd6331c86..84737a56e4 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -234,6 +234,9 @@ namespace osu.Game SelectedMods.BindValueChanged(modsChanged); Beatmap.BindValueChanged(beatmapChanged, true); + + windowMode = LocalConfig.GetBindable(OsuSetting.WindowSetting); + frameworkConfig.GetBindable(FrameworkSetting.WindowMode).Value = windowMode.Value; } private ExternalLinkOpener externalLinkOpener; @@ -633,12 +636,6 @@ namespace osu.Game loadComponentSingleFile(volume = new VolumeOverlay(), leftFloatingOverlayContent.Add, true); - frameworkConfig.GetBindable(FrameworkSetting.WindowMode); - windowMode.BindValueChanged(mode => ScheduleAfterChildren(() => - { - windowMode.Value = WindowMode.Windowed; - }), true); - var onScreenDisplay = new OnScreenDisplay(); onScreenDisplay.BeginTracking(this, frameworkConfig); diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index 4d5c2e06eb..ab662cb9a0 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -31,6 +31,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics private Bindable scalingMode; private Bindable sizeFullscreen; + private Bindable windowMode; private readonly BindableList resolutions = new BindableList(new[] { new Size(9999, 9999) }); @@ -56,6 +57,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics scalingSizeY = osuConfig.GetBindable(OsuSetting.ScalingSizeY); scalingPositionX = osuConfig.GetBindable(OsuSetting.ScalingPositionX); scalingPositionY = osuConfig.GetBindable(OsuSetting.ScalingPositionY); + windowMode = osuConfig.GetBindable(OsuSetting.WindowSetting); if (host.Window != null) { @@ -141,7 +143,10 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics scalingSettings.ForEach(s => bindPreviewEvent(s.Current)); - windowModeDropdown.Current.ValueChanged += _ => updateResolutionDropdown(); + windowModeDropdown.Current.ValueChanged += mode => { + windowMode.Value = mode.NewValue; + updateResolutionDropdown(); + }; windowModes.BindCollectionChanged((sender, args) => { From c4d08463addcf532926308dc319eebb981d530a8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Mar 2021 16:04:51 +0900 Subject: [PATCH 335/390] Fix spinners playing looping sound too long in the editor The `OnComplete` event was never being run due to the transform playing out longer than the spinner's lifetime. I've matched the durations, but also moved the `Stop()` call to what I deem a safer place to run it (I did notice that without this it would still potentially never fire). Note that this is more noticeable in the editor because of lifetime extension. In gameplay, the returning of a spinner to the pool will clean things up (but in the editor that can take longer, depending on timeline zoom level). Another thing worth mentioning is that the fade doesn't actually work. This is due to https://github.com/ppy/osu-framework/pull/4212. Closes #12119. --- .../Objects/Drawables/DrawableSpinner.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 3d614c2dbd..d92f63eb89 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -39,6 +39,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private Bindable isSpinning; private bool spinnerFrequencyModulate; + private const double fade_out_duration = 160; + public DrawableSpinner() : this(null) { @@ -136,7 +138,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } else { - spinningSample?.VolumeTo(0, 300).OnComplete(_ => spinningSample.Stop()); + if (spinningSample != null) + spinningSample.Volume.Value = 0; + + spinningSample?.VolumeTo(0, fade_out_duration); } } @@ -173,7 +178,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.UpdateHitStateTransforms(state); - this.FadeOut(160).Expire(); + this.FadeOut(fade_out_duration).OnComplete(_ => + { + // looping sample should be stopped here as it is safer than running in the OnComplete + // of the volume transition above. + spinningSample.Stop(); + }); + + Expire(); // skin change does a rewind of transforms, which will stop the spinning sound from playing if it's currently in playback. isSpinning?.TriggerChange(); From 690fb9224aaee2d2d4930de181992eb53970b32d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Mar 2021 16:18:31 +0900 Subject: [PATCH 336/390] Combine constants for readability --- osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHitExplosion.cs | 4 +++- osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyKeyArea.cs | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHitExplosion.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHitExplosion.cs index 73aece1ed4..9ec122a12c 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHitExplosion.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHitExplosion.cs @@ -17,6 +17,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy { public class LegacyHitExplosion : LegacyManiaColumnElement, IHitExplosion { + public const double FADE_IN_DURATION = 80; + private readonly IBindable direction = new Bindable(); private Drawable explosion; @@ -72,7 +74,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy (explosion as IFramedAnimation)?.GotoFrame(0); - explosion?.FadeInFromZero(80) + explosion?.FadeInFromZero(fade_in_duration) .Then().FadeOut(120); } } diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyKeyArea.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyKeyArea.cs index 174324f5f6..10319a7d4d 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyKeyArea.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyKeyArea.cs @@ -101,8 +101,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy { if (action == column.Action.Value) { - upSprite.Delay(80).FadeTo(1); - downSprite.Delay(80).FadeTo(0); + upSprite.Delay(LegacyHitExplosion.FADE_IN_DURATION).FadeTo(1); + downSprite.Delay(LegacyHitExplosion.FADE_IN_DURATION).FadeTo(0); } } } From 5b1d9f4cf07abd14860bc9ecf887fa98e55b4dc6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Mar 2021 16:19:29 +0900 Subject: [PATCH 337/390] Fix constant case --- osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHitExplosion.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHitExplosion.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHitExplosion.cs index 9ec122a12c..e4d466dca5 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHitExplosion.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHitExplosion.cs @@ -74,7 +74,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy (explosion as IFramedAnimation)?.GotoFrame(0); - explosion?.FadeInFromZero(fade_in_duration) + explosion?.FadeInFromZero(FADE_IN_DURATION) .Then().FadeOut(120); } } From e60ff45b73a2e236eed250f869a3b4afbafd3292 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Mar 2021 16:57:40 +0900 Subject: [PATCH 338/390] Add another test for colinear perfect curves --- .../colinear-perfect-curve-expected-conversion.json | 13 +++++++++++++ .../Testing/Beatmaps/colinear-perfect-curve.osu | 4 +++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/colinear-perfect-curve-expected-conversion.json b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/colinear-perfect-curve-expected-conversion.json index 96e4bf1637..1a0bd66246 100644 --- a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/colinear-perfect-curve-expected-conversion.json +++ b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/colinear-perfect-curve-expected-conversion.json @@ -1,5 +1,18 @@ { "Mappings": [{ + "StartTime": 114993, + "Objects": [{ + "StartTime": 114993, + "EndTime": 114993, + "X": 493, + "Y": 92 + }, { + "StartTime": 115290, + "EndTime": 115290, + "X": 451.659241, + "Y": 267.188 + }] + }, { "StartTime": 118858.0, "Objects": [{ "StartTime": 118858.0, diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/colinear-perfect-curve.osu b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/colinear-perfect-curve.osu index 8c3edc9571..dd35098502 100644 --- a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/colinear-perfect-curve.osu +++ b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/colinear-perfect-curve.osu @@ -9,7 +9,9 @@ SliderMultiplier:1.87 SliderTickRate:1 [TimingPoints] -49051,230.769230769231,4,2,1,15,1,0 +114000,346.820809248555,4,2,1,71,1,0 +118000,230.769230769231,4,2,1,15,1,0 [HitObjects] +493,92,114993,2,0,P|472:181|442:308,1,180,12|0,0:0|0:0,0:0:0:0: 219,215,118858,2,0,P|224:170|244:-10,1,187,8|2,0:0|0:0,0:0:0:0: From a65e491768441017e9cfe7e2e4fec2d15e57d708 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Mar 2021 20:00:36 +0100 Subject: [PATCH 339/390] Remove osuTK desktop rider run config No longer operational since 6eadae8. --- .../runConfigurations/osu___legacy_osuTK_.xml | 20 ------------------- 1 file changed, 20 deletions(-) delete mode 100644 .idea/.idea.osu.Desktop/.idea/runConfigurations/osu___legacy_osuTK_.xml diff --git a/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu___legacy_osuTK_.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu___legacy_osuTK_.xml deleted file mode 100644 index 9ece926b34..0000000000 --- a/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu___legacy_osuTK_.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - \ No newline at end of file From d85929d721254391d5e7e23d2e81d56180ee07f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Mar 2021 22:45:18 +0100 Subject: [PATCH 340/390] Adjust autoplay generation tests to match expected behaviour --- .../TestSceneAutoGeneration.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs index a5248c7712..399a46aa77 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Mania.Tests Assert.IsTrue(generated.Frames.Count == frame_offset + 2, "Replay must have 3 frames"); Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time"); - Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect release time"); + Assert.AreEqual(3000, generated.Frames[frame_offset + 1].Time, "Incorrect release time"); Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Special1), "Special1 has not been pressed"); Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Special1), "Special1 has not been released"); } @@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Mania.Tests Assert.IsTrue(generated.Frames.Count == frame_offset + 2, "Replay must have 3 frames"); Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time"); - Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect release time"); + Assert.AreEqual(3000, generated.Frames[frame_offset + 1].Time, "Incorrect release time"); Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed"); Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been released"); @@ -148,9 +148,9 @@ namespace osu.Game.Rulesets.Mania.Tests Assert.IsTrue(generated.Frames.Count == frame_offset + 4, "Replay must have 4 generated frames"); Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect first note hit time"); - Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 2].Time, "Incorrect first note release time"); + Assert.AreEqual(3000, generated.Frames[frame_offset + 2].Time, "Incorrect first note release time"); Assert.AreEqual(2000, generated.Frames[frame_offset + 1].Time, "Incorrect second note hit time"); - Assert.AreEqual(4000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 3].Time, "Incorrect second note release time"); + Assert.AreEqual(4000, generated.Frames[frame_offset + 3].Time, "Incorrect second note release time"); Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1), "Key1 has not been pressed"); Assert.IsTrue(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed"); Assert.IsFalse(checkContains(generated.Frames[frame_offset + 2], ManiaAction.Key1), "Key1 has not been released"); @@ -168,7 +168,7 @@ namespace osu.Game.Rulesets.Mania.Tests // | | | var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 }); - beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 - ManiaAutoGenerator.RELEASE_DELAY }); + beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 }); beatmap.HitObjects.Add(new Note { StartTime = 3000, Column = 1 }); var generated = new ManiaAutoGenerator(beatmap).Generate(); From 29d4162e4e2110973571a8fcfe83509e3586d50a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Mar 2021 22:38:51 +0100 Subject: [PATCH 341/390] Remove release delay for hold notes when generating autoplay It was more intended for normal notes anyway (as they would be released pretty much instantaneously, if it weren't for the delay). --- .../Replays/ManiaAutoGenerator.cs | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs index 3ebbe5af8e..7c51d58b74 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using osu.Game.Replays; using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Replays; @@ -85,20 +86,28 @@ namespace osu.Game.Rulesets.Mania.Replays { var currentObject = Beatmap.HitObjects[i]; var nextObjectInColumn = GetNextObject(i); // Get the next object that requires pressing the same button - - double endTime = currentObject.GetEndTime(); - - bool canDelayKeyUp = nextObjectInColumn == null || - nextObjectInColumn.StartTime > endTime + RELEASE_DELAY; - - double calculatedDelay = canDelayKeyUp ? RELEASE_DELAY : (nextObjectInColumn.StartTime - endTime) * 0.9; + var releaseTime = calculateReleaseTime(currentObject, nextObjectInColumn); yield return new HitPoint { Time = currentObject.StartTime, Column = currentObject.Column }; - yield return new ReleasePoint { Time = endTime + calculatedDelay, Column = currentObject.Column }; + yield return new ReleasePoint { Time = releaseTime, Column = currentObject.Column }; } } + private double calculateReleaseTime(HitObject currentObject, HitObject nextObject) + { + double endTime = currentObject.GetEndTime(); + + if (currentObject is HoldNote) + // hold note releases must be timed exactly. + return endTime; + + bool canDelayKeyUpFully = nextObject == null || + nextObject.StartTime > endTime + RELEASE_DELAY; + + return endTime + (canDelayKeyUpFully ? RELEASE_DELAY : (nextObject.StartTime - endTime) * 0.9); + } + protected override HitObject GetNextObject(int currentIndex) { int desiredColumn = Beatmap.HitObjects[currentIndex].Column; From 8ea7271d5c96b062bebcb8b702da739d9ef4dac1 Mon Sep 17 00:00:00 2001 From: Owen Young Date: Mon, 22 Mar 2021 19:48:52 -0500 Subject: [PATCH 342/390] moved windowmode code to LoadComplete (?) --- osu.Game/OsuGame.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 84737a56e4..ff215b63e5 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -234,9 +234,6 @@ namespace osu.Game SelectedMods.BindValueChanged(modsChanged); Beatmap.BindValueChanged(beatmapChanged, true); - - windowMode = LocalConfig.GetBindable(OsuSetting.WindowSetting); - frameworkConfig.GetBindable(FrameworkSetting.WindowMode).Value = windowMode.Value; } private ExternalLinkOpener externalLinkOpener; @@ -576,6 +573,9 @@ namespace osu.Game dependencies.CacheAs(idleTracker = new GameIdleTracker(6000)); + windowMode = LocalConfig.GetBindable(OsuSetting.WindowSetting); + frameworkConfig.GetBindable(FrameworkSetting.WindowMode).Value = windowMode.Value; + AddRange(new Drawable[] { new VolumeControlReceptor From bdcb9451f79798197825106a0762a0c823b771c7 Mon Sep 17 00:00:00 2001 From: Owen Young Date: Mon, 22 Mar 2021 20:17:05 -0500 Subject: [PATCH 343/390] added code to OsuGameBase to default to fullscreen, but that might not be a good place to put it. --- osu.Game/OsuGameBase.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index e1c7b67a8c..bcd384604f 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -21,6 +21,7 @@ using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Cursor; using osu.Game.Online.API; +using osu.Framework.Configuration; using osu.Framework.Graphics.Performance; using osu.Framework.Graphics.Textures; using osu.Framework.Input; @@ -119,6 +120,7 @@ namespace osu.Game protected Bindable Beatmap { get; private set; } // cached via load() method private Bindable fpsDisplayVisible; + private Bindable windowMode; public virtual Version AssemblyVersion => Assembly.GetEntryAssembly()?.GetName().Version ?? new Version(); @@ -361,6 +363,9 @@ namespace osu.Game fpsDisplayVisible.ValueChanged += visible => { FrameStatistics.Value = visible.NewValue ? FrameStatisticsMode.Minimal : FrameStatisticsMode.None; }; fpsDisplayVisible.TriggerChange(); + windowMode = LocalConfig.GetBindable(OsuSetting.WindowSetting); + windowMode.Value = WindowMode.Fullscreen; + FrameStatistics.ValueChanged += e => fpsDisplayVisible.Value = e.NewValue != FrameStatisticsMode.None; } From 098005393e999f5623769adc9e5458bb86e37513 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Mar 2021 10:38:37 +0900 Subject: [PATCH 344/390] Remove unnecessary null checks and debug code --- .../Objects/Drawables/DrawableSpinner.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index d92f63eb89..32a0a14dc0 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -133,15 +133,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (tracking.NewValue) { if (!spinningSample.IsPlaying) - spinningSample?.Play(); - spinningSample?.VolumeTo(1, 300); + spinningSample.Play(); + + spinningSample.VolumeTo(1, 300); } else { - if (spinningSample != null) - spinningSample.Volume.Value = 0; - - spinningSample?.VolumeTo(0, fade_out_duration); + spinningSample.VolumeTo(0, fade_out_duration); } } From 16b3f22caf2754d97aacbf40f0d429e8b3cecdce Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 22 Mar 2021 19:32:17 -0700 Subject: [PATCH 345/390] Fix incorrect trash icon being used on deleted comments counter --- osu.Game/Overlays/Comments/DeletedCommentsCounter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Comments/DeletedCommentsCounter.cs b/osu.Game/Overlays/Comments/DeletedCommentsCounter.cs index 56588ef0a8..8c40d79f7a 100644 --- a/osu.Game/Overlays/Comments/DeletedCommentsCounter.cs +++ b/osu.Game/Overlays/Comments/DeletedCommentsCounter.cs @@ -32,7 +32,7 @@ namespace osu.Game.Overlays.Comments { new SpriteIcon { - Icon = FontAwesome.Solid.Trash, + Icon = FontAwesome.Regular.TrashAlt, Size = new Vector2(14), }, countText = new OsuSpriteText From 9f788f58548d629144c4b8946b74542b424ea7a4 Mon Sep 17 00:00:00 2001 From: Owen Young Date: Mon, 22 Mar 2021 22:52:16 -0500 Subject: [PATCH 346/390] removed code from OsuGameBase for fullscreen.....OsuSetting still exists but cannot figure out a way to set it to a default and have it actually work --- osu.Game/OsuGame.cs | 6 +++--- osu.Game/OsuGameBase.cs | 4 ---- .../Overlays/Settings/Sections/Graphics/LayoutSettings.cs | 6 ++++-- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index ff215b63e5..2f2428e781 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -126,7 +126,7 @@ namespace osu.Game private Bindable configSkin; - private Bindable windowMode; + // private Bindable windowMode; private readonly string[] args; @@ -573,8 +573,8 @@ namespace osu.Game dependencies.CacheAs(idleTracker = new GameIdleTracker(6000)); - windowMode = LocalConfig.GetBindable(OsuSetting.WindowSetting); - frameworkConfig.GetBindable(FrameworkSetting.WindowMode).Value = windowMode.Value; + // windowMode = LocalConfig.GetBindable(OsuSetting.WindowSetting); + // frameworkConfig.GetBindable(FrameworkSetting.WindowMode).Value = windowMode.Value; AddRange(new Drawable[] { diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index bcd384604f..8b1fe20708 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -120,7 +120,6 @@ namespace osu.Game protected Bindable Beatmap { get; private set; } // cached via load() method private Bindable fpsDisplayVisible; - private Bindable windowMode; public virtual Version AssemblyVersion => Assembly.GetEntryAssembly()?.GetName().Version ?? new Version(); @@ -363,9 +362,6 @@ namespace osu.Game fpsDisplayVisible.ValueChanged += visible => { FrameStatistics.Value = visible.NewValue ? FrameStatisticsMode.Minimal : FrameStatisticsMode.None; }; fpsDisplayVisible.TriggerChange(); - windowMode = LocalConfig.GetBindable(OsuSetting.WindowSetting); - windowMode.Value = WindowMode.Fullscreen; - FrameStatistics.ValueChanged += e => fpsDisplayVisible.Value = e.NewValue != FrameStatisticsMode.None; } diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index ab662cb9a0..a0cb8fc2de 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Localisation; +using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Configuration; using osu.Game.Graphics.Containers; @@ -57,7 +58,8 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics scalingSizeY = osuConfig.GetBindable(OsuSetting.ScalingSizeY); scalingPositionX = osuConfig.GetBindable(OsuSetting.ScalingPositionX); scalingPositionY = osuConfig.GetBindable(OsuSetting.ScalingPositionY); - windowMode = osuConfig.GetBindable(OsuSetting.WindowSetting); + windowMode = config.GetBindable(FrameworkSetting.WindowMode); + Logger.Log($"windowMode {windowMode.Value}"); if (host.Window != null) { @@ -71,7 +73,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics { LabelText = "Screen mode", ItemSource = windowModes, - Current = config.GetBindable(FrameworkSetting.WindowMode), + Current = config.GetBindable(FrameworkSetting.WindowMode) }, resolutionDropdown = new ResolutionSettingsDropdown { From d9e2c44a34c17d9ed0f93587460fd506480ad478 Mon Sep 17 00:00:00 2001 From: Owen Young Date: Mon, 22 Mar 2021 23:36:55 -0500 Subject: [PATCH 347/390] implemented GetFrameworkConfigDefaults for overriding framework default, removed previous code that added a new OsuSetting and modified settings layout. --- osu.Game/Configuration/OsuConfigManager.cs | 3 --- osu.Game/OsuGame.cs | 11 ++++++----- osu.Game/OsuGameBase.cs | 1 - .../Settings/Sections/Graphics/LayoutSettings.cs | 11 ++--------- 4 files changed, 8 insertions(+), 18 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index cd74fe25f4..d0fa45bb7a 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -91,8 +91,6 @@ namespace osu.Game.Configuration Set(OsuSetting.MenuParallax, true); - Set(OsuSetting.WindowSetting, WindowMode.Fullscreen); - // Gameplay Set(OsuSetting.DimLevel, 0.8, 0, 1, 0.01); Set(OsuSetting.BlurLevel, 0, 0, 1, 0.01); @@ -235,7 +233,6 @@ namespace osu.Game.Configuration MenuVoice, CursorRotation, MenuParallax, - WindowSetting, BeatmapDetailTab, BeatmapDetailModsFilter, Username, diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 2f2428e781..ffb694c27e 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -126,8 +126,6 @@ namespace osu.Game private Bindable configSkin; - // private Bindable windowMode; - private readonly string[] args; private readonly List overlays = new List(); @@ -573,9 +571,6 @@ namespace osu.Game dependencies.CacheAs(idleTracker = new GameIdleTracker(6000)); - // windowMode = LocalConfig.GetBindable(OsuSetting.WindowSetting); - // frameworkConfig.GetBindable(FrameworkSetting.WindowMode).Value = windowMode.Value; - AddRange(new Drawable[] { new VolumeControlReceptor @@ -1012,5 +1007,11 @@ namespace osu.Game if (newScreen == null) Exit(); } + + protected override IDictionary GetFrameworkConfigDefaults() { + IDictionary defaultOverrides = new Dictionary(); + defaultOverrides.Add(FrameworkSetting.WindowMode, WindowMode.Fullscreen); + return defaultOverrides; + } } } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 8b1fe20708..e1c7b67a8c 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -21,7 +21,6 @@ using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Cursor; using osu.Game.Online.API; -using osu.Framework.Configuration; using osu.Framework.Graphics.Performance; using osu.Framework.Graphics.Textures; using osu.Framework.Input; diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index a0cb8fc2de..4d5c2e06eb 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -12,7 +12,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Localisation; -using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Configuration; using osu.Game.Graphics.Containers; @@ -32,7 +31,6 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics private Bindable scalingMode; private Bindable sizeFullscreen; - private Bindable windowMode; private readonly BindableList resolutions = new BindableList(new[] { new Size(9999, 9999) }); @@ -58,8 +56,6 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics scalingSizeY = osuConfig.GetBindable(OsuSetting.ScalingSizeY); scalingPositionX = osuConfig.GetBindable(OsuSetting.ScalingPositionX); scalingPositionY = osuConfig.GetBindable(OsuSetting.ScalingPositionY); - windowMode = config.GetBindable(FrameworkSetting.WindowMode); - Logger.Log($"windowMode {windowMode.Value}"); if (host.Window != null) { @@ -73,7 +69,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics { LabelText = "Screen mode", ItemSource = windowModes, - Current = config.GetBindable(FrameworkSetting.WindowMode) + Current = config.GetBindable(FrameworkSetting.WindowMode), }, resolutionDropdown = new ResolutionSettingsDropdown { @@ -145,10 +141,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics scalingSettings.ForEach(s => bindPreviewEvent(s.Current)); - windowModeDropdown.Current.ValueChanged += mode => { - windowMode.Value = mode.NewValue; - updateResolutionDropdown(); - }; + windowModeDropdown.Current.ValueChanged += _ => updateResolutionDropdown(); windowModes.BindCollectionChanged((sender, args) => { From 58c60100b431e6ce6ee720ff72206e9f9071d070 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Mar 2021 14:04:47 +0900 Subject: [PATCH 348/390] Fix APIScoreToken's data type not matching server side --- osu.Game/Online/Rooms/APIScoreToken.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Rooms/APIScoreToken.cs b/osu.Game/Online/Rooms/APIScoreToken.cs index f652c1720d..6b559876de 100644 --- a/osu.Game/Online/Rooms/APIScoreToken.cs +++ b/osu.Game/Online/Rooms/APIScoreToken.cs @@ -8,6 +8,6 @@ namespace osu.Game.Online.Rooms public class APIScoreToken { [JsonProperty("id")] - public int ID { get; set; } + public long ID { get; set; } } } From 9c690f9545959c117b4358e391333b3882abb34c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Mar 2021 14:08:00 +0900 Subject: [PATCH 349/390] Fix second usage --- osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs index a75e4bdc07..2b6dbd9dcb 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs @@ -28,7 +28,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists protected readonly PlaylistItem PlaylistItem; - protected int? Token { get; private set; } + protected long? Token { get; private set; } [Resolved] private IAPIProvider api { get; set; } From 254b0f5dc3b2469fdccf6baf0231af30baf11c9b Mon Sep 17 00:00:00 2001 From: Owen Young Date: Tue, 23 Mar 2021 00:24:33 -0500 Subject: [PATCH 350/390] removed line (?) - tried doing testing to see if it launched in fullscreen (i.e., overriding the method ppy mentioned), but to no avail :( --- osu.Game/OsuGame.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index ffb694c27e..a52899433a 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1007,7 +1007,6 @@ namespace osu.Game if (newScreen == null) Exit(); } - protected override IDictionary GetFrameworkConfigDefaults() { IDictionary defaultOverrides = new Dictionary(); defaultOverrides.Add(FrameworkSetting.WindowMode, WindowMode.Fullscreen); From 1171214541361b72ae23405a3549fd918af16fed Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Mar 2021 14:51:22 +0900 Subject: [PATCH 351/390] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index e0392bd687..75ac298626 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 9731c1d5ea..b90c938a8b 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -29,7 +29,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 11677d345e..ce182a3054 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 08fcdc8ee46748eee4c8d66d5842b57f16d9d896 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Mar 2021 15:38:00 +0900 Subject: [PATCH 352/390] Update difficulty calculator tests with floating point differences --- .../OsuDifficultyCalculatorTest.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs index a365ea10d4..c2119585ab 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs @@ -15,13 +15,13 @@ namespace osu.Game.Rulesets.Osu.Tests { protected override string ResourceAssembly => "osu.Game.Rulesets.Osu"; - [TestCase(6.9311451172608853d, "diffcalc-test")] - [TestCase(1.0736587013228804d, "zero-length-sliders")] + [TestCase(6.9311451172574934d, "diffcalc-test")] + [TestCase(1.0736586907780401d, "zero-length-sliders")] public void Test(double expected, string name) => base.Test(expected, name); - [TestCase(8.6228371119393064d, "diffcalc-test")] - [TestCase(1.2864585434597433d, "zero-length-sliders")] + [TestCase(8.6228371119271454d, "diffcalc-test")] + [TestCase(1.2864585280364178d, "zero-length-sliders")] public void TestClockRateAdjusted(double expected, string name) => Test(expected, name, new OsuModDoubleTime()); From f5ba746ae5522e6c1ba34f6a34218491ff4aa626 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Mar 2021 17:31:28 +0900 Subject: [PATCH 353/390] Fail all API requests sent to DummyAPIAccess Until now, API requests sent to dummy API were just lost in the void. In most cases this somehow worked as expected, but any logic which is waiting on a request to finish will potentially never get a response. Going forward, I'm not 100% sure that every `Wait` on a web response will have local timeout logic (I think there is a certain amount of assumption that this is being managed for us by `APIAccess`), so I've made this change to better handle such cases going forward. Now, rather than nothing happening, requests will trigger a failure via the existing exception logic rather than silently pretending the request never arrived. --- osu.Game/Online/API/APIRequest.cs | 8 ++++++-- osu.Game/Online/API/DummyAPIAccess.cs | 7 ++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs index a7174324d8..16d1b3ab17 100644 --- a/osu.Game/Online/API/APIRequest.cs +++ b/osu.Game/Online/API/APIRequest.cs @@ -181,9 +181,13 @@ namespace osu.Game.Online.API /// Whether we are in a failed or cancelled state. private bool checkAndScheduleFailure() { - if (API == null || pendingFailure == null) return cancelled; + if (pendingFailure == null) return cancelled; + + if (API == null) + pendingFailure(); + else + API.Schedule(pendingFailure); - API.Schedule(pendingFailure); pendingFailure = null; return true; } diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index 943b52db88..3cb22381c1 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -55,7 +55,12 @@ namespace osu.Game.Online.API public virtual void Queue(APIRequest request) { - HandleRequest?.Invoke(request); + if (HandleRequest != null) + HandleRequest.Invoke(request); + else + // this will fail due to not receiving an APIAccess, and trigger a failure on the request. + // this is intended - any request in testing that needs non-failures should use HandleRequest. + request.Perform(this); } public void Perform(APIRequest request) => HandleRequest?.Invoke(request); From ce452565f45bba3106ffe89a21c63d4662e8a40e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Mar 2021 17:50:31 +0900 Subject: [PATCH 354/390] Avoid firing any kind of failures after success --- osu.Game/Online/API/APIRequest.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs index 16d1b3ab17..1a6868cfa4 100644 --- a/osu.Game/Online/API/APIRequest.cs +++ b/osu.Game/Online/API/APIRequest.cs @@ -131,8 +131,11 @@ namespace osu.Game.Online.API { } + private bool succeeded; + internal virtual void TriggerSuccess() { + succeeded = true; Success?.Invoke(); } @@ -145,10 +148,7 @@ namespace osu.Game.Online.API public void Fail(Exception e) { - if (WebRequest?.Completed == true) - return; - - if (cancelled) + if (succeeded || cancelled) return; cancelled = true; From aeff9bd8531962e7d509623407a819f3c7f69ae8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Mar 2021 18:08:32 +0900 Subject: [PATCH 355/390] Add return bool to HandleRequest to better trigger failures --- .../Online/TestDummyAPIRequestHandling.cs | 8 ++++++-- .../TestSceneSeasonalBackgroundLoader.cs | 4 +++- .../Online/TestSceneBeatmapListingOverlay.cs | 13 ++++++------ .../Online/TestSceneChangelogOverlay.cs | 6 ++++-- .../Visual/Online/TestSceneChatOverlay.cs | 20 +++++++++++++++++++ .../Online/TestSceneCommentsContainer.cs | 3 ++- .../Visual/Online/TestSceneNewsOverlay.cs | 3 ++- .../TestScenePlaylistsResultsScreen.cs | 13 ++++++++++++ .../TestSceneBeatmapRecommendations.cs | 4 +++- osu.Game/Online/API/DummyAPIAccess.cs | 9 +++++---- .../Multiplayer/TestMultiplayerRoomManager.cs | 18 +++++++++-------- 11 files changed, 75 insertions(+), 26 deletions(-) diff --git a/osu.Game.Tests/Online/TestDummyAPIRequestHandling.cs b/osu.Game.Tests/Online/TestDummyAPIRequestHandling.cs index 42948c3731..aa29d76843 100644 --- a/osu.Game.Tests/Online/TestDummyAPIRequestHandling.cs +++ b/osu.Game.Tests/Online/TestDummyAPIRequestHandling.cs @@ -23,8 +23,10 @@ namespace osu.Game.Tests.Online { case CommentVoteRequest cRequest: cRequest.TriggerSuccess(new CommentBundle()); - break; + return true; } + + return false; }); CommentVoteRequest request = null; @@ -108,8 +110,10 @@ namespace osu.Game.Tests.Online { case LeaveChannelRequest cRequest: cRequest.TriggerSuccess(); - break; + return true; } + + return false; }); } } diff --git a/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs b/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs index e7cf830db0..dc5a4f4a3e 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs @@ -135,13 +135,15 @@ namespace osu.Game.Tests.Visual.Background dummyAPI.HandleRequest = request => { if (dummyAPI.State.Value != APIState.Online || !(request is GetSeasonalBackgroundsRequest backgroundsRequest)) - return; + return false; backgroundsRequest.TriggerSuccess(new APISeasonalBackgrounds { Backgrounds = seasonal_background_urls.Select(url => new APISeasonalBackground { Url = url }).ToList(), EndDate = endDate }); + + return true; }; }); diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index 1349264bf9..156d6b744e 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -30,13 +30,14 @@ namespace osu.Game.Tests.Visual.Online ((DummyAPIAccess)API).HandleRequest = req => { - if (req is SearchBeatmapSetsRequest searchBeatmapSetsRequest) + if (!(req is SearchBeatmapSetsRequest searchBeatmapSetsRequest)) return false; + + searchBeatmapSetsRequest.TriggerSuccess(new SearchBeatmapSetsResponse { - searchBeatmapSetsRequest.TriggerSuccess(new SearchBeatmapSetsResponse - { - BeatmapSets = setsForResponse, - }); - } + BeatmapSets = setsForResponse, + }); + + return true; }; } diff --git a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs index cd2c4e9346..8818ac75b1 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs @@ -63,13 +63,15 @@ namespace osu.Game.Tests.Visual.Online Builds = builds.Values.ToList() }; changelogRequest.TriggerSuccess(changelogResponse); - break; + return true; case GetChangelogBuildRequest buildRequest: if (requestedBuild != null) buildRequest.TriggerSuccess(requestedBuild); - break; + return true; } + + return false; }; Child = changelog = new TestChangelogOverlay(); diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index fca642ad6c..b13dd34ebc 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -11,6 +11,8 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using osu.Framework.Testing; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; using osu.Game.Online.Chat; using osu.Game.Overlays; using osu.Game.Overlays.Chat.Selection; @@ -64,6 +66,24 @@ namespace osu.Game.Tests.Visual.Online }); } + [SetUpSteps] + public void SetUpSteps() + { + AddStep("register request handling", () => + { + ((DummyAPIAccess)API).HandleRequest = req => + { + switch (req) + { + case JoinChannelRequest _: + return true; + } + + return false; + }; + }); + } + [Test] public void TestHideOverlay() { diff --git a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs index c2a18330c9..cd22bb2513 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs @@ -85,9 +85,10 @@ namespace osu.Game.Tests.Visual.Online dummyAPI.HandleRequest = request => { if (!(request is GetCommentsRequest getCommentsRequest)) - return; + return false; getCommentsRequest.TriggerSuccess(commentBundle); + return true; }; }); diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs index 37d51c16d2..6ebe8fcc07 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs @@ -33,9 +33,10 @@ namespace osu.Game.Tests.Visual.Online dummyAPI.HandleRequest = request => { if (!(request is GetNewsRequest getNewsRequest)) - return; + return false; getNewsRequest.TriggerSuccess(r); + return true; }; }); diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index be8032cde8..61d49e4018 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -170,6 +170,17 @@ namespace osu.Game.Tests.Visual.Playlists private void bindHandler(bool delayed = false, ScoreInfo userScore = null, bool failRequests = false) => ((DummyAPIAccess)API).HandleRequest = request => { + // pre-check for requests we should be handling (as they are scheduled below). + switch (request) + { + case ShowPlaylistUserScoreRequest _: + case IndexPlaylistScoresRequest _: + break; + + default: + return false; + } + requestComplete = false; double delay = delayed ? 3000 : 0; @@ -196,6 +207,8 @@ namespace osu.Game.Tests.Visual.Playlists break; } }, delay); + + return true; }; private void triggerSuccess(APIRequest req, T result) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs index 53a956c77c..5e2d5eba5d 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs @@ -32,8 +32,10 @@ namespace osu.Game.Tests.Visual.SongSelect { case GetUserRequest userRequest: userRequest.TriggerSuccess(getUser(userRequest.Ruleset.ID)); - break; + return true; } + + return false; }; }); diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index 3cb22381c1..52f2365165 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -34,8 +34,9 @@ namespace osu.Game.Online.API /// /// Provide handling logic for an arbitrary API request. + /// Should return true is a request was handled. If null or false return, the request will be failed with a . /// - public Action HandleRequest; + public Func HandleRequest; private readonly Bindable state = new Bindable(APIState.Online); @@ -55,12 +56,12 @@ namespace osu.Game.Online.API public virtual void Queue(APIRequest request) { - if (HandleRequest != null) - HandleRequest.Invoke(request); - else + if (HandleRequest?.Invoke(request) != true) + { // this will fail due to not receiving an APIAccess, and trigger a failure on the request. // this is intended - any request in testing that needs non-failures should use HandleRequest. request.Perform(this); + } } public void Perform(APIRequest request) => HandleRequest?.Invoke(request); diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs index 7e824c4d7c..315be510a3 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs @@ -52,15 +52,15 @@ namespace osu.Game.Tests.Visual.Multiplayer Rooms.Add(createdRoom); createRoomRequest.TriggerSuccess(createdRoom); - break; + return true; case JoinRoomRequest joinRoomRequest: joinRoomRequest.TriggerSuccess(); - break; + return true; case PartRoomRequest partRoomRequest: partRoomRequest.TriggerSuccess(); - break; + return true; case GetRoomsRequest getRoomsRequest: var roomsWithoutParticipants = new List(); @@ -76,11 +76,11 @@ namespace osu.Game.Tests.Visual.Multiplayer } getRoomsRequest.TriggerSuccess(roomsWithoutParticipants); - break; + return true; case GetRoomRequest getRoomRequest: getRoomRequest.TriggerSuccess(Rooms.Single(r => r.RoomID.Value == getRoomRequest.RoomId)); - break; + return true; case GetBeatmapSetRequest getBeatmapSetRequest: var onlineReq = new GetBeatmapSetRequest(getBeatmapSetRequest.ID, getBeatmapSetRequest.Type); @@ -89,11 +89,11 @@ namespace osu.Game.Tests.Visual.Multiplayer // Get the online API from the game's dependencies. game.Dependencies.Get().Queue(onlineReq); - break; + return true; case CreateRoomScoreRequest createRoomScoreRequest: createRoomScoreRequest.TriggerSuccess(new APIScoreToken { ID = 1 }); - break; + return true; case SubmitRoomScoreRequest submitRoomScoreRequest: submitRoomScoreRequest.TriggerSuccess(new MultiplayerScore @@ -108,8 +108,10 @@ namespace osu.Game.Tests.Visual.Multiplayer User = api.LocalUser.Value, Statistics = new Dictionary() }); - break; + return true; } + + return false; }; } From 5267fb74c441c1c7f6ba48637f6981876ae5227e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Mar 2021 14:44:06 +0900 Subject: [PATCH 356/390] Add submission requests --- .../Online/Solo/CreateSoloScoreRequest.cs | 32 +++++++++++++ .../Online/Solo/SubmitSoloScoreRequest.cs | 45 +++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 osu.Game/Online/Solo/CreateSoloScoreRequest.cs create mode 100644 osu.Game/Online/Solo/SubmitSoloScoreRequest.cs diff --git a/osu.Game/Online/Solo/CreateSoloScoreRequest.cs b/osu.Game/Online/Solo/CreateSoloScoreRequest.cs new file mode 100644 index 0000000000..ae5ac5e26c --- /dev/null +++ b/osu.Game/Online/Solo/CreateSoloScoreRequest.cs @@ -0,0 +1,32 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Net.Http; +using osu.Framework.IO.Network; +using osu.Game.Online.API; +using osu.Game.Online.Rooms; + +namespace osu.Game.Online.Solo +{ + public class CreateSoloScoreRequest : APIRequest + { + private readonly int beatmapId; + private readonly string versionHash; + + public CreateSoloScoreRequest(int beatmapId, string versionHash) + { + this.beatmapId = beatmapId; + this.versionHash = versionHash; + } + + protected override WebRequest CreateWebRequest() + { + var req = base.CreateWebRequest(); + req.Method = HttpMethod.Post; + req.AddParameter("version_hash", versionHash); + return req; + } + + protected override string Target => $@"solo/{beatmapId}/scores"; + } +} diff --git a/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs b/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs new file mode 100644 index 0000000000..98ba4fa052 --- /dev/null +++ b/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs @@ -0,0 +1,45 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Net.Http; +using Newtonsoft.Json; +using osu.Framework.IO.Network; +using osu.Game.Online.API; +using osu.Game.Online.Rooms; +using osu.Game.Scoring; + +namespace osu.Game.Online.Solo +{ + public class SubmitSoloScoreRequest : APIRequest + { + private readonly long scoreId; + + private readonly int beatmapId; + + private readonly ScoreInfo scoreInfo; + + public SubmitSoloScoreRequest(int beatmapId, long scoreId, ScoreInfo scoreInfo) + { + this.beatmapId = beatmapId; + this.scoreId = scoreId; + this.scoreInfo = scoreInfo; + } + + protected override WebRequest CreateWebRequest() + { + var req = base.CreateWebRequest(); + + req.ContentType = "application/json"; + req.Method = HttpMethod.Put; + + req.AddRaw(JsonConvert.SerializeObject(scoreInfo, new JsonSerializerSettings + { + ReferenceLoopHandling = ReferenceLoopHandling.Ignore + })); + + return req; + } + + protected override string Target => $@"solo/{beatmapId}/scores/{scoreId}"; + } +} From 6cb14e91c91043e1e3ddf0d2c999b9665def1d65 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Mar 2021 14:47:15 +0900 Subject: [PATCH 357/390] Make Player abstract and introduce SoloPlayer --- .../Visual/Navigation/TestScenePerformFromScreen.cs | 4 ++-- osu.Game/Screens/Play/Player.cs | 4 ++-- osu.Game/Screens/Play/SoloPlayer.cs | 9 +++++++++ osu.Game/Screens/Select/PlaySongSelect.cs | 2 +- 4 files changed, 14 insertions(+), 5 deletions(-) create mode 100644 osu.Game/Screens/Play/SoloPlayer.cs diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs b/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs index 21d3bdaae3..2791952b66 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs @@ -58,7 +58,7 @@ namespace osu.Game.Tests.Visual.Navigation public void TestPerformAtSongSelectFromPlayerLoader() { PushAndConfirm(() => new PlaySongSelect()); - PushAndConfirm(() => new PlayerLoader(() => new Player())); + PushAndConfirm(() => new PlayerLoader(() => new SoloPlayer())); AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true, new[] { typeof(PlaySongSelect) })); AddUntilStep("returned to song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect); @@ -69,7 +69,7 @@ namespace osu.Game.Tests.Visual.Navigation public void TestPerformAtMenuFromPlayerLoader() { PushAndConfirm(() => new PlaySongSelect()); - PushAndConfirm(() => new PlayerLoader(() => new Player())); + PushAndConfirm(() => new PlayerLoader(() => new SoloPlayer())); AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true)); AddUntilStep("returned to song select", () => Game.ScreenStack.CurrentScreen is MainMenu); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 0e221351aa..4cf0274614 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -39,7 +39,7 @@ namespace osu.Game.Screens.Play { [Cached] [Cached(typeof(ISamplePlaybackDisabler))] - public class Player : ScreenWithBeatmapBackground, ISamplePlaybackDisabler + public abstract class Player : ScreenWithBeatmapBackground, ISamplePlaybackDisabler { /// /// The delay upon completion of the beatmap before displaying the results screen. @@ -135,7 +135,7 @@ namespace osu.Game.Screens.Play /// /// Create a new player instance. /// - public Player(PlayerConfiguration configuration = null) + protected Player(PlayerConfiguration configuration = null) { Configuration = configuration ?? new PlayerConfiguration(); } diff --git a/osu.Game/Screens/Play/SoloPlayer.cs b/osu.Game/Screens/Play/SoloPlayer.cs new file mode 100644 index 0000000000..79860f3eda --- /dev/null +++ b/osu.Game/Screens/Play/SoloPlayer.cs @@ -0,0 +1,9 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Screens.Play +{ + public class SoloPlayer : Player + { + } +} diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index e61d5cce85..dfb4b59060 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -106,7 +106,7 @@ namespace osu.Game.Screens.Select SampleConfirm?.Play(); - this.Push(player = new PlayerLoader(() => new Player())); + this.Push(player = new PlayerLoader(() => new SoloPlayer())); return true; } From 7045fce55542b0f56a5907f0a87532bfef0728f3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Mar 2021 15:00:02 +0900 Subject: [PATCH 358/390] Move score submission logic in general out to its own Player type --- .../Multiplayer/MultiplayerPlayer.cs | 1 - .../OnlinePlay/Playlists/PlaylistsPlayer.cs | 74 ++----------- osu.Game/Screens/Play/SubmittingPlayer.cs | 100 ++++++++++++++++++ 3 files changed, 106 insertions(+), 69 deletions(-) create mode 100644 osu.Game/Screens/Play/SubmittingPlayer.cs diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs index b3cd44d55a..ef34d40497 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs @@ -19,7 +19,6 @@ using osuTK; namespace osu.Game.Screens.OnlinePlay.Multiplayer { - // Todo: The "room" part of PlaylistsPlayer should be split out into an abstract player class to be inherited instead. public class MultiplayerPlayer : PlaylistsPlayer { protected override bool PauseOnFocusLost => false; diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs index 2b6dbd9dcb..a6aff5e43f 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs @@ -4,11 +4,8 @@ using System; using System.Diagnostics; using System.Linq; -using System.Threading; -using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Logging; using osu.Framework.Screens; using osu.Game.Online.API; using osu.Game.Online.Rooms; @@ -19,23 +16,12 @@ using osu.Game.Screens.Ranking; namespace osu.Game.Screens.OnlinePlay.Playlists { - public class PlaylistsPlayer : Player + public class PlaylistsPlayer : RoomSubmittingPlayer { public Action Exited; - [Resolved(typeof(Room), nameof(Room.RoomID))] - protected Bindable RoomId { get; private set; } - protected readonly PlaylistItem PlaylistItem; - protected long? Token { get; private set; } - - [Resolved] - private IAPIProvider api { get; set; } - - [Resolved] - private IBindable ruleset { get; set; } - public PlaylistsPlayer(PlaylistItem playlistItem, PlayerConfiguration configuration = null) : base(configuration) { @@ -43,12 +29,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists } [BackgroundDependencyLoader] - private void load() + private void load(IBindable ruleset) { - Token = null; - - bool failed = false; - // Sanity checks to ensure that PlaylistsPlayer matches the settings for the current PlaylistItem if (Beatmap.Value.BeatmapInfo.OnlineBeatmapID != PlaylistItem.Beatmap.Value.OnlineBeatmapID) throw new InvalidOperationException("Current Beatmap does not match PlaylistItem's Beatmap"); @@ -58,31 +40,12 @@ namespace osu.Game.Screens.OnlinePlay.Playlists if (!PlaylistItem.RequiredMods.All(m => Mods.Value.Any(m.Equals))) throw new InvalidOperationException("Current Mods do not match PlaylistItem's RequiredMods"); - - var req = new CreateRoomScoreRequest(RoomId.Value ?? 0, PlaylistItem.ID, Game.VersionHash); - req.Success += r => Token = r.ID; - req.Failure += e => - { - failed = true; - - if (string.IsNullOrEmpty(e.Message)) - Logger.Error(e, "Failed to retrieve a score submission token."); - else - Logger.Log($"You are not able to submit a score: {e.Message}", level: LogLevel.Important); - - Schedule(() => - { - ValidForResume = false; - this.Exit(); - }); - }; - - api.Queue(req); - - while (!failed && !Token.HasValue) - Thread.Sleep(1000); } + protected override APIRequest CreateTokenRequestRequest() => new CreateRoomScoreRequest(RoomId.Value ?? 0, PlaylistItem.ID, Game.VersionHash); + + public override APIRequest CreateSubmissionRequest(Score score, int token) => new SubmitRoomScoreRequest(token, RoomId.Value ?? 0, PlaylistItem.ID, score.ScoreInfo); + public override bool OnExiting(IScreen next) { if (base.OnExiting(next)) @@ -106,31 +69,6 @@ namespace osu.Game.Screens.OnlinePlay.Playlists return score; } - protected override async Task SubmitScore(Score score) - { - await base.SubmitScore(score).ConfigureAwait(false); - - Debug.Assert(Token != null); - - var tcs = new TaskCompletionSource(); - var request = new SubmitRoomScoreRequest(Token.Value, RoomId.Value ?? 0, PlaylistItem.ID, score.ScoreInfo); - - request.Success += s => - { - score.ScoreInfo.OnlineScoreID = s.ID; - tcs.SetResult(true); - }; - - request.Failure += e => - { - Logger.Error(e, "Failed to submit score"); - tcs.SetResult(false); - }; - - api.Queue(request); - await tcs.Task.ConfigureAwait(false); - } - protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs new file mode 100644 index 0000000000..250e308b3c --- /dev/null +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -0,0 +1,100 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Logging; +using osu.Framework.Screens; +using osu.Game.Online.API; +using osu.Game.Online.Rooms; +using osu.Game.Scoring; + +namespace osu.Game.Screens.Play +{ + public abstract class RoomSubmittingPlayer : SubmittingPlayer + { + [Resolved(typeof(Room), nameof(Room.RoomID))] + protected Bindable RoomId { get; private set; } + + protected RoomSubmittingPlayer(PlayerConfiguration configuration) + : base(configuration) + { + } + } + + public abstract class SubmittingPlayer : Player + { + protected long? Token { get; private set; } + + [Resolved] + private IAPIProvider api { get; set; } + + protected SubmittingPlayer(PlayerConfiguration configuration) + : base(configuration) + { + } + + [BackgroundDependencyLoader] + private void load() + { + Token = null; + + bool failed = false; + + var req = CreateTokenRequestRequest(); + req.Success += r => Token = r.ID; + req.Failure += e => + { + failed = true; + + if (string.IsNullOrEmpty(e.Message)) + Logger.Error(e, "Failed to retrieve a score submission token."); + else + Logger.Log($"You are not able to submit a score: {e.Message}", level: LogLevel.Important); + + Schedule(() => + { + ValidForResume = false; + this.Exit(); + }); + }; + + api.Queue(req); + + while (!failed && !Token.HasValue) + Thread.Sleep(1000); + } + + protected override async Task SubmitScore(Score score) + { + await base.SubmitScore(score).ConfigureAwait(false); + + Debug.Assert(Token != null); + + var tcs = new TaskCompletionSource(); + var request = CreateSubmissionRequest(score, Token.Value); + + request.Success += s => + { + score.ScoreInfo.OnlineScoreID = s.ID; + tcs.SetResult(true); + }; + + request.Failure += e => + { + Logger.Error(e, "Failed to submit score"); + tcs.SetResult(false); + }; + + api.Queue(request); + await tcs.Task.ConfigureAwait(false); + } + + protected abstract APIRequest CreateSubmissionRequest(Score score, long token); + + protected abstract APIRequest CreateTokenRequestRequest(); + } +} From 12f050264aa31af09089a32d23472defe67a0ec5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Mar 2021 15:33:31 +0900 Subject: [PATCH 359/390] Further split out a player class which submits to "rooms" --- .../Multiplayer/MultiplayerPlayer.cs | 3 +- .../OnlinePlay/Playlists/PlaylistsPlayer.cs | 10 +----- osu.Game/Screens/Play/RoomSubmittingPlayer.cs | 32 +++++++++++++++++++ osu.Game/Screens/Play/SubmittingPlayer.cs | 15 ++------- 4 files changed, 37 insertions(+), 23 deletions(-) create mode 100644 osu.Game/Screens/Play/RoomSubmittingPlayer.cs diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs index ef34d40497..2ba04b75d6 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs @@ -11,7 +11,6 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; using osu.Game.Scoring; -using osu.Game.Screens.OnlinePlay.Playlists; using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Ranking; @@ -19,7 +18,7 @@ using osuTK; namespace osu.Game.Screens.OnlinePlay.Multiplayer { - public class MultiplayerPlayer : PlaylistsPlayer + public class MultiplayerPlayer : RoomSubmittingPlayer { protected override bool PauseOnFocusLost => false; diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs index a6aff5e43f..260d4961ff 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs @@ -7,7 +7,6 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Screens; -using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Rulesets; using osu.Game.Scoring; @@ -20,12 +19,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { public Action Exited; - protected readonly PlaylistItem PlaylistItem; - public PlaylistsPlayer(PlaylistItem playlistItem, PlayerConfiguration configuration = null) - : base(configuration) + : base(playlistItem, configuration) { - PlaylistItem = playlistItem; } [BackgroundDependencyLoader] @@ -42,10 +38,6 @@ namespace osu.Game.Screens.OnlinePlay.Playlists throw new InvalidOperationException("Current Mods do not match PlaylistItem's RequiredMods"); } - protected override APIRequest CreateTokenRequestRequest() => new CreateRoomScoreRequest(RoomId.Value ?? 0, PlaylistItem.ID, Game.VersionHash); - - public override APIRequest CreateSubmissionRequest(Score score, int token) => new SubmitRoomScoreRequest(token, RoomId.Value ?? 0, PlaylistItem.ID, score.ScoreInfo); - public override bool OnExiting(IScreen next) { if (base.OnExiting(next)) diff --git a/osu.Game/Screens/Play/RoomSubmittingPlayer.cs b/osu.Game/Screens/Play/RoomSubmittingPlayer.cs new file mode 100644 index 0000000000..c695e99874 --- /dev/null +++ b/osu.Game/Screens/Play/RoomSubmittingPlayer.cs @@ -0,0 +1,32 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Game.Online.API; +using osu.Game.Online.Rooms; +using osu.Game.Scoring; + +namespace osu.Game.Screens.Play +{ + /// + /// A player instance which submits to a room backing. This is generally used by playlists and multiplayer. + /// + public abstract class RoomSubmittingPlayer : SubmittingPlayer + { + [Resolved(typeof(Room), nameof(Room.RoomID))] + protected Bindable RoomId { get; private set; } + + protected readonly PlaylistItem PlaylistItem; + + protected RoomSubmittingPlayer(PlaylistItem playlistItem, PlayerConfiguration configuration = null) + : base(configuration) + { + PlaylistItem = playlistItem; + } + + protected override APIRequest CreateTokenRequestRequest() => new CreateRoomScoreRequest(RoomId.Value ?? 0, PlaylistItem.ID, Game.VersionHash); + + public override APIRequest CreateSubmissionRequest(Score score, int token) => new SubmitRoomScoreRequest(token, RoomId.Value ?? 0, PlaylistItem.ID, score.ScoreInfo); + } +} diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 250e308b3c..5577683f05 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -5,7 +5,6 @@ using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Logging; using osu.Framework.Screens; using osu.Game.Online.API; @@ -14,17 +13,9 @@ using osu.Game.Scoring; namespace osu.Game.Screens.Play { - public abstract class RoomSubmittingPlayer : SubmittingPlayer - { - [Resolved(typeof(Room), nameof(Room.RoomID))] - protected Bindable RoomId { get; private set; } - - protected RoomSubmittingPlayer(PlayerConfiguration configuration) - : base(configuration) - { - } - } - + /// + /// A player instance which supports submitting scores to an online store. + /// public abstract class SubmittingPlayer : Player { protected long? Token { get; private set; } From 194b2d05d3749b6b60796c699490df6320c62d59 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Mar 2021 15:35:06 +0900 Subject: [PATCH 360/390] Update SoloPlayer to derive SubmittingPlayer --- osu.Game/Screens/Play/SoloPlayer.cs | 15 ++++++++++++++- osu.Game/Screens/Play/SubmittingPlayer.cs | 2 +- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/SoloPlayer.cs b/osu.Game/Screens/Play/SoloPlayer.cs index 79860f3eda..f2f97c5d0d 100644 --- a/osu.Game/Screens/Play/SoloPlayer.cs +++ b/osu.Game/Screens/Play/SoloPlayer.cs @@ -1,9 +1,22 @@ // 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.Online.API; +using osu.Game.Online.Rooms; +using osu.Game.Scoring; + namespace osu.Game.Screens.Play { - public class SoloPlayer : Player + public class SoloPlayer : SubmittingPlayer { + public override APIRequest CreateSubmissionRequest(Score score, int token) + { + throw new System.NotImplementedException(); + } + + protected override APIRequest CreateTokenRequestRequest() + { + throw new System.NotImplementedException(); + } } } diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 5577683f05..e7847a9902 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -23,7 +23,7 @@ namespace osu.Game.Screens.Play [Resolved] private IAPIProvider api { get; set; } - protected SubmittingPlayer(PlayerConfiguration configuration) + protected SubmittingPlayer(PlayerConfiguration configuration = null) : base(configuration) { } From 571124669daf940ef35bc5a6a81cd8b64fa02bc7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Mar 2021 15:45:22 +0900 Subject: [PATCH 361/390] Remove all references to "score submission" from Player --- .../Multiplayer/MultiplayerPlayer.cs | 4 ++-- osu.Game/Screens/Play/Player.cs | 20 +++++++++---------- osu.Game/Screens/Play/SubmittingPlayer.cs | 4 ++-- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs index 2ba04b75d6..3797adf360 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs @@ -133,9 +133,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer private void onResultsReady() => resultsReady.SetResult(true); - protected override async Task SubmitScore(Score score) + protected override async Task PrepareScoreForResultsAsync(Score score) { - await base.SubmitScore(score).ConfigureAwait(false); + await base.PrepareScoreForResultsAsync(score).ConfigureAwait(false); await client.ChangeState(MultiplayerUserState.FinishedPlay).ConfigureAwait(false); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 4cf0274614..efe5d26409 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -559,7 +559,7 @@ namespace osu.Game.Screens.Play } private ScheduledDelegate completionProgressDelegate; - private Task scoreSubmissionTask; + private Task prepareScoreForDisplayTask; private void updateCompletionState(ValueChangedEvent completionState) { @@ -586,17 +586,17 @@ namespace osu.Game.Screens.Play if (!Configuration.ShowResults) return; - scoreSubmissionTask ??= Task.Run(async () => + prepareScoreForDisplayTask ??= Task.Run(async () => { var score = CreateScore(); try { - await SubmitScore(score).ConfigureAwait(false); + await PrepareScoreForResultsAsync(score).ConfigureAwait(false); } catch (Exception ex) { - Logger.Error(ex, "Score submission failed!"); + Logger.Error(ex, "Score preparation failed!"); } try @@ -617,7 +617,7 @@ namespace osu.Game.Screens.Play private void scheduleCompletion() => completionProgressDelegate = Schedule(() => { - if (!scoreSubmissionTask.IsCompleted) + if (!prepareScoreForDisplayTask.IsCompleted) { scheduleCompletion(); return; @@ -625,7 +625,7 @@ namespace osu.Game.Screens.Play // screen may be in the exiting transition phase. if (this.IsCurrentScreen()) - this.Push(CreateResults(scoreSubmissionTask.Result)); + this.Push(CreateResults(prepareScoreForDisplayTask.Result)); }); protected override bool OnScroll(ScrollEvent e) => mouseWheelDisabled.Value && !GameplayClockContainer.IsPaused.Value; @@ -895,11 +895,11 @@ namespace osu.Game.Screens.Play } /// - /// Submits the player's . + /// Prepare the for display at results. /// - /// The to submit. - /// The submitted score. - protected virtual Task SubmitScore(Score score) => Task.CompletedTask; + /// The to prepare. + /// A task that prepares the provided score. On completion, the score is assumed to be ready for display. + protected virtual Task PrepareScoreForResultsAsync(Score score) => Task.CompletedTask; /// /// Creates the for a . diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index e7847a9902..d876cad941 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -59,9 +59,9 @@ namespace osu.Game.Screens.Play Thread.Sleep(1000); } - protected override async Task SubmitScore(Score score) + protected override async Task PrepareScoreForResultsAsync(Score score) { - await base.SubmitScore(score).ConfigureAwait(false); + await base.PrepareScoreForResultsAsync(score).ConfigureAwait(false); Debug.Assert(Token != null); From 3cd8bf2d7f30d2be87d7e67acdcae1a4cfef69f6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Mar 2021 16:05:40 +0900 Subject: [PATCH 362/390] Move token request construction to LoadAsyncComplete to better allow DI usage --- .../Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs | 5 +++++ osu.Game/Screens/Play/SubmittingPlayer.cs | 6 +++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs index 3797adf360..a5adcdb8ad 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs @@ -61,6 +61,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(ScoreProcessor, userIds), HUDOverlay.Add); HUDOverlay.Add(loadingDisplay = new LoadingLayer(true) { Depth = float.MaxValue }); + } + + protected override void LoadAsyncComplete() + { + base.LoadAsyncComplete(); if (Token == null) return; // Todo: Somehow handle token retrieval failure. diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index d876cad941..356f70f4bf 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -28,11 +28,11 @@ namespace osu.Game.Screens.Play { } - [BackgroundDependencyLoader] - private void load() + protected override void LoadAsyncComplete() { - Token = null; + base.LoadAsyncComplete(); + // Token request construction should happen post-load to allow derived classes to potentially prepare DI backings that are used to create the request. bool failed = false; var req = CreateTokenRequestRequest(); From 242b847516cdf53f4f2c54fdd7097b9ec862f9a5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Mar 2021 16:41:36 +0900 Subject: [PATCH 363/390] Add flow for allowing gameplay to continue even when an error occurs with token retrieval --- osu.Game/Screens/Play/RoomSubmittingPlayer.cs | 2 +- osu.Game/Screens/Play/SubmittingPlayer.cs | 71 +++++++++++++------ 2 files changed, 51 insertions(+), 22 deletions(-) diff --git a/osu.Game/Screens/Play/RoomSubmittingPlayer.cs b/osu.Game/Screens/Play/RoomSubmittingPlayer.cs index c695e99874..6ef39e4b75 100644 --- a/osu.Game/Screens/Play/RoomSubmittingPlayer.cs +++ b/osu.Game/Screens/Play/RoomSubmittingPlayer.cs @@ -27,6 +27,6 @@ namespace osu.Game.Screens.Play protected override APIRequest CreateTokenRequestRequest() => new CreateRoomScoreRequest(RoomId.Value ?? 0, PlaylistItem.ID, Game.VersionHash); - public override APIRequest CreateSubmissionRequest(Score score, int token) => new SubmitRoomScoreRequest(token, RoomId.Value ?? 0, PlaylistItem.ID, score.ScoreInfo); + protected override APIRequest CreateSubmissionRequest(Score score, long token) => new SubmitRoomScoreRequest(token, RoomId.Value ?? 0, PlaylistItem.ID, score.ScoreInfo); } } diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 356f70f4bf..55f4ba4b9b 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -1,8 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Diagnostics; -using System.Threading; +using System; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Logging; @@ -30,40 +29,70 @@ namespace osu.Game.Screens.Play protected override void LoadAsyncComplete() { - base.LoadAsyncComplete(); - // Token request construction should happen post-load to allow derived classes to potentially prepare DI backings that are used to create the request. - bool failed = false; + var tcs = new TaskCompletionSource(); + + if (!api.IsLoggedIn) + { + fail(new InvalidOperationException("API is not online.")); + return; + } var req = CreateTokenRequestRequest(); - req.Success += r => Token = r.ID; - req.Failure += e => + + if (req == null) { - failed = true; + fail(new InvalidOperationException("Request could not be constructed.")); + return; + } - if (string.IsNullOrEmpty(e.Message)) - Logger.Error(e, "Failed to retrieve a score submission token."); - else - Logger.Log($"You are not able to submit a score: {e.Message}", level: LogLevel.Important); - - Schedule(() => - { - ValidForResume = false; - this.Exit(); - }); + req.Success += r => + { + Token = r.ID; + tcs.SetResult(true); }; + req.Failure += fail; api.Queue(req); - while (!failed && !Token.HasValue) - Thread.Sleep(1000); + tcs.Task.Wait(); + + void fail(Exception exception) + { + if (HandleTokenRetrievalFailure(exception)) + { + if (string.IsNullOrEmpty(exception.Message)) + Logger.Error(exception, "Failed to retrieve a score submission token."); + else + Logger.Log($"You are not able to submit a score: {exception.Message}", level: LogLevel.Important); + + Schedule(() => + { + ValidForResume = false; + this.Exit(); + }); + } + + tcs.SetResult(false); + } + + base.LoadAsyncComplete(); } + /// + /// Called when a token could not be retrieved for submission. + /// + /// The error causing the failure. + /// Whether gameplay should be immediately exited as a result. Returning false allows the gameplay session to continue. Defaults to true. + protected virtual bool HandleTokenRetrievalFailure(Exception exception) => true; + protected override async Task PrepareScoreForResultsAsync(Score score) { await base.PrepareScoreForResultsAsync(score).ConfigureAwait(false); - Debug.Assert(Token != null); + // token may be null if the request failed but gameplay was still allowed (see HandleTokenRetrievalFailure). + if (Token == null) + return; var tcs = new TaskCompletionSource(); var request = CreateSubmissionRequest(score, Token.Value); From e649a330a405d7d58490e50ea8c7f3b17cb3e27c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Mar 2021 16:41:52 +0900 Subject: [PATCH 364/390] Implement SoloPlayer's request construction --- osu.Game/Screens/Play/SoloPlayer.cs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/SoloPlayer.cs b/osu.Game/Screens/Play/SoloPlayer.cs index f2f97c5d0d..3dc9df146e 100644 --- a/osu.Game/Screens/Play/SoloPlayer.cs +++ b/osu.Game/Screens/Play/SoloPlayer.cs @@ -1,22 +1,34 @@ // 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 osu.Game.Online.API; using osu.Game.Online.Rooms; +using osu.Game.Online.Solo; using osu.Game.Scoring; namespace osu.Game.Screens.Play { public class SoloPlayer : SubmittingPlayer { - public override APIRequest CreateSubmissionRequest(Score score, int token) + protected override APIRequest CreateSubmissionRequest(Score score, long token) { - throw new System.NotImplementedException(); + Debug.Assert(Beatmap.Value.BeatmapInfo.OnlineBeatmapID != null); + + int beatmapId = Beatmap.Value.BeatmapInfo.OnlineBeatmapID.Value; + + return new SubmitSoloScoreRequest(beatmapId, token, score.ScoreInfo); } protected override APIRequest CreateTokenRequestRequest() { - throw new System.NotImplementedException(); + if (!(Beatmap.Value.BeatmapInfo.OnlineBeatmapID is int beatmapId)) + return null; + + return new CreateSoloScoreRequest(beatmapId, Game.VersionHash); } + + protected override bool HandleTokenRetrievalFailure(Exception exception) => false; } } From 64e85ba995a20dcf1cd3eb6f95fc65ae1a47a0cb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Mar 2021 19:19:07 +0900 Subject: [PATCH 365/390] Always fade out approach circles at a HitObject's start time to better match stable --- .../Objects/Drawables/DrawableHitCircle.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index 77094f928b..189003875d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -164,28 +164,29 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables ApproachCircle.Expire(true); } + protected override void UpdateStartTimeStateTransforms() + { + base.UpdateStartTimeStateTransforms(); + + ApproachCircle.FadeOut(50); + } + protected override void UpdateHitStateTransforms(ArmedState state) { Debug.Assert(HitObject.HitWindows != null); + // todo: temporary / arbitrary, used for lifetime optimisation. + this.Delay(800).FadeOut(); + switch (state) { case ArmedState.Idle: - this.Delay(HitObject.TimePreempt).FadeOut(500); HitArea.HitAction = null; break; case ArmedState.Miss: - ApproachCircle.FadeOut(50); this.FadeOut(100); break; - - case ArmedState.Hit: - ApproachCircle.FadeOut(50); - - // todo: temporary / arbitrary - this.Delay(800).FadeOut(); - break; } Expire(); From d10ff615feeadb9ece5d1f67863ddb9d26152bdc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Mar 2021 19:22:37 +0900 Subject: [PATCH 366/390] Fix default skin's glow resetting fade on miss --- osu.Game.Rulesets.Osu/Skinning/Default/MainCirclePiece.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/MainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Default/MainCirclePiece.cs index fcbe4c1b28..46aeadc59b 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/MainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/MainCirclePiece.cs @@ -74,10 +74,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default private void updateState(DrawableHitObject drawableObject, ArmedState state) { - using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime, true)) - { + using (BeginAbsoluteSequence(drawableObject.StateUpdateTime)) glow.FadeOut(400); + using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) + { switch (state) { case ArmedState.Hit: From d17c431faf92affd103a85b46f0a79fdf633040e Mon Sep 17 00:00:00 2001 From: Shivam Date: Tue, 23 Mar 2021 23:22:17 +0100 Subject: [PATCH 367/390] Disable relative mode for TournamentGame --- osu.Game.Tournament/TournamentGame.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tournament/TournamentGame.cs b/osu.Game.Tournament/TournamentGame.cs index fadb821bef..bf43b198c4 100644 --- a/osu.Game.Tournament/TournamentGame.cs +++ b/osu.Game.Tournament/TournamentGame.cs @@ -2,18 +2,21 @@ // See the LICENCE file in the repository root for full licence text. using System.Drawing; -using osu.Framework.Extensions.Color4Extensions; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Configuration; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Colour; -using osu.Game.Graphics.Cursor; -using osu.Game.Tournament.Models; +using osu.Framework.Input.Handlers.Mouse; +using osu.Framework.Platform; using osu.Game.Graphics; +using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; +using osu.Game.Tournament.Models; using osuTK; using osuTK.Graphics; @@ -36,7 +39,7 @@ namespace osu.Game.Tournament private LoadingSpinner loadingSpinner; [BackgroundDependencyLoader] - private void load(FrameworkConfigManager frameworkConfig) + private void load(FrameworkConfigManager frameworkConfig, GameHost host) { windowSize = frameworkConfig.GetBindable(FrameworkSetting.WindowedSize); windowMode = frameworkConfig.GetBindable(FrameworkSetting.WindowMode); @@ -48,6 +51,9 @@ namespace osu.Game.Tournament Margin = new MarginPadding(40), }); + var m = (MouseHandler)host.AvailableInputHandlers.Single(t => t is MouseHandler); + m.UseRelativeMode.Value = false; + loadingSpinner.Show(); BracketLoadTask.ContinueWith(_ => LoadComponentsAsync(new[] From fbb992fc7e432900ce9090d98446e6e3e18588ae Mon Sep 17 00:00:00 2001 From: Owen Young Date: Tue, 23 Mar 2021 19:18:32 -0500 Subject: [PATCH 368/390] Added a comment to new method --- osu.Game/OsuGame.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index a52899433a..e8284c0bad 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1008,6 +1008,7 @@ namespace osu.Game Exit(); } protected override IDictionary GetFrameworkConfigDefaults() { + // Overriding settings determined by Framework IDictionary defaultOverrides = new Dictionary(); defaultOverrides.Add(FrameworkSetting.WindowMode, WindowMode.Fullscreen); return defaultOverrides; From 67a03ebc2371c2bb6c14b721fd03f2627cb58b92 Mon Sep 17 00:00:00 2001 From: Owen Young Date: Tue, 23 Mar 2021 19:31:16 -0500 Subject: [PATCH 369/390] Fixed formatting issues to be in line with osu coding standards --- osu.Game/OsuGame.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index e8284c0bad..ca8fa9f1f6 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1007,7 +1007,9 @@ namespace osu.Game if (newScreen == null) Exit(); } - protected override IDictionary GetFrameworkConfigDefaults() { + + protected override IDictionary GetFrameworkConfigDefaults() + { // Overriding settings determined by Framework IDictionary defaultOverrides = new Dictionary(); defaultOverrides.Add(FrameworkSetting.WindowMode, WindowMode.Fullscreen); From 437dadc85f0927936004abc72bc5331289d7e333 Mon Sep 17 00:00:00 2001 From: Owen Young Date: Tue, 23 Mar 2021 19:37:55 -0500 Subject: [PATCH 370/390] Changed comment on GetFrameworkConfigDefaults() to be more accurate --- osu.Game/OsuGame.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index ca8fa9f1f6..f211723c59 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1010,7 +1010,7 @@ namespace osu.Game protected override IDictionary GetFrameworkConfigDefaults() { - // Overriding settings determined by Framework + // Overriding config defaults determined by Framework IDictionary defaultOverrides = new Dictionary(); defaultOverrides.Add(FrameworkSetting.WindowMode, WindowMode.Fullscreen); return defaultOverrides; From a1c35677efb5e68f5691896b8ca6dfb3763b12e3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Mar 2021 13:02:17 +0900 Subject: [PATCH 371/390] Add more xmldoc --- osu.Game/Screens/Play/SubmittingPlayer.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 55f4ba4b9b..24d540fbf3 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -17,6 +17,9 @@ namespace osu.Game.Screens.Play /// public abstract class SubmittingPlayer : Player { + /// + /// The token to be used for the current submission. This is fetched via a request created by . + /// protected long? Token { get; private set; } [Resolved] From 8bed7748d696666bfe0023f78710cf84440bc244 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Mar 2021 13:02:37 +0900 Subject: [PATCH 372/390] Rename token request method to avoid double Request terminology --- osu.Game/Screens/Play/RoomSubmittingPlayer.cs | 2 +- osu.Game/Screens/Play/SoloPlayer.cs | 2 +- osu.Game/Screens/Play/SubmittingPlayer.cs | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/RoomSubmittingPlayer.cs b/osu.Game/Screens/Play/RoomSubmittingPlayer.cs index 6ef39e4b75..d7b49bf2cb 100644 --- a/osu.Game/Screens/Play/RoomSubmittingPlayer.cs +++ b/osu.Game/Screens/Play/RoomSubmittingPlayer.cs @@ -25,7 +25,7 @@ namespace osu.Game.Screens.Play PlaylistItem = playlistItem; } - protected override APIRequest CreateTokenRequestRequest() => new CreateRoomScoreRequest(RoomId.Value ?? 0, PlaylistItem.ID, Game.VersionHash); + protected override APIRequest CreateTokenRequest() => new CreateRoomScoreRequest(RoomId.Value ?? 0, PlaylistItem.ID, Game.VersionHash); protected override APIRequest CreateSubmissionRequest(Score score, long token) => new SubmitRoomScoreRequest(token, RoomId.Value ?? 0, PlaylistItem.ID, score.ScoreInfo); } diff --git a/osu.Game/Screens/Play/SoloPlayer.cs b/osu.Game/Screens/Play/SoloPlayer.cs index 3dc9df146e..5c465b6d8f 100644 --- a/osu.Game/Screens/Play/SoloPlayer.cs +++ b/osu.Game/Screens/Play/SoloPlayer.cs @@ -21,7 +21,7 @@ namespace osu.Game.Screens.Play return new SubmitSoloScoreRequest(beatmapId, token, score.ScoreInfo); } - protected override APIRequest CreateTokenRequestRequest() + protected override APIRequest CreateTokenRequest() { if (!(Beatmap.Value.BeatmapInfo.OnlineBeatmapID is int beatmapId)) return null; diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 24d540fbf3..55b10abd5c 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -18,7 +18,7 @@ namespace osu.Game.Screens.Play public abstract class SubmittingPlayer : Player { /// - /// The token to be used for the current submission. This is fetched via a request created by . + /// The token to be used for the current submission. This is fetched via a request created by . /// protected long? Token { get; private set; } @@ -41,7 +41,7 @@ namespace osu.Game.Screens.Play return; } - var req = CreateTokenRequestRequest(); + var req = CreateTokenRequest(); if (req == null) { @@ -118,6 +118,6 @@ namespace osu.Game.Screens.Play protected abstract APIRequest CreateSubmissionRequest(Score score, long token); - protected abstract APIRequest CreateTokenRequestRequest(); + protected abstract APIRequest CreateTokenRequest(); } } From e372e355efba030da6866f61fad35abca462cd6d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Mar 2021 13:12:51 +0900 Subject: [PATCH 373/390] Reorder overrides in SoloPlayer to better follow chronological request order --- osu.Game/Screens/Play/SoloPlayer.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Play/SoloPlayer.cs b/osu.Game/Screens/Play/SoloPlayer.cs index 5c465b6d8f..ee1ccdc5b3 100644 --- a/osu.Game/Screens/Play/SoloPlayer.cs +++ b/osu.Game/Screens/Play/SoloPlayer.cs @@ -12,15 +12,6 @@ namespace osu.Game.Screens.Play { public class SoloPlayer : SubmittingPlayer { - protected override APIRequest CreateSubmissionRequest(Score score, long token) - { - Debug.Assert(Beatmap.Value.BeatmapInfo.OnlineBeatmapID != null); - - int beatmapId = Beatmap.Value.BeatmapInfo.OnlineBeatmapID.Value; - - return new SubmitSoloScoreRequest(beatmapId, token, score.ScoreInfo); - } - protected override APIRequest CreateTokenRequest() { if (!(Beatmap.Value.BeatmapInfo.OnlineBeatmapID is int beatmapId)) @@ -30,5 +21,14 @@ namespace osu.Game.Screens.Play } protected override bool HandleTokenRetrievalFailure(Exception exception) => false; + + protected override APIRequest CreateSubmissionRequest(Score score, long token) + { + Debug.Assert(Beatmap.Value.BeatmapInfo.OnlineBeatmapID != null); + + int beatmapId = Beatmap.Value.BeatmapInfo.OnlineBeatmapID.Value; + + return new SubmitSoloScoreRequest(beatmapId, token, score.ScoreInfo); + } } } From a0c6c4da35dfe5cf2a28efa20da64acab28f25ab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Mar 2021 13:17:13 +0900 Subject: [PATCH 374/390] Rename and refactor token request process to be easier to understand --- osu.Game/Screens/Play/SubmittingPlayer.cs | 24 +++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 55b10abd5c..5d087c212d 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -3,6 +3,7 @@ using System; using System.Threading.Tasks; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Logging; using osu.Framework.Screens; @@ -37,7 +38,7 @@ namespace osu.Game.Screens.Play if (!api.IsLoggedIn) { - fail(new InvalidOperationException("API is not online.")); + handleFailure(new InvalidOperationException("API is not online.")); return; } @@ -45,7 +46,7 @@ namespace osu.Game.Screens.Play if (req == null) { - fail(new InvalidOperationException("Request could not be constructed.")); + handleFailure(new InvalidOperationException("Request could not be constructed.")); return; } @@ -54,13 +55,13 @@ namespace osu.Game.Screens.Play Token = r.ID; tcs.SetResult(true); }; - req.Failure += fail; + req.Failure += handleFailure; api.Queue(req); tcs.Task.Wait(); - void fail(Exception exception) + void handleFailure(Exception exception) { if (HandleTokenRetrievalFailure(exception)) { @@ -116,8 +117,19 @@ namespace osu.Game.Screens.Play await tcs.Task.ConfigureAwait(false); } - protected abstract APIRequest CreateSubmissionRequest(Score score, long token); - + /// + /// Construct a request to be used for retrieval of the score token. + /// Can return null, at which point will be fired. + /// + [CanBeNull] protected abstract APIRequest CreateTokenRequest(); + + /// + /// Construct a request to submit the score. + /// Will only be invoked if the request constructed via was successful. + /// + /// The score to be submitted. + /// The submission token. + protected abstract APIRequest CreateSubmissionRequest(Score score, long token); } } From 84b2f9a848c0c61b047f5eb463e7848cdbd9b03a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Mar 2021 13:20:44 +0900 Subject: [PATCH 375/390] Make token private --- .../Multiplayer/MultiplayerPlayer.cs | 4 ++-- osu.Game/Screens/Play/SubmittingPlayer.cs | 20 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs index a5adcdb8ad..aaacf891bb 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs @@ -67,8 +67,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { base.LoadAsyncComplete(); - if (Token == null) - return; // Todo: Somehow handle token retrieval failure. + if (!ValidForResume) + return; // token retrieval may have failed. client.MatchStarted += onMatchStarted; client.ResultsReady += onResultsReady; diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 5d087c212d..87a4eb5efe 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -21,7 +21,7 @@ namespace osu.Game.Screens.Play /// /// The token to be used for the current submission. This is fetched via a request created by . /// - protected long? Token { get; private set; } + private long? token; [Resolved] private IAPIProvider api { get; set; } @@ -38,7 +38,7 @@ namespace osu.Game.Screens.Play if (!api.IsLoggedIn) { - handleFailure(new InvalidOperationException("API is not online.")); + handleTokenFailure(new InvalidOperationException("API is not online.")); return; } @@ -46,22 +46,24 @@ namespace osu.Game.Screens.Play if (req == null) { - handleFailure(new InvalidOperationException("Request could not be constructed.")); + handleTokenFailure(new InvalidOperationException("Request could not be constructed.")); return; } req.Success += r => { - Token = r.ID; + token = r.ID; tcs.SetResult(true); }; - req.Failure += handleFailure; + req.Failure += handleTokenFailure; api.Queue(req); tcs.Task.Wait(); - void handleFailure(Exception exception) + base.LoadAsyncComplete(); + + void handleTokenFailure(Exception exception) { if (HandleTokenRetrievalFailure(exception)) { @@ -79,8 +81,6 @@ namespace osu.Game.Screens.Play tcs.SetResult(false); } - - base.LoadAsyncComplete(); } /// @@ -95,11 +95,11 @@ namespace osu.Game.Screens.Play await base.PrepareScoreForResultsAsync(score).ConfigureAwait(false); // token may be null if the request failed but gameplay was still allowed (see HandleTokenRetrievalFailure). - if (Token == null) + if (token == null) return; var tcs = new TaskCompletionSource(); - var request = CreateSubmissionRequest(score, Token.Value); + var request = CreateSubmissionRequest(score, token.Value); request.Success += s => { From d55324585d678603435adcaed815d3d85186037b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Mar 2021 13:23:23 +0900 Subject: [PATCH 376/390] Change RoomSubmittingPlayer's request implementation to return null on RoomID missing, rather than silently succeeding --- osu.Game/Screens/Play/RoomSubmittingPlayer.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/RoomSubmittingPlayer.cs b/osu.Game/Screens/Play/RoomSubmittingPlayer.cs index d7b49bf2cb..7ba12f5db6 100644 --- a/osu.Game/Screens/Play/RoomSubmittingPlayer.cs +++ b/osu.Game/Screens/Play/RoomSubmittingPlayer.cs @@ -25,7 +25,13 @@ namespace osu.Game.Screens.Play PlaylistItem = playlistItem; } - protected override APIRequest CreateTokenRequest() => new CreateRoomScoreRequest(RoomId.Value ?? 0, PlaylistItem.ID, Game.VersionHash); + protected override APIRequest CreateTokenRequest() + { + if (!(RoomId.Value is long roomId)) + return null; + + return new CreateRoomScoreRequest(roomId, PlaylistItem.ID, Game.VersionHash); + } protected override APIRequest CreateSubmissionRequest(Score score, long token) => new SubmitRoomScoreRequest(token, RoomId.Value ?? 0, PlaylistItem.ID, score.ScoreInfo); } From f95175983ae6f4f79a2a27877467e8ef40f5cb22 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Mar 2021 13:37:37 +0900 Subject: [PATCH 377/390] Make code more concise and move method to a more appropriate place --- osu.Game/OsuGame.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 53dc900254..dd1fa32ad9 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -531,6 +531,13 @@ namespace osu.Game SentryLogger.Dispose(); } + protected override IDictionary GetFrameworkConfigDefaults() + => new Dictionary + { + // General expectation that osu! starts in fullscreen by default (also gives the most predictable performance) + { FrameworkSetting.WindowMode, WindowMode.Fullscreen } + }; + protected override void LoadComplete() { base.LoadComplete(); @@ -1013,13 +1020,5 @@ namespace osu.Game if (newScreen == null) Exit(); } - - protected override IDictionary GetFrameworkConfigDefaults() - { - // Overriding config defaults determined by Framework - IDictionary defaultOverrides = new Dictionary(); - defaultOverrides.Add(FrameworkSetting.WindowMode, WindowMode.Fullscreen); - return defaultOverrides; - } } } From 5ad8dc316fa1aeed2f2dc194c2f9520b7f93ba09 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Mar 2021 14:09:15 +0900 Subject: [PATCH 378/390] Add inline comment and improve linq robustness --- osu.Game.Tournament/TournamentGame.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/TournamentGame.cs b/osu.Game.Tournament/TournamentGame.cs index bf43b198c4..87e23e3404 100644 --- a/osu.Game.Tournament/TournamentGame.cs +++ b/osu.Game.Tournament/TournamentGame.cs @@ -51,8 +51,12 @@ namespace osu.Game.Tournament Margin = new MarginPadding(40), }); - var m = (MouseHandler)host.AvailableInputHandlers.Single(t => t is MouseHandler); - m.UseRelativeMode.Value = false; + // in order to have the OS mouse cursor visible, relative mode needs to be disabled. + // can potentially be removed when https://github.com/ppy/osu-framework/issues/4309 is resolved. + var mouseHandler = host.AvailableInputHandlers.OfType().FirstOrDefault(); + + if (mouseHandler != null) + mouseHandler.UseRelativeMode.Value = false; loadingSpinner.Show(); From fc5719e445750c10ddad28ead84239a9cb0f519d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 24 Mar 2021 21:31:53 +0300 Subject: [PATCH 379/390] Fix SkinManager not handling extensions casing comparsion properly --- osu.Game/Skinning/SkinManager.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 9257636301..dfc2981c6f 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -104,7 +104,7 @@ namespace osu.Game.Skinning protected override string ComputeHash(SkinInfo item, ArchiveReader reader = null) { // we need to populate early to create a hash based off skin.ini contents - if (item.Name?.Contains(".osk") == true) + if (item.Name?.EndsWith(".osk", StringComparison.OrdinalIgnoreCase) == true) populateMetadata(item); if (item.Creator != null && item.Creator != unknown_creator_string) @@ -122,7 +122,7 @@ namespace osu.Game.Skinning { await base.Populate(model, archive, cancellationToken).ConfigureAwait(false); - if (model.Name?.Contains(".osk") == true) + if (model.Name?.EndsWith(".osk", StringComparison.OrdinalIgnoreCase) == true) populateMetadata(model); } @@ -137,7 +137,7 @@ namespace osu.Game.Skinning } else { - item.Name = item.Name.Replace(".osk", ""); + item.Name = item.Name.Replace(".osk", "", StringComparison.OrdinalIgnoreCase); item.Creator ??= unknown_creator_string; } } From 35810bb2fb98b7f310d2e19b29a8369e1ddb99a8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 24 Mar 2021 22:55:15 +0300 Subject: [PATCH 380/390] Add test coverage --- osu.Game.Tests/Skins/IO/ImportSkinTest.cs | 25 +++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs index a5b4b04ef5..8124bd4199 100644 --- a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs +++ b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs @@ -113,6 +113,31 @@ namespace osu.Game.Tests.Skins.IO } } + [Test] + public async Task TestImportUpperCasedOskArchive() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportSkinTest))) + { + try + { + var osu = LoadOsuIntoHost(host); + + var imported = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("name 1", "author 1"), "skin1.OsK")); + + Assert.That(imported.Name, Is.EqualTo("name 1")); + Assert.That(imported.Creator, Is.EqualTo("author 1")); + + var imported2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("name 1", "author 1"), "skin1.oSK")); + + Assert.That(imported2.Hash, Is.EqualTo(imported.Hash)); + } + finally + { + host.Exit(); + } + } + } + private MemoryStream createOsk(string name, string author) { var zipStream = new MemoryStream(); From 8753d45b71baab76b83f6245e5e8ad98c3ea3e92 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Mar 2021 12:32:51 +0900 Subject: [PATCH 381/390] Remove duplicate crash report issue template --- .github/ISSUE_TEMPLATE/02-crash-issues.md | 20 ------------------- ...issues.md => 02-feature-request-issues.md} | 2 +- 2 files changed, 1 insertion(+), 21 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/02-crash-issues.md rename .github/ISSUE_TEMPLATE/{03-feature-request-issues.md => 02-feature-request-issues.md} (62%) diff --git a/.github/ISSUE_TEMPLATE/02-crash-issues.md b/.github/ISSUE_TEMPLATE/02-crash-issues.md deleted file mode 100644 index 04170312d1..0000000000 --- a/.github/ISSUE_TEMPLATE/02-crash-issues.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: Crash Report -about: Issues regarding crashes or permanent freezes. ---- -**Describe the crash:** - -**Screenshots or videos showing encountered issue:** - -**osu!lazer version:** - -**Logs:** - - -**Computer Specifications:** diff --git a/.github/ISSUE_TEMPLATE/03-feature-request-issues.md b/.github/ISSUE_TEMPLATE/02-feature-request-issues.md similarity index 62% rename from .github/ISSUE_TEMPLATE/03-feature-request-issues.md rename to .github/ISSUE_TEMPLATE/02-feature-request-issues.md index 54c4ff94e5..c3357dd780 100644 --- a/.github/ISSUE_TEMPLATE/03-feature-request-issues.md +++ b/.github/ISSUE_TEMPLATE/02-feature-request-issues.md @@ -1,6 +1,6 @@ --- name: Feature Request -about: Features you would like to see in the game! +about: Propose a feature you would like to see in the game! --- **Describe the new feature:** From f8f461a7a4b2c50edca8d0a2f3c9318d244394a9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Mar 2021 12:33:07 +0900 Subject: [PATCH 382/390] Include blurb in issue template to hopefully help people find existing reports --- .github/ISSUE_TEMPLATE/01-bug-issues.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/01-bug-issues.md b/.github/ISSUE_TEMPLATE/01-bug-issues.md index 6050036cbf..e45893b97a 100644 --- a/.github/ISSUE_TEMPLATE/01-bug-issues.md +++ b/.github/ISSUE_TEMPLATE/01-bug-issues.md @@ -1,7 +1,18 @@ --- name: Bug Report -about: Issues regarding encountered bugs. +about: Report a bug or crash to desktop --- + + + + **Describe the bug:** **Screenshots or videos showing encountered issue:** @@ -9,6 +20,7 @@ about: Issues regarding encountered bugs. **osu!lazer version:** **Logs:** +

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 Ni{DEH3<&Xe{{o+^{JH=D literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs index e882229570..14bc2c8733 100644 --- a/osu.Game.Tests/Resources/TestResources.cs +++ b/osu.Game.Tests/Resources/TestResources.cs @@ -13,8 +13,31 @@ namespace osu.Game.Tests.Resources public static Stream OpenResource(string name) => GetStore().GetStream($"Resources/{name}"); - public static Stream GetTestBeatmapStream(bool virtualTrack = false) => OpenResource($"Archives/241526 Soleily - Renatus{(virtualTrack ? "_virtual" : "")}.osz"); + public static Stream GetTestBeatmapStream(bool virtualTrack = false, bool quick = false) => OpenResource($"Archives/241526 Soleily - Renatus{(virtualTrack ? "_virtual" : "")}{(quick ? "_quick" : "")}.osz"); + ///