From 1d314a1f4b86ce5fd6ea95804acb8d411d95413c Mon Sep 17 00:00:00 2001 From: voidedWarranties Date: Wed, 11 Mar 2020 22:40:08 -0700 Subject: [PATCH 0001/1791] 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 0002/1791] 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 0003/1791] 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 0004/1791] 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 0005/1791] 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 030b55ae85460f8c1346c5d0574d7886e78bf3c9 Mon Sep 17 00:00:00 2001 From: "Jack Boswell (boswelja)" Date: Wed, 3 Jun 2020 17:55:15 +1200 Subject: [PATCH 0006/1791] Add a section to global keybind settings for song select --- .../Input/Bindings/GlobalActionContainer.cs | 17 +++++++++++++++++ .../KeyBinding/GlobalKeyBindingsSection.cs | 12 ++++++++++++ 2 files changed, 29 insertions(+) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 71771abede..568022b00e 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -56,6 +56,13 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Control, InputKey.Minus }, GlobalAction.DecreaseScrollSpeed), }; + public IEnumerable SongSelectKeyBindings => new[] + { + new KeyBinding(InputKey.F1, GlobalAction.ToggleMods), + new KeyBinding(InputKey.F2, GlobalAction.SelectRandom), + new KeyBinding(InputKey.F3, GlobalAction.ToggleOptions) + }; + public IEnumerable AudioControlKeyBindings => new[] { new KeyBinding(new[] { InputKey.Alt, InputKey.Up }, GlobalAction.IncreaseVolume), @@ -106,6 +113,16 @@ namespace osu.Game.Input.Bindings [Description("Toggle mute")] ToggleMute, + // Song select keybindings + [Description("Toggle mod selection overlay")] + ToggleMods, + + [Description("Select a random beatmap")] + SelectRandom, + + [Description("Toggle beatmap options overlay")] + ToggleOptions, + // In-Game Keybindings [Description("Skip cutscene")] SkipCutscene, diff --git a/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs b/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs index 5b44c486a3..1ae6de4386 100644 --- a/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs +++ b/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs @@ -21,6 +21,7 @@ namespace osu.Game.Overlays.KeyBinding { Add(new DefaultBindingsSubsection(manager)); Add(new AudioControlKeyBindingsSubsection(manager)); + Add(new SongSelectKeyBindingSubsection(manager)); Add(new InGameKeyBindingsSubsection(manager)); } @@ -35,6 +36,17 @@ namespace osu.Game.Overlays.KeyBinding } } + private class SongSelectKeyBindingSubsection : KeyBindingsSubsection + { + protected override string Header => "Song Select"; + + public SongSelectKeyBindingSubsection(GlobalActionContainer manager) + : base(null) + { + Defaults = manager.SongSelectKeyBindings; + } + } + private class InGameKeyBindingsSubsection : KeyBindingsSubsection { protected override string Header => "In Game"; From 55953b9e858e3828f80abe54a6d4e5eed65f644f Mon Sep 17 00:00:00 2001 From: "Jack Boswell (boswelja)" Date: Wed, 3 Jun 2020 18:13:02 +1200 Subject: [PATCH 0007/1791] Add a keybinding for selecting the previous random beatmap Also gave the new actions more meaningful names --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 568022b00e..7257b0ce15 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -58,9 +58,10 @@ namespace osu.Game.Input.Bindings public IEnumerable SongSelectKeyBindings => new[] { - new KeyBinding(InputKey.F1, GlobalAction.ToggleMods), - new KeyBinding(InputKey.F2, GlobalAction.SelectRandom), - new KeyBinding(InputKey.F3, GlobalAction.ToggleOptions) + new KeyBinding(InputKey.F1, GlobalAction.ToggleModSelection), + new KeyBinding(InputKey.F2, GlobalAction.SelectNextRandom), + new KeyBinding(new[] { InputKey.Shift, InputKey.F2 }, GlobalAction.SelectPreviousRandom), + new KeyBinding(InputKey.F3, GlobalAction.ToggleBeatmapOptions) }; public IEnumerable AudioControlKeyBindings => new[] @@ -115,13 +116,16 @@ namespace osu.Game.Input.Bindings // Song select keybindings [Description("Toggle mod selection overlay")] - ToggleMods, + ToggleModSelection, [Description("Select a random beatmap")] - SelectRandom, + SelectNextRandom, + + [Description("Select the last random beatmap")] + SelectPreviousRandom, [Description("Toggle beatmap options overlay")] - ToggleOptions, + ToggleBeatmapOptions, // In-Game Keybindings [Description("Skip cutscene")] From 782fddb6f1e722d0814c60d7caf1681e76581e9e Mon Sep 17 00:00:00 2001 From: "Jack Boswell (boswelja)" Date: Thu, 4 Jun 2020 15:21:13 +1200 Subject: [PATCH 0008/1791] Modify FooterButton to implement IKeyBindingHandler for responding to hotkeys --- osu.Game/Screens/Select/FooterButton.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Select/FooterButton.cs b/osu.Game/Screens/Select/FooterButton.cs index 35970cd960..a8393a81d4 100644 --- a/osu.Game/Screens/Select/FooterButton.cs +++ b/osu.Game/Screens/Select/FooterButton.cs @@ -12,10 +12,12 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Containers; +using osu.Game.Input.Bindings; +using osu.Framework.Input.Bindings; namespace osu.Game.Screens.Select { - public class FooterButton : OsuClickableContainer + public class FooterButton : OsuClickableContainer, IKeyBindingHandler { public const float SHEAR_WIDTH = 7.5f; @@ -117,7 +119,7 @@ namespace osu.Game.Screens.Select public Action Hovered; public Action HoverLost; - public Key? Hotkey; + public GlobalAction? Hotkey; protected override void UpdateAfterChildren() { @@ -167,15 +169,19 @@ namespace osu.Game.Screens.Select return base.OnClick(e); } - protected override bool OnKeyDown(KeyDownEvent e) + public virtual bool OnPressed(GlobalAction action) { - if (!e.Repeat && e.Key == Hotkey) + if (action == Hotkey) { Click(); return true; } + return false; + } - return base.OnKeyDown(e); + public virtual void OnReleased(GlobalAction action) + { + } } } From 18db31b50435802cac5b2c0437c293c611999575 Mon Sep 17 00:00:00 2001 From: "Jack Boswell (boswelja)" Date: Thu, 4 Jun 2020 15:25:05 +1200 Subject: [PATCH 0009/1791] Update FooterButtonMods to comply with the changes in FooterButton --- osu.Game/Screens/Select/FooterButtonMods.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/FooterButtonMods.cs b/osu.Game/Screens/Select/FooterButtonMods.cs index 02333da0dc..cde8113fa9 100644 --- a/osu.Game/Screens/Select/FooterButtonMods.cs +++ b/osu.Game/Screens/Select/FooterButtonMods.cs @@ -14,11 +14,12 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osuTK; using osuTK.Graphics; -using osuTK.Input; +using osu.Framework.Input.Bindings; +using osu.Game.Input.Bindings; namespace osu.Game.Screens.Select { - public class FooterButtonMods : FooterButton, IHasCurrentValue> + public class FooterButtonMods : FooterButton, IHasCurrentValue>, IKeyBindingHandler { public Bindable> Current { @@ -57,7 +58,7 @@ namespace osu.Game.Screens.Select lowMultiplierColour = colours.Red; highMultiplierColour = colours.Green; Text = @"mods"; - Hotkey = Key.F1; + Hotkey = GlobalAction.ToggleModSelection; } protected override void LoadComplete() From 05e4499bc11890090477cbe8d027996399a9c60a Mon Sep 17 00:00:00 2001 From: "Jack Boswell (boswelja)" Date: Thu, 4 Jun 2020 15:25:18 +1200 Subject: [PATCH 0010/1791] Update FooterButtonOptions to comply with the changes in FooterButton --- osu.Game/Screens/Select/FooterButtonOptions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/FooterButtonOptions.cs b/osu.Game/Screens/Select/FooterButtonOptions.cs index c000d8a8c8..e549656785 100644 --- a/osu.Game/Screens/Select/FooterButtonOptions.cs +++ b/osu.Game/Screens/Select/FooterButtonOptions.cs @@ -4,7 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Game.Graphics; -using osuTK.Input; +using osu.Game.Input.Bindings; namespace osu.Game.Screens.Select { @@ -16,7 +16,7 @@ namespace osu.Game.Screens.Select SelectedColour = colours.Blue; DeselectedColour = SelectedColour.Opacity(0.5f); Text = @"options"; - Hotkey = Key.F3; + Hotkey = GlobalAction.ToggleBeatmapOptions; } } } From 568503ef991981f190e17f86d742d80082441560 Mon Sep 17 00:00:00 2001 From: "Jack Boswell (boswelja)" Date: Thu, 4 Jun 2020 16:07:50 +1200 Subject: [PATCH 0011/1791] Update FooterButtonRandom to comply with the changes in FooterButton FooterButtonRandom now has 2 Action variables, one for both primary and secondary --- osu.Game/Screens/Select/FooterButtonRandom.cs | 52 +++++++++++++------ 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/osu.Game/Screens/Select/FooterButtonRandom.cs b/osu.Game/Screens/Select/FooterButtonRandom.cs index a42e6721db..728105200e 100644 --- a/osu.Game/Screens/Select/FooterButtonRandom.cs +++ b/osu.Game/Screens/Select/FooterButtonRandom.cs @@ -1,19 +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 System; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osuTK.Input; +using osu.Game.Input.Bindings; namespace osu.Game.Screens.Select { public class FooterButtonRandom : FooterButton { + public Action PrimaryAction { get; set; } + public Action SecondaryAction { get; set; } + private readonly SpriteText secondaryText; private bool secondaryActive; @@ -37,21 +40,7 @@ namespace osu.Game.Screens.Select SelectedColour = colours.Green; DeselectedColour = SelectedColour.Opacity(0.5f); Text = @"random"; - Hotkey = Key.F2; - } - - protected override bool OnKeyDown(KeyDownEvent e) - { - secondaryActive = e.ShiftPressed; - updateText(); - return base.OnKeyDown(e); - } - - protected override void OnKeyUp(KeyUpEvent e) - { - secondaryActive = e.ShiftPressed; - updateText(); - base.OnKeyUp(e); + Hotkey = GlobalAction.SelectNextRandom; } private void updateText() @@ -67,5 +56,34 @@ namespace osu.Game.Screens.Select secondaryText.FadeOut(120, Easing.InQuad); } } + + public override bool OnPressed(GlobalAction action) + { + switch (action) + { + case GlobalAction.SelectPreviousRandom: + secondaryActive = true; + Action = SecondaryAction; + updateText(); + Click(); + return true; + case GlobalAction.SelectNextRandom: + Action = PrimaryAction; + updateText(); + Click(); + return true; + default: + return false; + } + } + + public override void OnReleased(GlobalAction action) + { + if (action == GlobalAction.SelectPreviousRandom) + { + secondaryActive = false; + updateText(); + } + } } } From aa08847bc9fb73b9f1c77dcb711726ef5abfe5e9 Mon Sep 17 00:00:00 2001 From: "Jack Boswell (boswelja)" Date: Thu, 4 Jun 2020 16:08:16 +1200 Subject: [PATCH 0012/1791] Set FooterButtonRandom actions properly when creating the button --- osu.Game/Screens/Select/SongSelect.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index d613b0ae8d..e22c055047 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -272,7 +272,7 @@ namespace osu.Game.Screens.Select if (Footer != null) { Footer.AddButton(new FooterButtonMods { Current = Mods }, ModSelect); - Footer.AddButton(new FooterButtonRandom { Action = triggerRandom }); + Footer.AddButton(new FooterButtonRandom { PrimaryAction = triggerNextRandom, SecondaryAction = triggerPreviousRandom }); Footer.AddButton(new FooterButtonOptions(), BeatmapOptions); BeatmapOptions.AddButton(@"Remove", @"from unplayed", FontAwesome.Regular.TimesCircle, colours.Purple, null, Key.Number1); @@ -496,12 +496,14 @@ namespace osu.Game.Screens.Select } } - private void triggerRandom() + private void triggerNextRandom() { - if (GetContainingInputManager().CurrentState.Keyboard.ShiftPressed) - Carousel.SelectPreviousRandom(); - else - Carousel.SelectNextRandom(); + Carousel.SelectNextRandom(); + } + + private void triggerPreviousRandom() + { + Carousel.SelectPreviousRandom(); } public override void OnEntering(IScreen last) From aeb736e9d2a78d7b913a6acfc35c2494fa0e2acd Mon Sep 17 00:00:00 2001 From: "Jack Boswell (boswelja)" Date: Thu, 4 Jun 2020 16:13:50 +1200 Subject: [PATCH 0013/1791] Fix CodeFactor code style issues --- osu.Game/Screens/Select/FooterButton.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/FooterButton.cs b/osu.Game/Screens/Select/FooterButton.cs index a8393a81d4..6006d3cbfd 100644 --- a/osu.Game/Screens/Select/FooterButton.cs +++ b/osu.Game/Screens/Select/FooterButton.cs @@ -179,9 +179,6 @@ namespace osu.Game.Screens.Select return false; } - public virtual void OnReleased(GlobalAction action) - { - - } + public virtual void OnReleased(GlobalAction action) { } } } From eb242085c689249663a938e171c5473b33139b18 Mon Sep 17 00:00:00 2001 From: "Jack Boswell (boswelja)" Date: Thu, 4 Jun 2020 16:24:32 +1200 Subject: [PATCH 0014/1791] Remove redundant interface from FooterButtonMods --- osu.Game/Screens/Select/FooterButtonMods.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/FooterButtonMods.cs b/osu.Game/Screens/Select/FooterButtonMods.cs index cde8113fa9..b98b48a0c0 100644 --- a/osu.Game/Screens/Select/FooterButtonMods.cs +++ b/osu.Game/Screens/Select/FooterButtonMods.cs @@ -14,12 +14,11 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osuTK; using osuTK.Graphics; -using osu.Framework.Input.Bindings; using osu.Game.Input.Bindings; namespace osu.Game.Screens.Select { - public class FooterButtonMods : FooterButton, IHasCurrentValue>, IKeyBindingHandler + public class FooterButtonMods : FooterButton, IHasCurrentValue> { public Bindable> Current { From 7141bed78d2d5e9004a10a7cf92cd0b1f0cdf049 Mon Sep 17 00:00:00 2001 From: "Jack Boswell (boswelja)" Date: Thu, 4 Jun 2020 16:40:16 +1200 Subject: [PATCH 0015/1791] Remove redundant directive from FooterButton --- osu.Game/Screens/Select/FooterButton.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Select/FooterButton.cs b/osu.Game/Screens/Select/FooterButton.cs index 6006d3cbfd..6ea519fb80 100644 --- a/osu.Game/Screens/Select/FooterButton.cs +++ b/osu.Game/Screens/Select/FooterButton.cs @@ -4,7 +4,6 @@ using System; using osuTK; using osuTK.Graphics; -using osuTK.Input; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; From a78a8c0d0d4bd567aef4b3263edcde776d7fe743 Mon Sep 17 00:00:00 2001 From: "Jack Boswell (boswelja)" Date: Thu, 4 Jun 2020 16:59:04 +1200 Subject: [PATCH 0016/1791] Add missing blank lines --- osu.Game/Screens/Select/FooterButton.cs | 1 + osu.Game/Screens/Select/FooterButtonRandom.cs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/osu.Game/Screens/Select/FooterButton.cs b/osu.Game/Screens/Select/FooterButton.cs index 6ea519fb80..0502546de8 100644 --- a/osu.Game/Screens/Select/FooterButton.cs +++ b/osu.Game/Screens/Select/FooterButton.cs @@ -175,6 +175,7 @@ namespace osu.Game.Screens.Select Click(); return true; } + return false; } diff --git a/osu.Game/Screens/Select/FooterButtonRandom.cs b/osu.Game/Screens/Select/FooterButtonRandom.cs index 728105200e..fdb1159484 100644 --- a/osu.Game/Screens/Select/FooterButtonRandom.cs +++ b/osu.Game/Screens/Select/FooterButtonRandom.cs @@ -67,11 +67,13 @@ namespace osu.Game.Screens.Select updateText(); Click(); return true; + case GlobalAction.SelectNextRandom: Action = PrimaryAction; updateText(); Click(); return true; + default: return false; } From bd3e40a8cfd6c103d05f399db4186cfc1c6339b7 Mon Sep 17 00:00:00 2001 From: "Jack Boswell (boswelja)" Date: Thu, 4 Jun 2020 20:57:24 +1200 Subject: [PATCH 0017/1791] Move default return out of switch/case --- osu.Game/Screens/Select/FooterButtonRandom.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/FooterButtonRandom.cs b/osu.Game/Screens/Select/FooterButtonRandom.cs index fdb1159484..e2e5c63740 100644 --- a/osu.Game/Screens/Select/FooterButtonRandom.cs +++ b/osu.Game/Screens/Select/FooterButtonRandom.cs @@ -73,10 +73,9 @@ namespace osu.Game.Screens.Select updateText(); Click(); return true; - - default: - return false; } + + return false; } public override void OnReleased(GlobalAction action) From df148cf9d1ffe5fbb9ed6caf3dd4727b215b971e Mon Sep 17 00:00:00 2001 From: "Jack Boswell (boswelja)" Date: Thu, 4 Jun 2020 20:58:19 +1200 Subject: [PATCH 0018/1791] Rename secondaryAction to rewindSearch --- osu.Game/Screens/Select/FooterButtonRandom.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/FooterButtonRandom.cs b/osu.Game/Screens/Select/FooterButtonRandom.cs index e2e5c63740..e239211c04 100644 --- a/osu.Game/Screens/Select/FooterButtonRandom.cs +++ b/osu.Game/Screens/Select/FooterButtonRandom.cs @@ -18,7 +18,7 @@ namespace osu.Game.Screens.Select public Action SecondaryAction { get; set; } private readonly SpriteText secondaryText; - private bool secondaryActive; + private bool rewindSearch; public FooterButtonRandom() { @@ -45,7 +45,7 @@ namespace osu.Game.Screens.Select private void updateText() { - if (secondaryActive) + if (rewindSearch) { SpriteText.FadeOut(120, Easing.InQuad); secondaryText.FadeIn(120, Easing.InQuad); @@ -62,7 +62,7 @@ namespace osu.Game.Screens.Select switch (action) { case GlobalAction.SelectPreviousRandom: - secondaryActive = true; + rewindSearch = true; Action = SecondaryAction; updateText(); Click(); @@ -82,7 +82,7 @@ namespace osu.Game.Screens.Select { if (action == GlobalAction.SelectPreviousRandom) { - secondaryActive = false; + rewindSearch = false; updateText(); } } From 62984cb7f5da4380939200833cccfe06b4128816 Mon Sep 17 00:00:00 2001 From: "Jack Boswell (boswelja)" Date: Thu, 4 Jun 2020 20:58:53 +1200 Subject: [PATCH 0019/1791] Remove unused Hotkey assignment --- osu.Game/Screens/Select/FooterButtonRandom.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Select/FooterButtonRandom.cs b/osu.Game/Screens/Select/FooterButtonRandom.cs index e239211c04..1c3c699a2d 100644 --- a/osu.Game/Screens/Select/FooterButtonRandom.cs +++ b/osu.Game/Screens/Select/FooterButtonRandom.cs @@ -40,7 +40,6 @@ namespace osu.Game.Screens.Select SelectedColour = colours.Green; DeselectedColour = SelectedColour.Opacity(0.5f); Text = @"random"; - Hotkey = GlobalAction.SelectNextRandom; } private void updateText() From 8533d7573dbc1d468cadc4aa82ecc58ab0631c56 Mon Sep 17 00:00:00 2001 From: "Jack Boswell (boswelja)" Date: Thu, 4 Jun 2020 21:00:29 +1200 Subject: [PATCH 0020/1791] Rename FooterButtonRandom actions to better describe what they are used for --- osu.Game/Screens/Select/FooterButtonRandom.cs | 8 ++++---- osu.Game/Screens/Select/SongSelect.cs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Select/FooterButtonRandom.cs b/osu.Game/Screens/Select/FooterButtonRandom.cs index 1c3c699a2d..9316d4e064 100644 --- a/osu.Game/Screens/Select/FooterButtonRandom.cs +++ b/osu.Game/Screens/Select/FooterButtonRandom.cs @@ -14,8 +14,8 @@ namespace osu.Game.Screens.Select { public class FooterButtonRandom : FooterButton { - public Action PrimaryAction { get; set; } - public Action SecondaryAction { get; set; } + public Action NextRandom { get; set; } + public Action PreviousRandom { get; set; } private readonly SpriteText secondaryText; private bool rewindSearch; @@ -62,13 +62,13 @@ namespace osu.Game.Screens.Select { case GlobalAction.SelectPreviousRandom: rewindSearch = true; - Action = SecondaryAction; + Action = PreviousRandom; updateText(); Click(); return true; case GlobalAction.SelectNextRandom: - Action = PrimaryAction; + Action = NextRandom; updateText(); Click(); return true; diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index e22c055047..70034780d1 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -272,7 +272,7 @@ namespace osu.Game.Screens.Select if (Footer != null) { Footer.AddButton(new FooterButtonMods { Current = Mods }, ModSelect); - Footer.AddButton(new FooterButtonRandom { PrimaryAction = triggerNextRandom, SecondaryAction = triggerPreviousRandom }); + Footer.AddButton(new FooterButtonRandom { NextRandom = triggerNextRandom, PreviousRandom = triggerPreviousRandom }); Footer.AddButton(new FooterButtonOptions(), BeatmapOptions); BeatmapOptions.AddButton(@"Remove", @"from unplayed", FontAwesome.Regular.TimesCircle, colours.Purple, null, Key.Number1); From cab132673aa226f77039311a005e0ac69379fb08 Mon Sep 17 00:00:00 2001 From: "Jack Boswell (boswelja)" Date: Thu, 4 Jun 2020 21:03:10 +1200 Subject: [PATCH 0021/1791] Break FooterButtonRandom creation into multiple lines --- osu.Game/Screens/Select/SongSelect.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 70034780d1..fdec0395bd 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -272,7 +272,11 @@ namespace osu.Game.Screens.Select if (Footer != null) { Footer.AddButton(new FooterButtonMods { Current = Mods }, ModSelect); - Footer.AddButton(new FooterButtonRandom { NextRandom = triggerNextRandom, PreviousRandom = triggerPreviousRandom }); + Footer.AddButton(new FooterButtonRandom + { + NextRandom = triggerNextRandom, + PreviousRandom = triggerPreviousRandom + }); Footer.AddButton(new FooterButtonOptions(), BeatmapOptions); BeatmapOptions.AddButton(@"Remove", @"from unplayed", FontAwesome.Regular.TimesCircle, colours.Purple, null, Key.Number1); From cb6e4739107892079b84987a0322c2a5ba963a76 Mon Sep 17 00:00:00 2001 From: "Jack Boswell (boswelja)" Date: Thu, 4 Jun 2020 21:08:07 +1200 Subject: [PATCH 0022/1791] Remove triggerPreviousRandom from SongSelect --- osu.Game/Screens/Select/SongSelect.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index fdec0395bd..453ecd2b4e 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -275,7 +275,7 @@ namespace osu.Game.Screens.Select Footer.AddButton(new FooterButtonRandom { NextRandom = triggerNextRandom, - PreviousRandom = triggerPreviousRandom + PreviousRandom = Carousel.SelectPreviousRandom }); Footer.AddButton(new FooterButtonOptions(), BeatmapOptions); @@ -505,11 +505,6 @@ namespace osu.Game.Screens.Select Carousel.SelectNextRandom(); } - private void triggerPreviousRandom() - { - Carousel.SelectPreviousRandom(); - } - public override void OnEntering(IScreen last) { base.OnEntering(last); From a40475e6aa95b2585e6d811824d5690fec21830a Mon Sep 17 00:00:00 2001 From: "Jack Boswell (boswelja)" Date: Thu, 4 Jun 2020 21:09:47 +1200 Subject: [PATCH 0023/1791] Remove triggerNextRandom from SongSelect --- osu.Game/Screens/Select/SongSelect.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 453ecd2b4e..6ca53bcfa9 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -274,7 +274,7 @@ namespace osu.Game.Screens.Select Footer.AddButton(new FooterButtonMods { Current = Mods }, ModSelect); Footer.AddButton(new FooterButtonRandom { - NextRandom = triggerNextRandom, + NextRandom = () => Carousel.SelectNextRandom(), PreviousRandom = Carousel.SelectPreviousRandom }); Footer.AddButton(new FooterButtonOptions(), BeatmapOptions); @@ -500,11 +500,6 @@ namespace osu.Game.Screens.Select } } - private void triggerNextRandom() - { - Carousel.SelectNextRandom(); - } - public override void OnEntering(IScreen last) { base.OnEntering(last); From 0e155c8eb9729106891708bee5eadc63acdf0ef6 Mon Sep 17 00:00:00 2001 From: "Jack Boswell (boswelja)" Date: Sun, 7 Jun 2020 15:34:19 +1200 Subject: [PATCH 0024/1791] Don't switch FooterButtonRandon Action on pressed. Instead, create a new Action that invokes either NextRandom or PreviousRandom, depending on rewindSearch --- osu.Game/Screens/Select/FooterButtonRandom.cs | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Select/FooterButtonRandom.cs b/osu.Game/Screens/Select/FooterButtonRandom.cs index 9316d4e064..07de29a0ea 100644 --- a/osu.Game/Screens/Select/FooterButtonRandom.cs +++ b/osu.Game/Screens/Select/FooterButtonRandom.cs @@ -40,6 +40,17 @@ namespace osu.Game.Screens.Select SelectedColour = colours.Green; DeselectedColour = SelectedColour.Opacity(0.5f); Text = @"random"; + Action = () => + { + if (rewindSearch) + { + PreviousRandom.Invoke(); + } + else + { + NextRandom.Invoke(); + } + }; } private void updateText() @@ -58,23 +69,14 @@ namespace osu.Game.Screens.Select public override bool OnPressed(GlobalAction action) { - switch (action) + rewindSearch = action == GlobalAction.SelectPreviousRandom; + if (action != GlobalAction.SelectNextRandom && !rewindSearch) { - case GlobalAction.SelectPreviousRandom: - rewindSearch = true; - Action = PreviousRandom; - updateText(); - Click(); - return true; - - case GlobalAction.SelectNextRandom: - Action = NextRandom; - updateText(); - Click(); - return true; + return false; } - - return false; + updateText(); + Click(); + return true; } public override void OnReleased(GlobalAction action) From 7c04e9aca4a6a302ffd601fd4e406b645387daaa Mon Sep 17 00:00:00 2001 From: "Jack Boswell (boswelja)" Date: Sun, 7 Jun 2020 15:37:19 +1200 Subject: [PATCH 0025/1791] Move new GlobalAction keybinding entries to the end of the class. The new keybindings shouldn't mess with existing bindings anymore --- .../Input/Bindings/GlobalActionContainer.cs | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 7257b0ce15..bdfad68a13 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -114,19 +114,6 @@ namespace osu.Game.Input.Bindings [Description("Toggle mute")] ToggleMute, - // Song select keybindings - [Description("Toggle mod selection overlay")] - ToggleModSelection, - - [Description("Select a random beatmap")] - SelectNextRandom, - - [Description("Select the last random beatmap")] - SelectPreviousRandom, - - [Description("Toggle beatmap options overlay")] - ToggleBeatmapOptions, - // In-Game Keybindings [Description("Skip cutscene")] SkipCutscene, @@ -173,5 +160,18 @@ namespace osu.Game.Input.Bindings [Description("Next Selection")] SelectNext, + + // Song select keybindings + [Description("Toggle mod selection overlay")] + ToggleModSelection, + + [Description("Select a random beatmap")] + SelectNextRandom, + + [Description("Select the last random beatmap")] + SelectPreviousRandom, + + [Description("Toggle beatmap options overlay")] + ToggleBeatmapOptions, } } From 8b7718116ded732bd062ee103dd875b8af9a6ed5 Mon Sep 17 00:00:00 2001 From: "Jack Boswell (boswelja)" Date: Sun, 7 Jun 2020 16:06:18 +1200 Subject: [PATCH 0026/1791] Add missing blank lines --- osu.Game/Screens/Select/FooterButtonRandom.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Select/FooterButtonRandom.cs b/osu.Game/Screens/Select/FooterButtonRandom.cs index 07de29a0ea..2c4dd58763 100644 --- a/osu.Game/Screens/Select/FooterButtonRandom.cs +++ b/osu.Game/Screens/Select/FooterButtonRandom.cs @@ -70,10 +70,12 @@ namespace osu.Game.Screens.Select public override bool OnPressed(GlobalAction action) { rewindSearch = action == GlobalAction.SelectPreviousRandom; + if (action != GlobalAction.SelectNextRandom && !rewindSearch) { return false; } + updateText(); Click(); return true; From bddd2b72ba257370e999a9f958f090cb83ef57c9 Mon Sep 17 00:00:00 2001 From: "Jack Boswell (boswelja)" Date: Mon, 8 Jun 2020 15:05:53 +1200 Subject: [PATCH 0027/1791] Remove secondaryText and related code from FooterButtonRandom --- osu.Game/Screens/Select/FooterButtonRandom.cs | 31 +------------------ 1 file changed, 1 insertion(+), 30 deletions(-) diff --git a/osu.Game/Screens/Select/FooterButtonRandom.cs b/osu.Game/Screens/Select/FooterButtonRandom.cs index 2c4dd58763..7574d01a7b 100644 --- a/osu.Game/Screens/Select/FooterButtonRandom.cs +++ b/osu.Game/Screens/Select/FooterButtonRandom.cs @@ -17,22 +17,9 @@ namespace osu.Game.Screens.Select public Action NextRandom { get; set; } public Action PreviousRandom { get; set; } - private readonly SpriteText secondaryText; private bool rewindSearch; - public FooterButtonRandom() - { - TextContainer.Add(secondaryText = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = @"rewind", - Alpha = 0, - }); - - // force both text sprites to always be present to avoid width flickering while they're being swapped out - SpriteText.AlwaysPresent = secondaryText.AlwaysPresent = true; - } + public FooterButtonRandom() { } [BackgroundDependencyLoader] private void load(OsuColour colours) @@ -53,20 +40,6 @@ namespace osu.Game.Screens.Select }; } - private void updateText() - { - if (rewindSearch) - { - SpriteText.FadeOut(120, Easing.InQuad); - secondaryText.FadeIn(120, Easing.InQuad); - } - else - { - SpriteText.FadeIn(120, Easing.InQuad); - secondaryText.FadeOut(120, Easing.InQuad); - } - } - public override bool OnPressed(GlobalAction action) { rewindSearch = action == GlobalAction.SelectPreviousRandom; @@ -76,7 +49,6 @@ namespace osu.Game.Screens.Select return false; } - updateText(); Click(); return true; } @@ -86,7 +58,6 @@ namespace osu.Game.Screens.Select if (action == GlobalAction.SelectPreviousRandom) { rewindSearch = false; - updateText(); } } } From ea9207e0ad21434d79eace9e527a3719ad1232c0 Mon Sep 17 00:00:00 2001 From: "Jack Boswell (boswelja)" Date: Mon, 8 Jun 2020 17:31:31 +1200 Subject: [PATCH 0028/1791] Clean up FooterButtonRandom Removed unnecessary using statements Removed unnecessary constructor --- osu.Game/Screens/Select/FooterButtonRandom.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game/Screens/Select/FooterButtonRandom.cs b/osu.Game/Screens/Select/FooterButtonRandom.cs index 7574d01a7b..363c2e4c10 100644 --- a/osu.Game/Screens/Select/FooterButtonRandom.cs +++ b/osu.Game/Screens/Select/FooterButtonRandom.cs @@ -4,10 +4,7 @@ using System; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Input.Bindings; namespace osu.Game.Screens.Select @@ -19,8 +16,6 @@ namespace osu.Game.Screens.Select private bool rewindSearch; - public FooterButtonRandom() { } - [BackgroundDependencyLoader] private void load(OsuColour colours) { From 9dc1eab6ae702550a6c3c8a9c5734183816a870f Mon Sep 17 00:00:00 2001 From: "Jack Boswell (boswelja)" Date: Wed, 10 Jun 2020 13:05:11 +1200 Subject: [PATCH 0029/1791] Improve code readability of FooterButtonRandom OnPressed --- osu.Game/Screens/Select/FooterButtonRandom.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/FooterButtonRandom.cs b/osu.Game/Screens/Select/FooterButtonRandom.cs index 363c2e4c10..b314971cb3 100644 --- a/osu.Game/Screens/Select/FooterButtonRandom.cs +++ b/osu.Game/Screens/Select/FooterButtonRandom.cs @@ -39,7 +39,7 @@ namespace osu.Game.Screens.Select { rewindSearch = action == GlobalAction.SelectPreviousRandom; - if (action != GlobalAction.SelectNextRandom && !rewindSearch) + if (action != GlobalAction.SelectNextRandom && action != GlobalAction.SelectPreviousRandom) { return false; } From 080bf1e85a034e4a3fa58a91b66683fd482ca8b3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Jun 2020 13:46:16 +0900 Subject: [PATCH 0030/1791] Fix missing default inclusion --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index bdfad68a13..4429bf27cf 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -21,7 +21,10 @@ namespace osu.Game.Input.Bindings handler = game; } - public override IEnumerable DefaultKeyBindings => GlobalKeyBindings.Concat(InGameKeyBindings).Concat(AudioControlKeyBindings); + public override IEnumerable DefaultKeyBindings => GlobalKeyBindings + .Concat(InGameKeyBindings) + .Concat(AudioControlKeyBindings) + .Concat(SongSelectKeyBindings); public IEnumerable GlobalKeyBindings => new[] { From e15324ca900cde9e37d3d52b79f27be690bd66eb Mon Sep 17 00:00:00 2001 From: "Jack Boswell (boswelja)" Date: Mon, 15 Jun 2020 21:44:38 +1200 Subject: [PATCH 0031/1791] Shorten new binding descriptions --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 4429bf27cf..851a848b8f 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -165,16 +165,16 @@ namespace osu.Game.Input.Bindings SelectNext, // Song select keybindings - [Description("Toggle mod selection overlay")] + [Description("Toggle Mod Select")] ToggleModSelection, - [Description("Select a random beatmap")] + [Description("Random")] SelectNextRandom, - [Description("Select the last random beatmap")] + [Description("Rewind")] SelectPreviousRandom, - [Description("Toggle beatmap options overlay")] + [Description("Beatmap Options")] ToggleBeatmapOptions, } } From ac91f0e2707bf57eacacf079c4de09b1c108926d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 10 Dec 2020 00:25:46 +0900 Subject: [PATCH 0032/1791] Add extended limits to difficulty adjustment mod --- .../Mods/CatchModDifficultyAdjust.cs | 12 ++- .../Mods/OsuModDifficultyAdjust.cs | 12 ++- osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs | 79 ++++++++++++++++++- 3 files changed, 97 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs index acdd0a420c..859dfb7647 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Catch.Mods public class CatchModDifficultyAdjust : ModDifficultyAdjust { [SettingSource("Circle Size", "Override a beatmap's set CS.", FIRST_SETTING_ORDER - 1)] - public BindableNumber CircleSize { get; } = new BindableFloat + public BindableNumber CircleSize { get; } = new BindableFloatWithLimitExtension { Precision = 0.1f, MinValue = 1, @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Catch.Mods }; [SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1)] - public BindableNumber ApproachRate { get; } = new BindableFloat + public BindableNumber ApproachRate { get; } = new BindableFloatWithLimitExtension { Precision = 0.1f, MinValue = 1, @@ -31,6 +31,14 @@ namespace osu.Game.Rulesets.Catch.Mods Value = 5, }; + protected override void ApplyLimits(bool extended) + { + base.ApplyLimits(extended); + + CircleSize.MaxValue = extended ? 11 : 10; + ApproachRate.MaxValue = extended ? 11 : 10; + } + public override string SettingDescription { get diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs index ff995e38ce..a6ad2e75f1 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Mods public class OsuModDifficultyAdjust : ModDifficultyAdjust { [SettingSource("Circle Size", "Override a beatmap's set CS.", FIRST_SETTING_ORDER - 1)] - public BindableNumber CircleSize { get; } = new BindableFloat + public BindableNumber CircleSize { get; } = new BindableFloatWithLimitExtension { Precision = 0.1f, MinValue = 0, @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Mods }; [SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1)] - public BindableNumber ApproachRate { get; } = new BindableFloat + public BindableNumber ApproachRate { get; } = new BindableFloatWithLimitExtension { Precision = 0.1f, MinValue = 0, @@ -31,6 +31,14 @@ namespace osu.Game.Rulesets.Osu.Mods Value = 5, }; + protected override void ApplyLimits(bool extended) + { + base.ApplyLimits(extended); + + CircleSize.MaxValue = extended ? 11 : 10; + ApproachRate.MaxValue = extended ? 11 : 10; + } + public override string SettingDescription { get diff --git a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs index 165644edbe..7df663ad3a 100644 --- a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Mods protected const int LAST_SETTING_ORDER = 2; [SettingSource("HP Drain", "Override a beatmap's set HP.", FIRST_SETTING_ORDER)] - public BindableNumber DrainRate { get; } = new BindableFloat + public BindableNumber DrainRate { get; } = new BindableFloatWithLimitExtension { Precision = 0.1f, MinValue = 0, @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Mods }; [SettingSource("Accuracy", "Override a beatmap's set OD.", LAST_SETTING_ORDER)] - public BindableNumber OverallDifficulty { get; } = new BindableFloat + public BindableNumber OverallDifficulty { get; } = new BindableFloatWithLimitExtension { Precision = 0.1f, MinValue = 0, @@ -53,6 +53,24 @@ namespace osu.Game.Rulesets.Mods Value = 5, }; + [SettingSource("Extended Limits", "Adjust difficulty beyond sane limits.")] + public BindableBool ExtendedLimits { get; } = new BindableBool(); + + protected ModDifficultyAdjust() + { + ExtendedLimits.BindValueChanged(extend => ApplyLimits(extend.NewValue)); + } + + /// + /// Changes the difficulty adjustment limits. Occurs when the value of is changed. + /// + /// Whether limits should extend beyond sane ranges. + protected virtual void ApplyLimits(bool extended) + { + DrainRate.MaxValue = extended ? 11 : 10; + OverallDifficulty.MaxValue = extended ? 11 : 10; + } + public override string SettingDescription { get @@ -123,5 +141,62 @@ namespace osu.Game.Rulesets.Mods difficulty.DrainRate = DrainRate.Value; difficulty.OverallDifficulty = OverallDifficulty.Value; } + + /// + /// A that extends its min/max values to support any assigned value. + /// + protected class BindableDoubleWithLimitExtension : BindableDouble + { + public override double Value + { + get => base.Value; + set + { + if (value < MinValue) + MinValue = value; + if (value > MaxValue) + MaxValue = value; + base.Value = value; + } + } + } + + /// + /// A that extends its min/max values to support any assigned value. + /// + protected class BindableFloatWithLimitExtension : BindableFloat + { + public override float Value + { + get => base.Value; + set + { + if (value < MinValue) + MinValue = value; + if (value > MaxValue) + MaxValue = value; + base.Value = value; + } + } + } + + /// + /// A that extends its min/max values to support any assigned value. + /// + protected class BindableIntWithLimitExtension : BindableInt + { + public override int Value + { + get => base.Value; + set + { + if (value < MinValue) + MinValue = value; + if (value > MaxValue) + MaxValue = value; + base.Value = value; + } + } + } } } From 47a93d8614eff32b5fd7bb9db8b81e8d97a47f34 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 10 Dec 2020 00:26:35 +0900 Subject: [PATCH 0033/1791] Adjust osu! hitobject fade-ins to support AR>10 --- .../Objects/Drawables/Connections/FollowPointConnection.cs | 5 ++++- osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs index 6e7b1050cb..40154ca84c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs @@ -110,8 +110,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections double startTime = start.GetEndTime(); double duration = end.StartTime - startTime; + // For now, adjust the pre-empt for approach rates > 10. + double preempt = PREEMPT * Math.Min(1, start.TimePreempt / 450); + fadeOutTime = startTime + fraction * duration; - fadeInTime = fadeOutTime - PREEMPT; + fadeInTime = fadeOutTime - preempt; } } } diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index 15af141c99..6d28a576a4 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.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.Linq; using osu.Framework.Bindables; using osu.Game.Beatmaps; @@ -113,7 +114,7 @@ namespace osu.Game.Rulesets.Osu.Objects base.ApplyDefaultsToSelf(controlPointInfo, difficulty); TimePreempt = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450); - TimeFadeIn = 400; // as per osu-stable + TimeFadeIn = 400 * Math.Min(1, TimePreempt / 450); Scale = (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5) / 2; } From 9835245ea29e3dcc053feb818b65f2e959cb5d06 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 10 Dec 2020 00:32:31 +0900 Subject: [PATCH 0034/1791] Add test --- .../Online/TestAPIModSerialization.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/osu.Game.Tests/Online/TestAPIModSerialization.cs b/osu.Game.Tests/Online/TestAPIModSerialization.cs index 5948582d77..84862ebb07 100644 --- a/osu.Game.Tests/Online/TestAPIModSerialization.cs +++ b/osu.Game.Tests/Online/TestAPIModSerialization.cs @@ -68,12 +68,29 @@ namespace osu.Game.Tests.Online Assert.That(converted.FinalRate.Value, Is.EqualTo(0.25)); } + [Test] + public void TestDeserialiseDifficultyAdjustModWithExtendedLimits() + { + var apiMod = new APIMod(new TestModDifficultyAdjust + { + OverallDifficulty = { Value = 11 }, + ExtendedLimits = { Value = true } + }); + + var deserialised = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(apiMod)); + var converted = (TestModDifficultyAdjust)deserialised.ToMod(new TestRuleset()); + + Assert.That(converted.ExtendedLimits.Value, Is.True); + Assert.That(converted.OverallDifficulty.Value, Is.EqualTo(11)); + } + private class TestRuleset : Ruleset { public override IEnumerable GetModsFor(ModType type) => new Mod[] { new TestMod(), new TestModTimeRamp(), + new TestModDifficultyAdjust() }; public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => throw new System.NotImplementedException(); @@ -135,5 +152,9 @@ namespace osu.Game.Tests.Online Value = true }; } + + private class TestModDifficultyAdjust : ModDifficultyAdjust + { + } } } From 43f8f3638a9550a0d2d14d827ef3ee53f552f4e3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 27 Dec 2020 02:42:13 +0300 Subject: [PATCH 0035/1791] Fix mod using reference equality unless casted to `IMod` --- osu.Game/Online/API/APIMod.cs | 4 ++-- osu.Game/Rulesets/Mods/IMod.cs | 3 +-- osu.Game/Rulesets/Mods/Mod.cs | 4 ++-- osu.Game/Scoring/ScoreInfo.cs | 4 ++-- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/osu.Game/Online/API/APIMod.cs b/osu.Game/Online/API/APIMod.cs index 780e5daa16..f4e0e1b11f 100644 --- a/osu.Game/Online/API/APIMod.cs +++ b/osu.Game/Online/API/APIMod.cs @@ -13,7 +13,7 @@ using osu.Game.Rulesets.Mods; namespace osu.Game.Online.API { - public class APIMod : IMod + public class APIMod : IMod, IEquatable { [JsonProperty("acronym")] public string Acronym { get; set; } @@ -52,7 +52,7 @@ namespace osu.Game.Online.API return resultMod; } - public bool Equals(IMod other) => Acronym == other?.Acronym; + public bool Equals(APIMod other) => Acronym == other?.Acronym; public override string ToString() { diff --git a/osu.Game/Rulesets/Mods/IMod.cs b/osu.Game/Rulesets/Mods/IMod.cs index a5e19f293c..448ad0eb30 100644 --- a/osu.Game/Rulesets/Mods/IMod.cs +++ b/osu.Game/Rulesets/Mods/IMod.cs @@ -1,12 +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 System; using Newtonsoft.Json; namespace osu.Game.Rulesets.Mods { - public interface IMod : IEquatable + public interface IMod { /// /// The shortened name of this mod. diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index b8dc7a2661..33550e070b 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Mods /// The base class for gameplay modifiers. /// [ExcludeFromDynamicCompile] - public abstract class Mod : IMod, IJsonSerializable + public abstract class Mod : IMod, IEquatable, IJsonSerializable { /// /// The name of this mod. @@ -149,6 +149,6 @@ namespace osu.Game.Rulesets.Mods return copy; } - public bool Equals(IMod other) => GetType() == other?.GetType(); + public bool Equals(Mod other) => GetType() == other?.GetType(); } } diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index f5192f3a40..59eaa994c2 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -252,11 +252,11 @@ namespace osu.Game.Scoring } [Serializable] - protected class DeserializedMod : IMod + protected class DeserializedMod : IMod, IEquatable { public string Acronym { get; set; } - public bool Equals(IMod other) => Acronym == other?.Acronym; + public bool Equals(DeserializedMod other) => Acronym == other?.Acronym; } public override string ToString() => $"{User} playing {Beatmap}"; From 5efcdbd431153a902fd510d727f9b02400afcb66 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 28 Dec 2020 15:19:28 +0300 Subject: [PATCH 0036/1791] Fix IMod now using reference equality as well --- osu.Game/Online/API/APIMod.cs | 3 ++- osu.Game/Rulesets/Mods/IMod.cs | 3 ++- osu.Game/Rulesets/Mods/Mod.cs | 3 ++- osu.Game/Scoring/ScoreInfo.cs | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/API/APIMod.cs b/osu.Game/Online/API/APIMod.cs index f4e0e1b11f..c1b243c743 100644 --- a/osu.Game/Online/API/APIMod.cs +++ b/osu.Game/Online/API/APIMod.cs @@ -52,7 +52,8 @@ namespace osu.Game.Online.API return resultMod; } - public bool Equals(APIMod other) => Acronym == other?.Acronym; + public bool Equals(IMod other) => other is APIMod them && Equals(them); + public bool Equals(APIMod other) => ((IMod)this).Equals(other); public override string ToString() { diff --git a/osu.Game/Rulesets/Mods/IMod.cs b/osu.Game/Rulesets/Mods/IMod.cs index 448ad0eb30..a5e19f293c 100644 --- a/osu.Game/Rulesets/Mods/IMod.cs +++ b/osu.Game/Rulesets/Mods/IMod.cs @@ -1,11 +1,12 @@ // 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; namespace osu.Game.Rulesets.Mods { - public interface IMod + public interface IMod : IEquatable { /// /// The shortened name of this mod. diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 33550e070b..3ccebe4174 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -149,6 +149,7 @@ namespace osu.Game.Rulesets.Mods return copy; } - public bool Equals(Mod other) => GetType() == other?.GetType(); + public bool Equals(IMod other) => other is Mod them && Equals(them); + public bool Equals(Mod other) => Acronym == other?.Acronym; } } diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 59eaa994c2..335671ea4e 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -256,7 +256,8 @@ namespace osu.Game.Scoring { public string Acronym { get; set; } - public bool Equals(DeserializedMod other) => Acronym == other?.Acronym; + bool IEquatable.Equals(IMod other) => other is DeserializedMod them && Equals(them); + public bool Equals(DeserializedMod other) => ((IMod)this).Equals(other); } public override string ToString() => $"{User} playing {Beatmap}"; From 41b79d938b401d7101440e4ee21f6f28a7590b4a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 28 Dec 2020 15:30:52 +0300 Subject: [PATCH 0037/1791] Fix wrong checks.. --- osu.Game/Online/API/APIMod.cs | 2 +- osu.Game/Rulesets/Mods/Mod.cs | 2 +- osu.Game/Scoring/ScoreInfo.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/API/APIMod.cs b/osu.Game/Online/API/APIMod.cs index c1b243c743..f4fed4e5c5 100644 --- a/osu.Game/Online/API/APIMod.cs +++ b/osu.Game/Online/API/APIMod.cs @@ -53,7 +53,7 @@ namespace osu.Game.Online.API } public bool Equals(IMod other) => other is APIMod them && Equals(them); - public bool Equals(APIMod other) => ((IMod)this).Equals(other); + public bool Equals(APIMod other) => Acronym == other?.Acronym; public override string ToString() { diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 3ccebe4174..dbb2a0fdc1 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -150,6 +150,6 @@ namespace osu.Game.Rulesets.Mods } public bool Equals(IMod other) => other is Mod them && Equals(them); - public bool Equals(Mod other) => Acronym == other?.Acronym; + public bool Equals(Mod other) => GetType() == other?.GetType(); } } diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 335671ea4e..1e5742c358 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -257,7 +257,7 @@ namespace osu.Game.Scoring public string Acronym { get; set; } bool IEquatable.Equals(IMod other) => other is DeserializedMod them && Equals(them); - public bool Equals(DeserializedMod other) => ((IMod)this).Equals(other); + public bool Equals(DeserializedMod other) => Acronym == other?.Acronym; } public override string ToString() => $"{User} playing {Beatmap}"; From 013b9b62a1ff7dfd22db139d0571259bcd89de43 Mon Sep 17 00:00:00 2001 From: Firmatorenio Date: Tue, 29 Dec 2020 20:22:56 +0600 Subject: [PATCH 0038/1791] add SV multipliers to taiko difficulty mods --- osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs | 9 +++++++++ osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs | 9 +++++++++ osu.Game/Rulesets/Mods/ModHardRock.cs | 2 +- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs index d1ad4c9d8d..5ff91eec9f 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.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.Game.Beatmaps; using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Taiko.Mods @@ -8,5 +9,13 @@ namespace osu.Game.Rulesets.Taiko.Mods public class TaikoModEasy : ModEasy { public override string Description => @"Beats move slower, and less accuracy required!"; + + private const double slider_multiplier = 0.8; + + public override void ApplyToDifficulty(BeatmapDifficulty difficulty) + { + base.ApplyToDifficulty(difficulty); + difficulty.SliderMultiplier *= slider_multiplier; + } } } diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs index 49d225cdb5..37c8dab2de 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.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.Game.Beatmaps; using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Taiko.Mods @@ -9,5 +10,13 @@ namespace osu.Game.Rulesets.Taiko.Mods { public override double ScoreMultiplier => 1.06; public override bool Ranked => true; + + private const double slider_multiplier = 1.87; + + public override void ApplyToDifficulty(BeatmapDifficulty difficulty) + { + base.ApplyToDifficulty(difficulty); + difficulty.SliderMultiplier *= slider_multiplier; + } } } diff --git a/osu.Game/Rulesets/Mods/ModHardRock.cs b/osu.Game/Rulesets/Mods/ModHardRock.cs index 0e589735c1..4edcb0b074 100644 --- a/osu.Game/Rulesets/Mods/ModHardRock.cs +++ b/osu.Game/Rulesets/Mods/ModHardRock.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mods { } - public void ApplyToDifficulty(BeatmapDifficulty difficulty) + public virtual void ApplyToDifficulty(BeatmapDifficulty difficulty) { const float ratio = 1.4f; difficulty.CircleSize = Math.Min(difficulty.CircleSize * 1.3f, 10.0f); // CS uses a custom 1.3 ratio. From 9d9c0df64c9213757c51f379ee6be56cce68b87b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 29 Dec 2020 17:17:44 +0100 Subject: [PATCH 0039/1791] Make DeserializedMod equality members match other IMods --- osu.Game/Scoring/ScoreInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 1e5742c358..3084afb833 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -256,7 +256,7 @@ namespace osu.Game.Scoring { public string Acronym { get; set; } - bool IEquatable.Equals(IMod other) => other is DeserializedMod them && Equals(them); + public bool Equals(IMod other) => other is DeserializedMod them && Equals(them); public bool Equals(DeserializedMod other) => Acronym == other?.Acronym; } From 669c42a38d7f3ac2d015754166b7af7aacbd13c9 Mon Sep 17 00:00:00 2001 From: Firmatorenio Date: Wed, 30 Dec 2020 20:57:41 +0600 Subject: [PATCH 0040/1791] add remarks explaining HR SV multiplier --- osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs | 3 +++ osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs | 10 +++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs index 5ff91eec9f..ad6fdf59e2 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs @@ -10,6 +10,9 @@ namespace osu.Game.Rulesets.Taiko.Mods { public override string Description => @"Beats move slower, and less accuracy required!"; + /// + /// Multiplier factor added to the scrolling speed. + /// private const double slider_multiplier = 0.8; public override void ApplyToDifficulty(BeatmapDifficulty difficulty) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs index 37c8dab2de..a5a8b75f80 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs @@ -11,7 +11,15 @@ namespace osu.Game.Rulesets.Taiko.Mods public override double ScoreMultiplier => 1.06; public override bool Ranked => true; - private const double slider_multiplier = 1.87; + /// + /// Multiplier factor added to the scrolling speed. + /// + /// + /// This factor is made up of two parts: the base part (1.4) and the aspect ratio adjustment (4/3). + /// Stable applies the latter by dividing the width of the user's display by the width of a display with the same height, but 4:3 aspect ratio. + /// TODO: Revisit if taiko playfield ever changes away from a hard-coded 16:9 (see https://github.com/ppy/osu/issues/5685). + /// + private const double slider_multiplier = 1.4 * 4 / 3; public override void ApplyToDifficulty(BeatmapDifficulty difficulty) { From 0e0cb94ed5804fc9ef1b2316ec3e2663a5341fe8 Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Sun, 3 Jan 2021 03:20:25 +0100 Subject: [PATCH 0041/1791] testing (#2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Catch multiplayer client-related unobserved exceptions better Silencing an exception from a task continuation requires accessing `task.Exception` in any way, which was not done previously if `logOnError` was false. To resolve without having to worry whether the compiler will optimise away a useless access or now, just always log, but switch the logging level. The unimportant errors will be logged as debug and therefore essentially silenced on release builds (but could still be potentially useful in debugging). * move SkinnableHealthDisplay Similar components are in osu.Game.Screens.Play.HUD while this is not * Bump Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson Bumps [Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson](https://github.com/aspnet/AspNetCore) from 3.1.9 to 3.1.10. - [Release notes](https://github.com/aspnet/AspNetCore/releases) - [Commits](https://github.com/aspnet/AspNetCore/compare/v3.1.9...v3.1.10) Signed-off-by: dependabot-preview[bot] * Bump Microsoft.NET.Test.Sdk from 16.8.0 to 16.8.3 Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 16.8.0 to 16.8.3. - [Release notes](https://github.com/microsoft/vstest/releases) - [Commits](https://github.com/microsoft/vstest/compare/v16.8.0...v16.8.3) Signed-off-by: dependabot-preview[bot] * Bump Microsoft.AspNetCore.SignalR.Client from 3.1.9 to 3.1.10 Bumps [Microsoft.AspNetCore.SignalR.Client](https://github.com/aspnet/AspNetCore) from 3.1.9 to 3.1.10. - [Release notes](https://github.com/aspnet/AspNetCore/releases) - [Commits](https://github.com/aspnet/AspNetCore/compare/v3.1.9...v3.1.10) Signed-off-by: dependabot-preview[bot] * Bump Microsoft.CodeAnalysis.BannedApiAnalyzers from 3.3.1 to 3.3.2 Bumps [Microsoft.CodeAnalysis.BannedApiAnalyzers](https://github.com/dotnet/roslyn-analyzers) from 3.3.1 to 3.3.2. - [Release notes](https://github.com/dotnet/roslyn-analyzers/releases) - [Changelog](https://github.com/dotnet/roslyn-analyzers/blob/master/PostReleaseActivities.md) - [Commits](https://github.com/dotnet/roslyn-analyzers/compare/v3.3.1...v3.3.2) Signed-off-by: dependabot-preview[bot] * Keep SignalR at last working version on iOS * Allow signalr to retry connecting when connection is closed without an exception * Bump InspectCode tool to 2020.3.2 * Disable "merge sequential patterns" suggestions As they were considered to be detrimental to code readability. * Replace using static with explicit nested reference This seems to be an inspectcode bug, as the code is correct and compiles, but let's just work around it for now. Co-authored-by: Bartłomiej Dach Co-authored-by: mcendu Co-authored-by: Dean Herbert Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> --- .config/dotnet-tools.json | 2 +- Directory.Build.props | 2 +- .../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 +- .../Components/TestScenePreviewTrackManager.cs | 7 +++---- .../TestSceneSkinnableHealthDisplay.cs | 2 +- osu.Game.Tests/osu.Game.Tests.csproj | 2 +- .../osu.Game.Tournament.Tests.csproj | 2 +- osu.Game/Extensions/TaskExtensions.cs | 18 ++++++++++++++---- .../Online/Multiplayer/MultiplayerClient.cs | 9 +++++---- .../Play/{ => HUD}/SkinnableHealthDisplay.cs | 3 +-- osu.Game/osu.Game.csproj | 4 ++-- osu.sln.DotSettings | 1 + 15 files changed, 35 insertions(+), 25 deletions(-) rename osu.Game/Screens/Play/{ => HUD}/SkinnableHealthDisplay.cs (95%) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index dd53eefd23..58c24181d3 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -15,7 +15,7 @@ ] }, "jetbrains.resharper.globaltools": { - "version": "2020.2.4", + "version": "2020.3.2", "commands": [ "jb" ] diff --git a/Directory.Build.props b/Directory.Build.props index 551cb75077..9ec442aafa 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -16,7 +16,7 @@ - + diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj index 61ecd79e3d..51d2032795 100644 --- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj +++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj index fa7bfd7169..3261f632f2 100644 --- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj +++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj index d6a03da807..32243e0bc3 100644 --- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj +++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj index a89645d881..210f81d111 100644 --- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj +++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs b/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs index a3db20ce83..9a999a4931 100644 --- a/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs +++ b/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs @@ -8,7 +8,6 @@ using osu.Framework.Audio.Track; using osu.Framework.Graphics.Containers; using osu.Game.Audio; using osu.Game.Beatmaps; -using static osu.Game.Tests.Visual.Components.TestScenePreviewTrackManager.TestPreviewTrackManager; namespace osu.Game.Tests.Visual.Components { @@ -100,7 +99,7 @@ namespace osu.Game.Tests.Visual.Components [Test] public void TestNonPresentTrack() { - TestPreviewTrack track = null; + TestPreviewTrackManager.TestPreviewTrack track = null; AddStep("get non-present track", () => { @@ -182,9 +181,9 @@ namespace osu.Game.Tests.Visual.Components AddAssert("track stopped", () => !track.IsRunning); } - private TestPreviewTrack getTrack() => (TestPreviewTrack)trackManager.Get(null); + private TestPreviewTrackManager.TestPreviewTrack getTrack() => (TestPreviewTrackManager.TestPreviewTrack)trackManager.Get(null); - private TestPreviewTrack getOwnedTrack() + private TestPreviewTrackManager.TestPreviewTrack getOwnedTrack() { var track = getTrack(); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs index e1b0820662..5bac8582d7 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHealthDisplay.cs @@ -9,7 +9,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Judgements; -using osu.Game.Screens.Play; +using osu.Game.Screens.Play.HUD; namespace osu.Game.Tests.Visual.Gameplay { diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 83d7b4135a..9049b67f90 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -3,7 +3,7 @@ - + diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj index bc6b994988..dc4f22788d 100644 --- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj +++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj @@ -5,7 +5,7 @@ - + diff --git a/osu.Game/Extensions/TaskExtensions.cs b/osu.Game/Extensions/TaskExtensions.cs index a1215d786b..4138c2757a 100644 --- a/osu.Game/Extensions/TaskExtensions.cs +++ b/osu.Game/Extensions/TaskExtensions.cs @@ -1,7 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + +using System; using System.Threading.Tasks; +using osu.Framework.Extensions.ExceptionExtensions; using osu.Framework.Logging; namespace osu.Game.Extensions @@ -13,13 +17,19 @@ namespace osu.Game.Extensions /// Avoids unobserved exceptions from being fired. /// /// The task. - /// Whether errors should be logged as important, or silently ignored. - public static void CatchUnobservedExceptions(this Task task, bool logOnError = false) + /// + /// Whether errors should be logged as errors visible to users, or as debug messages. + /// Logging as debug will essentially silence the errors on non-release builds. + /// + public static void CatchUnobservedExceptions(this Task task, bool logAsError = false) { task.ContinueWith(t => { - if (logOnError) - Logger.Log($"Error running task: {t.Exception?.Message ?? "unknown"}", LoggingTarget.Runtime, LogLevel.Important); + Exception? exception = t.Exception?.AsSingular(); + if (logAsError) + Logger.Error(exception, $"Error running task: {exception?.Message ?? "(unknown)"}", LoggingTarget.Runtime, true); + else + Logger.Log($"Error running task: {exception}", LoggingTarget.Runtime, LogLevel.Debug); }, TaskContinuationOptions.NotOnRanToCompletion); } } diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 24ea6abc4a..7cd1ef78f7 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -88,11 +88,12 @@ namespace osu.Game.Online.Multiplayer { isConnected.Value = false; - if (ex != null) - { - Logger.Log($"Multiplayer client lost connection: {ex}", LoggingTarget.Network); + Logger.Log(ex != null + ? $"Multiplayer client lost connection: {ex}" + : "Multiplayer client disconnected", LoggingTarget.Network); + + if (connection != null) await tryUntilConnected(); - } }; await tryUntilConnected(); diff --git a/osu.Game/Screens/Play/SkinnableHealthDisplay.cs b/osu.Game/Screens/Play/HUD/SkinnableHealthDisplay.cs similarity index 95% rename from osu.Game/Screens/Play/SkinnableHealthDisplay.cs rename to osu.Game/Screens/Play/HUD/SkinnableHealthDisplay.cs index d35d15d665..1f91f5e50f 100644 --- a/osu.Game/Screens/Play/SkinnableHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableHealthDisplay.cs @@ -5,10 +5,9 @@ using System; using osu.Framework.Bindables; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; -using osu.Game.Screens.Play.HUD; using osu.Game.Skinning; -namespace osu.Game.Screens.Play +namespace osu.Game.Screens.Play.HUD { public class SkinnableHealthDisplay : SkinnableDrawable, IHealthDisplay { diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 93aa2bc701..6c220a5c21 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -21,8 +21,8 @@ - - + + diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 22ea73858e..aa8f8739c1 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -106,6 +106,7 @@ HINT WARNING WARNING + DO_NOT_SHOW WARNING WARNING WARNING From c6e9a6cd5a3d990b2c72fab3f82148cd65dfb6fe Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 15 Jan 2021 14:28:49 +0900 Subject: [PATCH 0042/1791] Make most common BPM more accurate --- osu.Game/Beatmaps/Beatmap.cs | 38 +++++++++++++++++++ osu.Game/Beatmaps/BeatmapManager.cs | 2 +- .../ControlPoints/ControlPointInfo.cs | 7 ---- osu.Game/Beatmaps/IBeatmap.cs | 5 +++ osu.Game/Screens/Edit/EditorBeatmap.cs | 2 + osu.Game/Screens/Play/GameplayBeatmap.cs | 2 + osu.Game/Screens/Select/BeatmapInfoWedge.cs | 2 +- 7 files changed, 49 insertions(+), 9 deletions(-) diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index be2006e67a..410fd5e92e 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -48,6 +48,44 @@ namespace osu.Game.Beatmaps public virtual IEnumerable GetStatistics() => Enumerable.Empty(); + public double GetMostCommonBeatLength() + { + // The last playable time in the beatmap - the last timing point extends to this time. + // Note: This is more accurate and may present different results because osu-stable didn't have the ability to calculate slider durations in this context. + double lastTime = HitObjects.LastOrDefault()?.GetEndTime() ?? ControlPointInfo.TimingPoints.LastOrDefault()?.Time ?? 0; + + var beatLengthsAndDurations = + // Construct a set of (beatLength, duration) tuples for each individual timing point. + ControlPointInfo.TimingPoints.Select((t, i) => + { + if (t.Time > lastTime) + return (beatLength: t.BeatLength, 0); + + var nextTime = i == ControlPointInfo.TimingPoints.Count - 1 ? lastTime : ControlPointInfo.TimingPoints[i + 1].Time; + return (beatLength: t.BeatLength, duration: nextTime - t.Time); + }) + // Aggregate durations into a set of (beatLength, duration) tuples for each beat length + .GroupBy(t => t.beatLength) + .Select(g => (beatLength: g.Key, duration: g.Sum(t => t.duration))) + // And if there are no timing points, use a default. + .DefaultIfEmpty((TimingControlPoint.DEFAULT_BEAT_LENGTH, 0)); + + // Find the single beat length with the maximum aggregate duration. + double maxDurationBeatLength = double.NegativeInfinity; + double maxDuration = double.NegativeInfinity; + + foreach (var (beatLength, duration) in beatLengthsAndDurations) + { + if (duration > maxDuration) + { + maxDuration = duration; + maxDurationBeatLength = beatLength; + } + } + + return 60000 / maxDurationBeatLength; + } + IBeatmap IBeatmap.Clone() => Clone(); public Beatmap Clone() diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 42418e532b..0d4cc38ac3 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -451,7 +451,7 @@ namespace osu.Game.Beatmaps // TODO: this should be done in a better place once we actually need to dynamically update it. beatmap.BeatmapInfo.StarDifficulty = ruleset?.CreateInstance().CreateDifficultyCalculator(new DummyConversionBeatmap(beatmap)).Calculate().StarRating ?? 0; beatmap.BeatmapInfo.Length = calculateLength(beatmap); - beatmap.BeatmapInfo.BPM = beatmap.ControlPointInfo.BPMMode; + beatmap.BeatmapInfo.BPM = beatmap.GetMostCommonBeatLength(); beatmapInfos.Add(beatmap.BeatmapInfo); } diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index e8a91e4001..5cc60a5758 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -101,13 +101,6 @@ namespace osu.Game.Beatmaps.ControlPoints public double BPMMinimum => 60000 / (TimingPoints.OrderByDescending(c => c.BeatLength).FirstOrDefault() ?? TimingControlPoint.DEFAULT).BeatLength; - /// - /// Finds the mode BPM (most common BPM) represented by the control points. - /// - [JsonIgnore] - public double BPMMode => - 60000 / (TimingPoints.GroupBy(c => c.BeatLength).OrderByDescending(grp => grp.Count()).FirstOrDefault()?.FirstOrDefault() ?? TimingControlPoint.DEFAULT).BeatLength; - /// /// Remove all s and return to a pristine state. /// diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index 8f27e0b0e9..aaca8f7658 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -47,6 +47,11 @@ namespace osu.Game.Beatmaps /// IEnumerable GetStatistics(); + /// + /// Finds the most common beat length represented by the control points in this beatmap. + /// + double GetMostCommonBeatLength(); + /// /// Creates a shallow-clone of this beatmap and returns it. /// diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 165d2ba278..0e9008ba68 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -84,6 +84,8 @@ namespace osu.Game.Screens.Edit public IEnumerable GetStatistics() => PlayableBeatmap.GetStatistics(); + public double GetMostCommonBeatLength() => PlayableBeatmap.GetMostCommonBeatLength(); + public IBeatmap Clone() => (EditorBeatmap)MemberwiseClone(); private IList mutableHitObjects => (IList)PlayableBeatmap.HitObjects; diff --git a/osu.Game/Screens/Play/GameplayBeatmap.cs b/osu.Game/Screens/Play/GameplayBeatmap.cs index 64894544f4..53c1360bfa 100644 --- a/osu.Game/Screens/Play/GameplayBeatmap.cs +++ b/osu.Game/Screens/Play/GameplayBeatmap.cs @@ -39,6 +39,8 @@ namespace osu.Game.Screens.Play public IEnumerable GetStatistics() => PlayableBeatmap.GetStatistics(); + public double GetMostCommonBeatLength() => PlayableBeatmap.GetMostCommonBeatLength(); + public IBeatmap Clone() => PlayableBeatmap.Clone(); private readonly Bindable lastJudgementResult = new Bindable(); diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 04c1f6efe4..abcd697d85 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -391,7 +391,7 @@ namespace osu.Game.Screens.Select if (Precision.AlmostEquals(bpmMin, bpmMax)) return $"{bpmMin:0}"; - return $"{bpmMin:0}-{bpmMax:0} (mostly {beatmap.ControlPointInfo.BPMMode:0})"; + return $"{bpmMin:0}-{bpmMax:0} (mostly {beatmap.GetMostCommonBeatLength():0})"; } private OsuSpriteText[] getMapper(BeatmapMetadata metadata) From 24e991a5ef9c1797e4bebddf1d0a2f623845a40f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 15 Jan 2021 14:32:06 +0900 Subject: [PATCH 0043/1791] Actually return beat length and not BPM --- osu.Game/Beatmaps/Beatmap.cs | 2 +- osu.Game/Beatmaps/BeatmapManager.cs | 2 +- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 410fd5e92e..4e2e9eb96d 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -83,7 +83,7 @@ namespace osu.Game.Beatmaps } } - return 60000 / maxDurationBeatLength; + return maxDurationBeatLength; } IBeatmap IBeatmap.Clone() => Clone(); diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 0d4cc38ac3..b934ac556d 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -451,7 +451,7 @@ namespace osu.Game.Beatmaps // TODO: this should be done in a better place once we actually need to dynamically update it. beatmap.BeatmapInfo.StarDifficulty = ruleset?.CreateInstance().CreateDifficultyCalculator(new DummyConversionBeatmap(beatmap)).Calculate().StarRating ?? 0; beatmap.BeatmapInfo.Length = calculateLength(beatmap); - beatmap.BeatmapInfo.BPM = beatmap.GetMostCommonBeatLength(); + beatmap.BeatmapInfo.BPM = 60000 / beatmap.GetMostCommonBeatLength(); beatmapInfos.Add(beatmap.BeatmapInfo); } diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index abcd697d85..86cb561bc7 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -391,7 +391,7 @@ namespace osu.Game.Screens.Select if (Precision.AlmostEquals(bpmMin, bpmMax)) return $"{bpmMin:0}"; - return $"{bpmMin:0}-{bpmMax:0} (mostly {beatmap.GetMostCommonBeatLength():0})"; + return $"{bpmMin:0}-{bpmMax:0} (mostly {60000 / beatmap.GetMostCommonBeatLength():0})"; } private OsuSpriteText[] getMapper(BeatmapMetadata metadata) From b5e784ed427a47c93eae96a2a459b6a4b9dc224c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Jan 2021 16:34:28 +0900 Subject: [PATCH 0044/1791] Fix possibility of crash when selecting a random skin during skin import --- osu.Game/Overlays/Settings/Sections/SkinSection.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 5898482e4a..123cecb0cd 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -91,7 +91,7 @@ namespace osu.Game.Overlays.Settings.Sections if (skinDropdown.Items.All(s => s.ID != configBindable.Value)) configBindable.Value = 0; - configBindable.BindValueChanged(id => dropdownBindable.Value = skinDropdown.Items.Single(s => s.ID == id.NewValue), true); + configBindable.BindValueChanged(id => Scheduler.AddOnce(updateSelectedSkinFromConfig), true); dropdownBindable.BindValueChanged(skin => { if (skin.NewValue == random_skin_info) @@ -104,6 +104,8 @@ namespace osu.Game.Overlays.Settings.Sections }); } + private void updateSelectedSkinFromConfig() => dropdownBindable.Value = skinDropdown.Items.Single(s => s.ID == configBindable.Value); + private void updateItems() { skinItems = skins.GetAllUsableSkins(); From d6e6b4bbeea9ec532b7d927fa3d38c7e1efbf49f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Jan 2021 17:34:01 +0900 Subject: [PATCH 0045/1791] Revert forced cloning of ControlPointInfo This reverts commit 3c3e860dbc34d37855b79786a1abb754af1667e8. Closes https://github.com/ppy/osu/issues/11491. --- osu.Game/Beatmaps/Beatmap.cs | 10 +--------- osu.Game/Beatmaps/IBeatmap.cs | 2 +- osu.Game/Beatmaps/WorkingBeatmap.cs | 2 ++ osu.Game/Screens/Edit/EditorBeatmap.cs | 6 +++++- osu.Game/Screens/Play/GameplayBeatmap.cs | 6 +++++- 5 files changed, 14 insertions(+), 12 deletions(-) diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index be2006e67a..5435e86dfd 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -50,15 +50,7 @@ namespace osu.Game.Beatmaps IBeatmap IBeatmap.Clone() => Clone(); - public Beatmap Clone() - { - var clone = (Beatmap)MemberwiseClone(); - - clone.ControlPointInfo = ControlPointInfo.CreateCopy(); - // todo: deep clone other elements as required. - - return clone; - } + public Beatmap Clone() => (Beatmap)MemberwiseClone(); } public class Beatmap : Beatmap diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index 8f27e0b0e9..7dd85e1232 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -24,7 +24,7 @@ namespace osu.Game.Beatmaps /// /// The control points in this beatmap. /// - ControlPointInfo ControlPointInfo { get; } + ControlPointInfo ControlPointInfo { get; set; } /// /// The breaks in this beatmap. diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 30382c444f..d25adca92b 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -111,6 +111,8 @@ namespace osu.Game.Beatmaps // Convert IBeatmap converted = converter.Convert(cancellationSource.Token); + converted.ControlPointInfo = converted.ControlPointInfo.CreateCopy(); + // Apply conversion mods to the result foreach (var mod in mods.OfType()) { diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 165d2ba278..a54a95f59d 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -74,7 +74,11 @@ namespace osu.Game.Screens.Edit public BeatmapMetadata Metadata => PlayableBeatmap.Metadata; - public ControlPointInfo ControlPointInfo => PlayableBeatmap.ControlPointInfo; + public ControlPointInfo ControlPointInfo + { + get => PlayableBeatmap.ControlPointInfo; + set => PlayableBeatmap.ControlPointInfo = value; + } public List Breaks => PlayableBeatmap.Breaks; diff --git a/osu.Game/Screens/Play/GameplayBeatmap.cs b/osu.Game/Screens/Play/GameplayBeatmap.cs index 64894544f4..565595656f 100644 --- a/osu.Game/Screens/Play/GameplayBeatmap.cs +++ b/osu.Game/Screens/Play/GameplayBeatmap.cs @@ -29,7 +29,11 @@ namespace osu.Game.Screens.Play public BeatmapMetadata Metadata => PlayableBeatmap.Metadata; - public ControlPointInfo ControlPointInfo => PlayableBeatmap.ControlPointInfo; + public ControlPointInfo ControlPointInfo + { + get => PlayableBeatmap.ControlPointInfo; + set => PlayableBeatmap.ControlPointInfo = value; + } public List Breaks => PlayableBeatmap.Breaks; From d9034eab26e8abe8698a4de5feacd1be0ae8ecff Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jan 2021 22:54:54 +0300 Subject: [PATCH 0046/1791] Make model manager in `DownloadTrackingComposite` protected --- osu.Game/Online/DownloadTrackingComposite.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Online/DownloadTrackingComposite.cs b/osu.Game/Online/DownloadTrackingComposite.cs index 7a64c9002d..4e52761813 100644 --- a/osu.Game/Online/DownloadTrackingComposite.cs +++ b/osu.Game/Online/DownloadTrackingComposite.cs @@ -20,7 +20,7 @@ namespace osu.Game.Online protected readonly Bindable Model = new Bindable(); [Resolved(CanBeNull = true)] - private TModelManager manager { get; set; } + protected TModelManager Manager { get; private set; } /// /// Holds the current download state of the , whether is has already been downloaded, is in progress, or is not downloaded. @@ -49,19 +49,19 @@ namespace osu.Game.Online else if (manager?.IsAvailableLocally(modelInfo.NewValue) == true) State.Value = DownloadState.LocallyAvailable; else - attachDownload(manager?.GetExistingDownload(modelInfo.NewValue)); + attachDownload(Manager?.GetExistingDownload(modelInfo.NewValue)); }, true); - if (manager == null) + if (Manager == null) return; - managerDownloadBegan = manager.DownloadBegan.GetBoundCopy(); + managerDownloadBegan = Manager.DownloadBegan.GetBoundCopy(); managerDownloadBegan.BindValueChanged(downloadBegan); - managerDownloadFailed = manager.DownloadFailed.GetBoundCopy(); + managerDownloadFailed = Manager.DownloadFailed.GetBoundCopy(); managerDownloadFailed.BindValueChanged(downloadFailed); - managedUpdated = manager.ItemUpdated.GetBoundCopy(); + managedUpdated = Manager.ItemUpdated.GetBoundCopy(); managedUpdated.BindValueChanged(itemUpdated); - managerRemoved = manager.ItemRemoved.GetBoundCopy(); + managerRemoved = Manager.ItemRemoved.GetBoundCopy(); managerRemoved.BindValueChanged(itemRemoved); } From 04d17aadfa4685f54e670b934e17fc3be3cf2b3e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jan 2021 22:57:55 +0300 Subject: [PATCH 0047/1791] Add overridable method for verifying models in database --- osu.Game/Online/DownloadTrackingComposite.cs | 42 +++++++++++++++----- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/osu.Game/Online/DownloadTrackingComposite.cs b/osu.Game/Online/DownloadTrackingComposite.cs index 4e52761813..9dd8258e78 100644 --- a/osu.Game/Online/DownloadTrackingComposite.cs +++ b/osu.Game/Online/DownloadTrackingComposite.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; @@ -65,6 +66,15 @@ namespace osu.Game.Online managerRemoved.BindValueChanged(itemRemoved); } + /// + /// Verifies that the given databased model is in a correct state to be considered available. + /// + /// + /// In the case of multiplayer/playlists, this has to verify that the databased beatmap set with the selected beatmap matches what's online. + /// + /// The model in database. + protected virtual bool VerifyDatabasedModel([NotNull] TModel databasedModel) => true; + private void downloadBegan(ValueChangedEvent>> weakRequest) { if (weakRequest.NewValue.TryGetTarget(out var request)) @@ -134,23 +144,35 @@ namespace osu.Game.Online private void itemUpdated(ValueChangedEvent> weakItem) { if (weakItem.NewValue.TryGetTarget(out var item)) - setDownloadStateFromManager(item, DownloadState.LocallyAvailable); + { + Schedule(() => + { + if (!item.Equals(Model.Value)) + return; + + if (!VerifyDatabasedModel(item)) + { + State.Value = DownloadState.NotDownloaded; + return; + } + + State.Value = DownloadState.LocallyAvailable; + }); + } } private void itemRemoved(ValueChangedEvent> weakItem) { if (weakItem.NewValue.TryGetTarget(out var item)) - setDownloadStateFromManager(item, DownloadState.NotDownloaded); + { + Schedule(() => + { + if (item.Equals(Model.Value)) + State.Value = DownloadState.NotDownloaded; + }); + } } - private void setDownloadStateFromManager(TModel s, DownloadState state) => Schedule(() => - { - if (!s.Equals(Model.Value)) - return; - - State.Value = state; - }); - #region Disposal protected override void Dispose(bool isDisposing) From 7ad8b167ccb2f73247f2506cace66b22ec333665 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jan 2021 22:58:29 +0300 Subject: [PATCH 0048/1791] Add overridable method for checking local availability of current model --- osu.Game/Online/DownloadTrackingComposite.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/DownloadTrackingComposite.cs b/osu.Game/Online/DownloadTrackingComposite.cs index 9dd8258e78..1631d9790b 100644 --- a/osu.Game/Online/DownloadTrackingComposite.cs +++ b/osu.Game/Online/DownloadTrackingComposite.cs @@ -47,7 +47,7 @@ namespace osu.Game.Online { if (modelInfo.NewValue == null) attachDownload(null); - else if (manager?.IsAvailableLocally(modelInfo.NewValue) == true) + else if (IsModelAvailableLocally()) State.Value = DownloadState.LocallyAvailable; else attachDownload(Manager?.GetExistingDownload(modelInfo.NewValue)); @@ -75,6 +75,13 @@ namespace osu.Game.Online /// The model in database. protected virtual bool VerifyDatabasedModel([NotNull] TModel databasedModel) => true; + /// + /// Whether the given model is available in the database. + /// By default, this calls , + /// but can be overriden to add additional checks for verifying the model in database. + /// + protected virtual bool IsModelAvailableLocally() => Manager.IsAvailableLocally(Model.Value); + private void downloadBegan(ValueChangedEvent>> weakRequest) { if (weakRequest.NewValue.TryGetTarget(out var request)) From da9c23f3478356b9183fecbe82d0d416c039a26d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jan 2021 23:00:56 +0300 Subject: [PATCH 0049/1791] Add beatmap availability tracker component for multiplayer --- .../Online/Rooms/MultiplayerBeatmapTracker.cs | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs diff --git a/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs b/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs new file mode 100644 index 0000000000..b22d17f3ef --- /dev/null +++ b/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs @@ -0,0 +1,89 @@ +// 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.Bindables; +using osu.Game.Beatmaps; + +namespace osu.Game.Online.Rooms +{ + public class MultiplayerBeatmapTracker : DownloadTrackingComposite + { + public readonly IBindable SelectedItem = new Bindable(); + + /// + /// The availability state of the currently selected playlist item. + /// + public IBindable Availability => availability; + + private readonly Bindable availability = new Bindable(); + + public MultiplayerBeatmapTracker() + { + State.BindValueChanged(_ => updateAvailability()); + Progress.BindValueChanged(_ => updateAvailability()); + updateAvailability(); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + SelectedItem.BindValueChanged(item => Model.Value = item.NewValue?.Beatmap.Value.BeatmapSet, true); + } + + protected override bool VerifyDatabasedModel(BeatmapSetInfo databasedSet) + { + int? beatmapId = SelectedItem.Value.Beatmap.Value.OnlineBeatmapID; + string checksum = SelectedItem.Value.Beatmap.Value.MD5Hash; + + BeatmapInfo matchingBeatmap; + + if (databasedSet.Beatmaps == null) + { + // The given databased beatmap set is not passed in a usable state to check with. + // Perform a full query instead, as per https://github.com/ppy/osu/pull/11415. + matchingBeatmap = Manager.QueryBeatmap(b => b.OnlineBeatmapID == beatmapId && b.MD5Hash == checksum); + return matchingBeatmap != null; + } + + matchingBeatmap = databasedSet.Beatmaps.FirstOrDefault(b => b.OnlineBeatmapID == beatmapId && b.MD5Hash == checksum); + return matchingBeatmap != null; + } + + protected override bool IsModelAvailableLocally() + { + int? beatmapId = SelectedItem.Value.Beatmap.Value.OnlineBeatmapID; + string checksum = SelectedItem.Value.Beatmap.Value.MD5Hash; + + var beatmap = Manager.QueryBeatmap(b => b.OnlineBeatmapID == beatmapId && b.MD5Hash == checksum); + return beatmap?.BeatmapSet.DeletePending == false; + } + + private void updateAvailability() + { + switch (State.Value) + { + case DownloadState.NotDownloaded: + availability.Value = BeatmapAvailability.NotDownloaded(); + break; + + case DownloadState.Downloading: + availability.Value = BeatmapAvailability.Downloading(Progress.Value); + break; + + case DownloadState.Importing: + availability.Value = BeatmapAvailability.Importing(); + break; + + case DownloadState.LocallyAvailable: + availability.Value = BeatmapAvailability.LocallyAvailable(); + break; + + default: + throw new ArgumentOutOfRangeException(nameof(State)); + } + } + } +} From cf2378103656863879a08b9e2e2704bdffdebd03 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jan 2021 23:02:30 +0300 Subject: [PATCH 0050/1791] Cache beatmap tracker and bind to selected item in `RoomSubScreen` --- osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 2449563c73..c049d4be20 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -40,6 +40,17 @@ namespace osu.Game.Screens.OnlinePlay.Match private IBindable> managerUpdated; + [Cached] + protected readonly MultiplayerBeatmapTracker BeatmapTracker; + + protected RoomSubScreen() + { + InternalChild = BeatmapTracker = new MultiplayerBeatmapTracker + { + SelectedItem = { BindTarget = SelectedItem }, + }; + } + [BackgroundDependencyLoader] private void load(AudioManager audio) { From 96feaa027ded707f31ef18482aa0b632dad5627c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jan 2021 23:06:54 +0300 Subject: [PATCH 0051/1791] Make `ArchiveModelManager` import method overridable (for testing purposes) --- osu.Game/Database/ArchiveModelManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 36cc4cce39..8502ab5965 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -308,7 +308,7 @@ namespace osu.Game.Database /// The model to be imported. /// An optional archive to use for model population. /// An optional cancellation token. - public async Task Import(TModel item, ArchiveReader archive = null, CancellationToken cancellationToken = default) => await Task.Factory.StartNew(async () => + public virtual async Task Import(TModel item, ArchiveReader archive = null, CancellationToken cancellationToken = default) => await Task.Factory.StartNew(async () => { cancellationToken.ThrowIfCancellationRequested(); From 4778686dc4b712e7c93a30661718fd383674fee2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jan 2021 23:07:46 +0300 Subject: [PATCH 0052/1791] Expose method for triggering filename-backed success in `APIDownloadRequest` Exactly like in `APIRequest` --- osu.Game/Online/API/APIDownloadRequest.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/API/APIDownloadRequest.cs b/osu.Game/Online/API/APIDownloadRequest.cs index 940b9b4803..02c589403c 100644 --- a/osu.Game/Online/API/APIDownloadRequest.cs +++ b/osu.Game/Online/API/APIDownloadRequest.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.IO; using osu.Framework.IO.Network; @@ -28,13 +29,19 @@ namespace osu.Game.Online.API private void request_Progress(long current, long total) => API.Schedule(() => Progressed?.Invoke(current, total)); - protected APIDownloadRequest() + internal void TriggerSuccess(string filename) { - base.Success += onSuccess; + if (this.filename != null) + throw new InvalidOperationException("Attempted to trigger success more than once"); + + this.filename = filename; + + TriggerSuccess(); } - private void onSuccess() + internal override void TriggerSuccess() { + base.TriggerSuccess(); Success?.Invoke(filename); } From 23c7afa573b6d9b8b0f4af49283224e2231394b7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jan 2021 23:17:05 +0300 Subject: [PATCH 0053/1791] Expose method for setting progress of archive download request --- osu.Game/Online/API/ArchiveDownloadRequest.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/ArchiveDownloadRequest.cs b/osu.Game/Online/API/ArchiveDownloadRequest.cs index f1966aeb2b..bb57a7a5f8 100644 --- a/osu.Game/Online/API/ArchiveDownloadRequest.cs +++ b/osu.Game/Online/API/ArchiveDownloadRequest.cs @@ -18,7 +18,13 @@ namespace osu.Game.Online.API { Model = model; - Progressed += (current, total) => DownloadProgressed?.Invoke(Progress = (float)current / total); + Progressed += (current, total) => SetProgress((float)current / total); + } + + protected void SetProgress(float progress) + { + Progress = progress; + DownloadProgressed?.Invoke(progress); } } } From adb2605d5d7ddb30fea80a5f289e28e3c357a8db Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jan 2021 23:17:47 +0300 Subject: [PATCH 0054/1791] Enforce `double` type in the download progress path Wasn't sure where to exactly put this, or whether to split it, but it's very small change to worry about, so I guess it's fine being here --- osu.Game/Online/API/ArchiveDownloadRequest.cs | 8 ++++---- osu.Game/Online/DownloadTrackingComposite.cs | 2 +- .../Overlays/Notifications/ProgressNotification.cs | 10 +++++----- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game/Online/API/ArchiveDownloadRequest.cs b/osu.Game/Online/API/ArchiveDownloadRequest.cs index bb57a7a5f8..fdb2a984dc 100644 --- a/osu.Game/Online/API/ArchiveDownloadRequest.cs +++ b/osu.Game/Online/API/ArchiveDownloadRequest.cs @@ -10,18 +10,18 @@ namespace osu.Game.Online.API { public readonly TModel Model; - public float Progress; + public double Progress { get; private set; } - public event Action DownloadProgressed; + public event Action DownloadProgressed; protected ArchiveDownloadRequest(TModel model) { Model = model; - Progressed += (current, total) => SetProgress((float)current / total); + Progressed += (current, total) => SetProgress((double)current / total); } - protected void SetProgress(float progress) + protected void SetProgress(double progress) { Progress = progress; DownloadProgressed?.Invoke(progress); diff --git a/osu.Game/Online/DownloadTrackingComposite.cs b/osu.Game/Online/DownloadTrackingComposite.cs index 1631d9790b..b72cf38369 100644 --- a/osu.Game/Online/DownloadTrackingComposite.cs +++ b/osu.Game/Online/DownloadTrackingComposite.cs @@ -144,7 +144,7 @@ namespace osu.Game.Online private void onRequestSuccess(string _) => Schedule(() => State.Value = DownloadState.Importing); - private void onRequestProgress(float progress) => Schedule(() => Progress.Value = progress); + private void onRequestProgress(double progress) => Schedule(() => Progress.Value = progress); private void onRequestFailure(Exception e) => Schedule(() => attachDownload(null)); diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index 3105ecd742..e18bab8e83 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -23,9 +23,9 @@ namespace osu.Game.Overlays.Notifications public string CompletionText { get; set; } = "Task has completed!"; - private float progress; + private double progress; - public float Progress + public double Progress { get => progress; set @@ -185,9 +185,9 @@ namespace osu.Game.Overlays.Notifications private Color4 colourActive; private Color4 colourInactive; - private float progress; + private double progress; - public float Progress + public double Progress { get => progress; set @@ -195,7 +195,7 @@ namespace osu.Game.Overlays.Notifications if (progress == value) return; progress = value; - box.ResizeTo(new Vector2(progress, 1), 100, Easing.OutQuad); + box.ResizeTo(new Vector2((float)progress, 1), 100, Easing.OutQuad); } } From f0602243bfa792f3880e9d083f1ddc67431a29cf Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jan 2021 23:30:01 +0300 Subject: [PATCH 0055/1791] Add beatmapset file containing the eaxct beatmap in `TestBeatmap` solely --- .../Resources/Archives/test-beatmap.osz | Bin 0 -> 7286 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 osu.Game.Tests/Resources/Archives/test-beatmap.osz diff --git a/osu.Game.Tests/Resources/Archives/test-beatmap.osz b/osu.Game.Tests/Resources/Archives/test-beatmap.osz new file mode 100644 index 0000000000000000000000000000000000000000..f98fd2f8ffef0968270a1f2c7d75162de80417c9 GIT binary patch literal 7286 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 BJ!=2} literal 0 HcmV?d00001 From 80f7db8db364dc4903bf2ec4888b1269e82cde3a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jan 2021 23:31:15 +0300 Subject: [PATCH 0056/1791] Add test coverage for the new multiplayer beatmap tracker --- .../TestSceneMultiplayerBeatmapTracker.cs | 178 ++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs diff --git a/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs b/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs new file mode 100644 index 0000000000..839d47afc2 --- /dev/null +++ b/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs @@ -0,0 +1,178 @@ +// 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.IO; +using System.Threading; +using System.Threading.Tasks; +using JetBrains.Annotations; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Bindables; +using osu.Framework.Platform; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.IO.Archives; +using osu.Game.Online.API; +using osu.Game.Online.Rooms; +using osu.Game.Rulesets; +using osu.Game.Tests.Beatmaps; +using osu.Game.Tests.Resources; +using osu.Game.Tests.Visual; + +namespace osu.Game.Tests.Online +{ + [HeadlessTest] + public class TestSceneMultiplayerBeatmapTracker : OsuTestScene + { + private RulesetStore rulesets; + private TestBeatmapManager beatmaps; + + private string testBeatmapFile; + private BeatmapInfo testBeatmapInfo; + private BeatmapSetInfo testBeatmapSet; + + private readonly Bindable selectedItem = new Bindable(); + private MultiplayerBeatmapTracker tracker; + + [BackgroundDependencyLoader] + private void load(AudioManager audio, GameHost host) + { + Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); + Dependencies.CacheAs(beatmaps = new TestBeatmapManager(LocalStorage, ContextFactory, rulesets, API, audio, host, Beatmap.Default)); + } + + [SetUp] + public void SetUp() => Schedule(() => + { + testBeatmapFile = getTestBeatmapOsz(); + + testBeatmapInfo = new TestBeatmap(Ruleset.Value).BeatmapInfo; + testBeatmapSet = testBeatmapInfo.BeatmapSet; + + var existing = beatmaps.QueryBeatmapSet(s => s.OnlineBeatmapSetID == testBeatmapSet.OnlineBeatmapSetID); + if (existing != null) + beatmaps.Delete(existing); + + selectedItem.Value = new PlaylistItem + { + Beatmap = { Value = testBeatmapInfo }, + Ruleset = { Value = testBeatmapInfo.Ruleset }, + }; + + Child = tracker = new MultiplayerBeatmapTracker + { + SelectedItem = { BindTarget = selectedItem, } + }; + }); + + [Test] + public void TestBeatmapDownloadingFlow() + { + AddAssert("ensure beatmap unavailable", () => !beatmaps.IsAvailableLocally(testBeatmapSet)); + addAvailabilityCheckStep("state not downloaded", BeatmapAvailability.NotDownloaded); + + AddStep("start downloading", () => beatmaps.Download(testBeatmapSet)); + addAvailabilityCheckStep("state downloading 0%", () => BeatmapAvailability.Downloading(0.0)); + + AddStep("set progress 40%", () => ((TestDownloadRequest)beatmaps.GetExistingDownload(testBeatmapSet)).SetProgress(0.4)); + addAvailabilityCheckStep("state downloading 40%", () => BeatmapAvailability.Downloading(0.4)); + + AddStep("finish download", () => ((TestDownloadRequest)beatmaps.GetExistingDownload(testBeatmapSet)).TriggerSuccess(testBeatmapFile)); + addAvailabilityCheckStep("state importing", BeatmapAvailability.Importing); + + AddStep("allow importing", () => beatmaps.AllowImport.Set()); + AddUntilStep("wait for import", () => beatmaps.IsAvailableLocally(testBeatmapSet)); + addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable); + } + + [Test] + public void TestTrackerRespectsSoftDeleting() + { + AddStep("allow importing", () => beatmaps.AllowImport.Set()); + AddStep("import beatmap", () => beatmaps.Import(testBeatmapSet).Wait()); + addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable); + + AddStep("delete beatmap", () => beatmaps.Delete(testBeatmapSet)); + addAvailabilityCheckStep("state not downloaded", BeatmapAvailability.NotDownloaded); + + AddStep("undelete beatmap", () => beatmaps.Undelete(testBeatmapSet)); + addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable); + } + + [Test] + public void TestTrackerRespectsChecksum() + { + AddStep("allow importing", () => beatmaps.AllowImport.Set()); + + BeatmapInfo wrongBeatmap = null; + + AddStep("import wrong checksum beatmap", () => + { + wrongBeatmap = new TestBeatmap(Ruleset.Value).BeatmapInfo; + wrongBeatmap.MD5Hash = "1337"; + + beatmaps.Import(wrongBeatmap.BeatmapSet).Wait(); + }); + AddAssert("wrong beatmap available", () => beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == wrongBeatmap.OnlineBeatmapID) != null); + addAvailabilityCheckStep("state still not downloaded", BeatmapAvailability.NotDownloaded); + + AddStep("recreate tracker", () => Child = tracker = new MultiplayerBeatmapTracker + { + SelectedItem = { BindTarget = selectedItem } + }); + addAvailabilityCheckStep("state not downloaded as well", BeatmapAvailability.NotDownloaded); + } + + private void addAvailabilityCheckStep(string description, Func expected) + { + AddAssert(description, () => tracker.Availability.Value.Equals(expected.Invoke())); + } + + private string getTestBeatmapOsz() + { + var filename = Path.GetTempFileName() + ".osz"; + + using (var stream = TestResources.OpenResource("Archives/test-beatmap.osz")) + using (var file = File.Create(filename)) + stream.CopyTo(file); + + return filename; + } + + private class TestBeatmapManager : BeatmapManager + { + public readonly ManualResetEventSlim AllowImport = new ManualResetEventSlim(); + + protected override ArchiveDownloadRequest CreateDownloadRequest(BeatmapSetInfo set, bool minimiseDownloadSize) + => new TestDownloadRequest(set); + + public TestBeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, GameHost host = null, WorkingBeatmap defaultBeatmap = null, bool performOnlineLookups = false) + : base(storage, contextFactory, rulesets, api, audioManager, host, defaultBeatmap, performOnlineLookups) + { + } + + public override async Task Import(BeatmapSetInfo item, ArchiveReader archive = null, CancellationToken cancellationToken = default) + { + while (!AllowImport.IsSet) + await Task.Delay(10, cancellationToken); + + return await base.Import(item, archive, cancellationToken); + } + } + + private class TestDownloadRequest : ArchiveDownloadRequest + { + public new void SetProgress(double progress) => base.SetProgress(progress); + + public TestDownloadRequest(BeatmapSetInfo model) + : base(model) + { + } + + protected override string Target => null; + } + } +} From 59ae50b0e58d1ff813498093b39c60c73b3e790f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jan 2021 23:02:30 +0300 Subject: [PATCH 0057/1791] Clean up ready button logic into using `MultiplayerBeatmapTracker` --- .../OnlinePlay/Components/ReadyButton.cs | 62 +++---------------- .../OnlinePlay/Match/Components/Footer.cs | 4 -- .../Match/MultiplayerMatchFooter.cs | 5 -- .../Match/MultiplayerReadyButton.cs | 3 - .../Multiplayer/MultiplayerMatchSubScreen.cs | 5 +- .../Playlists/PlaylistsReadyButton.cs | 6 +- .../Playlists/PlaylistsRoomSubScreen.cs | 5 +- 7 files changed, 16 insertions(+), 74 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs b/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs index 64ddba669d..d4e34b5331 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs @@ -1,77 +1,29 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Game.Beatmaps; -using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; +using osu.Game.Online; using osu.Game.Online.Rooms; namespace osu.Game.Screens.OnlinePlay.Components { public abstract class ReadyButton : TriangleButton { - public readonly Bindable SelectedItem = new Bindable(); - public new readonly BindableBool Enabled = new BindableBool(); - [Resolved] - protected IBindable GameBeatmap { get; private set; } - - [Resolved] - private BeatmapManager beatmaps { get; set; } - - private bool hasBeatmap; - - private IBindable> managerUpdated; - private IBindable> managerRemoved; + private IBindable availability; [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(MultiplayerBeatmapTracker beatmapTracker) { - managerUpdated = beatmaps.ItemUpdated.GetBoundCopy(); - managerUpdated.BindValueChanged(beatmapUpdated); - managerRemoved = beatmaps.ItemRemoved.GetBoundCopy(); - managerRemoved.BindValueChanged(beatmapRemoved); + availability = beatmapTracker.Availability.GetBoundCopy(); - SelectedItem.BindValueChanged(item => updateSelectedItem(item.NewValue), true); + availability.BindValueChanged(_ => updateState()); + Enabled.BindValueChanged(_ => updateState(), true); } - private void updateSelectedItem(PlaylistItem _) => Scheduler.AddOnce(updateBeatmapState); - private void beatmapUpdated(ValueChangedEvent> _) => Scheduler.AddOnce(updateBeatmapState); - private void beatmapRemoved(ValueChangedEvent> _) => Scheduler.AddOnce(updateBeatmapState); - - private void updateBeatmapState() - { - int? beatmapId = SelectedItem.Value?.Beatmap.Value?.OnlineBeatmapID; - string checksum = SelectedItem.Value?.Beatmap.Value?.MD5Hash; - - if (beatmapId == null || checksum == null) - return; - - var databasedBeatmap = beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == beatmapId && b.MD5Hash == checksum); - - hasBeatmap = databasedBeatmap?.BeatmapSet?.DeletePending == false; - } - - protected override void Update() - { - base.Update(); - - updateEnabledState(); - } - - private void updateEnabledState() - { - if (GameBeatmap.Value == null || SelectedItem.Value == null) - { - base.Enabled.Value = false; - return; - } - - base.Enabled.Value = hasBeatmap && Enabled.Value; - } + private void updateState() => base.Enabled.Value = availability.Value.State == DownloadState.LocallyAvailable && Enabled.Value; } } diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/Footer.cs b/osu.Game/Screens/OnlinePlay/Match/Components/Footer.cs index 5c27d78d50..e91c46beed 100644 --- a/osu.Game/Screens/OnlinePlay/Match/Components/Footer.cs +++ b/osu.Game/Screens/OnlinePlay/Match/Components/Footer.cs @@ -3,13 +3,11 @@ using System; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; -using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Playlists; using osuTK; @@ -20,7 +18,6 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components public const float HEIGHT = 50; public Action OnStart; - public readonly Bindable SelectedItem = new Bindable(); private readonly Drawable background; @@ -37,7 +34,6 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(600, 50), - SelectedItem = { BindTarget = SelectedItem }, Action = () => OnStart?.Invoke() } }; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchFooter.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchFooter.cs index bbf861fac3..fdc1ae9d3c 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchFooter.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchFooter.cs @@ -3,13 +3,11 @@ using System; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; -using osu.Game.Online.Rooms; using osuTK; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match @@ -18,8 +16,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { public const float HEIGHT = 50; - public readonly Bindable SelectedItem = new Bindable(); - public Action OnReadyClick { set => readyButton.OnReadyClick = value; @@ -41,7 +37,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(600, 50), - SelectedItem = { BindTarget = SelectedItem } } }; } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs index 04030cdbfd..389a2380fa 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs @@ -13,7 +13,6 @@ using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Online.API; using osu.Game.Online.Multiplayer; -using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Components; using osuTK; @@ -21,8 +20,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { public class MultiplayerReadyButton : MultiplayerRoomComposite { - public Bindable SelectedItem => button.SelectedItem; - public Action OnReadyClick { set => button.Action = value; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 80991569dc..a641935b9a 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -53,7 +53,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer [BackgroundDependencyLoader] private void load() { - InternalChildren = new Drawable[] + AddRangeInternal(new Drawable[] { new GridContainer { @@ -161,7 +161,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { new MultiplayerMatchFooter { - SelectedItem = { BindTarget = SelectedItem }, OnReadyClick = onReadyClick } } @@ -177,7 +176,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer RelativeSizeAxes = Axes.Both, State = { Value = client.Room == null ? Visibility.Visible : Visibility.Hidden } } - }; + }); } protected override void LoadComplete() diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs index edee8e571a..9ac1fe1722 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs @@ -4,6 +4,7 @@ using System; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Components; @@ -15,6 +16,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists [Resolved(typeof(Room), nameof(Room.EndDate))] private Bindable endDate { get; set; } + [Resolved] + private IBindable gameBeatmap { get; set; } + public PlaylistsReadyButton() { Text = "Start"; @@ -32,7 +36,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { base.Update(); - Enabled.Value = endDate.Value != null && DateTimeOffset.UtcNow.AddSeconds(30).AddMilliseconds(GameBeatmap.Value.Track.Length) < endDate.Value; + Enabled.Value = endDate.Value != null && DateTimeOffset.UtcNow.AddSeconds(30).AddMilliseconds(gameBeatmap.Value.Track.Length) < endDate.Value; } } } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index e76ca995bf..7b3cdf16db 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -42,7 +42,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists [BackgroundDependencyLoader] private void load() { - InternalChildren = new Drawable[] + AddRangeInternal(new Drawable[] { new GridContainer { @@ -173,7 +173,6 @@ namespace osu.Game.Screens.OnlinePlay.Playlists new Footer { OnStart = onStart, - SelectedItem = { BindTarget = SelectedItem } } } }, @@ -189,7 +188,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists EditPlaylist = () => this.Push(new MatchSongSelect()), State = { Value = roomId.Value == null ? Visibility.Visible : Visibility.Hidden } } - }; + }); } [Resolved] From 63c0dc9bd9cf9d7c7f7251aa91d778c23a021071 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jan 2021 23:04:28 +0300 Subject: [PATCH 0058/1791] Update ready button test scene with new logic --- .../TestSceneMultiplayerReadyButton.cs | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs index 878776bf51..1357adb39a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs @@ -6,6 +6,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Platform; using osu.Framework.Testing; @@ -26,8 +27,11 @@ namespace osu.Game.Tests.Visual.Multiplayer public class TestSceneMultiplayerReadyButton : MultiplayerTestScene { private MultiplayerReadyButton button; + private MultiplayerBeatmapTracker beatmapTracker; private BeatmapSetInfo importedSet; + private readonly Bindable selectedItem = new Bindable(); + private BeatmapManager beatmaps; private RulesetStore rulesets; @@ -39,6 +43,13 @@ 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(); + + Add(beatmapTracker = new MultiplayerBeatmapTracker + { + SelectedItem = { BindTarget = selectedItem } + }); + + Dependencies.Cache(beatmapTracker); } [SetUp] @@ -46,20 +57,20 @@ namespace osu.Game.Tests.Visual.Multiplayer { importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); Beatmap.Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()); + selectedItem.Value = new PlaylistItem + { + Beatmap = { Value = Beatmap.Value.BeatmapInfo }, + Ruleset = { Value = Beatmap.Value.BeatmapInfo.Ruleset }, + }; - Child = button = new MultiplayerReadyButton + if (button != null) + Remove(button); + + Add(button = new MultiplayerReadyButton { Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(200, 50), - SelectedItem = - { - Value = new PlaylistItem - { - Beatmap = { Value = Beatmap.Value.BeatmapInfo }, - Ruleset = { Value = Beatmap.Value.BeatmapInfo.Ruleset } - } - }, OnReadyClick = async () => { readyClickOperation = OngoingOperationTracker.BeginOperation(); @@ -73,7 +84,7 @@ namespace osu.Game.Tests.Visual.Multiplayer await Client.ToggleReady(); readyClickOperation.Dispose(); } - }; + }); }); [Test] From 172552d55173791a5a5293bcab1d2240b8b02783 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 17 Jan 2021 21:02:33 +0300 Subject: [PATCH 0059/1791] Use `TaskCompletionSource` for better awaiting --- .../Online/TestSceneMultiplayerBeatmapTracker.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs b/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs index 839d47afc2..05e8f8b2e4 100644 --- a/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs +++ b/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs @@ -47,6 +47,8 @@ namespace osu.Game.Tests.Online [SetUp] public void SetUp() => Schedule(() => { + beatmaps.AllowImport = new TaskCompletionSource(); + testBeatmapFile = getTestBeatmapOsz(); testBeatmapInfo = new TestBeatmap(Ruleset.Value).BeatmapInfo; @@ -83,7 +85,7 @@ namespace osu.Game.Tests.Online AddStep("finish download", () => ((TestDownloadRequest)beatmaps.GetExistingDownload(testBeatmapSet)).TriggerSuccess(testBeatmapFile)); addAvailabilityCheckStep("state importing", BeatmapAvailability.Importing); - AddStep("allow importing", () => beatmaps.AllowImport.Set()); + AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true)); AddUntilStep("wait for import", () => beatmaps.IsAvailableLocally(testBeatmapSet)); addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable); } @@ -91,7 +93,7 @@ namespace osu.Game.Tests.Online [Test] public void TestTrackerRespectsSoftDeleting() { - AddStep("allow importing", () => beatmaps.AllowImport.Set()); + AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true)); AddStep("import beatmap", () => beatmaps.Import(testBeatmapSet).Wait()); addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable); @@ -105,7 +107,7 @@ namespace osu.Game.Tests.Online [Test] public void TestTrackerRespectsChecksum() { - AddStep("allow importing", () => beatmaps.AllowImport.Set()); + AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true)); BeatmapInfo wrongBeatmap = null; @@ -144,7 +146,7 @@ namespace osu.Game.Tests.Online private class TestBeatmapManager : BeatmapManager { - public readonly ManualResetEventSlim AllowImport = new ManualResetEventSlim(); + public TaskCompletionSource AllowImport = new TaskCompletionSource(); protected override ArchiveDownloadRequest CreateDownloadRequest(BeatmapSetInfo set, bool minimiseDownloadSize) => new TestDownloadRequest(set); @@ -156,9 +158,7 @@ namespace osu.Game.Tests.Online public override async Task Import(BeatmapSetInfo item, ArchiveReader archive = null, CancellationToken cancellationToken = default) { - while (!AllowImport.IsSet) - await Task.Delay(10, cancellationToken); - + await AllowImport.Task; return await base.Import(item, archive, cancellationToken); } } From d93a853dfd531e9d8d83473b04e3c22b5d290023 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 17 Jan 2021 21:16:45 +0300 Subject: [PATCH 0060/1791] Enforce `float` type in the download progress path instead --- .../Online/TestSceneMultiplayerBeatmapTracker.cs | 8 ++++---- osu.Game/Online/API/ArchiveDownloadRequest.cs | 6 +++--- osu.Game/Online/DownloadTrackingComposite.cs | 2 +- osu.Game/Online/Rooms/BeatmapAvailability.cs | 6 +++--- osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs | 2 +- .../Overlays/Notifications/ProgressNotification.cs | 10 +++++----- 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs b/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs index 05e8f8b2e4..60a4508b3d 100644 --- a/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs +++ b/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs @@ -77,10 +77,10 @@ namespace osu.Game.Tests.Online addAvailabilityCheckStep("state not downloaded", BeatmapAvailability.NotDownloaded); AddStep("start downloading", () => beatmaps.Download(testBeatmapSet)); - addAvailabilityCheckStep("state downloading 0%", () => BeatmapAvailability.Downloading(0.0)); + addAvailabilityCheckStep("state downloading 0%", () => BeatmapAvailability.Downloading(0.0f)); - AddStep("set progress 40%", () => ((TestDownloadRequest)beatmaps.GetExistingDownload(testBeatmapSet)).SetProgress(0.4)); - addAvailabilityCheckStep("state downloading 40%", () => BeatmapAvailability.Downloading(0.4)); + AddStep("set progress 40%", () => ((TestDownloadRequest)beatmaps.GetExistingDownload(testBeatmapSet)).SetProgress(0.4f)); + addAvailabilityCheckStep("state downloading 40%", () => BeatmapAvailability.Downloading(0.4f)); AddStep("finish download", () => ((TestDownloadRequest)beatmaps.GetExistingDownload(testBeatmapSet)).TriggerSuccess(testBeatmapFile)); addAvailabilityCheckStep("state importing", BeatmapAvailability.Importing); @@ -165,7 +165,7 @@ namespace osu.Game.Tests.Online private class TestDownloadRequest : ArchiveDownloadRequest { - public new void SetProgress(double progress) => base.SetProgress(progress); + public new void SetProgress(float progress) => base.SetProgress(progress); public TestDownloadRequest(BeatmapSetInfo model) : base(model) diff --git a/osu.Game/Online/API/ArchiveDownloadRequest.cs b/osu.Game/Online/API/ArchiveDownloadRequest.cs index fdb2a984dc..ccb4e9c119 100644 --- a/osu.Game/Online/API/ArchiveDownloadRequest.cs +++ b/osu.Game/Online/API/ArchiveDownloadRequest.cs @@ -12,16 +12,16 @@ namespace osu.Game.Online.API public double Progress { get; private set; } - public event Action DownloadProgressed; + public event Action DownloadProgressed; protected ArchiveDownloadRequest(TModel model) { Model = model; - Progressed += (current, total) => SetProgress((double)current / total); + Progressed += (current, total) => SetProgress((float)current / total); } - protected void SetProgress(double progress) + protected void SetProgress(float progress) { Progress = progress; DownloadProgressed?.Invoke(progress); diff --git a/osu.Game/Online/DownloadTrackingComposite.cs b/osu.Game/Online/DownloadTrackingComposite.cs index b72cf38369..1631d9790b 100644 --- a/osu.Game/Online/DownloadTrackingComposite.cs +++ b/osu.Game/Online/DownloadTrackingComposite.cs @@ -144,7 +144,7 @@ namespace osu.Game.Online private void onRequestSuccess(string _) => Schedule(() => State.Value = DownloadState.Importing); - private void onRequestProgress(double progress) => Schedule(() => Progress.Value = progress); + private void onRequestProgress(float progress) => Schedule(() => Progress.Value = progress); private void onRequestFailure(Exception e) => Schedule(() => attachDownload(null)); diff --git a/osu.Game/Online/Rooms/BeatmapAvailability.cs b/osu.Game/Online/Rooms/BeatmapAvailability.cs index e7dbc5f436..170009a85b 100644 --- a/osu.Game/Online/Rooms/BeatmapAvailability.cs +++ b/osu.Game/Online/Rooms/BeatmapAvailability.cs @@ -19,17 +19,17 @@ namespace osu.Game.Online.Rooms /// /// The beatmap's downloading progress, null when not in state. /// - public readonly double? DownloadProgress; + public readonly float? DownloadProgress; [JsonConstructor] - private BeatmapAvailability(DownloadState state, double? downloadProgress = null) + private BeatmapAvailability(DownloadState state, float? downloadProgress = null) { State = state; DownloadProgress = downloadProgress; } public static BeatmapAvailability NotDownloaded() => new BeatmapAvailability(DownloadState.NotDownloaded); - public static BeatmapAvailability Downloading(double progress) => new BeatmapAvailability(DownloadState.Downloading, progress); + public static BeatmapAvailability Downloading(float progress) => new BeatmapAvailability(DownloadState.Downloading, progress); public static BeatmapAvailability Importing() => new BeatmapAvailability(DownloadState.Importing); public static BeatmapAvailability LocallyAvailable() => new BeatmapAvailability(DownloadState.LocallyAvailable); diff --git a/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs b/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs index b22d17f3ef..b15399ad62 100644 --- a/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs +++ b/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs @@ -70,7 +70,7 @@ namespace osu.Game.Online.Rooms break; case DownloadState.Downloading: - availability.Value = BeatmapAvailability.Downloading(Progress.Value); + availability.Value = BeatmapAvailability.Downloading((float)Progress.Value); break; case DownloadState.Importing: diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index e18bab8e83..3105ecd742 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -23,9 +23,9 @@ namespace osu.Game.Overlays.Notifications public string CompletionText { get; set; } = "Task has completed!"; - private double progress; + private float progress; - public double Progress + public float Progress { get => progress; set @@ -185,9 +185,9 @@ namespace osu.Game.Overlays.Notifications private Color4 colourActive; private Color4 colourInactive; - private double progress; + private float progress; - public double Progress + public float Progress { get => progress; set @@ -195,7 +195,7 @@ namespace osu.Game.Overlays.Notifications if (progress == value) return; progress = value; - box.ResizeTo(new Vector2((float)progress, 1), 100, Easing.OutQuad); + box.ResizeTo(new Vector2(progress, 1), 100, Easing.OutQuad); } } From 0425a659a8c0bae3117e77dd512e899ca6a5c3da Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 17 Jan 2021 21:19:55 +0300 Subject: [PATCH 0061/1791] Add null-permissive operator to manager back --- osu.Game/Online/DownloadTrackingComposite.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/DownloadTrackingComposite.cs b/osu.Game/Online/DownloadTrackingComposite.cs index 1631d9790b..188cb9be7a 100644 --- a/osu.Game/Online/DownloadTrackingComposite.cs +++ b/osu.Game/Online/DownloadTrackingComposite.cs @@ -80,7 +80,7 @@ namespace osu.Game.Online /// By default, this calls , /// but can be overriden to add additional checks for verifying the model in database. /// - protected virtual bool IsModelAvailableLocally() => Manager.IsAvailableLocally(Model.Value); + protected virtual bool IsModelAvailableLocally() => Manager?.IsAvailableLocally(Model.Value) == true; private void downloadBegan(ValueChangedEvent>> weakRequest) { From ccef50e2a2e16767565c75bdc70c52faba7c57e4 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 17 Jan 2021 22:04:12 +0300 Subject: [PATCH 0062/1791] Log important message if user imported bad-checksum beatmap --- osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs b/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs index b15399ad62..659f52f00f 100644 --- a/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs +++ b/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using osu.Framework.Bindables; +using osu.Framework.Logging; using osu.Game.Beatmaps; namespace osu.Game.Online.Rooms @@ -34,6 +35,15 @@ namespace osu.Game.Online.Rooms } protected override bool VerifyDatabasedModel(BeatmapSetInfo databasedSet) + { + var verified = verifyDatabasedModel(databasedSet); + if (!verified) + Logger.Log("The imported beatmapset does not match the online version.", LoggingTarget.Runtime, LogLevel.Important); + + return verified; + } + + private bool verifyDatabasedModel(BeatmapSetInfo databasedSet) { int? beatmapId = SelectedItem.Value.Beatmap.Value.OnlineBeatmapID; string checksum = SelectedItem.Value.Beatmap.Value.MD5Hash; From b6a37c1c15fe2a95b7efb94b61e37de132d9864e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 17 Jan 2021 22:08:28 +0300 Subject: [PATCH 0063/1791] Make `TriggerSuccess(filename)` protected and expose in test instead --- osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs | 1 + osu.Game/Online/API/APIDownloadRequest.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs b/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs index 60a4508b3d..4b8992052e 100644 --- a/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs +++ b/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs @@ -166,6 +166,7 @@ namespace osu.Game.Tests.Online private class TestDownloadRequest : ArchiveDownloadRequest { public new void SetProgress(float progress) => base.SetProgress(progress); + public new void TriggerSuccess(string filename) => base.TriggerSuccess(filename); public TestDownloadRequest(BeatmapSetInfo model) : base(model) diff --git a/osu.Game/Online/API/APIDownloadRequest.cs b/osu.Game/Online/API/APIDownloadRequest.cs index 02c589403c..62e22d8f88 100644 --- a/osu.Game/Online/API/APIDownloadRequest.cs +++ b/osu.Game/Online/API/APIDownloadRequest.cs @@ -29,7 +29,7 @@ namespace osu.Game.Online.API private void request_Progress(long current, long total) => API.Schedule(() => Progressed?.Invoke(current, total)); - internal void TriggerSuccess(string filename) + protected void TriggerSuccess(string filename) { if (this.filename != null) throw new InvalidOperationException("Attempted to trigger success more than once"); From 27ffc9844520036e81a652d9d8d7b45b7b5845be Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 18 Jan 2021 10:48:12 +0300 Subject: [PATCH 0064/1791] Implement WebOverlay component --- .../Online/TestSceneFullscreenOverlay.cs | 4 +- osu.Game/Overlays/BeatmapListingOverlay.cs | 86 ++++++++----------- osu.Game/Overlays/BeatmapSetOverlay.cs | 74 ++++++---------- osu.Game/Overlays/ChangelogOverlay.cs | 56 +++--------- osu.Game/Overlays/DashboardOverlay.cs | 56 ++---------- osu.Game/Overlays/FullscreenOverlay.cs | 26 +++++- osu.Game/Overlays/NewsOverlay.cs | 62 +++---------- osu.Game/Overlays/RankingsOverlay.cs | 81 +++-------------- osu.Game/Overlays/UserProfileOverlay.cs | 13 ++- osu.Game/Overlays/WebOverlay.cs | 50 +++++++++++ 10 files changed, 193 insertions(+), 315 deletions(-) create mode 100644 osu.Game/Overlays/WebOverlay.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneFullscreenOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneFullscreenOverlay.cs index 8f20bcdcc1..f8b059e471 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneFullscreenOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneFullscreenOverlay.cs @@ -41,7 +41,7 @@ namespace osu.Game.Tests.Visual.Online private class TestFullscreenOverlay : FullscreenOverlay { public TestFullscreenOverlay() - : base(OverlayColourScheme.Pink, null) + : base(OverlayColourScheme.Pink) { Children = new Drawable[] { @@ -52,6 +52,8 @@ namespace osu.Game.Tests.Visual.Online }, }; } + + protected override OverlayHeader CreateHeader() => null; } } } diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 0c9c995dd6..ae1667d403 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -15,98 +15,82 @@ using osu.Framework.Graphics.Textures; using osu.Framework.Input.Events; using osu.Game.Audio; using osu.Game.Beatmaps; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.BeatmapListing; using osu.Game.Overlays.BeatmapListing.Panels; using osuTK; +using osuTK.Graphics; namespace osu.Game.Overlays { - public class BeatmapListingOverlay : FullscreenOverlay + public class BeatmapListingOverlay : WebOverlay { [Resolved] private PreviewTrackManager previewTrackManager { get; set; } private Drawable currentContent; - private LoadingLayer loadingLayer; private Container panelTarget; private FillFlowContainer foundContent; private NotFoundDrawable notFoundContent; - - private OverlayScrollContainer resultScrollContainer; + private BeatmapListingFilterControl filterControl; public BeatmapListingOverlay() - : base(OverlayColourScheme.Blue, new BeatmapListingHeader()) + : base(OverlayColourScheme.Blue) { } - private BeatmapListingFilterControl filterControl; - [BackgroundDependencyLoader] private void load() { - Children = new Drawable[] + Child = new FillFlowContainer { - new Box + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Colour = ColourProvider.Background6 - }, - resultScrollContainer = new OverlayScrollContainer - { - RelativeSizeAxes = Axes.Both, - ScrollbarVisible = false, - Child = new ReverseChildIDFillFlowContainer + filterControl = new BeatmapListingFilterControl + { + TypingStarted = onTypingStarted, + SearchStarted = onSearchStarted, + SearchFinished = onSearchFinished, + }, + new Container { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical, Children = new Drawable[] { - Header, - filterControl = new BeatmapListingFilterControl + new Box { - TypingStarted = onTypingStarted, - SearchStarted = onSearchStarted, - SearchFinished = onSearchFinished, + RelativeSizeAxes = Axes.Both, + Colour = ColourProvider.Background4, }, - new Container + panelTarget = new Container { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, + Padding = new MarginPadding { Horizontal = 20 }, Children = new Drawable[] { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourProvider.Background4, - }, - panelTarget = new Container - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Padding = new MarginPadding { Horizontal = 20 }, - Children = new Drawable[] - { - foundContent = new FillFlowContainer(), - notFoundContent = new NotFoundDrawable(), - } - } - }, - }, - } + foundContent = new FillFlowContainer(), + notFoundContent = new NotFoundDrawable(), + } + } + }, }, - }, - loadingLayer = new LoadingLayer(true) + } }; } + protected override BeatmapListingHeader CreateHeader() => new BeatmapListingHeader(); + + protected override Color4 GetBackgroundColour() => ColourProvider.Background6; + private void onTypingStarted() { // temporary until the textbox/header is updated to always stay on screen. - resultScrollContainer.ScrollToStart(); + ScrollFlow.ScrollToStart(); } protected override void OnFocus(FocusEvent e) @@ -125,7 +109,7 @@ namespace osu.Game.Overlays previewTrackManager.StopAnyPlaying(this); if (panelTarget.Any()) - loadingLayer.Show(); + Loading.Show(); } private Task panelLoadDelegate; @@ -173,7 +157,7 @@ namespace osu.Game.Overlays private void addContentToPlaceholder(Drawable content) { - loadingLayer.Hide(); + Loading.Hide(); lastFetchDisplayedTime = Time.Current; var lastContent = currentContent; @@ -256,7 +240,7 @@ namespace osu.Game.Overlays bool shouldShowMore = panelLoadDelegate?.IsCompleted != false && Time.Current - lastFetchDisplayedTime > time_between_fetches - && (resultScrollContainer.ScrollableExtent > 0 && resultScrollContainer.IsScrolledToEnd(pagination_scroll_distance)); + && (ScrollFlow.ScrollableExtent > 0 && ScrollFlow.IsScrolledToEnd(pagination_scroll_distance)); if (shouldShowMore) filterControl.FetchNextPage(); diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index bbec62a85a..10953415ed 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -6,7 +6,6 @@ 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.Events; using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; @@ -16,10 +15,11 @@ using osu.Game.Overlays.BeatmapSet.Scores; using osu.Game.Overlays.Comments; using osu.Game.Rulesets; using osuTK; +using osuTK.Graphics; namespace osu.Game.Overlays { - public class BeatmapSetOverlay : FullscreenOverlay // we don't provide a standard header for now. + public class BeatmapSetOverlay : WebOverlay // we don't provide a standard header for now. { public const float X_PADDING = 40; public const float Y_PADDING = 25; @@ -36,55 +36,40 @@ namespace osu.Game.Overlays // receive input outside our bounds so we can trigger a close event on ourselves. public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; - private readonly Box background; - public BeatmapSetOverlay() - : base(OverlayColourScheme.Blue, null) + : base(OverlayColourScheme.Blue) { - OverlayScrollContainer scroll; Info info; CommentsSection comments; - Children = new Drawable[] + Child = new FillFlowContainer { - background = new Box + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 20), + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both - }, - scroll = new OverlayScrollContainer - { - RelativeSizeAxes = Axes.Both, - ScrollbarVisible = false, - Child = new ReverseChildIDFillFlowContainer + new BeatmapSetLayoutSection { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 20), - Children = new[] + Child = new ReverseChildIDFillFlowContainer { - new BeatmapSetLayoutSection + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Children = new Drawable[] { - Child = new ReverseChildIDFillFlowContainer - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - Header = new Header(), - info = new Info() - } - }, - }, - new ScoresContainer - { - Beatmap = { BindTarget = Header.Picker.Beatmap } - }, - comments = new CommentsSection() + Header = new Header(), + info = new Info() + } }, }, - }, + new ScoresContainer + { + Beatmap = { BindTarget = Header.Picker.Beatmap } + }, + comments = new CommentsSection() + } }; Header.BeatmapSet.BindTo(beatmapSet); @@ -94,16 +79,13 @@ namespace osu.Game.Overlays Header.Picker.Beatmap.ValueChanged += b => { info.Beatmap = b.NewValue; - - scroll.ScrollToStart(); + ScrollFlow.ScrollToStart(); }; } - [BackgroundDependencyLoader] - private void load() - { - background.Colour = ColourProvider.Background6; - } + protected override OverlayHeader CreateHeader() => null; + + protected override Color4 GetBackgroundColour() => ColourProvider.Background6; protected override void PopOutComplete() { diff --git a/osu.Game/Overlays/ChangelogOverlay.cs b/osu.Game/Overlays/ChangelogOverlay.cs index c7e9a86fa4..5d99887053 100644 --- a/osu.Game/Overlays/ChangelogOverlay.cs +++ b/osu.Game/Overlays/ChangelogOverlay.cs @@ -11,22 +11,18 @@ using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics.Containers; using osu.Game.Input.Bindings; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.Changelog; +using osuTK.Graphics; namespace osu.Game.Overlays { - public class ChangelogOverlay : FullscreenOverlay + public class ChangelogOverlay : WebOverlay { public readonly Bindable Current = new Bindable(); - private Container content; - private SampleChannel sampleBack; private List builds; @@ -34,45 +30,14 @@ namespace osu.Game.Overlays protected List Streams; public ChangelogOverlay() - : base(OverlayColourScheme.Purple, new ChangelogHeader()) + : base(OverlayColourScheme.Purple) { } [BackgroundDependencyLoader] private void load(AudioManager audio) { - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourProvider.Background4, - }, - new OverlayScrollContainer - { - RelativeSizeAxes = Axes.Both, - ScrollbarVisible = false, - Child = new ReverseChildIDFillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - Header.With(h => - { - h.ListingSelected = ShowListing; - h.Build.BindTarget = Current; - }), - content = new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - } - }, - }, - }, - }; + Header.Build.BindTarget = Current; sampleBack = audio.Samples.Get(@"UI/generic-select-soft"); @@ -85,6 +50,13 @@ namespace osu.Game.Overlays }); } + protected override ChangelogHeader CreateHeader() => new ChangelogHeader + { + ListingSelected = ShowListing, + }; + + protected override Color4 GetBackgroundColour() => ColourProvider.Background4; + public void ShowListing() { Current.Value = null; @@ -198,16 +170,16 @@ namespace osu.Game.Overlays private void loadContent(ChangelogContent newContent) { - content.FadeTo(0.2f, 300, Easing.OutQuint); + Content.FadeTo(0.2f, 300, Easing.OutQuint); loadContentCancellation?.Cancel(); LoadComponentAsync(newContent, c => { - content.FadeIn(300, Easing.OutQuint); + Content.FadeIn(300, Easing.OutQuint); c.BuildSelected = ShowBuild; - content.Child = c; + Child = c; }, (loadContentCancellation = new CancellationTokenSource()).Token); } } diff --git a/osu.Game/Overlays/DashboardOverlay.cs b/osu.Game/Overlays/DashboardOverlay.cs index 03c320debe..3722d36388 100644 --- a/osu.Game/Overlays/DashboardOverlay.cs +++ b/osu.Game/Overlays/DashboardOverlay.cs @@ -7,29 +7,18 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Overlays.Dashboard; using osu.Game.Overlays.Dashboard.Friends; namespace osu.Game.Overlays { - public class DashboardOverlay : FullscreenOverlay + public class DashboardOverlay : WebOverlay { private CancellationTokenSource cancellationToken; - private Container content; - private LoadingLayer loading; - private OverlayScrollContainer scrollFlow; - public DashboardOverlay() - : base(OverlayColourScheme.Purple, new DashboardOverlayHeader - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Depth = -float.MaxValue - }) + : base(OverlayColourScheme.Purple) { } @@ -40,45 +29,16 @@ namespace osu.Game.Overlays { apiState.BindTo(api.State); apiState.BindValueChanged(onlineStateChanged, true); - - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourProvider.Background5 - }, - scrollFlow = new OverlayScrollContainer - { - RelativeSizeAxes = Axes.Both, - ScrollbarVisible = false, - Child = new FillFlowContainer - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - Header, - content = new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y - } - } - } - }, - loading = new LoadingLayer(true), - }; } protected override void LoadComplete() { base.LoadComplete(); - Header.Current.BindValueChanged(onTabChanged); } + protected override DashboardOverlayHeader CreateHeader() => new DashboardOverlayHeader(); + private bool displayUpdateRequired = true; protected override void PopIn() @@ -102,21 +62,21 @@ namespace osu.Game.Overlays private void loadDisplay(Drawable display) { - scrollFlow.ScrollToStart(); + ScrollFlow.ScrollToStart(); LoadComponentAsync(display, loaded => { if (API.IsLoggedIn) - loading.Hide(); + Loading.Hide(); - content.Child = loaded; + Child = loaded; }, (cancellationToken = new CancellationTokenSource()).Token); } private void onTabChanged(ValueChangedEvent tab) { cancellationToken?.Cancel(); - loading.Show(); + Loading.Show(); if (!API.IsLoggedIn) { diff --git a/osu.Game/Overlays/FullscreenOverlay.cs b/osu.Game/Overlays/FullscreenOverlay.cs index 6f56d95929..d65213f573 100644 --- a/osu.Game/Overlays/FullscreenOverlay.cs +++ b/osu.Game/Overlays/FullscreenOverlay.cs @@ -6,6 +6,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Containers; using osu.Game.Online.API; using osuTK.Graphics; @@ -27,9 +28,13 @@ namespace osu.Game.Overlays [Cached] protected readonly OverlayColourProvider ColourProvider; - protected FullscreenOverlay(OverlayColourScheme colourScheme, T header) + protected override Container Content => content; + + private readonly Container content; + + protected FullscreenOverlay(OverlayColourScheme colourScheme) { - Header = header; + Header = CreateHeader(); ColourProvider = new OverlayColourProvider(colourScheme); @@ -47,6 +52,19 @@ namespace osu.Game.Overlays Type = EdgeEffectType.Shadow, Radius = 10 }; + + base.Content.AddRange(new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = GetBackgroundColour() + }, + content = new Container + { + RelativeSizeAxes = Axes.Both + } + }); } [BackgroundDependencyLoader] @@ -58,6 +76,10 @@ namespace osu.Game.Overlays Waves.FourthWaveColour = ColourProvider.Dark3; } + protected abstract T CreateHeader(); + + protected virtual Color4 GetBackgroundColour() => ColourProvider.Background5; + public override void Show() { if (State.Value == Visibility.Visible) diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs index 5820d405d4..268d2bb6a2 100644 --- a/osu.Game/Overlays/NewsOverlay.cs +++ b/osu.Game/Overlays/NewsOverlay.cs @@ -2,67 +2,22 @@ // See the LICENCE file in the repository root for full licence text. using System.Threading; -using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.News; using osu.Game.Overlays.News.Displays; namespace osu.Game.Overlays { - public class NewsOverlay : FullscreenOverlay + public class NewsOverlay : WebOverlay { private readonly Bindable article = new Bindable(null); - private Container content; - private LoadingLayer loading; - private OverlayScrollContainer scrollFlow; - public NewsOverlay() - : base(OverlayColourScheme.Purple, new NewsHeader()) + : base(OverlayColourScheme.Purple) { } - [BackgroundDependencyLoader] - private void load() - { - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourProvider.Background5, - }, - scrollFlow = new OverlayScrollContainer - { - RelativeSizeAxes = Axes.Both, - ScrollbarVisible = false, - Child = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - Header.With(h => - { - h.ShowFrontPage = ShowFrontPage; - }), - content = new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - } - }, - }, - }, - loading = new LoadingLayer(true), - }; - } - protected override void LoadComplete() { base.LoadComplete(); @@ -71,6 +26,11 @@ namespace osu.Game.Overlays article.BindValueChanged(onArticleChanged); } + protected override NewsHeader CreateHeader() => new NewsHeader + { + ShowFrontPage = ShowFrontPage + }; + private bool displayUpdateRequired = true; protected override void PopIn() @@ -107,7 +67,7 @@ namespace osu.Game.Overlays private void onArticleChanged(ValueChangedEvent e) { cancellationToken?.Cancel(); - loading.Show(); + Loading.Show(); if (e.NewValue == null) { @@ -122,11 +82,11 @@ namespace osu.Game.Overlays protected void LoadDisplay(Drawable display) { - scrollFlow.ScrollToStart(); + ScrollFlow.ScrollToStart(); LoadComponentAsync(display, loaded => { - content.Child = loaded; - loading.Hide(); + Child = loaded; + Loading.Hide(); }, (cancellationToken = new CancellationTokenSource()).Token); } diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs index 25350e310a..89853e9044 100644 --- a/osu.Game/Overlays/RankingsOverlay.cs +++ b/osu.Game/Overlays/RankingsOverlay.cs @@ -4,96 +4,41 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Game.Overlays.Rankings; using osu.Game.Users; using osu.Game.Rulesets; using osu.Game.Online.API; using System.Threading; -using osu.Game.Graphics.UserInterface; using osu.Game.Online.API.Requests; using osu.Game.Overlays.Rankings.Tables; namespace osu.Game.Overlays { - public class RankingsOverlay : FullscreenOverlay + public class RankingsOverlay : WebOverlay { protected Bindable Country => Header.Country; protected Bindable Scope => Header.Current; - private readonly OverlayScrollContainer scrollFlow; - private readonly Container contentContainer; - private readonly LoadingLayer loading; - private readonly Box background; - private APIRequest lastRequest; private CancellationTokenSource cancellationToken; [Resolved] private IAPIProvider api { get; set; } - public RankingsOverlay() - : base(OverlayColourScheme.Green, new RankingsOverlayHeader - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Depth = -float.MaxValue - }) - { - loading = new LoadingLayer(true); + [Resolved] + private Bindable ruleset { get; set; } - Children = new Drawable[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both - }, - scrollFlow = new OverlayScrollContainer - { - RelativeSizeAxes = Axes.Both, - ScrollbarVisible = false, - Child = new FillFlowContainer - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - Header, - new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] - { - contentContainer = new Container - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Margin = new MarginPadding { Bottom = 10 } - }, - } - } - } - } - }, - loading - }; + public RankingsOverlay() + : base(OverlayColourScheme.Green) + { } [BackgroundDependencyLoader] private void load() { - background.Colour = ColourProvider.Background5; } - [Resolved] - private Bindable ruleset { get; set; } - protected override void LoadComplete() { base.LoadComplete(); @@ -129,6 +74,8 @@ namespace osu.Game.Overlays Scheduler.AddOnce(loadNewContent); } + protected override RankingsOverlayHeader CreateHeader() => new RankingsOverlayHeader(); + public void ShowCountry(Country requested) { if (requested == null) @@ -147,7 +94,7 @@ namespace osu.Game.Overlays private void loadNewContent() { - loading.Show(); + Loading.Show(); cancellationToken?.Cancel(); lastRequest?.Cancel(); @@ -218,19 +165,19 @@ namespace osu.Game.Overlays private void loadContent(Drawable content) { - scrollFlow.ScrollToStart(); + ScrollFlow.ScrollToStart(); if (content == null) { - contentContainer.Clear(); - loading.Hide(); + Clear(); + Loading.Hide(); return; } LoadComponentAsync(content, loaded => { - loading.Hide(); - contentContainer.Child = loaded; + Loading.Hide(); + Child = loaded; }, (cancellationToken = new CancellationTokenSource()).Token); } diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index 81027667fa..c29df72501 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -15,6 +15,7 @@ using osu.Game.Overlays.Profile; using osu.Game.Overlays.Profile.Sections; using osu.Game.Users; using osuTK; +using osuTK.Graphics; namespace osu.Game.Overlays { @@ -29,10 +30,14 @@ namespace osu.Game.Overlays public const float CONTENT_X_MARGIN = 70; public UserProfileOverlay() - : base(OverlayColourScheme.Pink, new ProfileHeader()) + : base(OverlayColourScheme.Pink) { } + protected override ProfileHeader CreateHeader() => new ProfileHeader(); + + protected override Color4 GetBackgroundColour() => ColourProvider.Background6; + public void ShowUser(int userId) => ShowUser(new User { Id = userId }); public void ShowUser(User user, bool fetchOnline = true) @@ -72,12 +77,6 @@ namespace osu.Game.Overlays Origin = Anchor.TopCentre, }; - Add(new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourProvider.Background6 - }); - Add(sectionsContainer = new ProfileSectionsContainer { ExpandableHeader = Header, diff --git a/osu.Game/Overlays/WebOverlay.cs b/osu.Game/Overlays/WebOverlay.cs new file mode 100644 index 0000000000..aca767e32f --- /dev/null +++ b/osu.Game/Overlays/WebOverlay.cs @@ -0,0 +1,50 @@ +// 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.UserInterface; + +namespace osu.Game.Overlays +{ + public abstract class WebOverlay : FullscreenOverlay + where T : OverlayHeader + { + protected override Container Content => content; + + protected readonly OverlayScrollContainer ScrollFlow; + protected readonly LoadingLayer Loading; + private readonly Container content; + + protected WebOverlay(OverlayColourScheme colourScheme) + : base(colourScheme) + { + FillFlowContainer flow = new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical + }; + + if (Header != null) + flow.Add(Header.With(h => h.Depth = -float.MaxValue)); + + flow.Add(content = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + }); + + base.Content.AddRange(new Drawable[] + { + ScrollFlow = new OverlayScrollContainer + { + RelativeSizeAxes = Axes.Both, + ScrollbarVisible = false, + Child = flow + }, + Loading = new LoadingLayer(true) + }); + } + } +} From 88abee705b37d0a6c9db00aa7b8a3f5323703590 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 18 Jan 2021 10:48:53 +0300 Subject: [PATCH 0065/1791] Add missing event mapping for user beatmap availability change --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 50dc8f661c..6f789b1ef5 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -81,6 +81,7 @@ namespace osu.Game.Online.Multiplayer connection.On(nameof(IMultiplayerClient.HostChanged), ((IMultiplayerClient)this).HostChanged); connection.On(nameof(IMultiplayerClient.SettingsChanged), ((IMultiplayerClient)this).SettingsChanged); connection.On(nameof(IMultiplayerClient.UserStateChanged), ((IMultiplayerClient)this).UserStateChanged); + connection.On(nameof(IMultiplayerClient.UserBeatmapAvailabilityChanged), ((IMultiplayerClient)this).UserBeatmapAvailabilityChanged); connection.On(nameof(IMultiplayerClient.LoadRequested), ((IMultiplayerClient)this).LoadRequested); connection.On(nameof(IMultiplayerClient.MatchStarted), ((IMultiplayerClient)this).MatchStarted); connection.On(nameof(IMultiplayerClient.ResultsReady), ((IMultiplayerClient)this).ResultsReady); From 4e6c1a3906ed40be5752b13994499f73b4c10142 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 18 Jan 2021 10:49:38 +0300 Subject: [PATCH 0066/1791] Update client beatmap availability in-line with tracker --- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 2 ++ .../Multiplayer/MultiplayerMatchSubScreen.cs | 20 +++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index c049d4be20..7569da00f2 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -43,6 +43,8 @@ namespace osu.Game.Screens.OnlinePlay.Match [Cached] protected readonly MultiplayerBeatmapTracker BeatmapTracker; + protected IBindable BeatmapAvailability => BeatmapTracker.Availability; + protected RoomSubScreen() { InternalChild = BeatmapTracker = new MultiplayerBeatmapTracker diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index a641935b9a..94288673fb 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -184,7 +184,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer base.LoadComplete(); Playlist.BindCollectionChanged(onPlaylistChanged, true); + BeatmapAvailability.BindValueChanged(updateClientAvailability, true); + client.RoomUpdated += onRoomUpdated; client.LoadRequested += onLoadRequested; isConnected = client.IsConnected.GetBoundCopy(); @@ -208,6 +210,21 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer private void onPlaylistChanged(object sender, NotifyCollectionChangedEventArgs e) => SelectedItem.Value = Playlist.FirstOrDefault(); + private void updateClientAvailability(ValueChangedEvent _ = null) + { + if (client.Room != null) + client.ChangeBeatmapAvailability(BeatmapAvailability.Value).CatchUnobservedExceptions(true); + } + + private void onRoomUpdated() + { + if (client.Room == null) + return; + + if (client.LocalUser?.BeatmapAvailability.Equals(BeatmapAvailability.Value) == false) + updateClientAvailability(); + } + private void onReadyClick() { Debug.Assert(readyClickOperation == null); @@ -262,7 +279,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer base.Dispose(isDisposing); if (client != null) + { client.LoadRequested -= onLoadRequested; + client.RoomUpdated -= onRoomUpdated; + } } } } From bd44bf8c0b9348f9443df2dfc95470d86bd80ecf Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 18 Jan 2021 10:46:48 +0300 Subject: [PATCH 0067/1791] Extract disabling progress bar user-interactivity --- osu.Game/Graphics/UserInterface/ProgressBar.cs | 8 +++++++- .../Overlays/BeatmapListing/Panels/DownloadProgressBar.cs | 8 +------- osu.Game/Overlays/NowPlayingOverlay.cs | 5 +++++ 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/ProgressBar.cs b/osu.Game/Graphics/UserInterface/ProgressBar.cs index d271cd121c..4ee1c73bf5 100644 --- a/osu.Game/Graphics/UserInterface/ProgressBar.cs +++ b/osu.Game/Graphics/UserInterface/ProgressBar.cs @@ -40,8 +40,14 @@ namespace osu.Game.Graphics.UserInterface set => CurrentNumber.Value = value; } - public ProgressBar() + private readonly bool userInteractive; + public override bool HandlePositionalInput => userInteractive; + public override bool HandleNonPositionalInput => userInteractive; + + public ProgressBar(bool userInteractive) { + this.userInteractive = userInteractive; + CurrentNumber.MinValue = 0; CurrentNumber.MaxValue = 1; RelativeSizeAxes = Axes.X; diff --git a/osu.Game/Overlays/BeatmapListing/Panels/DownloadProgressBar.cs b/osu.Game/Overlays/BeatmapListing/Panels/DownloadProgressBar.cs index 6a2f2e4569..ca94078401 100644 --- a/osu.Game/Overlays/BeatmapListing/Panels/DownloadProgressBar.cs +++ b/osu.Game/Overlays/BeatmapListing/Panels/DownloadProgressBar.cs @@ -19,7 +19,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels public DownloadProgressBar(BeatmapSetInfo beatmapSet) : base(beatmapSet) { - AddInternal(progressBar = new InteractionDisabledProgressBar + AddInternal(progressBar = new ProgressBar(false) { Height = 0, Alpha = 0, @@ -64,11 +64,5 @@ namespace osu.Game.Overlays.BeatmapListing.Panels } }, true); } - - private class InteractionDisabledProgressBar : ProgressBar - { - public override bool HandlePositionalInput => false; - public override bool HandleNonPositionalInput => false; - } } } diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index 9beb859f28..d64c61044d 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -411,6 +411,11 @@ namespace osu.Game.Overlays private class HoverableProgressBar : ProgressBar { + public HoverableProgressBar() + : base(true) + { + } + protected override bool OnHover(HoverEvent e) { this.ResizeHeightTo(progress_height, 500, Easing.OutQuint); From 6deb10e0750423c60175975edf62e383535dc346 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 18 Jan 2021 11:09:45 +0300 Subject: [PATCH 0068/1791] Add UI state display for each client's beatmap availability --- .../Participants/ParticipantPanel.cs | 2 +- .../Multiplayer/Participants/StateDisplay.cs | 193 +++++++++++------- 2 files changed, 119 insertions(+), 76 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index f99655e305..17bf3a58ec 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs @@ -143,7 +143,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants const double fade_time = 50; - userStateDisplay.Status = User.State; + userStateDisplay.UpdateStatus(User.State, User.BeatmapAvailability); if (Room.Host?.Equals(User) == true) crown.FadeIn(fade_time); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs index 8d2879fc93..4245628a59 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.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.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -8,119 +9,161 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online; using osu.Game.Online.Multiplayer; +using osu.Game.Online.Rooms; using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants { public class StateDisplay : CompositeDrawable { + private const double fade_time = 50; + + private SpriteIcon icon; + private OsuSpriteText text; + private ProgressBar progressBar; + public StateDisplay() { AutoSizeAxes = Axes.Both; Alpha = 0; } - private MultiplayerUserState status; - - private OsuSpriteText text; - private SpriteIcon icon; - - private const double fade_time = 50; - - public MultiplayerUserState Status - { - set - { - if (value == status) - return; - - status = value; - - if (IsLoaded) - updateStatus(); - } - } - [BackgroundDependencyLoader] - private void load() + private void load(OsuColour colours) { + this.colours = colours; + InternalChild = new FillFlowContainer { AutoSizeAxes = Axes.Both, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, Spacing = new Vector2(5), Children = new Drawable[] { - text = new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Font = OsuFont.GetFont(weight: FontWeight.Regular, size: 12), - Colour = Color4Extensions.FromHex("#DDFFFF") - }, icon = new SpriteIcon { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, Icon = FontAwesome.Solid.CheckCircle, Size = new Vector2(12), - } + }, + new CircularContainer + { + Masking = true, + AutoSizeAxes = Axes.Both, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Children = new Drawable[] + { + progressBar = new ProgressBar(false) + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + BackgroundColour = Color4.Black.Opacity(0.4f), + FillColour = colours.Blue, + Alpha = 0f, + }, + text = new OsuSpriteText + { + Padding = new MarginPadding { Horizontal = 5f, Vertical = 1f }, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Font = OsuFont.GetFont(weight: FontWeight.Regular, size: 12), + Colour = Color4Extensions.FromHex("#DDFFFF") + }, + } + }, } }; } - protected override void LoadComplete() - { - base.LoadComplete(); - updateStatus(); - } + private OsuColour colours; - [Resolved] - private OsuColour colours { get; set; } - - private void updateStatus() + public void UpdateStatus(MultiplayerUserState state, BeatmapAvailability availability) { - switch (status) + if (availability.State != DownloadState.LocallyAvailable) { - default: - this.FadeOut(fade_time); - return; + switch (availability.State) + { + case DownloadState.NotDownloaded: + progressBar.FadeOut(fade_time); + text.Text = "no map"; + icon.Icon = FontAwesome.Solid.MinusCircle; + icon.Colour = colours.RedLight; + break; - case MultiplayerUserState.Ready: - text.Text = "ready"; - icon.Icon = FontAwesome.Solid.CheckCircle; - icon.Colour = Color4Extensions.FromHex("#AADD00"); - break; + case DownloadState.Downloading: + Debug.Assert(availability.DownloadProgress != null); - case MultiplayerUserState.WaitingForLoad: - text.Text = "loading"; - icon.Icon = FontAwesome.Solid.PauseCircle; - icon.Colour = colours.Yellow; - break; + var progress = availability.DownloadProgress.Value; + progressBar.FadeIn(fade_time); + progressBar.CurrentTime = progress; - case MultiplayerUserState.Loaded: - text.Text = "loaded"; - icon.Icon = FontAwesome.Solid.DotCircle; - icon.Colour = colours.YellowLight; - break; + text.Text = "downloading map"; + icon.Icon = FontAwesome.Solid.ArrowAltCircleDown; + icon.Colour = colours.Blue; + break; - case MultiplayerUserState.Playing: - text.Text = "playing"; - icon.Icon = FontAwesome.Solid.PlayCircle; - icon.Colour = colours.BlueLight; - break; + case DownloadState.Importing: + progressBar.FadeOut(fade_time); + text.Text = "importing map"; + icon.Icon = FontAwesome.Solid.ArrowAltCircleDown; + icon.Colour = colours.Yellow; + break; + } + } + else + { + progressBar.FadeOut(fade_time); - case MultiplayerUserState.FinishedPlay: - text.Text = "results pending"; - icon.Icon = FontAwesome.Solid.ArrowAltCircleUp; - icon.Colour = colours.BlueLighter; - break; + switch (state) + { + default: + this.FadeOut(fade_time); + return; - case MultiplayerUserState.Results: - text.Text = "results"; - icon.Icon = FontAwesome.Solid.ArrowAltCircleUp; - icon.Colour = colours.BlueLighter; - break; + case MultiplayerUserState.Ready: + text.Text = "ready"; + icon.Icon = FontAwesome.Solid.CheckCircle; + icon.Colour = Color4Extensions.FromHex("#AADD00"); + break; + + case MultiplayerUserState.WaitingForLoad: + text.Text = "loading"; + icon.Icon = FontAwesome.Solid.PauseCircle; + icon.Colour = colours.Yellow; + break; + + case MultiplayerUserState.Loaded: + text.Text = "loaded"; + icon.Icon = FontAwesome.Solid.DotCircle; + icon.Colour = colours.YellowLight; + break; + + case MultiplayerUserState.Playing: + text.Text = "playing"; + icon.Icon = FontAwesome.Solid.PlayCircle; + icon.Colour = colours.BlueLight; + break; + + case MultiplayerUserState.FinishedPlay: + text.Text = "results pending"; + icon.Icon = FontAwesome.Solid.ArrowAltCircleUp; + icon.Colour = colours.BlueLighter; + break; + + case MultiplayerUserState.Results: + text.Text = "results"; + icon.Icon = FontAwesome.Solid.ArrowAltCircleUp; + icon.Colour = colours.BlueLighter; + break; + } } this.FadeIn(fade_time); From 6e34ab5d152f37d19702ff305c2a53323e89ef75 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 18 Jan 2021 11:13:38 +0300 Subject: [PATCH 0069/1791] Rename WebOverlay to OnlineOverlay --- osu.Game/Overlays/BeatmapListingOverlay.cs | 2 +- osu.Game/Overlays/BeatmapSetOverlay.cs | 2 +- osu.Game/Overlays/ChangelogOverlay.cs | 2 +- osu.Game/Overlays/DashboardOverlay.cs | 2 +- osu.Game/Overlays/NewsOverlay.cs | 2 +- osu.Game/Overlays/{WebOverlay.cs => OnlineOverlay.cs} | 4 ++-- osu.Game/Overlays/RankingsOverlay.cs | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) rename osu.Game/Overlays/{WebOverlay.cs => OnlineOverlay.cs} (91%) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index ae1667d403..eafb7e95d5 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -23,7 +23,7 @@ using osuTK.Graphics; namespace osu.Game.Overlays { - public class BeatmapListingOverlay : WebOverlay + public class BeatmapListingOverlay : OnlineOverlay { [Resolved] private PreviewTrackManager previewTrackManager { get; set; } diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index 10953415ed..872621801a 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -19,7 +19,7 @@ using osuTK.Graphics; namespace osu.Game.Overlays { - public class BeatmapSetOverlay : WebOverlay // we don't provide a standard header for now. + public class BeatmapSetOverlay : OnlineOverlay // we don't provide a standard header for now. { public const float X_PADDING = 40; public const float Y_PADDING = 25; diff --git a/osu.Game/Overlays/ChangelogOverlay.cs b/osu.Game/Overlays/ChangelogOverlay.cs index 5d99887053..5200b567ff 100644 --- a/osu.Game/Overlays/ChangelogOverlay.cs +++ b/osu.Game/Overlays/ChangelogOverlay.cs @@ -19,7 +19,7 @@ using osuTK.Graphics; namespace osu.Game.Overlays { - public class ChangelogOverlay : WebOverlay + public class ChangelogOverlay : OnlineOverlay { public readonly Bindable Current = new Bindable(); diff --git a/osu.Game/Overlays/DashboardOverlay.cs b/osu.Game/Overlays/DashboardOverlay.cs index 3722d36388..39a23fe3d4 100644 --- a/osu.Game/Overlays/DashboardOverlay.cs +++ b/osu.Game/Overlays/DashboardOverlay.cs @@ -13,7 +13,7 @@ using osu.Game.Overlays.Dashboard.Friends; namespace osu.Game.Overlays { - public class DashboardOverlay : WebOverlay + public class DashboardOverlay : OnlineOverlay { private CancellationTokenSource cancellationToken; diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs index 268d2bb6a2..08e8331dd3 100644 --- a/osu.Game/Overlays/NewsOverlay.cs +++ b/osu.Game/Overlays/NewsOverlay.cs @@ -9,7 +9,7 @@ using osu.Game.Overlays.News.Displays; namespace osu.Game.Overlays { - public class NewsOverlay : WebOverlay + public class NewsOverlay : OnlineOverlay { private readonly Bindable article = new Bindable(null); diff --git a/osu.Game/Overlays/WebOverlay.cs b/osu.Game/Overlays/OnlineOverlay.cs similarity index 91% rename from osu.Game/Overlays/WebOverlay.cs rename to osu.Game/Overlays/OnlineOverlay.cs index aca767e32f..b44ccc32c9 100644 --- a/osu.Game/Overlays/WebOverlay.cs +++ b/osu.Game/Overlays/OnlineOverlay.cs @@ -7,7 +7,7 @@ using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays { - public abstract class WebOverlay : FullscreenOverlay + public abstract class OnlineOverlay : FullscreenOverlay where T : OverlayHeader { protected override Container Content => content; @@ -16,7 +16,7 @@ namespace osu.Game.Overlays protected readonly LoadingLayer Loading; private readonly Container content; - protected WebOverlay(OverlayColourScheme colourScheme) + protected OnlineOverlay(OverlayColourScheme colourScheme) : base(colourScheme) { FillFlowContainer flow = new FillFlowContainer diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs index 89853e9044..f6bbac4407 100644 --- a/osu.Game/Overlays/RankingsOverlay.cs +++ b/osu.Game/Overlays/RankingsOverlay.cs @@ -14,7 +14,7 @@ using osu.Game.Overlays.Rankings.Tables; namespace osu.Game.Overlays { - public class RankingsOverlay : WebOverlay + public class RankingsOverlay : OnlineOverlay { protected Bindable Country => Header.Country; From 5f2e9c5485d854c3a5a85dee64613226fd77cd38 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 18 Jan 2021 11:10:38 +0300 Subject: [PATCH 0070/1791] Add visual test case for displaying beatmap availability states --- .../TestSceneMultiplayerParticipantsList.cs | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs index 968a869532..e2f1a13593 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs @@ -7,7 +7,9 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Testing; using osu.Framework.Utils; +using osu.Game.Online; using osu.Game.Online.Multiplayer; +using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Multiplayer.Participants; using osu.Game.Users; using osuTK; @@ -73,6 +75,20 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("single panel is for second user", () => this.ChildrenOfType().Single().User.User == secondUser); } + [Test] + public void TestBeatmapDownloadingStates() + { + AddStep("set to no map", () => Client.ChangeBeatmapAvailability(BeatmapAvailability.NotDownloaded())); + AddStep("set to downloading map", () => Client.ChangeBeatmapAvailability(BeatmapAvailability.Downloading(0))); + AddRepeatStep("increment progress", () => + { + var progress = this.ChildrenOfType().Single().User.BeatmapAvailability.DownloadProgress ?? 0; + Client.ChangeBeatmapAvailability(BeatmapAvailability.Downloading(progress + RNG.NextSingle(0.1f))); + }, 25); + AddStep("set to importing map", () => Client.ChangeBeatmapAvailability(BeatmapAvailability.Importing())); + AddStep("set to available", () => Client.ChangeBeatmapAvailability(BeatmapAvailability.LocallyAvailable())); + } + [Test] public void TestToggleReadyState() { @@ -120,6 +136,26 @@ namespace osu.Game.Tests.Visual.Multiplayer }); Client.ChangeUserState(i, (MultiplayerUserState)RNG.Next(0, (int)MultiplayerUserState.Results + 1)); + + if (RNG.NextBool()) + { + var beatmapState = (DownloadState)RNG.Next(0, (int)DownloadState.LocallyAvailable + 1); + + switch (beatmapState) + { + case DownloadState.NotDownloaded: + Client.ChangeUserBeatmapAvailability(i, BeatmapAvailability.NotDownloaded()); + break; + + case DownloadState.Downloading: + Client.ChangeUserBeatmapAvailability(i, BeatmapAvailability.Downloading(RNG.NextSingle())); + break; + + case DownloadState.Importing: + Client.ChangeUserBeatmapAvailability(i, BeatmapAvailability.Importing()); + break; + } + } } }); } From e6ceaad73233c8322f93554ebcbf136da5c47285 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 18 Jan 2021 17:23:51 +0300 Subject: [PATCH 0071/1791] Revert user state back to idle upon availability change --- .../Multiplayer/MultiplayerMatchSubScreen.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 94288673fb..826859c598 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -184,7 +184,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer base.LoadComplete(); Playlist.BindCollectionChanged(onPlaylistChanged, true); - BeatmapAvailability.BindValueChanged(updateClientAvailability, true); + BeatmapAvailability.BindValueChanged(updateBeatmapAvailability, true); client.RoomUpdated += onRoomUpdated; client.LoadRequested += onLoadRequested; @@ -210,10 +210,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer private void onPlaylistChanged(object sender, NotifyCollectionChangedEventArgs e) => SelectedItem.Value = Playlist.FirstOrDefault(); - private void updateClientAvailability(ValueChangedEvent _ = null) + private void updateBeatmapAvailability(ValueChangedEvent _ = null) { - if (client.Room != null) - client.ChangeBeatmapAvailability(BeatmapAvailability.Value).CatchUnobservedExceptions(true); + if (client.Room == null) + return; + + client.ChangeBeatmapAvailability(BeatmapAvailability.Value).CatchUnobservedExceptions(true); + + if (client.LocalUser?.State == MultiplayerUserState.Ready) + client.ChangeState(MultiplayerUserState.Idle).CatchUnobservedExceptions(true); } private void onRoomUpdated() @@ -222,7 +227,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer return; if (client.LocalUser?.BeatmapAvailability.Equals(BeatmapAvailability.Value) == false) - updateClientAvailability(); + updateBeatmapAvailability(); } private void onReadyClick() From ddcfd854bd1c66b673d73e5459526ded76ebcb41 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 18 Jan 2021 19:20:19 +0300 Subject: [PATCH 0072/1791] Wait for scheduled state changes before asserting --- osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs b/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs index 4b8992052e..d692e6f9a3 100644 --- a/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs +++ b/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs @@ -130,6 +130,8 @@ namespace osu.Game.Tests.Online private void addAvailabilityCheckStep(string description, Func expected) { + // In DownloadTrackingComposite, state changes are scheduled one frame later, wait one step. + AddWaitStep("wait for potential change", 1); AddAssert(description, () => tracker.Availability.Value.Equals(expected.Invoke())); } From c6b0f3c247aa54a64d9e61c47643a09ea91ec177 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 18 Jan 2021 21:22:50 +0300 Subject: [PATCH 0073/1791] Implement TabbableOnlineOverlay component --- osu.Game/Overlays/DashboardOverlay.cs | 92 ++----------------- osu.Game/Overlays/RankingsOverlay.cs | 46 ++-------- osu.Game/Overlays/TabbableOnlineOverlay.cs | 101 +++++++++++++++++++++ 3 files changed, 116 insertions(+), 123 deletions(-) create mode 100644 osu.Game/Overlays/TabbableOnlineOverlay.cs diff --git a/osu.Game/Overlays/DashboardOverlay.cs b/osu.Game/Overlays/DashboardOverlay.cs index 39a23fe3d4..83ad8faf1c 100644 --- a/osu.Game/Overlays/DashboardOverlay.cs +++ b/osu.Game/Overlays/DashboardOverlay.cs @@ -2,115 +2,35 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Threading; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Online.API; using osu.Game.Overlays.Dashboard; using osu.Game.Overlays.Dashboard.Friends; namespace osu.Game.Overlays { - public class DashboardOverlay : OnlineOverlay + public class DashboardOverlay : TabbableOnlineOverlay { - private CancellationTokenSource cancellationToken; - public DashboardOverlay() : base(OverlayColourScheme.Purple) { } - private readonly IBindable apiState = new Bindable(); - - [BackgroundDependencyLoader] - private void load(IAPIProvider api) - { - apiState.BindTo(api.State); - apiState.BindValueChanged(onlineStateChanged, true); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - Header.Current.BindValueChanged(onTabChanged); - } - protected override DashboardOverlayHeader CreateHeader() => new DashboardOverlayHeader(); - private bool displayUpdateRequired = true; - - protected override void PopIn() + protected override void CreateDisplayToLoad(DashboardOverlayTabs tab) { - base.PopIn(); - - // We don't want to create a new display on every call, only when exiting from fully closed state. - if (displayUpdateRequired) - { - Header.Current.TriggerChange(); - displayUpdateRequired = false; - } - } - - protected override void PopOutComplete() - { - base.PopOutComplete(); - loadDisplay(Empty()); - displayUpdateRequired = true; - } - - private void loadDisplay(Drawable display) - { - ScrollFlow.ScrollToStart(); - - LoadComponentAsync(display, loaded => - { - if (API.IsLoggedIn) - Loading.Hide(); - - Child = loaded; - }, (cancellationToken = new CancellationTokenSource()).Token); - } - - private void onTabChanged(ValueChangedEvent tab) - { - cancellationToken?.Cancel(); - Loading.Show(); - - if (!API.IsLoggedIn) - { - loadDisplay(Empty()); - return; - } - - switch (tab.NewValue) + switch (tab) { case DashboardOverlayTabs.Friends: - loadDisplay(new FriendDisplay()); + LoadDisplay(new FriendDisplay()); break; case DashboardOverlayTabs.CurrentlyPlaying: - loadDisplay(new CurrentlyPlayingDisplay()); + LoadDisplay(new CurrentlyPlayingDisplay()); break; default: - throw new NotImplementedException($"Display for {tab.NewValue} tab is not implemented"); + throw new NotImplementedException($"Display for {tab} tab is not implemented"); } } - - private void onlineStateChanged(ValueChangedEvent state) => Schedule(() => - { - if (State.Value == Visibility.Hidden) - return; - - Header.Current.TriggerChange(); - }); - - protected override void Dispose(bool isDisposing) - { - cancellationToken?.Cancel(); - base.Dispose(isDisposing); - } } } diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs index f6bbac4407..6cd72d6e2c 100644 --- a/osu.Game/Overlays/RankingsOverlay.cs +++ b/osu.Game/Overlays/RankingsOverlay.cs @@ -8,20 +8,18 @@ using osu.Game.Overlays.Rankings; using osu.Game.Users; using osu.Game.Rulesets; using osu.Game.Online.API; -using System.Threading; using osu.Game.Online.API.Requests; using osu.Game.Overlays.Rankings.Tables; namespace osu.Game.Overlays { - public class RankingsOverlay : OnlineOverlay + public class RankingsOverlay : TabbableOnlineOverlay { protected Bindable Country => Header.Country; protected Bindable Scope => Header.Current; private APIRequest lastRequest; - private CancellationTokenSource cancellationToken; [Resolved] private IAPIProvider api { get; set; } @@ -34,11 +32,6 @@ namespace osu.Game.Overlays { } - [BackgroundDependencyLoader] - private void load() - { - } - protected override void LoadComplete() { base.LoadComplete(); @@ -54,6 +47,8 @@ namespace osu.Game.Overlays Scheduler.AddOnce(loadNewContent); }); + // Unbind events from scope so base class event will not be called + Scope.UnbindEvents(); Scope.BindValueChanged(_ => { // country filtering is only valid for performance scope. @@ -70,8 +65,6 @@ namespace osu.Game.Overlays Scheduler.AddOnce(loadNewContent); }); - - Scheduler.AddOnce(loadNewContent); } protected override RankingsOverlayHeader CreateHeader() => new RankingsOverlayHeader(); @@ -92,16 +85,13 @@ namespace osu.Game.Overlays Show(); } - private void loadNewContent() + protected override void CreateDisplayToLoad(RankingsScope tab) { - Loading.Show(); - - cancellationToken?.Cancel(); lastRequest?.Cancel(); if (Scope.Value == RankingsScope.Spotlights) { - loadContent(new SpotlightsLayout + LoadDisplay(new SpotlightsLayout { Ruleset = { BindTarget = ruleset } }); @@ -113,12 +103,12 @@ namespace osu.Game.Overlays if (request == null) { - loadContent(null); + LoadDisplay(Empty()); return; } - request.Success += () => Schedule(() => loadContent(createTableFromResponse(request))); - request.Failure += _ => Schedule(() => loadContent(null)); + request.Success += () => Schedule(() => LoadDisplay(createTableFromResponse(request))); + request.Failure += _ => Schedule(() => LoadDisplay(Empty())); api.Queue(request); } @@ -163,29 +153,11 @@ namespace osu.Game.Overlays return null; } - private void loadContent(Drawable content) - { - ScrollFlow.ScrollToStart(); - - if (content == null) - { - Clear(); - Loading.Hide(); - return; - } - - LoadComponentAsync(content, loaded => - { - Loading.Hide(); - Child = loaded; - }, (cancellationToken = new CancellationTokenSource()).Token); - } + private void loadNewContent() => OnTabChanged(Scope.Value); protected override void Dispose(bool isDisposing) { lastRequest?.Cancel(); - cancellationToken?.Cancel(); - base.Dispose(isDisposing); } } diff --git a/osu.Game/Overlays/TabbableOnlineOverlay.cs b/osu.Game/Overlays/TabbableOnlineOverlay.cs new file mode 100644 index 0000000000..cbcf3cd96e --- /dev/null +++ b/osu.Game/Overlays/TabbableOnlineOverlay.cs @@ -0,0 +1,101 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Threading; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Online.API; + +namespace osu.Game.Overlays +{ + public abstract class TabbableOnlineOverlay : OnlineOverlay + where THeader : TabControlOverlayHeader + { + private readonly IBindable apiState = new Bindable(); + + private CancellationTokenSource cancellationToken; + private bool displayUpdateRequired = true; + + protected TabbableOnlineOverlay(OverlayColourScheme colourScheme) + : base(colourScheme) + { + } + + [BackgroundDependencyLoader] + private void load(IAPIProvider api) + { + apiState.BindTo(api.State); + apiState.BindValueChanged(onlineStateChanged, true); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + Header.Current.BindValueChanged(tab => OnTabChanged(tab.NewValue)); + } + + protected override void PopIn() + { + base.PopIn(); + + // We don't want to create a new display on every call, only when exiting from fully closed state. + if (displayUpdateRequired) + { + Header.Current.TriggerChange(); + displayUpdateRequired = false; + } + } + + protected override void PopOutComplete() + { + base.PopOutComplete(); + LoadDisplay(Empty()); + displayUpdateRequired = true; + } + + protected void LoadDisplay(Drawable display) + { + ScrollFlow.ScrollToStart(); + + LoadComponentAsync(display, loaded => + { + if (API.IsLoggedIn) + Loading.Hide(); + + Child = loaded; + }, (cancellationToken = new CancellationTokenSource()).Token); + } + + protected void OnTabChanged(TEnum tab) + { + cancellationToken?.Cancel(); + Loading.Show(); + + if (!API.IsLoggedIn) + { + LoadDisplay(Empty()); + return; + } + + CreateDisplayToLoad(tab); + } + + protected abstract void CreateDisplayToLoad(TEnum tab); + + private void onlineStateChanged(ValueChangedEvent state) => Schedule(() => + { + if (State.Value == Visibility.Hidden) + return; + + Header.Current.TriggerChange(); + }); + + protected override void Dispose(bool isDisposing) + { + cancellationToken?.Cancel(); + base.Dispose(isDisposing); + } + } +} From 25f511fd5b2b6b2bfbd9e1787182a8a87f086de0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 18 Jan 2021 21:34:24 +0300 Subject: [PATCH 0074/1791] Remove unnecessary full querying --- osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs b/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs index 659f52f00f..c2c800badc 100644 --- a/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs +++ b/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs @@ -48,17 +48,7 @@ namespace osu.Game.Online.Rooms int? beatmapId = SelectedItem.Value.Beatmap.Value.OnlineBeatmapID; string checksum = SelectedItem.Value.Beatmap.Value.MD5Hash; - BeatmapInfo matchingBeatmap; - - if (databasedSet.Beatmaps == null) - { - // The given databased beatmap set is not passed in a usable state to check with. - // Perform a full query instead, as per https://github.com/ppy/osu/pull/11415. - matchingBeatmap = Manager.QueryBeatmap(b => b.OnlineBeatmapID == beatmapId && b.MD5Hash == checksum); - return matchingBeatmap != null; - } - - matchingBeatmap = databasedSet.Beatmaps.FirstOrDefault(b => b.OnlineBeatmapID == beatmapId && b.MD5Hash == checksum); + var matchingBeatmap = databasedSet.Beatmaps.FirstOrDefault(b => b.OnlineBeatmapID == beatmapId && b.MD5Hash == checksum); return matchingBeatmap != null; } From 5e476fa189d7637db79c228721a601a005f2d355 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 18 Jan 2021 22:07:25 +0300 Subject: [PATCH 0075/1791] Enforce one missed property back to single-floating type --- osu.Game/Online/API/ArchiveDownloadRequest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/ArchiveDownloadRequest.cs b/osu.Game/Online/API/ArchiveDownloadRequest.cs index ccb4e9c119..0bf238109e 100644 --- a/osu.Game/Online/API/ArchiveDownloadRequest.cs +++ b/osu.Game/Online/API/ArchiveDownloadRequest.cs @@ -10,7 +10,7 @@ namespace osu.Game.Online.API { public readonly TModel Model; - public double Progress { get; private set; } + public float Progress { get; private set; } public event Action DownloadProgressed; From 63ca9de7e4a85990bd162d25cd9be01f8c91c239 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 18 Jan 2021 23:00:07 +0300 Subject: [PATCH 0076/1791] Rewerite beatmap term properly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs b/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs index c2c800badc..32a6c39ea8 100644 --- a/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs +++ b/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs @@ -38,7 +38,7 @@ namespace osu.Game.Online.Rooms { var verified = verifyDatabasedModel(databasedSet); if (!verified) - Logger.Log("The imported beatmapset does not match the online version.", LoggingTarget.Runtime, LogLevel.Important); + Logger.Log("The imported beatmap set does not match the online version.", LoggingTarget.Runtime, LogLevel.Important); return verified; } From de9d075f941fe840e14e9a576e3329956884ee4c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 19 Jan 2021 17:11:40 +0900 Subject: [PATCH 0077/1791] Initial sample + samplechannel rework --- .../Legacy/ManiaLegacySkinTransformer.cs | 4 +- .../TestSceneCursorTrail.cs | 2 +- .../TestSceneSkinFallbacks.cs | 2 +- .../Objects/Drawables/DrawableSpinner.cs | 3 +- .../Legacy/TaikoLegacySkinTransformer.cs | 2 +- .../TestSceneHitObjectAccentColour.cs | 2 +- .../Gameplay/TestSceneStoryboardSamples.cs | 4 +- .../Skinning/LegacySkinAnimationTest.cs | 2 +- .../TestSceneDrawableRulesetDependencies.cs | 4 +- .../Skins/TestSceneSkinConfigurationLookup.cs | 2 +- .../Editing/TestSceneEditorSamplePlayback.cs | 16 +++--- .../TestSceneGameplaySamplePlayback.cs | 7 +-- .../Gameplay/TestSceneSkinnableDrawable.cs | 6 +- .../Gameplay/TestSceneSkinnableSound.cs | 56 +++++++------------ .../Containers/OsuFocusedOverlayContainer.cs | 4 +- osu.Game/Graphics/ScreenshotManager.cs | 2 +- .../UserInterface/DrawableOsuMenuItem.cs | 4 +- .../UserInterface/HoverClickSounds.cs | 2 +- .../Graphics/UserInterface/HoverSounds.cs | 2 +- .../Graphics/UserInterface/OsuCheckbox.cs | 4 +- .../Graphics/UserInterface/OsuSliderBar.cs | 12 ++-- osu.Game/Graphics/UserInterface/OsuTextBox.cs | 10 ++-- osu.Game/Overlays/ChangelogOverlay.cs | 2 +- osu.Game/Overlays/MedalOverlay.cs | 2 +- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 2 +- .../UI/DrawableRulesetDependencies.cs | 4 +- osu.Game/Screens/Menu/Button.cs | 4 +- osu.Game/Screens/Menu/ButtonSystem.cs | 2 +- osu.Game/Screens/Menu/IntroCircles.cs | 2 +- osu.Game/Screens/Menu/IntroScreen.cs | 2 +- osu.Game/Screens/Menu/IntroTriangles.cs | 2 +- osu.Game/Screens/Menu/IntroWelcome.cs | 11 ++-- osu.Game/Screens/Menu/OsuLogo.cs | 4 +- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 2 +- .../Match/MultiplayerReadyButton.cs | 6 +- osu.Game/Screens/OsuScreen.cs | 2 +- osu.Game/Screens/Play/FailAnimation.cs | 2 +- osu.Game/Screens/Play/Player.cs | 2 +- osu.Game/Screens/Play/SkipOverlay.cs | 2 +- .../Screens/Select/Carousel/CarouselHeader.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 6 +- osu.Game/Skinning/DefaultSkin.cs | 2 +- osu.Game/Skinning/ISkin.cs | 2 +- osu.Game/Skinning/LegacyBeatmapSkin.cs | 2 +- osu.Game/Skinning/LegacySkin.cs | 4 +- osu.Game/Skinning/LegacySkinTransformer.cs | 4 +- osu.Game/Skinning/PausableSkinnableSound.cs | 4 +- osu.Game/Skinning/PoolableSkinnableSample.cs | 23 +++++--- osu.Game/Skinning/Skin.cs | 2 +- osu.Game/Skinning/SkinManager.cs | 2 +- osu.Game/Skinning/SkinProvidingContainer.cs | 4 +- osu.Game/Skinning/SkinnableSound.cs | 7 ++- 52 files changed, 131 insertions(+), 138 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs index 7e2a8823b6..cbbbacfe19 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs @@ -140,11 +140,11 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy return animation == null ? null : new LegacyManiaJudgementPiece(result, animation); } - public override SampleChannel GetSample(ISampleInfo sampleInfo) + public override Sample GetSample(ISampleInfo sampleInfo) { // layered hit sounds never play in mania if (sampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacySample && legacySample.IsLayered) - return new SampleChannelVirtual(); + return new SampleVirtual(); return Source.GetSample(sampleInfo); } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs index fefe983f97..e2d9f144c0 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 SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); + public Sample 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 10baca438d..8dbb48c048 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 SampleChannel GetSample(ISampleInfo sampleInfo) => null; + public Sample 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.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 56aedebed3..b9c5b6e83a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -130,7 +130,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { if (tracking.NewValue) { - spinningSample?.Play(!spinningSample.IsPlaying); + if (!spinningSample.IsPlaying) + spinningSample?.Play(); spinningSample?.VolumeTo(1, 300); } else diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs index d8e3100048..9f29675230 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 SampleChannel GetSample(ISampleInfo sampleInfo) => Source.GetSample(new LegacyTaikoSampleInfo(sampleInfo)); + public override Sample 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 de46f9d1cf..3ded3009bd 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 SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); + public Sample 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 38cb6729c3..7a0dd5b719 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; - SampleChannel channel = null; + Sample 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; - SampleChannel channel = null; + Sample 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 a5c937119e..da004b9088 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 SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotSupportedException(); + public Sample GetSample(ISampleInfo sampleInfo) => throw new NotSupportedException(); public IBindable GetConfig(TLookup lookup) => throw new NotSupportedException(); } diff --git a/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs b/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs index 987a5812db..787f72ba79 100644 --- a/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs +++ b/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs @@ -105,9 +105,9 @@ namespace osu.Game.Tests.Rulesets IsDisposed = true; } - public SampleChannel Get(string name) => null; + public Sample Get(string name) => null; - public Task GetAsync(string name) => null; + public Task GetAsync(string name) => null; public Stream GetStream(string name) => null; diff --git a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs index ad5b3ec0f6..414f7d3f88 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 SampleChannel GetSample(ISampleInfo sampleInfo) => skin.GetSample(sampleInfo); + public Sample GetSample(ISampleInfo sampleInfo) => skin.GetSample(sampleInfo); public IBindable GetConfig(TLookup lookup) => skin.GetConfig(lookup); } diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSamplePlayback.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSamplePlayback.cs index f182023c0e..876c1308b4 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSamplePlayback.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSamplePlayback.cs @@ -3,11 +3,11 @@ using System.Linq; using NUnit.Framework; -using osu.Framework.Graphics.Audio; using osu.Framework.Testing; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Skinning; namespace osu.Game.Tests.Visual.Editing { @@ -19,14 +19,14 @@ namespace osu.Game.Tests.Visual.Editing public void TestSlidingSampleStopsOnSeek() { DrawableSlider slider = null; - DrawableSample[] loopingSamples = null; - DrawableSample[] onceOffSamples = null; + SkinnableSound[] loopingSamples = null; + SkinnableSound[] onceOffSamples = null; AddStep("get first slider", () => { slider = Editor.ChildrenOfType().OrderBy(s => s.HitObject.StartTime).First(); - onceOffSamples = slider.ChildrenOfType().Where(s => !s.Looping).ToArray(); - loopingSamples = slider.ChildrenOfType().Where(s => s.Looping).ToArray(); + onceOffSamples = slider.ChildrenOfType().Where(s => !s.Looping).ToArray(); + loopingSamples = slider.ChildrenOfType().Where(s => s.Looping).ToArray(); }); AddStep("start playback", () => EditorClock.Start()); @@ -36,15 +36,15 @@ namespace osu.Game.Tests.Visual.Editing if (!slider.Tracking.Value) return false; - if (!loopingSamples.Any(s => s.Playing)) + if (!loopingSamples.Any(s => s.IsPlaying)) return false; EditorClock.Seek(20000); return true; }); - AddAssert("non-looping samples are playing", () => onceOffSamples.Length == 4 && loopingSamples.All(s => s.Played || s.Playing)); - AddAssert("looping samples are not playing", () => loopingSamples.Length == 1 && loopingSamples.All(s => s.Played && !s.Playing)); + AddAssert("non-looping samples are playing", () => onceOffSamples.Length == 4 && loopingSamples.All(s => s.IsPlayed || s.IsPlaying)); + AddAssert("looping samples are not playing", () => loopingSamples.Length == 1 && loopingSamples.All(s => s.IsPlayed && !s.IsPlaying)); } } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs index 7c6a213fe2..b13acdcb95 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; -using osu.Framework.Graphics.Audio; using osu.Framework.Testing; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; @@ -20,14 +19,14 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestAllSamplesStopDuringSeek() { DrawableSlider slider = null; - DrawableSample[] samples = null; + SkinnableSound[] samples = null; ISamplePlaybackDisabler sampleDisabler = null; AddUntilStep("get variables", () => { sampleDisabler = Player; slider = Player.ChildrenOfType().OrderBy(s => s.HitObject.StartTime).FirstOrDefault(); - samples = slider?.ChildrenOfType().ToArray(); + samples = slider?.ChildrenOfType().ToArray(); return slider != null; }); @@ -37,7 +36,7 @@ namespace osu.Game.Tests.Visual.Gameplay if (!slider.Tracking.Value) return false; - if (!samples.Any(s => s.Playing)) + if (!samples.Any(s => s.IsPlaying)) return false; Player.ChildrenOfType().First().Seek(40000); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs index bed48f3d86..44142b69d7 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 SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); + public Sample 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 SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); + public Sample 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 SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); + public Sample 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 fc0cda2c1f..28c266f7d8 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs @@ -43,70 +43,52 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestStoppedSoundDoesntResumeAfterPause() { - DrawableSample sample = null; - AddStep("start sample with looping", () => - { - sample = skinnableSound.ChildrenOfType().First(); + AddStep("start sample with looping", () => skinnableSound.Looping = true); - skinnableSound.Looping = true; - skinnableSound.Play(); - }); - - AddUntilStep("wait for sample to start playing", () => sample.Playing); + AddUntilStep("wait for sample to start playing", () => skinnableSound.IsPlaying); AddStep("stop sample", () => skinnableSound.Stop()); - AddUntilStep("wait for sample to stop playing", () => !sample.Playing); + AddUntilStep("wait for sample to stop playing", () => !skinnableSound.IsPlaying); AddStep("disable sample playback", () => skinSource.SamplePlaybackDisabled.Value = true); AddStep("enable sample playback", () => skinSource.SamplePlaybackDisabled.Value = false); AddWaitStep("wait a bit", 5); - AddAssert("sample not playing", () => !sample.Playing); + AddAssert("sample not playing", () => !skinnableSound.IsPlaying); } [Test] public void TestLoopingSoundResumesAfterPause() { - DrawableSample sample = null; - AddStep("start sample with looping", () => - { - skinnableSound.Looping = true; - skinnableSound.Play(); - sample = skinnableSound.ChildrenOfType().First(); - }); + AddStep("start sample with looping", () => skinnableSound.Looping = true); - AddUntilStep("wait for sample to start playing", () => sample.Playing); + AddUntilStep("wait for sample to start playing", () => skinnableSound.IsPlaying); AddStep("disable sample playback", () => skinSource.SamplePlaybackDisabled.Value = true); - AddUntilStep("wait for sample to stop playing", () => !sample.Playing); + AddUntilStep("wait for sample to stop playing", () => !skinnableSound.IsPlaying); AddStep("enable sample playback", () => skinSource.SamplePlaybackDisabled.Value = false); - AddUntilStep("wait for sample to start playing", () => sample.Playing); + AddUntilStep("wait for sample to start playing", () => skinnableSound.IsPlaying); } [Test] public void TestNonLoopingStopsWithPause() { - DrawableSample sample = null; - AddStep("start sample", () => - { - skinnableSound.Play(); - sample = skinnableSound.ChildrenOfType().First(); - }); + AddStep("start sample", () => skinnableSound.Play()); - AddAssert("sample playing", () => sample.Playing); + AddAssert("sample playing", () => skinnableSound.IsPlaying); AddStep("disable sample playback", () => skinSource.SamplePlaybackDisabled.Value = true); - AddUntilStep("sample not playing", () => !sample.Playing); + AddUntilStep("sample not playing", () => !skinnableSound.IsPlaying); AddStep("enable sample playback", () => skinSource.SamplePlaybackDisabled.Value = false); - AddAssert("sample not playing", () => !sample.Playing); - AddAssert("sample not playing", () => !sample.Playing); - AddAssert("sample not playing", () => !sample.Playing); + AddAssert("sample not playing", () => !skinnableSound.IsPlaying); + AddAssert("sample not playing", () => !skinnableSound.IsPlaying); + AddAssert("sample not playing", () => !skinnableSound.IsPlaying); } [Test] @@ -119,10 +101,10 @@ namespace osu.Game.Tests.Visual.Gameplay sample = skinnableSound.ChildrenOfType().Single(); }); - AddAssert("sample playing", () => sample.Playing); + AddAssert("sample playing", () => skinnableSound.IsPlaying); AddStep("disable sample playback", () => skinSource.SamplePlaybackDisabled.Value = true); - AddUntilStep("wait for sample to stop playing", () => !sample.Playing); + AddUntilStep("wait for sample to stop playing", () => !skinnableSound.IsPlaying); AddStep("trigger skin change", () => skinSource.TriggerSourceChanged()); @@ -133,11 +115,11 @@ namespace osu.Game.Tests.Visual.Gameplay return sample != oldSample; }); - AddAssert("new sample stopped", () => !sample.Playing); + AddAssert("new sample stopped", () => !skinnableSound.IsPlaying); AddStep("enable sample playback", () => skinSource.SamplePlaybackDisabled.Value = false); AddWaitStep("wait a bit", 5); - AddAssert("new sample not played", () => !sample.Playing); + AddAssert("new sample not played", () => !skinnableSound.IsPlaying); } [Cached(typeof(ISkinSource))] @@ -155,7 +137,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 SampleChannel GetSample(ISampleInfo sampleInfo) => source?.GetSample(sampleInfo); + public Sample GetSample(ISampleInfo sampleInfo) => source?.GetSample(sampleInfo); public IBindable GetConfig(TLookup lookup) => source?.GetConfig(lookup); public void TriggerSourceChanged() diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index 41fd37a0d7..d623622434 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs @@ -18,8 +18,8 @@ namespace osu.Game.Graphics.Containers [Cached(typeof(IPreviewTrackOwner))] public abstract class OsuFocusedOverlayContainer : FocusedOverlayContainer, IPreviewTrackOwner, IKeyBindingHandler { - private SampleChannel samplePopIn; - private SampleChannel samplePopOut; + private Sample samplePopIn; + private Sample samplePopOut; protected override bool BlockNonPositionalInput => true; diff --git a/osu.Game/Graphics/ScreenshotManager.cs b/osu.Game/Graphics/ScreenshotManager.cs index 53ee711626..f7914cbbca 100644 --- a/osu.Game/Graphics/ScreenshotManager.cs +++ b/osu.Game/Graphics/ScreenshotManager.cs @@ -44,7 +44,7 @@ namespace osu.Game.Graphics [Resolved] private NotificationOverlay notificationOverlay { get; set; } - private SampleChannel shutter; + private Sample shutter; [BackgroundDependencyLoader] private void load(OsuConfigManager config, Storage storage, AudioManager audio) diff --git a/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs b/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs index abaae7b43c..b499b26f38 100644 --- a/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs @@ -22,8 +22,8 @@ namespace osu.Game.Graphics.UserInterface private const int text_size = 17; private const int transition_length = 80; - private SampleChannel sampleClick; - private SampleChannel sampleHover; + private Sample sampleClick; + private Sample sampleHover; private TextContainer text; diff --git a/osu.Game/Graphics/UserInterface/HoverClickSounds.cs b/osu.Game/Graphics/UserInterface/HoverClickSounds.cs index 803facae04..c1963ce62d 100644 --- a/osu.Game/Graphics/UserInterface/HoverClickSounds.cs +++ b/osu.Game/Graphics/UserInterface/HoverClickSounds.cs @@ -17,7 +17,7 @@ namespace osu.Game.Graphics.UserInterface /// public class HoverClickSounds : HoverSounds { - private SampleChannel sampleClick; + private Sample sampleClick; private readonly MouseButton[] buttons; /// diff --git a/osu.Game/Graphics/UserInterface/HoverSounds.cs b/osu.Game/Graphics/UserInterface/HoverSounds.cs index a1d06711db..a91e2ffcab 100644 --- a/osu.Game/Graphics/UserInterface/HoverSounds.cs +++ b/osu.Game/Graphics/UserInterface/HoverSounds.cs @@ -20,7 +20,7 @@ namespace osu.Game.Graphics.UserInterface /// public class HoverSounds : CompositeDrawable { - private SampleChannel sampleHover; + private Sample sampleHover; /// /// Length of debounce for hover sound playback, in milliseconds. diff --git a/osu.Game/Graphics/UserInterface/OsuCheckbox.cs b/osu.Game/Graphics/UserInterface/OsuCheckbox.cs index 6593531099..c075fbb328 100644 --- a/osu.Game/Graphics/UserInterface/OsuCheckbox.cs +++ b/osu.Game/Graphics/UserInterface/OsuCheckbox.cs @@ -40,8 +40,8 @@ namespace osu.Game.Graphics.UserInterface protected readonly Nub Nub; private readonly OsuTextFlowContainer labelText; - private SampleChannel sampleChecked; - private SampleChannel sampleUnchecked; + private Sample sampleChecked; + private Sample sampleUnchecked; public OsuCheckbox() { diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index d0356e77c7..bcf5220380 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -25,7 +25,7 @@ namespace osu.Game.Graphics.UserInterface /// private const int max_decimal_digits = 5; - private SampleChannel sample; + private Sample sample; private double lastSampleTime; private T lastSampleValue; @@ -157,14 +157,14 @@ namespace osu.Game.Graphics.UserInterface lastSampleValue = value; lastSampleTime = Clock.CurrentTime; - sample.Frequency.Value = 1 + NormalizedValue * 0.2f; + var channel = sample.Play(); + + channel.Frequency.Value = 1 + NormalizedValue * 0.2f; if (NormalizedValue == 0) - sample.Frequency.Value -= 0.4f; + channel.Frequency.Value -= 0.4f; else if (NormalizedValue == 1) - sample.Frequency.Value += 0.4f; - - sample.Play(); + channel.Frequency.Value += 0.4f; } private void updateTooltipText(T value) diff --git a/osu.Game/Graphics/UserInterface/OsuTextBox.cs b/osu.Game/Graphics/UserInterface/OsuTextBox.cs index 1ec4dfc91a..75af9efc38 100644 --- a/osu.Game/Graphics/UserInterface/OsuTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTextBox.cs @@ -23,11 +23,11 @@ namespace osu.Game.Graphics.UserInterface { public class OsuTextBox : BasicTextBox { - private readonly SampleChannel[] textAddedSamples = new SampleChannel[4]; - private SampleChannel capsTextAddedSample; - private SampleChannel textRemovedSample; - private SampleChannel textCommittedSample; - private SampleChannel caretMovedSample; + private readonly Sample[] textAddedSamples = new Sample[4]; + private Sample capsTextAddedSample; + private Sample textRemovedSample; + private Sample textCommittedSample; + private Sample caretMovedSample; /// /// Whether to allow playing a different samples based on the type of character. diff --git a/osu.Game/Overlays/ChangelogOverlay.cs b/osu.Game/Overlays/ChangelogOverlay.cs index c7e9a86fa4..a4f46517d5 100644 --- a/osu.Game/Overlays/ChangelogOverlay.cs +++ b/osu.Game/Overlays/ChangelogOverlay.cs @@ -27,7 +27,7 @@ namespace osu.Game.Overlays private Container content; - private SampleChannel sampleBack; + private Sample sampleBack; private List builds; diff --git a/osu.Game/Overlays/MedalOverlay.cs b/osu.Game/Overlays/MedalOverlay.cs index 4425c2f168..0feae16b68 100644 --- a/osu.Game/Overlays/MedalOverlay.cs +++ b/osu.Game/Overlays/MedalOverlay.cs @@ -39,7 +39,7 @@ namespace osu.Game.Overlays private readonly Sprite innerSpin, outerSpin; private DrawableMedal drawableMedal; - private SampleChannel getSample; + private Sample getSample; private readonly Container content; diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 0c8245bebe..7bbffc6172 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -58,7 +58,7 @@ namespace osu.Game.Overlays.Mods private readonly FillFlowContainer footerContainer; - private SampleChannel sampleOn, sampleOff; + private Sample sampleOn, sampleOff; public ModSelectOverlay() { diff --git a/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs index 81ec73a6c5..deec948d14 100644 --- a/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs +++ b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs @@ -102,9 +102,9 @@ namespace osu.Game.Rulesets.UI this.fallback = fallback; } - public SampleChannel Get(string name) => primary.Get(name) ?? fallback.Get(name); + public Sample Get(string name) => primary.Get(name) ?? fallback.Get(name); - public Task GetAsync(string name) => primary.GetAsync(name) ?? fallback.GetAsync(name); + public Task GetAsync(string name) => primary.GetAsync(name) ?? fallback.GetAsync(name); public Stream GetStream(string name) => primary.GetStream(name) ?? fallback.GetStream(name); diff --git a/osu.Game/Screens/Menu/Button.cs b/osu.Game/Screens/Menu/Button.cs index be6ed9700c..d956394ebb 100644 --- a/osu.Game/Screens/Menu/Button.cs +++ b/osu.Game/Screens/Menu/Button.cs @@ -45,8 +45,8 @@ namespace osu.Game.Screens.Menu public ButtonSystemState VisibleState = ButtonSystemState.TopLevel; private readonly Action clickAction; - private SampleChannel sampleClick; - private SampleChannel sampleHover; + private Sample sampleClick; + private Sample sampleHover; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => box.ReceivePositionalInputAt(screenSpacePos); diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index f400b2114b..00061d6ea6 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -81,7 +81,7 @@ namespace osu.Game.Screens.Menu private readonly List public virtual bool DisallowExternalBeatmapRulesetChanges => false; - private SampleChannel sampleExit; + private Sample sampleExit; protected virtual bool PlayResumeSound => true; diff --git a/osu.Game/Screens/Play/FailAnimation.cs b/osu.Game/Screens/Play/FailAnimation.cs index 608f20affd..71bea2a145 100644 --- a/osu.Game/Screens/Play/FailAnimation.cs +++ b/osu.Game/Screens/Play/FailAnimation.cs @@ -34,7 +34,7 @@ namespace osu.Game.Screens.Play private const float duration = 2500; - private SampleChannel failSample; + private Sample failSample; public FailAnimation(DrawableRuleset drawableRuleset) { diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 1fcbed7ef7..7dda5973a0 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -91,7 +91,7 @@ namespace osu.Game.Screens.Play [Resolved] private MusicController musicController { get; set; } - private SampleChannel sampleRestart; + private Sample sampleRestart; public BreakOverlay BreakOverlay; diff --git a/osu.Game/Screens/Play/SkipOverlay.cs b/osu.Game/Screens/Play/SkipOverlay.cs index 92b304de91..3f214e49d9 100644 --- a/osu.Game/Screens/Play/SkipOverlay.cs +++ b/osu.Game/Screens/Play/SkipOverlay.cs @@ -230,7 +230,7 @@ namespace osu.Game.Screens.Play private Box background; private AspectContainer aspect; - private SampleChannel sampleConfirm; + private Sample sampleConfirm; public Button() { diff --git a/osu.Game/Screens/Select/Carousel/CarouselHeader.cs b/osu.Game/Screens/Select/Carousel/CarouselHeader.cs index f1120f55a6..c5c1e2eac7 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselHeader.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselHeader.cs @@ -20,7 +20,7 @@ namespace osu.Game.Screens.Select.Carousel { public class CarouselHeader : Container { - private SampleChannel sampleHover; + private Sample sampleHover; private readonly Box hoverLayer; diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 6c0bd3a228..a91dc49069 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -86,10 +86,10 @@ namespace osu.Game.Screens.Select protected ModSelectOverlay ModSelect { get; private set; } - protected SampleChannel SampleConfirm { get; private set; } + protected Sample SampleConfirm { get; private set; } - private SampleChannel sampleChangeDifficulty; - private SampleChannel sampleChangeBeatmap; + private Sample sampleChangeDifficulty; + private Sample sampleChangeBeatmap; private Container carouselContainer; diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs index 61d0112c89..346c7b3c65 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 SampleChannel GetSample(ISampleInfo sampleInfo) => null; + public override Sample 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 5abd963773..ef8de01042 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] - SampleChannel GetSample(ISampleInfo sampleInfo); + Sample GetSample(ISampleInfo sampleInfo); /// /// Retrieve a configuration value. diff --git a/osu.Game/Skinning/LegacyBeatmapSkin.cs b/osu.Game/Skinning/LegacyBeatmapSkin.cs index fdcb81b574..fb4207b647 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 SampleChannel GetSample(ISampleInfo sampleInfo) + public override Sample 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 090ffaebd7..e5d0217671 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -29,7 +29,7 @@ namespace osu.Game.Skinning protected TextureStore Textures; [CanBeNull] - protected IResourceStore Samples; + protected ISampleStore Samples; /// /// Whether texture for the keys exists. @@ -452,7 +452,7 @@ namespace osu.Game.Skinning return null; } - public override SampleChannel GetSample(ISampleInfo sampleInfo) + public override Sample GetSample(ISampleInfo sampleInfo) { IEnumerable lookupNames; diff --git a/osu.Game/Skinning/LegacySkinTransformer.cs b/osu.Game/Skinning/LegacySkinTransformer.cs index ebc4757e75..e2f4a82a54 100644 --- a/osu.Game/Skinning/LegacySkinTransformer.cs +++ b/osu.Game/Skinning/LegacySkinTransformer.cs @@ -34,14 +34,14 @@ namespace osu.Game.Skinning public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => Source.GetTexture(componentName, wrapModeS, wrapModeT); - public virtual SampleChannel GetSample(ISampleInfo sampleInfo) + public virtual Sample GetSample(ISampleInfo sampleInfo) { if (!(sampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacySample)) return Source.GetSample(sampleInfo); var playLayeredHitSounds = GetConfig(LegacySetting.LayeredHitSounds); if (legacySample.IsLayered && playLayeredHitSounds?.Value == false) - return new SampleChannelVirtual(); + return new SampleVirtual(); return Source.GetSample(sampleInfo); } diff --git a/osu.Game/Skinning/PausableSkinnableSound.cs b/osu.Game/Skinning/PausableSkinnableSound.cs index cb5234c847..4b6099e85f 100644 --- a/osu.Game/Skinning/PausableSkinnableSound.cs +++ b/osu.Game/Skinning/PausableSkinnableSound.cs @@ -67,7 +67,7 @@ namespace osu.Game.Skinning } } - public override void Play(bool restart = true) + public override void Play() { cancelPendingStart(); RequestedPlaying = true; @@ -75,7 +75,7 @@ namespace osu.Game.Skinning if (samplePlaybackDisabled.Value) return; - base.Play(restart); + base.Play(); } public override void Stop() diff --git a/osu.Game/Skinning/PoolableSkinnableSample.cs b/osu.Game/Skinning/PoolableSkinnableSample.cs index 2a0f480b48..2c83023fdc 100644 --- a/osu.Game/Skinning/PoolableSkinnableSample.cs +++ b/osu.Game/Skinning/PoolableSkinnableSample.cs @@ -27,6 +27,7 @@ namespace osu.Game.Skinning private readonly AudioContainer sampleContainer; private ISampleInfo sampleInfo; + private SampleChannel activeChannel; [Resolved] private ISampleStore sampleStore { get; set; } @@ -99,7 +100,7 @@ namespace osu.Game.Skinning if (ch == null) return; - sampleContainer.Add(Sample = new DrawableSample(ch) { Looping = Looping }); + sampleContainer.Add(Sample = new DrawableSample(ch)); // Start playback internally for the new sample if the previous one was playing beforehand. if (wasPlaying && Looping) @@ -109,18 +110,26 @@ namespace osu.Game.Skinning /// /// Plays the sample. /// - /// Whether to play the sample from the beginning. - public void Play(bool restart = true) => Sample?.Play(restart); + public void Play() + { + if (Sample == null) + return; + + activeChannel = Sample.Play(); + activeChannel.Looping = Looping; + } /// /// Stops the sample. /// - public void Stop() => Sample?.Stop(); + public void Stop() => activeChannel?.Stop(); /// /// Whether the sample is currently playing. /// - public bool Playing => Sample?.Playing ?? false; + public bool Playing => activeChannel?.Playing ?? false; + + public bool Played => activeChannel?.Played ?? false; private bool looping; @@ -134,8 +143,8 @@ namespace osu.Game.Skinning { looping = value; - if (Sample != null) - Sample.Looping = value; + if (activeChannel != null) + activeChannel.Looping = value; } } diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 4b0cf02c0a..e8d84b49f9 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 SampleChannel GetSample(ISampleInfo sampleInfo); + public abstract Sample 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 99c64b13a4..2826c826a5 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 SampleChannel GetSample(ISampleInfo sampleInfo) => CurrentSkin.Value.GetSample(sampleInfo); + public Sample 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 27cf0c697a..ba67d0a678 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 SampleChannel GetSample(ISampleInfo sampleInfo) + public Sample GetSample(ISampleInfo sampleInfo) { - SampleChannel sourceChannel; + Sample sourceChannel; if (AllowSampleLookup(sampleInfo) && (sourceChannel = skin?.GetSample(sampleInfo)) != null) return sourceChannel; diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index a874e9a0db..06c694dc7a 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -119,13 +119,12 @@ namespace osu.Game.Skinning /// /// Plays the samples. /// - /// Whether to play the sample from the beginning. - public virtual void Play(bool restart = true) + public virtual void Play() { samplesContainer.ForEach(c => { if (PlayWhenZeroVolume || c.AggregateVolume.Value > 0) - c.Play(restart); + c.Play(); }); } @@ -188,6 +187,8 @@ namespace osu.Game.Skinning /// public bool IsPlaying => samplesContainer.Any(s => s.Playing); + public bool IsPlayed => samplesContainer.Any(s => s.Played); + #endregion } } From 5a64abee648f7c49f92305f6ba988c19199e9f64 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 19 Jan 2021 11:51:31 +0300 Subject: [PATCH 0078/1791] Inline with above method --- .../Online/Rooms/MultiplayerBeatmapTracker.cs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs b/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs index 32a6c39ea8..59dfdcf464 100644 --- a/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs +++ b/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs @@ -35,21 +35,19 @@ namespace osu.Game.Online.Rooms } protected override bool VerifyDatabasedModel(BeatmapSetInfo databasedSet) - { - var verified = verifyDatabasedModel(databasedSet); - if (!verified) - Logger.Log("The imported beatmap set does not match the online version.", LoggingTarget.Runtime, LogLevel.Important); - - return verified; - } - - private bool verifyDatabasedModel(BeatmapSetInfo databasedSet) { int? beatmapId = SelectedItem.Value.Beatmap.Value.OnlineBeatmapID; string checksum = SelectedItem.Value.Beatmap.Value.MD5Hash; var matchingBeatmap = databasedSet.Beatmaps.FirstOrDefault(b => b.OnlineBeatmapID == beatmapId && b.MD5Hash == checksum); - return matchingBeatmap != null; + + if (matchingBeatmap == null) + { + Logger.Log("The imported beatmap set does not match the online version.", LoggingTarget.Runtime, LogLevel.Important); + return false; + } + + return true; } protected override bool IsModelAvailableLocally() From 63b4c529a6fe1f3a996f6074bfb736144e2e9c76 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 19 Jan 2021 11:57:40 +0300 Subject: [PATCH 0079/1791] Add xmldoc explaining what the multiplayer beatmap tracker is for --- osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs b/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs index 59dfdcf464..28e4872ad3 100644 --- a/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs +++ b/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs @@ -9,6 +9,12 @@ using osu.Game.Beatmaps; namespace osu.Game.Online.Rooms { + /// + /// Represent a checksum-verifying beatmap availability tracker usable for online play screens. + /// + /// This differs from a regular download tracking composite as this accounts for the + /// databased beatmap set's checksum, to disallow from playing with an altered version of the beatmap. + /// public class MultiplayerBeatmapTracker : DownloadTrackingComposite { public readonly IBindable SelectedItem = new Bindable(); From ed3dece9f8f7c0c0e0d35ac3fa330f2359b84cbc Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 19 Jan 2021 18:36:05 +0300 Subject: [PATCH 0080/1791] Fix wrong importing of test beatmaps Importing via `testBeatmapSet` causes the beatmapset hash to not be calculated due to no files existing in the importing process, which leads into not reusing existing test beatmaps due to no hash. --- osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs b/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs index d692e6f9a3..a6275f14e6 100644 --- a/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs +++ b/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs @@ -94,13 +94,13 @@ namespace osu.Game.Tests.Online public void TestTrackerRespectsSoftDeleting() { AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true)); - AddStep("import beatmap", () => beatmaps.Import(testBeatmapSet).Wait()); + AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).Wait()); addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable); - AddStep("delete beatmap", () => beatmaps.Delete(testBeatmapSet)); + AddStep("delete beatmap", () => beatmaps.Delete(beatmaps.QueryBeatmapSet(b => b.OnlineBeatmapSetID == testBeatmapSet.OnlineBeatmapSetID))); addAvailabilityCheckStep("state not downloaded", BeatmapAvailability.NotDownloaded); - AddStep("undelete beatmap", () => beatmaps.Undelete(testBeatmapSet)); + AddStep("undelete beatmap", () => beatmaps.Undelete(beatmaps.QueryBeatmapSet(b => b.OnlineBeatmapSetID == testBeatmapSet.OnlineBeatmapSetID))); addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable); } From dba01cf2b1ea7cf3445f14e30855ce9e95e6bd76 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 19 Jan 2021 18:43:16 +0300 Subject: [PATCH 0081/1791] Use beatmap "soleily" and remove no longer needed archive --- .../TestSceneMultiplayerBeatmapTracker.cs | 41 ++++++++++-------- .../Resources/Archives/test-beatmap.osz | Bin 7286 -> 0 bytes 2 files changed, 24 insertions(+), 17 deletions(-) delete mode 100644 osu.Game.Tests/Resources/Archives/test-beatmap.osz diff --git a/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs b/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs index a6275f14e6..9caecc198a 100644 --- a/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs +++ b/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -10,15 +11,17 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Formats; using osu.Game.Database; +using osu.Game.IO; using osu.Game.IO.Archives; using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Rulesets; -using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Resources; using osu.Game.Tests.Visual; @@ -49,9 +52,9 @@ namespace osu.Game.Tests.Online { beatmaps.AllowImport = new TaskCompletionSource(); - testBeatmapFile = getTestBeatmapOsz(); + testBeatmapFile = TestResources.GetTestBeatmapForImport(); - testBeatmapInfo = new TestBeatmap(Ruleset.Value).BeatmapInfo; + testBeatmapInfo = getTestBeatmapInfo(testBeatmapFile); testBeatmapSet = testBeatmapInfo.BeatmapSet; var existing = beatmaps.QueryBeatmapSet(s => s.OnlineBeatmapSetID == testBeatmapSet.OnlineBeatmapSetID); @@ -109,16 +112,10 @@ namespace osu.Game.Tests.Online { AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true)); - BeatmapInfo wrongBeatmap = null; - - AddStep("import wrong checksum beatmap", () => + AddStep("import altered beatmap", () => { - wrongBeatmap = new TestBeatmap(Ruleset.Value).BeatmapInfo; - wrongBeatmap.MD5Hash = "1337"; - - beatmaps.Import(wrongBeatmap.BeatmapSet).Wait(); + beatmaps.Import(TestResources.GetTestBeatmapForImport(true)).Wait(); }); - AddAssert("wrong beatmap available", () => beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == wrongBeatmap.OnlineBeatmapID) != null); addAvailabilityCheckStep("state still not downloaded", BeatmapAvailability.NotDownloaded); AddStep("recreate tracker", () => Child = tracker = new MultiplayerBeatmapTracker @@ -135,15 +132,25 @@ namespace osu.Game.Tests.Online AddAssert(description, () => tracker.Availability.Value.Equals(expected.Invoke())); } - private string getTestBeatmapOsz() + private static BeatmapInfo getTestBeatmapInfo(string archiveFile) { - var filename = Path.GetTempFileName() + ".osz"; + BeatmapInfo info; - using (var stream = TestResources.OpenResource("Archives/test-beatmap.osz")) - using (var file = File.Create(filename)) - stream.CopyTo(file); + using (var archive = new ZipArchiveReader(File.OpenRead(archiveFile))) + using (var stream = archive.GetStream("Soleily - Renatus (Gamu) [Insane].osu")) + using (var reader = new LineBufferedReader(stream)) + { + var decoder = Decoder.GetDecoder(reader); + var beatmap = decoder.Decode(reader); - return filename; + info = beatmap.BeatmapInfo; + info.BeatmapSet.Beatmaps = new List { info }; + info.BeatmapSet.Metadata = info.Metadata; + info.MD5Hash = stream.ComputeMD5Hash(); + info.Hash = stream.ComputeSHA2Hash(); + } + + return info; } private class TestBeatmapManager : BeatmapManager diff --git a/osu.Game.Tests/Resources/Archives/test-beatmap.osz b/osu.Game.Tests/Resources/Archives/test-beatmap.osz deleted file mode 100644 index f98fd2f8ffef0968270a1f2c7d75162de80417c9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7286 zcma)BRa6vEy9JRFrMo+%k?uwux?|{W36WAjVu%48V(9MfMi2(1bCB+C7(hBM|GH22 z;a}^X?_r<)?fvbCv(DS60YG_8h=hcMf%K5attHDLX3|4}gk)-mgv9pJ1$?ozpjCJA zq~)Yl^`%vD^R}U7)u*+mb)fa4wPB+*{%GN0ZNlZ|`6UME6i3;d^vEUsoGWD;yuxeN zrxX}n;Y@i%il;aKu13ZjT%i0_E=9A`R2 z49_mB7vFE56!#aae;sc(YxI9S_*dTbh`9M2(ANHZf2iN}e22L7@$b05DF(q@HD1)O z>iuTw{ee&0m-`W7Gle&If1BgXXtJavp9s3TxmQ-d-30``l;qKh)YB!HV*D%3_f`qX zj946+?=W@Oo= zJMQmqFOCCc67N5qAwvCePmcG8FP34T&HI;7HJoSZ`xxKHgHgoE?eYFBJh{IZhP`>a z3E$tmhd)zo<3xQ6^b>x%-rOuk_^#4qpGvMstUhLrj$ik~uDgz1X#ypnQqLFbm%%-1 zUjv^n!6Y3>NIi$s1zw*V z!+4xP79I{&y*nrk;JO z-d`Nj>F(O`m<$J*ZGmq82HyO;IeS2@e%zrjJYH^pc_st{^YGEPD4FB@cQOcd3DTIZ zf4JL!gjPq=YTAF@AKx@+x3B&Rwtd{hJh(=frAbY;uRbm=?{_ZSdw2PNz1{}2Pv-4z z!fPMTt~*+Xqn;Gd(E56ee))@UuSf2U^{4Y3Ew3CQ(8KHcg)=;0PiBo;+nu^MQY*77 z)7K`gQjGaVUDre5GpBMNM|VL5>Er5>B|DF}+*wkI_eMC$D4(@Mx+uG+VWRMWu%-}~ zE-RkW$Jp-uE3qH0ICAK4G9|4{cgAIEkY=d_cD}AZ<)6?iVX*SCJ882)tOmI=sW|zF zqlpXu%FH{MkCgAK*6Vg40zqW(c2TdpPy_Pra~3SerGnyN+B`gW9#+1VF43XZG|#~5 zk}mP1$)%KMzipo3Jx#g<{gZ${W8uQ)$CNAYxUIC!wcqKRXYc>)2#f2qiC|afyT-!g zh|)ICm>Xtci@Cq3SMIL-e1u+z*s3WG4rr3PpXY^GksiC>yr|S=i_}V=Gye^E!}su9 z>?F*ej@=m-4db#(`?6At3scA6ZG4)!%J%cl^5dRuv)ePe=Abg|vO2KepA&s0+dnrs zRqI{pzY`0M{gq#6b@n1tb5bR|I4|t?NhOR#^{Q3Jk6ujvE`H&0(AaG!_=j%|Ue0Fe zS%??^##1sN);Sz{sBq}MLo<$%H=*O~$d!pFy-ypmA#UgQ1sRt3) zR?L5AN%!pAsrXg39xMkhlhtHsdTw?h*N2(=HQ}m^6WdE3BVI4ohpoL(;5f0Y z5H!@419RB`zpa+h%Sw6gPy9p-Q!WMI{|Mez%13|VOSFsts`rr?S5BB7!?-5J>UD>R zkJ0;vR!53Ob1^El6seX%e0{FgQOg5_?GQF^2JQbUXh}G!l-b27Usv$%X9;LET=~Sm z;G)|&7?pX`*@mp%x5@l9c7?ilN&|QFc+Nfj)rqrxWzBm zaV`Jf=h0B*A2#Cu5?%*klW5)VOMG8v>v_luE5^9+CngmfIW;t&5hVUg}jv;&=dt8EnWs_9`-5H&7fV0f9Wt9GpIm4u9^J zrfNlAej9dYmzAfzLoSkoVcp&Z9N14jIaRNyM=pA5xDnDCcWTHG4<1W9V16^eDdF@w z=&;h^$T0tRjIMP&l$ynQRycg9A9#O-ctRW!I2o);e!oL#o??qp(SjBk;O~WEmKywfVEl>c z#6rF(Yk+zU4k44))TQsFTeqv3SN<5^K-h8PT(vmHP$Vr8!J+@N+Eivpe0z+^bnL`Pw0> z2F;d@R!L$=YTy~SyV^EraI^2~h`(ycRrz=#50Ons%yyMtk9B~w&PCU0&|vGd^Aq}&{wA+Kl>sS&|>JEoRJ_kC&rY)Ms$ZH$7? z**%RZS2eHN0Ti&T;!V~HSn?Yy4Kos-kp)@madT#s4xljQr5K+Jr>+87iAGbh_b!kc zeTu^vfoAU;ZqmXSYd)7HwIp(2WN_QMYGX&lUT>c}3346gC>huvW`KJJC>~q=exK9j z>$raOW8+mFuaQWb^x+`HMG5*BLFDqDUeVhA^~OR}5V9aS7{k8MIt!Jb^AKbY#d?oj zGuHy&a~qc_U|m+N7dw%&Y22br-t6g(2*hRaZ4Ju69E;!qN6+Vky9?%-I^f$tn)`-E zo*$F38G^SOn*{yN0s6@lS1@L^s{ z1bFLXbP~!+P!g}JT5b%xU84xj8FWf-$5QX14@)@mmMMh7MIAnjV+!P2{s#@}>^(Nu z(^TO=_ap{lS~c$;eW?NzGJwFQ?%syI$VO*D$slLtqPMYS+9;0d%pR;0bo)?KF_^RP+qDmha-?EwPh=MRxXnbUC}H7q zXtD_tq1S2{$5$$QN=x3l{&YBR$JGX+XOeQLvE7xZsz{Sz4Vp!0uRJda!HooHD8fCo z0x}a`g^4F^iLu<>I4&^_{O`U9FS4oyc8aowsbgeE1iBj!R|X-AHzPNKu~H^Oo8CAX zDqNzyqamfJ#=@q1UAQHoG;g-R#{64C!%Dd7+ln^FV93TCSu5`l88fwb9Aqf6l4lwxKrtvFP$vz6oHMM zHzbu8daGH)(2~YUY#Iiv2z$!;#)j*WFr@#*uD5)(V4Hl1pE2iB zt(IejY^`S| zJ+hzt>K`v+LwN~iuG(>sl1KGj^2r>$$90zz9c2o{?{MsY@Mjd zY9#!}Q*Im*zF`y!nXMj_Tf;q{O z{K~I%|6*3dcOt>;R^)|g_@JW;u(53E@T7qx1imsrTEXOx5j;g{R}n5B@+!Q4im_3E zT4`xSg>E$X&pSQ(7*AVlfnz+wDio1=(F9W1w^s5x;c|JGT2qyh$PLB>(swGFx=coY zFiBb!r)Xtjv<6He5b7kKpz@#L^}Y*9orn0>0qoU0pCW|Jc8QSCJ`#lz@n@vDo6~2! zb)lpEg-_s9N@r5E7SG|aMbPLEaMWMw31`L>Lx&tL^~m_U5DE&qq=UMR`A9*l>s>k-X|$~9QcPz+{FE2Raq#U7v4;C0K8cBuUxgd>Wu>C+HaHP6lAH`Uyu01tL5 zYi0$M!TW}POj+9TO(#~W>!cPXy@Lnrk<|lWA6$wtY6yB|E&XS6$m=)qh}n9xS61d% zL&0!{iE3$?Y91^vK*rGA+3JrCT(Fhua>T_qnI`s!0vqJ~ymZwnb_}+THyqJV-4O@9 zQ1+|^+pBP$rlhwanp&g!ayveM+*P?kOHk^H(83r>C2WOtV`uj$H~ZOtSi=xpD>A=A z`;$IW*~B@j1gvCgf+!j2?A$LH^mS88)iH52uZ7_M0sg4eux(!kau|ep={G@ zmFVEJ+WHQ=Ws#UUiKOjscjduvTmh3WSrXm2JMef*eXM+pF}tuMVY6eL}O4~m@zZt;7+8mD!;w!Dx96xn~D1xug&xoDmCAJrJ6)5oP zo!XTtCKJO3HHh38qs#>j@c;|DLt6r#)=L8CghkqSf44LfH<*zVR(4{AWyx$p#D0iO zB$TF!@*&omTXBW}mxbgbkeEvf`|=cqqO+AZ6>8}tX&QPuB)WJ?X+74z+M~1~e}pkiApwpZQzd9I5qV_?es=hPR7{ASDxLDa zDUz!Ky>f7YrL>W{&9MRj+W}e}KP%f|j*D$PFan)%X>2|uX7NEQX6GkhO!DVCR4{9;BCW@vjJ4CZqho_ zQbQFpbyE1lr@A9Jhag$ueyTumVt`B(g_i%+DD4QDzjm6!tkq#c~7FGyKDPs0Y(z2bUH%6r65k zKU}l{Wo$Q3O5cv*4=!bH-BOZq^d1EC3R9Hs>G)WVa?SHfrs?gVFxjT?eAz+apkox> zuTHK3Y*e;H6?Jp-(hb9^F_`Ch?F3stF=zSCeL0aETf7}(mwif2t;1L5);ZyGL!HC8{A@16vq16+H)leRi|-wiH!j#3JqCD|_(;i+=72m|m-9`qlbDh@m)cbu>B4G0&^zdY!jBzF!nX_6bZ^-Bo`C1|? zlAP+f#lmeQfR7L0EcM8b9q~3^yQ*kJHO(UBiFVv(`kJwo4Z)~|hys})&{=dsUr1NXWsJaw9D&KEb zH-0_|=4-2>ICy-fJJigAVEb^w2BSW_uAu4GUoQ1v?*s zq6H`}a`fSNq+eWvn!cQKT=9M9sgV}6C%OC(vL_A=BNtNw$(rGkd z()vlm@!efXjzyl>%GTYD0n2VuX@w_T_rTbiuZiCgzey$pLj;B%8ifXNA?1)#?&&Di zWFnhOj>|}VnyG6GeS!seROA~~?5>f}={3oLFhv+Yvd5K0A!!O9kT^@>F6jGJN4Ik9)Rey8;p_Lfu+sfE z2e4$5OPO1sl7r!bwcX+%X&BTn!z?won6_&2Hg0s<+qB58lEz3SXAujcKnve1n9BtT z+i6Z;##pSG7U`7aW)e8$EBx>0-GtSdZx6QIU6Guo`Ofwb?ePoSxz{<(6TXc|JY4=M z1_<)dId^!cVFR6I1T3JFZ3jXoqcQu4%@8#$N#8T%ah^cwF`tYBBfN`ax$_dqJ`kEU z$LH2j@g=-s1Rhnop2ye4BxE$EH-|=YZe5fw7G#dzUrf^J7MO?^0Gm~H8!cUw!I7_? ztU{=Bwee&*i-uNrLyYWL66&UJi@Io5Av!>|UlTuYd3BXUAvaVH%+fzAjRP!op#mJp z?^#e=*?DAYSlZJY)$OL=#`liqh|uPEX19n6Nc_Tq`hCR`JOe8uEm$p))r zOiZ%sOJYLyVW z8B*zN3yP>!Wq6v5RGCQ94qQU3PdPqHi-?UiwPL)#lTxi-pp7z-Oek~AaJ@hV>LFLD zR3Ft1`OEG4vBcks{~80Pn>xj-R0k|u-7N+E<@>d?^N_dy&Uaj}V{ET>72^)mwj*&= z&-hKo)^CGb>WOpfU}(R-7!&%5juJl49LWiVO^L3qE?+jYoY3&EeQ#}$>WU;trIOyI zfg@I4%xa3+9BT3bSioc+hg_={&C*c?Q%f?e`rJHhT(DbiqJH@)+2(M*Ga%01#DCn= zgott7Nn|ky^RL(S7b7hTk?G1Zf=8p>ee9D3FTlE1Ct6-l06C+B-jt_}^V09B)DzO- zS$VG_Ms8vAEbg45!@7|3jS9_4f^Y5vy0K2}(VUH(kE4X+odFN_wIV^Fl$$lj1jfu= zfcWPbATyV4?$7)40_kDS#$-Es{l(LWlb|3l?oR$ynlxO}805uGOtL8kFJ;W56EGyrTokh8*F&{bI2>klcg4X*6#E1#mI10X0+_3?otJyOxY zU-a3NU4rck(D|6ZlT5d-Wq+L9_9M+p2>$tLA*vx{AZm?Lk^lqoLEoi>1)b(qd!LTmV{}ISOMd=HL zNP^L=?uscIm7MtqAlMqPEv1~iuR<~dK-8M!FV`I!YSqJ_%&48u2(ox3qxR})tL4go zA!SKXZOJnJ=e=iS4FK{hLZtt`3VhiH{_hF?5B$FugBk!-wEqkszm)z9Vp9Hx{SV^2 BJ!=2} From 34612ae233fbf664c7db40567b09505be918d3f2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 19 Jan 2021 19:03:29 +0300 Subject: [PATCH 0082/1791] Forward internal management to a container alongside tracker --- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 17 ++++++++++++++--- .../Multiplayer/MultiplayerMatchSubScreen.cs | 4 ++-- .../Playlists/PlaylistsRoomSubScreen.cs | 4 ++-- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index c049d4be20..4b89a0c278 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -7,6 +7,8 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Game.Audio; using osu.Game.Beatmaps; @@ -43,14 +45,23 @@ namespace osu.Game.Screens.OnlinePlay.Match [Cached] protected readonly MultiplayerBeatmapTracker BeatmapTracker; + private readonly Container content = new Container { RelativeSizeAxes = Axes.Both }; + protected RoomSubScreen() { - InternalChild = BeatmapTracker = new MultiplayerBeatmapTracker + base.AddInternal(BeatmapTracker = new MultiplayerBeatmapTracker { - SelectedItem = { BindTarget = SelectedItem }, - }; + SelectedItem = { BindTarget = SelectedItem } + }); + + base.AddInternal(content); } + // This is a bit ugly but we don't have the concept of InternalContent so it'll have to do for now. (https://github.com/ppy/osu-framework/issues/1690) + protected override void AddInternal(Drawable drawable) => content.Add(drawable); + protected override bool RemoveInternal(Drawable drawable) => content.Remove(drawable); + protected override void ClearInternal(bool disposeChildren = true) => content.Clear(disposeChildren); + [BackgroundDependencyLoader] private void load(AudioManager audio) { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index a641935b9a..fa4b972f98 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -53,7 +53,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer [BackgroundDependencyLoader] private void load() { - AddRangeInternal(new Drawable[] + InternalChildren = new Drawable[] { new GridContainer { @@ -176,7 +176,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer RelativeSizeAxes = Axes.Both, State = { Value = client.Room == null ? Visibility.Visible : Visibility.Hidden } } - }); + }; } protected override void LoadComplete() diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index 7b3cdf16db..781c455eb4 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -42,7 +42,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists [BackgroundDependencyLoader] private void load() { - AddRangeInternal(new Drawable[] + InternalChildren = new Drawable[] { new GridContainer { @@ -188,7 +188,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists EditPlaylist = () => this.Push(new MatchSongSelect()), State = { Value = roomId.Value == null ? Visibility.Visible : Visibility.Hidden } } - }); + }; } [Resolved] From 58269f931491a2e2b6367a68b9c3a178415aa3c1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 20 Jan 2021 13:35:43 +0900 Subject: [PATCH 0083/1791] Update with framework changes --- 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 2c83023fdc..0157af002e 100644 --- a/osu.Game/Skinning/PoolableSkinnableSample.cs +++ b/osu.Game/Skinning/PoolableSkinnableSample.cs @@ -129,7 +129,7 @@ namespace osu.Game.Skinning /// public bool Playing => activeChannel?.Playing ?? false; - public bool Played => activeChannel?.Played ?? false; + public bool Played => !activeChannel?.Playing ?? false; private bool looping; From bdb9d4f7d0ac8c0b20adffeb85823e60ee0d5ceb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 20 Jan 2021 13:59:30 +0900 Subject: [PATCH 0084/1791] Restart sound on play --- osu.Game/Skinning/SkinnableSound.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index 06c694dc7a..b3db2d6558 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -124,7 +124,10 @@ namespace osu.Game.Skinning samplesContainer.ForEach(c => { if (PlayWhenZeroVolume || c.AggregateVolume.Value > 0) + { + c.Stop(); c.Play(); + } }); } From 8ffbcc9860e48e1940774540281511f32387f2e6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 20 Jan 2021 14:05:35 +0900 Subject: [PATCH 0085/1791] Fix test failures and general discrepancies --- .../Editing/TestSceneEditorSamplePlayback.cs | 14 +++++++------- .../Gameplay/TestSceneGameplaySamplePlayback.cs | 6 +++--- .../Visual/Gameplay/TestSceneSkinnableSound.cs | 12 ++++++++++-- osu.Game/Graphics/UserInterface/OsuSliderBar.cs | 1 - osu.Game/Skinning/PoolableSkinnableSample.cs | 10 ++++++++-- 5 files changed, 28 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSamplePlayback.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSamplePlayback.cs index 876c1308b4..2abc8a8dec 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSamplePlayback.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSamplePlayback.cs @@ -19,14 +19,14 @@ namespace osu.Game.Tests.Visual.Editing public void TestSlidingSampleStopsOnSeek() { DrawableSlider slider = null; - SkinnableSound[] loopingSamples = null; - SkinnableSound[] onceOffSamples = null; + PoolableSkinnableSample[] loopingSamples = null; + PoolableSkinnableSample[] onceOffSamples = null; AddStep("get first slider", () => { slider = Editor.ChildrenOfType().OrderBy(s => s.HitObject.StartTime).First(); - onceOffSamples = slider.ChildrenOfType().Where(s => !s.Looping).ToArray(); - loopingSamples = slider.ChildrenOfType().Where(s => s.Looping).ToArray(); + onceOffSamples = slider.ChildrenOfType().Where(s => !s.Looping).ToArray(); + loopingSamples = slider.ChildrenOfType().Where(s => s.Looping).ToArray(); }); AddStep("start playback", () => EditorClock.Start()); @@ -36,15 +36,15 @@ namespace osu.Game.Tests.Visual.Editing if (!slider.Tracking.Value) return false; - if (!loopingSamples.Any(s => s.IsPlaying)) + if (!loopingSamples.Any(s => s.Playing)) return false; EditorClock.Seek(20000); return true; }); - AddAssert("non-looping samples are playing", () => onceOffSamples.Length == 4 && loopingSamples.All(s => s.IsPlayed || s.IsPlaying)); - AddAssert("looping samples are not playing", () => loopingSamples.Length == 1 && loopingSamples.All(s => s.IsPlayed && !s.IsPlaying)); + AddAssert("non-looping samples are playing", () => onceOffSamples.Length == 4 && loopingSamples.All(s => s.Played || s.Playing)); + AddAssert("looping samples are not playing", () => loopingSamples.Length == 1 && loopingSamples.All(s => s.Played && !s.Playing)); } } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs index b13acdcb95..6b3fc304e0 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs @@ -19,14 +19,14 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestAllSamplesStopDuringSeek() { DrawableSlider slider = null; - SkinnableSound[] samples = null; + PoolableSkinnableSample[] samples = null; ISamplePlaybackDisabler sampleDisabler = null; AddUntilStep("get variables", () => { sampleDisabler = Player; slider = Player.ChildrenOfType().OrderBy(s => s.HitObject.StartTime).FirstOrDefault(); - samples = slider?.ChildrenOfType().ToArray(); + samples = slider?.ChildrenOfType().ToArray(); return slider != null; }); @@ -36,7 +36,7 @@ namespace osu.Game.Tests.Visual.Gameplay if (!slider.Tracking.Value) return false; - if (!samples.Any(s => s.IsPlaying)) + if (!samples.Any(s => s.Playing)) return false; Player.ChildrenOfType().First().Seek(40000); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs index 28c266f7d8..d688e9cb21 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs @@ -43,7 +43,11 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestStoppedSoundDoesntResumeAfterPause() { - AddStep("start sample with looping", () => skinnableSound.Looping = true); + AddStep("start sample with looping", () => + { + skinnableSound.Looping = true; + skinnableSound.Play(); + }); AddUntilStep("wait for sample to start playing", () => skinnableSound.IsPlaying); @@ -62,7 +66,11 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestLoopingSoundResumesAfterPause() { - AddStep("start sample with looping", () => skinnableSound.Looping = true); + AddStep("start sample with looping", () => + { + skinnableSound.Looping = true; + skinnableSound.Play(); + }); AddUntilStep("wait for sample to start playing", () => skinnableSound.IsPlaying); diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index bcf5220380..f58962f8e1 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -155,7 +155,6 @@ namespace osu.Game.Graphics.UserInterface return; lastSampleValue = value; - lastSampleTime = Clock.CurrentTime; var channel = sample.Play(); diff --git a/osu.Game/Skinning/PoolableSkinnableSample.cs b/osu.Game/Skinning/PoolableSkinnableSample.cs index 0157af002e..cff793e8d4 100644 --- a/osu.Game/Skinning/PoolableSkinnableSample.cs +++ b/osu.Game/Skinning/PoolableSkinnableSample.cs @@ -117,19 +117,25 @@ namespace osu.Game.Skinning activeChannel = Sample.Play(); activeChannel.Looping = Looping; + + Played = true; } /// /// Stops the sample. /// - public void Stop() => activeChannel?.Stop(); + public void Stop() + { + activeChannel?.Stop(); + activeChannel = null; + } /// /// Whether the sample is currently playing. /// public bool Playing => activeChannel?.Playing ?? false; - public bool Played => !activeChannel?.Playing ?? false; + public bool Played { get; private set; } private bool looping; From 5261c0184919c79973c08a7bc43466adf4f0d3ba Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 20 Jan 2021 19:43:42 +0900 Subject: [PATCH 0086/1791] Tie JoinRoom() and PartRoom() together --- .../Multiplayer/StatefulMultiplayerClient.cs | 154 +++++++++++------- 1 file changed, 92 insertions(+), 62 deletions(-) diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index f0e11b2b8b..18a63171c4 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -4,7 +4,6 @@ #nullable enable using System; -using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; @@ -109,30 +108,54 @@ namespace osu.Game.Online.Multiplayer }); } + private readonly object joinOrLeaveTaskLock = new object(); + private Task? joinOrLeaveTask; + /// /// Joins the for a given API . /// /// The API . public async Task JoinRoom(Room room) { - if (Room != null) - throw new InvalidOperationException("Cannot join a multiplayer room while already in one."); + Task? lastTask; + Task newTask; - Debug.Assert(room.RoomID.Value != null); + lock (joinOrLeaveTaskLock) + { + lastTask = joinOrLeaveTask; + joinOrLeaveTask = newTask = Task.Run(async () => + { + if (lastTask != null) + await lastTask; - apiRoom = room; - playlistItemId = room.Playlist.SingleOrDefault()?.ID ?? 0; + // Should be thread-safe since joinOrLeaveTask is locked on in both JoinRoom() and LeaveRoom(). + if (Room != null) + throw new InvalidOperationException("Cannot join a multiplayer room while already in one."); - Room = await JoinRoom(room.RoomID.Value.Value); + Debug.Assert(room.RoomID.Value != null); - Debug.Assert(Room != null); + // Join the server-side room. + var joinedRoom = await JoinRoom(room.RoomID.Value.Value); + Debug.Assert(joinedRoom != null); - var users = await getRoomUsers(); - Debug.Assert(users != null); + // Populate users. + Debug.Assert(joinedRoom.Users != null); + await Task.WhenAll(joinedRoom.Users.Select(PopulateUser)); - await Task.WhenAll(users.Select(PopulateUser)); + // Update the stored room (must be done on update thread for thread-safety). + await scheduleAsync(() => + { + Room = joinedRoom; + apiRoom = room; + playlistItemId = room.Playlist.SingleOrDefault()?.ID ?? 0; + }); - updateLocalRoomSettings(Room.Settings); + // Update room settings. + await updateLocalRoomSettings(joinedRoom.Settings); + }); + } + + await newTask; } /// @@ -142,21 +165,35 @@ namespace osu.Game.Online.Multiplayer /// The joined . protected abstract Task JoinRoom(long roomId); - public virtual Task LeaveRoom() + public virtual async Task LeaveRoom() { - Scheduler.Add(() => + Task? lastTask; + Task newTask; + + lock (joinOrLeaveTaskLock) { - if (Room == null) - return; + lastTask = joinOrLeaveTask; + joinOrLeaveTask = newTask = Task.Run(async () => + { + if (lastTask != null) + await lastTask; - apiRoom = null; - Room = null; - CurrentMatchPlayingUserIds.Clear(); + // Should be thread-safe since joinOrLeaveTask is locked on in both JoinRoom() and LeaveRoom(). + if (Room == null) + return; - RoomUpdated?.Invoke(); - }, false); + await scheduleAsync(() => + { + apiRoom = null; + Room = null; + CurrentMatchPlayingUserIds.Clear(); - return Task.CompletedTask; + RoomUpdated?.Invoke(); + }); + }); + } + + await newTask; } /// @@ -432,27 +469,6 @@ namespace osu.Game.Online.Multiplayer /// The to populate. protected async Task PopulateUser(MultiplayerRoomUser multiplayerUser) => multiplayerUser.User ??= await userLookupCache.GetUserAsync(multiplayerUser.UserID); - /// - /// Retrieve a copy of users currently in the joined in a thread-safe manner. - /// This should be used whenever accessing users from outside of an Update thread context (ie. when not calling ). - /// - /// A copy of users in the current room, or null if unavailable. - private Task?> getRoomUsers() - { - var tcs = new TaskCompletionSource?>(); - - // at some point we probably want to replace all these schedule calls with Room.LockForUpdate. - // for now, as this would require quite some consideration due to the number of accesses to the room instance, - // let's just add a manual schedule for the non-scheduled usages instead. - Scheduler.Add(() => - { - var users = Room?.Users.ToList(); - tcs.SetResult(users); - }, false); - - return tcs.Task; - } - /// /// Updates the local room settings with the given . /// @@ -460,34 +476,28 @@ namespace osu.Game.Online.Multiplayer /// This updates both the joined and the respective API . /// /// The new to update from. - private void updateLocalRoomSettings(MultiplayerRoomSettings settings) + private Task updateLocalRoomSettings(MultiplayerRoomSettings settings) => scheduleAsync(() => { if (Room == null) return; - Scheduler.Add(() => - { - if (Room == null) - return; + Debug.Assert(apiRoom != null); - Debug.Assert(apiRoom != null); + // Update a few properties of the room instantaneously. + Room.Settings = settings; + apiRoom.Name.Value = Room.Settings.Name; - // Update a few properties of the room instantaneously. - Room.Settings = settings; - apiRoom.Name.Value = Room.Settings.Name; + // The playlist update is delayed until an online beatmap lookup (below) succeeds. + // In-order for the client to not display an outdated beatmap, the playlist is forcefully cleared here. + apiRoom.Playlist.Clear(); - // The playlist update is delayed until an online beatmap lookup (below) succeeds. - // In-order for the client to not display an outdated beatmap, the playlist is forcefully cleared here. - apiRoom.Playlist.Clear(); + RoomUpdated?.Invoke(); - RoomUpdated?.Invoke(); + var req = new GetBeatmapSetRequest(settings.BeatmapID, BeatmapSetLookupType.BeatmapId); + req.Success += res => updatePlaylist(settings, res); - var req = new GetBeatmapSetRequest(settings.BeatmapID, BeatmapSetLookupType.BeatmapId); - req.Success += res => updatePlaylist(settings, res); - - api.Queue(req); - }, false); - } + api.Queue(req); + }); private void updatePlaylist(MultiplayerRoomSettings settings, APIBeatmapSet onlineSet) { @@ -534,5 +544,25 @@ namespace osu.Game.Online.Multiplayer else CurrentMatchPlayingUserIds.Remove(userId); } + + private Task scheduleAsync(Action action) + { + var tcs = new TaskCompletionSource(); + + Scheduler.Add(() => + { + try + { + action(); + tcs.SetResult(true); + } + catch (Exception ex) + { + tcs.SetException(ex); + } + }); + + return tcs.Task; + } } } From 5ff76be052a73b95e4aa2b562dfb88055939018b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 20 Jan 2021 19:43:51 +0900 Subject: [PATCH 0087/1791] Fix potential test failures due to timing --- .../Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs index 7a3845cbf3..6de5704410 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs @@ -101,7 +101,7 @@ namespace osu.Game.Tests.Visual.Multiplayer }); }); - AddAssert("multiplayer room joined", () => roomContainer.Client.Room != null); + AddUntilStep("multiplayer room joined", () => roomContainer.Client.Room != null); } [Test] @@ -133,7 +133,7 @@ namespace osu.Game.Tests.Visual.Multiplayer }); }); - AddAssert("multiplayer room joined", () => roomContainer.Client.Room != null); + AddUntilStep("multiplayer room joined", () => roomContainer.Client.Room != null); } private TestMultiplayerRoomManager createRoomManager() From e005a1cc9fc72413fe5a6c4592eb867cde860c14 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 20 Jan 2021 20:16:34 +0900 Subject: [PATCH 0088/1791] Remove unnecessary condition blocking the part --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 50dc8f661c..5d18521eac 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -143,9 +143,6 @@ namespace osu.Game.Online.Multiplayer return; } - if (Room == null) - return; - await base.LeaveRoom(); await connection.InvokeAsync(nameof(IMultiplayerServer.LeaveRoom)); } From 6b139d4cf312cb75970a1555dab546d65f8ba898 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 20 Jan 2021 20:21:07 +0900 Subject: [PATCH 0089/1791] Reset task post-execution --- .../Multiplayer/StatefulMultiplayerClient.cs | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index 18a63171c4..80162eae59 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -155,7 +155,19 @@ namespace osu.Game.Online.Multiplayer }); } - await newTask; + try + { + await newTask; + } + finally + { + // The task will be awaited in the future, so reset it so that the user doesn't get into a permanently faulted state if anything fails. + lock (joinOrLeaveTask) + { + if (joinOrLeaveTask == newTask) + joinOrLeaveTask = null; + } + } } /// @@ -193,7 +205,19 @@ namespace osu.Game.Online.Multiplayer }); } - await newTask; + try + { + await newTask; + } + finally + { + // The task will be awaited in the future, so reset it so that the user doesn't get into a permanently faulted state if anything fails. + lock (joinOrLeaveTask) + { + if (joinOrLeaveTask == newTask) + joinOrLeaveTask = null; + } + } } /// From ce3c2f07dc8f36d6cfea1af94943cc74093833d7 Mon Sep 17 00:00:00 2001 From: vmaggioli Date: Tue, 19 Jan 2021 20:13:21 -0500 Subject: [PATCH 0090/1791] Fix zero length spinners and sliders --- .../TestSceneSliderSelectionBlueprint.cs | 21 +++++++++++++++++++ .../Sliders/SliderSelectionBlueprint.cs | 5 ++++- .../Timeline/TimelineHitObjectBlueprint.cs | 2 +- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs index f6e1be693b..55b3707c38 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs @@ -161,6 +161,27 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor checkControlPointSelected(1, false); } + [Test] + public void TestZeroLengthSliderNotAllowed() + { + moveMouseToControlPoint(1); + AddStep("drag control point 1 to control point 0", () => + { + InputManager.PressButton(MouseButton.Left); + moveMouseToControlPoint(0); + InputManager.ReleaseButton(MouseButton.Left); + }); + moveMouseToControlPoint(2); + AddStep("drag control point 2 to control point 0", () => + { + InputManager.PressButton(MouseButton.Left); + moveMouseToControlPoint(0); + InputManager.ReleaseButton(MouseButton.Left); + }); + checkPositions(); + + } + private void moveHitObject() { AddStep("move hitobject", () => diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 3d3dff653a..99edcd2149 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -226,7 +226,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private void updatePath() { - HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance; + float expectedDistance = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance; + if (expectedDistance < 1) + return; + HitObject.Path.ExpectedDistance.Value = expectedDistance; editorBeatmap?.Update(HitObject); } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index ae2a82fa10..1dc37510ad 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -387,7 +387,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline case IHasDuration endTimeHitObject: var snappedTime = Math.Max(hitObject.StartTime, beatSnapProvider.SnapTime(time)); - if (endTimeHitObject.EndTime == snappedTime) + if (endTimeHitObject.EndTime == snappedTime || (snappedTime - hitObject.StartTime) < 1) return; endTimeHitObject.Duration = snappedTime - hitObject.StartTime; From d42773ebb2b731e93b03feab519fa951cbc60421 Mon Sep 17 00:00:00 2001 From: vmaggioli Date: Wed, 20 Jan 2021 12:36:31 -0500 Subject: [PATCH 0091/1791] Fix preceeding space --- .../Editor/TestSceneSliderSelectionBlueprint.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs index 55b3707c38..ce1c13dac5 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs @@ -179,7 +179,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor InputManager.ReleaseButton(MouseButton.Left); }); checkPositions(); - } private void moveHitObject() From 5ee3a5f230435a166c85fdb8d542f4dc34be1f91 Mon Sep 17 00:00:00 2001 From: vmaggioli Date: Wed, 20 Jan 2021 13:00:25 -0500 Subject: [PATCH 0092/1791] Use AlmostEquals --- .../Compose/Components/Timeline/TimelineHitObjectBlueprint.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 1dc37510ad..301543b3c1 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -387,7 +388,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline case IHasDuration endTimeHitObject: var snappedTime = Math.Max(hitObject.StartTime, beatSnapProvider.SnapTime(time)); - if (endTimeHitObject.EndTime == snappedTime || (snappedTime - hitObject.StartTime) < 1) + if (endTimeHitObject.EndTime == snappedTime || Precision.AlmostEquals(snappedTime, hitObject.StartTime, 1)) return; endTimeHitObject.Duration = snappedTime - hitObject.StartTime; From 76e1f6e57bbd27297a320b64cbb27dcbdad6c85a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 21 Jan 2021 12:45:44 +0900 Subject: [PATCH 0093/1791] Fix locking on incorrect object MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index 80162eae59..f2b5a44fcf 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -162,7 +162,7 @@ namespace osu.Game.Online.Multiplayer finally { // The task will be awaited in the future, so reset it so that the user doesn't get into a permanently faulted state if anything fails. - lock (joinOrLeaveTask) + lock (joinOrLeaveTaskLock) { if (joinOrLeaveTask == newTask) joinOrLeaveTask = null; @@ -212,7 +212,7 @@ namespace osu.Game.Online.Multiplayer finally { // The task will be awaited in the future, so reset it so that the user doesn't get into a permanently faulted state if anything fails. - lock (joinOrLeaveTask) + lock (joinOrLeaveTaskLock) { if (joinOrLeaveTask == newTask) joinOrLeaveTask = null; From 65ece1aa722b74e6590b4b3d208a10141238164d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 21 Jan 2021 07:50:41 +0300 Subject: [PATCH 0094/1791] Mark OverlayHeader as NotNull in FullscreenOverlay --- osu.Game/Overlays/FullscreenOverlay.cs | 8 ++++--- osu.Game/Overlays/OnlineOverlay.cs | 32 ++++++++++++-------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/osu.Game/Overlays/FullscreenOverlay.cs b/osu.Game/Overlays/FullscreenOverlay.cs index d65213f573..d0a0c994aa 100644 --- a/osu.Game/Overlays/FullscreenOverlay.cs +++ b/osu.Game/Overlays/FullscreenOverlay.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.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -16,9 +17,9 @@ namespace osu.Game.Overlays public abstract class FullscreenOverlay : WaveOverlayContainer, INamedOverlayComponent where T : OverlayHeader { - public virtual string IconTexture => Header?.Title.IconTexture ?? string.Empty; - public virtual string Title => Header?.Title.Title ?? string.Empty; - public virtual string Description => Header?.Title.Description ?? string.Empty; + public virtual string IconTexture => Header.Title.IconTexture ?? string.Empty; + public virtual string Title => Header.Title.Title ?? string.Empty; + public virtual string Description => Header.Title.Description ?? string.Empty; public T Header { get; } @@ -76,6 +77,7 @@ namespace osu.Game.Overlays Waves.FourthWaveColour = ColourProvider.Dark3; } + [NotNull] protected abstract T CreateHeader(); protected virtual Color4 GetBackgroundColour() => ColourProvider.Background5; diff --git a/osu.Game/Overlays/OnlineOverlay.cs b/osu.Game/Overlays/OnlineOverlay.cs index b44ccc32c9..4a7318d065 100644 --- a/osu.Game/Overlays/OnlineOverlay.cs +++ b/osu.Game/Overlays/OnlineOverlay.cs @@ -19,29 +19,27 @@ namespace osu.Game.Overlays protected OnlineOverlay(OverlayColourScheme colourScheme) : base(colourScheme) { - FillFlowContainer flow = new FillFlowContainer - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical - }; - - if (Header != null) - flow.Add(Header.With(h => h.Depth = -float.MaxValue)); - - flow.Add(content = new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y - }); - base.Content.AddRange(new Drawable[] { ScrollFlow = new OverlayScrollContainer { RelativeSizeAxes = Axes.Both, ScrollbarVisible = false, - Child = flow + Child = new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + Header.With(h => h.Depth = -float.MaxValue), + content = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + } + } + } }, Loading = new LoadingLayer(true) }); From 153149554bdb5c7ba44b57d6bc75acda7cfc1763 Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Thu, 21 Jan 2021 16:25:16 +0100 Subject: [PATCH 0095/1791] add more mime types --- osu.Android/OsuGameActivity.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Android/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs index 788e5f82be..48b059b482 100644 --- a/osu.Android/OsuGameActivity.cs +++ b/osu.Android/OsuGameActivity.cs @@ -20,7 +20,8 @@ namespace osu.Android [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false, LaunchMode = LaunchMode.SingleInstance)] [IntentFilter(new[] { Intent.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" })] + [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataMimeTypes = new[] { "application/x-osu-beatmap", "application/x-osu-skin" })] + [IntentFilter(new[] { Intent.ActionSend, Intent.ActionSendMultiple }, Categories = new[] { Intent.CategoryDefault }, DataMimeTypes = new[] { "application/x-osu-beatmap", "application/x-osu-skin", "application/zip", "application/octet-stream", "application/x-zip", "application/x-zip-compressed" })] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryBrowsable, Intent.CategoryDefault }, DataSchemes = new[] { "osu", "osump" })] public class OsuGameActivity : AndroidGameActivity { From e4b59c7317b7788f03b184ccc900aee717153658 Mon Sep 17 00:00:00 2001 From: vmaggioli Date: Thu, 21 Jan 2021 11:54:26 -0500 Subject: [PATCH 0096/1791] Test setup --- .../TestSceneTimelineHitObjectBlueprint.cs | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs new file mode 100644 index 0000000000..ed427c2020 --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Screens.Edit.Compose.Components.Timeline; +using osuTK; + +namespace osu.Game.Tests.Visual.Editing +{ + public class TestSceneTimelineHitObjectBlueprint : TimelineTestScene + { + private Spinner spinner; + private TimelineHitObjectBlueprint blueprint; + + public TestSceneTimelineHitObjectBlueprint() + { + var spinner = new Spinner + { + Position = new Vector2(256, 256), + StartTime = -1000, + EndTime = 2000 + }; + + spinner.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 }); + Add(new Container + { + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.5f), + Child = _ = new DrawableSpinner(spinner) + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + Clock.Seek(10000); + } + + public override Drawable CreateTestComponent() => blueprint = new TimelineHitObjectBlueprint(spinner); + + [Test] + public void TestDisallowZeroLengthSpinners() + { + + } + } +} From a5f866d95ce0a6e97c8620b22266b5bf3f470173 Mon Sep 17 00:00:00 2001 From: vmaggioli Date: Thu, 21 Jan 2021 15:14:24 -0500 Subject: [PATCH 0097/1791] Test updates --- .../TestSceneTimelineHitObjectBlueprint.cs | 37 ++++++++----------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs index ed427c2020..1fa37470cb 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs @@ -1,28 +1,27 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Screens.Edit.Compose.Components.Timeline; using osuTK; +using osuTK.Input; +using static osu.Game.Screens.Edit.Compose.Components.Timeline.TimelineHitObjectBlueprint; namespace osu.Game.Tests.Visual.Editing { public class TestSceneTimelineHitObjectBlueprint : TimelineTestScene { private Spinner spinner; - private TimelineHitObjectBlueprint blueprint; public TestSceneTimelineHitObjectBlueprint() { - var spinner = new Spinner + spinner = new Spinner { Position = new Vector2(256, 256), StartTime = -1000, @@ -30,26 +29,22 @@ namespace osu.Game.Tests.Visual.Editing }; spinner.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 }); - Add(new Container - { - RelativeSizeAxes = Axes.Both, - Size = new Vector2(0.5f), - Child = _ = new DrawableSpinner(spinner) - }); } - protected override void LoadComplete() - { - base.LoadComplete(); - Clock.Seek(10000); - } - - public override Drawable CreateTestComponent() => blueprint = new TimelineHitObjectBlueprint(spinner); + public override Drawable CreateTestComponent() => new TimelineHitObjectBlueprint(spinner); [Test] public void TestDisallowZeroLengthSpinners() { - + DragBar dragBar = this.ChildrenOfType().First(); + Circle circle = this.ChildrenOfType().First(); + InputManager.MoveMouseTo(dragBar.ScreenSpaceDrawQuad.TopRight); + AddStep("drag dragbar to hit object", () => + { + InputManager.PressButton(MouseButton.Left); + InputManager.MoveMouseTo(circle.ScreenSpaceDrawQuad.TopLeft); + InputManager.ReleaseButton(MouseButton.Left); + }); } } } From 7046f64e7f7eaec8d9aa65cdac54aca13d01bc06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 21 Jan 2021 22:07:08 +0100 Subject: [PATCH 0098/1791] Rewrite test scene --- .../TestSceneTimelineHitObjectBlueprint.cs | 54 ++++++++++--------- .../Visual/Editing/TimelineTestScene.cs | 10 ++-- 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs index 1fa37470cb..15fa1d995b 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs @@ -1,13 +1,9 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Framework.Testing; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Screens.Edit.Compose.Components.Timeline; using osuTK; using osuTK.Input; @@ -17,34 +13,40 @@ namespace osu.Game.Tests.Visual.Editing { public class TestSceneTimelineHitObjectBlueprint : TimelineTestScene { - private Spinner spinner; - - public TestSceneTimelineHitObjectBlueprint() - { - spinner = new Spinner - { - Position = new Vector2(256, 256), - StartTime = -1000, - EndTime = 2000 - }; - - spinner.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 }); - } - - public override Drawable CreateTestComponent() => new TimelineHitObjectBlueprint(spinner); + public override Drawable CreateTestComponent() => new TimelineBlueprintContainer(Composer); [Test] - public void TestDisallowZeroLengthSpinners() + public void TestDisallowZeroDurationObjects() { - DragBar dragBar = this.ChildrenOfType().First(); - Circle circle = this.ChildrenOfType().First(); - InputManager.MoveMouseTo(dragBar.ScreenSpaceDrawQuad.TopRight); - AddStep("drag dragbar to hit object", () => + DragBar dragBar; + + AddStep("add spinner", () => { + EditorBeatmap.Clear(); + EditorBeatmap.Add(new Spinner + { + Position = new Vector2(256, 256), + StartTime = 150, + Duration = 500 + }); + }); + + AddStep("hold down drag bar", () => + { + // distinguishes between the actual drag bar and its "underlay shadow". + dragBar = this.ChildrenOfType().Single(bar => bar.HandlePositionalInput); + InputManager.MoveMouseTo(dragBar); InputManager.PressButton(MouseButton.Left); - InputManager.MoveMouseTo(circle.ScreenSpaceDrawQuad.TopLeft); + }); + + AddStep("try to drag bar past start", () => + { + var blueprint = this.ChildrenOfType().Single(); + InputManager.MoveMouseTo(blueprint.SelectionQuad.TopLeft - new Vector2(100, 0)); InputManager.ReleaseButton(MouseButton.Left); }); + + AddAssert("object has non-zero duration", () => EditorBeatmap.HitObjects.OfType().Single().Duration > 0); } } } diff --git a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs index 63bb018d6e..d6db171cf0 100644 --- a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs +++ b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs @@ -23,22 +23,24 @@ namespace osu.Game.Tests.Visual.Editing protected HitObjectComposer Composer { get; private set; } + protected EditorBeatmap EditorBeatmap { get; private set; } + [BackgroundDependencyLoader] private void load(AudioManager audio) { Beatmap.Value = new WaveformTestBeatmap(audio); var playable = Beatmap.Value.GetPlayableBeatmap(Beatmap.Value.BeatmapInfo.Ruleset); - var editorBeatmap = new EditorBeatmap(playable); + EditorBeatmap = new EditorBeatmap(playable); - Dependencies.Cache(editorBeatmap); - Dependencies.CacheAs(editorBeatmap); + Dependencies.Cache(EditorBeatmap); + Dependencies.CacheAs(EditorBeatmap); Composer = playable.BeatmapInfo.Ruleset.CreateInstance().CreateHitObjectComposer().With(d => d.Alpha = 0); AddRange(new Drawable[] { - editorBeatmap, + EditorBeatmap, Composer, new FillFlowContainer { From a71f769cce26eb9a010e2dd67d2ee637f74cd932 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 21 Jan 2021 22:09:42 +0100 Subject: [PATCH 0099/1791] Add missing license header --- .../Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs index 15fa1d995b..35f394fe1d 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs @@ -1,4 +1,7 @@ -using System.Linq; +// 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.Graphics; using osu.Framework.Testing; From e4c5e5ba17ae1b7359d26b628fc92a9565f4bcfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 21 Jan 2021 22:11:51 +0100 Subject: [PATCH 0100/1791] Separate return statement with blank line --- .../Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 99edcd2149..508783a499 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -229,6 +229,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders float expectedDistance = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance; if (expectedDistance < 1) return; + HitObject.Path.ExpectedDistance.Value = expectedDistance; editorBeatmap?.Update(HitObject); } From d0fd2ae432700dc8a7d125b827e6931ab69940c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 21 Jan 2021 22:20:07 +0100 Subject: [PATCH 0101/1791] Fix added zero-length slider test not working properly --- .../TestSceneSliderSelectionBlueprint.cs | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs index ce1c13dac5..4edf778bfd 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs @@ -165,20 +165,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor public void TestZeroLengthSliderNotAllowed() { moveMouseToControlPoint(1); - AddStep("drag control point 1 to control point 0", () => - { - InputManager.PressButton(MouseButton.Left); - moveMouseToControlPoint(0); - InputManager.ReleaseButton(MouseButton.Left); - }); + dragMouseToControlPoint(0); + moveMouseToControlPoint(2); - AddStep("drag control point 2 to control point 0", () => - { - InputManager.PressButton(MouseButton.Left); - moveMouseToControlPoint(0); - InputManager.ReleaseButton(MouseButton.Left); - }); - checkPositions(); + dragMouseToControlPoint(0); + + AddAssert("slider has non-zero duration", () => slider.Duration > 0); } private void moveHitObject() @@ -209,6 +201,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor }); } + private void dragMouseToControlPoint(int index) + { + AddStep("hold down mouse button", () => InputManager.PressButton(MouseButton.Left)); + moveMouseToControlPoint(index); + AddStep("release mouse button", () => InputManager.ReleaseButton(MouseButton.Left)); + } + private void checkControlPointSelected(int index, bool selected) => AddAssert($"control point {index} {(selected ? "selected" : "not selected")}", () => blueprint.ControlPointVisualiser.Pieces[index].IsSelected.Value == selected); From b220939650672b6e2056464f32082a31b8f0c531 Mon Sep 17 00:00:00 2001 From: Mysfit <8022806+Mysfit@users.noreply.github.com> Date: Thu, 21 Jan 2021 17:10:11 -0500 Subject: [PATCH 0102/1791] Fix storyboard samples continuing to play when the beatmap is paused or the intro is skipped. --- osu.Game/Screens/Play/Player.cs | 13 ++++++++++++- osu.Game/Skinning/PausableSkinnableSound.cs | 8 ++++---- .../Drawables/DrawableStoryboardSample.cs | 10 ++++++++++ 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 1fcbed7ef7..b622f11775 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -353,7 +353,7 @@ namespace osu.Game.Screens.Play }, skipOverlay = new SkipOverlay(DrawableRuleset.GameplayStartTime) { - RequestSkip = GameplayClockContainer.Skip + RequestSkip = performUserRequestedSkip }, FailOverlay = new FailOverlay { @@ -488,6 +488,17 @@ namespace osu.Game.Screens.Play this.Exit(); } + private void performUserRequestedSkip() + { + // user requested skip + // disable sample playback to stop currently playing samples and perform skip + samplePlaybackDisabled.Value = true; + GameplayClockContainer.Skip(); + + // return samplePlaybackDisabled.Value to what is defined by the beatmap's current state + updateSampleDisabledState(); + } + private void performUserRequestedExit() { if (ValidForResume && HasFailed && !FailOverlay.IsPresent) diff --git a/osu.Game/Skinning/PausableSkinnableSound.cs b/osu.Game/Skinning/PausableSkinnableSound.cs index cb5234c847..e0891fbda2 100644 --- a/osu.Game/Skinning/PausableSkinnableSound.cs +++ b/osu.Game/Skinning/PausableSkinnableSound.cs @@ -32,7 +32,7 @@ namespace osu.Game.Skinning { } - private readonly IBindable samplePlaybackDisabled = new Bindable(); + protected readonly IBindable SamplePlaybackDisabled = new Bindable(); private ScheduledDelegate scheduledStart; @@ -42,8 +42,8 @@ namespace osu.Game.Skinning // if in a gameplay context, pause sample playback when gameplay is paused. if (samplePlaybackDisabler != null) { - samplePlaybackDisabled.BindTo(samplePlaybackDisabler.SamplePlaybackDisabled); - samplePlaybackDisabled.BindValueChanged(disabled => + SamplePlaybackDisabled.BindTo(samplePlaybackDisabler.SamplePlaybackDisabled); + SamplePlaybackDisabled.BindValueChanged(disabled => { if (!RequestedPlaying) return; @@ -72,7 +72,7 @@ namespace osu.Game.Skinning cancelPendingStart(); RequestedPlaying = true; - if (samplePlaybackDisabled.Value) + if (SamplePlaybackDisabled.Value) return; base.Play(restart); diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs index 218f051bf0..5b49e71daa 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs @@ -40,6 +40,16 @@ namespace osu.Game.Storyboards.Drawables foreach (var sample in DrawableSamples) mod.ApplyToSample(sample); } + + SamplePlaybackDisabled.BindValueChanged(disabled => + { + if (!RequestedPlaying) return; + + // Since storyboard samples can be very long we want to stop the playback regardless of + // whether or not the sample is looping or not + if (disabled.NewValue) + Stop(); + }); } protected override void Update() From 07ec0c0e0bbe7047442033bde8f23ea7c8533e4b Mon Sep 17 00:00:00 2001 From: Mysfit <8022806+Mysfit@users.noreply.github.com> Date: Thu, 21 Jan 2021 17:46:47 -0500 Subject: [PATCH 0103/1791] Updated DrawableStoryboardSample to use GetBoundCopy() --- osu.Game/Skinning/PausableSkinnableSound.cs | 6 ++-- .../Drawables/DrawableStoryboardSample.cs | 28 ++++++++++++------- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/osu.Game/Skinning/PausableSkinnableSound.cs b/osu.Game/Skinning/PausableSkinnableSound.cs index e0891fbda2..361360035d 100644 --- a/osu.Game/Skinning/PausableSkinnableSound.cs +++ b/osu.Game/Skinning/PausableSkinnableSound.cs @@ -18,6 +18,10 @@ namespace osu.Game.Skinning protected bool RequestedPlaying { get; private set; } + protected IBindable SamplePlaybackDisabled => samplePlaybackDisabled; + + private readonly Bindable samplePlaybackDisabled = new Bindable(); + public PausableSkinnableSound() { } @@ -32,8 +36,6 @@ namespace osu.Game.Skinning { } - protected readonly IBindable SamplePlaybackDisabled = new Bindable(); - private ScheduledDelegate scheduledStart; [BackgroundDependencyLoader(true)] diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs index 5b49e71daa..b924b0551f 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs @@ -21,11 +21,29 @@ namespace osu.Game.Storyboards.Drawables public override bool RemoveWhenNotAlive => false; + private readonly IBindable samplePlaybackDisabled; + public DrawableStoryboardSample(StoryboardSampleInfo sampleInfo) : base(sampleInfo) { this.sampleInfo = sampleInfo; LifetimeStart = sampleInfo.StartTime; + + samplePlaybackDisabled = SamplePlaybackDisabled.GetBoundCopy(); + } + + [BackgroundDependencyLoader(true)] + private void load() + { + samplePlaybackDisabled.BindValueChanged(disabled => + { + if (!RequestedPlaying) return; + + // Since storyboard samples can be very long we want to stop the playback regardless of + // whether or not the sample is looping or not + if (disabled.NewValue) + Stop(); + }); } [Resolved] @@ -40,16 +58,6 @@ namespace osu.Game.Storyboards.Drawables foreach (var sample in DrawableSamples) mod.ApplyToSample(sample); } - - SamplePlaybackDisabled.BindValueChanged(disabled => - { - if (!RequestedPlaying) return; - - // Since storyboard samples can be very long we want to stop the playback regardless of - // whether or not the sample is looping or not - if (disabled.NewValue) - Stop(); - }); } protected override void Update() From b53ad50cd48461b564fafe30c3c6885b3a6b11dd Mon Sep 17 00:00:00 2001 From: Mysfit <8022806+Mysfit@users.noreply.github.com> Date: Thu, 21 Jan 2021 18:00:37 -0500 Subject: [PATCH 0104/1791] Remove redundant variable --- osu.Game/Skinning/PausableSkinnableSound.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Skinning/PausableSkinnableSound.cs b/osu.Game/Skinning/PausableSkinnableSound.cs index 361360035d..0f97307372 100644 --- a/osu.Game/Skinning/PausableSkinnableSound.cs +++ b/osu.Game/Skinning/PausableSkinnableSound.cs @@ -18,9 +18,7 @@ namespace osu.Game.Skinning protected bool RequestedPlaying { get; private set; } - protected IBindable SamplePlaybackDisabled => samplePlaybackDisabled; - - private readonly Bindable samplePlaybackDisabled = new Bindable(); + protected readonly IBindable SamplePlaybackDisabled = new Bindable(); public PausableSkinnableSound() { From 5b1bdfbdc502a0500bb0cfdeb3b3075c8d530324 Mon Sep 17 00:00:00 2001 From: Mysfit <8022806+Mysfit@users.noreply.github.com> Date: Thu, 21 Jan 2021 20:06:24 -0500 Subject: [PATCH 0105/1791] Use callback method override --- osu.Game/Skinning/PausableSkinnableSound.cs | 59 ++++++++++--------- .../Drawables/DrawableStoryboardSample.cs | 31 ++++------ 2 files changed, 45 insertions(+), 45 deletions(-) diff --git a/osu.Game/Skinning/PausableSkinnableSound.cs b/osu.Game/Skinning/PausableSkinnableSound.cs index 0f97307372..b48cf5d448 100644 --- a/osu.Game/Skinning/PausableSkinnableSound.cs +++ b/osu.Game/Skinning/PausableSkinnableSound.cs @@ -18,7 +18,7 @@ namespace osu.Game.Skinning protected bool RequestedPlaying { get; private set; } - protected readonly IBindable SamplePlaybackDisabled = new Bindable(); + private readonly IBindable samplePlaybackDisabled = new Bindable(); public PausableSkinnableSound() { @@ -42,37 +42,32 @@ namespace osu.Game.Skinning // if in a gameplay context, pause sample playback when gameplay is paused. if (samplePlaybackDisabler != null) { - SamplePlaybackDisabled.BindTo(samplePlaybackDisabler.SamplePlaybackDisabled); - SamplePlaybackDisabled.BindValueChanged(disabled => - { - if (!RequestedPlaying) return; - - // let non-looping samples that have already been started play out to completion (sounds better than abruptly cutting off). - if (!Looping) return; - - cancelPendingStart(); - - if (disabled.NewValue) - base.Stop(); - else - { - // schedule so we don't start playing a sample which is no longer alive. - scheduledStart = Schedule(() => - { - if (RequestedPlaying) - base.Play(); - }); - } - }); + samplePlaybackDisabled.BindTo(samplePlaybackDisabler.SamplePlaybackDisabled); + samplePlaybackDisabled.BindValueChanged(SamplePlaybackDisabledChanged); } } + protected virtual void SamplePlaybackDisabledChanged(ValueChangedEvent disabled) + { + if (!RequestedPlaying) return; + + // let non-looping samples that have already been started play out to completion (sounds better than abruptly cutting off). + if (!Looping) return; + + CancelPendingStart(); + + if (disabled.NewValue) + base.Stop(); + else + ScheduleStart(); + } + public override void Play(bool restart = true) { - cancelPendingStart(); + CancelPendingStart(); RequestedPlaying = true; - if (SamplePlaybackDisabled.Value) + if (samplePlaybackDisabled.Value) return; base.Play(restart); @@ -80,15 +75,25 @@ namespace osu.Game.Skinning public override void Stop() { - cancelPendingStart(); + CancelPendingStart(); RequestedPlaying = false; base.Stop(); } - private void cancelPendingStart() + protected void CancelPendingStart() { scheduledStart?.Cancel(); scheduledStart = null; } + + protected void ScheduleStart() + { + // schedule so we don't start playing a sample which is no longer alive. + scheduledStart = Schedule(() => + { + if (RequestedPlaying) + base.Play(); + }); + } } } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs index b924b0551f..d5e1e19666 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs @@ -21,34 +21,29 @@ namespace osu.Game.Storyboards.Drawables public override bool RemoveWhenNotAlive => false; - private readonly IBindable samplePlaybackDisabled; - public DrawableStoryboardSample(StoryboardSampleInfo sampleInfo) : base(sampleInfo) { this.sampleInfo = sampleInfo; LifetimeStart = sampleInfo.StartTime; - - samplePlaybackDisabled = SamplePlaybackDisabled.GetBoundCopy(); - } - - [BackgroundDependencyLoader(true)] - private void load() - { - samplePlaybackDisabled.BindValueChanged(disabled => - { - if (!RequestedPlaying) return; - - // Since storyboard samples can be very long we want to stop the playback regardless of - // whether or not the sample is looping or not - if (disabled.NewValue) - Stop(); - }); } [Resolved] private IBindable> mods { get; set; } + protected override void SamplePlaybackDisabledChanged(ValueChangedEvent disabled) + { + if (!RequestedPlaying) return; + + if (disabled.NewValue) + Stop(); + else + { + CancelPendingStart(); + ScheduleStart(); + } + } + protected override void SkinChanged(ISkinSource skin, bool allowFallback) { base.SkinChanged(skin, allowFallback); From 9f89b4e6d79822f7e7eb382767818add29bda6db Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jan 2021 14:25:21 +0900 Subject: [PATCH 0106/1791] Rewrite connection logic to better handle failure cases The main goal here is to ensure the connection is built each connection attempt. Previously, the access token would never be updated, leading to outdated tokens failing repeatedly (in the connection retry loop) and never being able to establish a new connection as a result. Due to threading considerations, this isn't as simple as I would hope it to be. I'm open to proposals as to a better way of handling this. Also, keep in mind that this logic will need to be abstracted and (re)used in `SpectatorClient` as well. I've intentionally not done that yet until we agree that this is a good direction forward. --- .../Online/Multiplayer/MultiplayerClient.cs | 141 ++++++++++++------ 1 file changed, 93 insertions(+), 48 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 50dc8f661c..34616a45a5 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -4,7 +4,6 @@ #nullable enable using System; -using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR.Client; @@ -30,6 +29,8 @@ namespace osu.Game.Online.Multiplayer private HubConnection? connection; + private CancellationTokenSource connectCancelSource = new CancellationTokenSource(); + private readonly string endpoint; public MultiplayerClient(EndpointConfiguration endpoints) @@ -50,8 +51,7 @@ namespace osu.Game.Online.Multiplayer { case APIState.Failing: case APIState.Offline: - connection?.StopAsync(); - connection = null; + Task.Run(Disconnect); break; case APIState.Online: @@ -60,70 +60,57 @@ namespace osu.Game.Online.Multiplayer } } - protected virtual async Task Connect() + private readonly SemaphoreSlim connectionLock = new SemaphoreSlim(1); + + public Task Disconnect() => disconnect(true); + + protected async Task Connect() { - if (connection != null) - return; + cancelExistingConnect(); - connection = new HubConnectionBuilder() - .WithUrl(endpoint, options => - { - options.Headers.Add("Authorization", $"Bearer {api.AccessToken}"); - }) - .AddNewtonsoftJsonProtocol(options => { options.PayloadSerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; }) - .Build(); + await connectionLock.WaitAsync(); - // this is kind of SILLY - // https://github.com/dotnet/aspnetcore/issues/15198 - connection.On(nameof(IMultiplayerClient.RoomStateChanged), ((IMultiplayerClient)this).RoomStateChanged); - connection.On(nameof(IMultiplayerClient.UserJoined), ((IMultiplayerClient)this).UserJoined); - connection.On(nameof(IMultiplayerClient.UserLeft), ((IMultiplayerClient)this).UserLeft); - connection.On(nameof(IMultiplayerClient.HostChanged), ((IMultiplayerClient)this).HostChanged); - connection.On(nameof(IMultiplayerClient.SettingsChanged), ((IMultiplayerClient)this).SettingsChanged); - connection.On(nameof(IMultiplayerClient.UserStateChanged), ((IMultiplayerClient)this).UserStateChanged); - connection.On(nameof(IMultiplayerClient.LoadRequested), ((IMultiplayerClient)this).LoadRequested); - connection.On(nameof(IMultiplayerClient.MatchStarted), ((IMultiplayerClient)this).MatchStarted); - connection.On(nameof(IMultiplayerClient.ResultsReady), ((IMultiplayerClient)this).ResultsReady); - - connection.Closed += async ex => + try { - isConnected.Value = false; + await disconnect(false); - Logger.Log(ex != null - ? $"Multiplayer client lost connection: {ex}" - : "Multiplayer client disconnected", LoggingTarget.Network); + // 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. + var cancellationToken = connectCancelSource.Token; - if (connection != null) - await tryUntilConnected(); - }; - - await tryUntilConnected(); - - async Task tryUntilConnected() - { - Logger.Log("Multiplayer client connecting...", LoggingTarget.Network); - - while (api.State.Value == APIState.Online) + while (api.State.Value == APIState.Online && !cancellationToken.IsCancellationRequested) { + Logger.Log("Multiplayer client connecting...", LoggingTarget.Network); + try { - Debug.Assert(connection != null); + // importantly, rebuild the connection each attempt to get an updated access token. + connection = createConnection(cancellationToken); + + await connection.StartAsync(cancellationToken); - // reconnect on any failure - await connection.StartAsync(); Logger.Log("Multiplayer client connected!", LoggingTarget.Network); - - // Success. isConnected.Value = true; - break; + return; + } + catch (OperationCanceledException) + { + //connection process was cancelled. + return; } catch (Exception e) { Logger.Log($"Multiplayer client connection error: {e}", LoggingTarget.Network); - await Task.Delay(5000); + + // retry on any failure. + await Task.Delay(5000, cancellationToken); } } } + finally + { + connectionLock.Release(); + } } protected override Task JoinRoom(long roomId) @@ -189,5 +176,63 @@ namespace osu.Game.Online.Multiplayer return connection.InvokeAsync(nameof(IMultiplayerServer.StartMatch)); } + + private async Task disconnect(bool takeLock) + { + cancelExistingConnect(); + + if (takeLock) + await connectionLock.WaitAsync(); + + try + { + if (connection != null) + await connection.StopAsync(); + } + finally + { + connection = null; + if (takeLock) + connectionLock.Release(); + } + } + + private void cancelExistingConnect() + { + connectCancelSource.Cancel(); + connectCancelSource = new CancellationTokenSource(); + } + + private HubConnection createConnection(CancellationToken cancellationToken) + { + var newConnection = new HubConnectionBuilder() + .WithUrl(endpoint, options => { options.Headers.Add("Authorization", $"Bearer {api.AccessToken}"); }) + .AddNewtonsoftJsonProtocol(options => { options.PayloadSerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; }) + .Build(); + + // this is kind of SILLY + // https://github.com/dotnet/aspnetcore/issues/15198 + newConnection.On(nameof(IMultiplayerClient.RoomStateChanged), ((IMultiplayerClient)this).RoomStateChanged); + newConnection.On(nameof(IMultiplayerClient.UserJoined), ((IMultiplayerClient)this).UserJoined); + newConnection.On(nameof(IMultiplayerClient.UserLeft), ((IMultiplayerClient)this).UserLeft); + newConnection.On(nameof(IMultiplayerClient.HostChanged), ((IMultiplayerClient)this).HostChanged); + newConnection.On(nameof(IMultiplayerClient.SettingsChanged), ((IMultiplayerClient)this).SettingsChanged); + newConnection.On(nameof(IMultiplayerClient.UserStateChanged), ((IMultiplayerClient)this).UserStateChanged); + newConnection.On(nameof(IMultiplayerClient.LoadRequested), ((IMultiplayerClient)this).LoadRequested); + newConnection.On(nameof(IMultiplayerClient.MatchStarted), ((IMultiplayerClient)this).MatchStarted); + newConnection.On(nameof(IMultiplayerClient.ResultsReady), ((IMultiplayerClient)this).ResultsReady); + + newConnection.Closed += async ex => + { + isConnected.Value = false; + + Logger.Log(ex != null ? $"Multiplayer client lost connection: {ex}" : "Multiplayer client disconnected", LoggingTarget.Network); + + // make sure a disconnect wasn't triggered (and this is still the active connection). + if (!cancellationToken.IsCancellationRequested) + await Connect(); + }; + return newConnection; + } } } From d24d23646875b0bca4755c9d5b8a0caa24f30cae Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jan 2021 14:34:58 +0900 Subject: [PATCH 0107/1791] Make OperationCanceledException throwing behaviour consistent --- .../Online/Multiplayer/MultiplayerClient.cs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 34616a45a5..aa2305c991 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -12,6 +12,7 @@ using Newtonsoft.Json; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Logging; +using osu.Game.Extensions; using osu.Game.Online.API; using osu.Game.Online.Rooms; @@ -51,11 +52,11 @@ namespace osu.Game.Online.Multiplayer { case APIState.Failing: case APIState.Offline: - Task.Run(Disconnect); + Task.Run(Disconnect).CatchUnobservedExceptions(); break; case APIState.Online: - Task.Run(Connect); + Task.Run(Connect).CatchUnobservedExceptions(); break; } } @@ -78,8 +79,10 @@ namespace osu.Game.Online.Multiplayer // if cancelled, we can be sure that a disconnect or reconnect is handled elsewhere. var cancellationToken = connectCancelSource.Token; - while (api.State.Value == APIState.Online && !cancellationToken.IsCancellationRequested) + while (api.State.Value == APIState.Online) { + cancellationToken.ThrowIfCancellationRequested(); + Logger.Log("Multiplayer client connecting...", LoggingTarget.Network); try @@ -96,7 +99,7 @@ namespace osu.Game.Online.Multiplayer catch (OperationCanceledException) { //connection process was cancelled. - return; + throw; } catch (Exception e) { @@ -222,7 +225,7 @@ namespace osu.Game.Online.Multiplayer newConnection.On(nameof(IMultiplayerClient.MatchStarted), ((IMultiplayerClient)this).MatchStarted); newConnection.On(nameof(IMultiplayerClient.ResultsReady), ((IMultiplayerClient)this).ResultsReady); - newConnection.Closed += async ex => + newConnection.Closed += ex => { isConnected.Value = false; @@ -230,7 +233,9 @@ namespace osu.Game.Online.Multiplayer // make sure a disconnect wasn't triggered (and this is still the active connection). if (!cancellationToken.IsCancellationRequested) - await Connect(); + Task.Run(Connect, default).CatchUnobservedExceptions(); + + return Task.CompletedTask; }; return newConnection; } From a9c8f9bd4aebe9929007a89bdb5a938aa1541077 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jan 2021 17:47:38 +0900 Subject: [PATCH 0108/1791] Fix a potential crash when exiting the editor before a new beatmap is added to the database --- osu.Game/Screens/Edit/Editor.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index b7ebf0c0a4..66fe1a9507 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Threading; using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -109,7 +110,16 @@ namespace osu.Game.Screens.Edit if (Beatmap.Value is DummyWorkingBeatmap) { isNewBeatmap = true; - Beatmap.Value = beatmapManager.CreateNew(Ruleset.Value, api.LocalUser.Value); + + var newBeatmap = beatmapManager.CreateNew(Ruleset.Value, api.LocalUser.Value); + + // 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; From b44bd8c4eea20cb3338c3f85e639a7472b6f5262 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jan 2021 18:03:33 +0900 Subject: [PATCH 0109/1791] Remove unused using statement --- osu.Game/Screens/Edit/Editor.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 66fe1a9507..a49de3b887 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Threading; using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; From e0f8f6a23f64b584e5592a39e81e21e5e555195a Mon Sep 17 00:00:00 2001 From: Mysfit Date: Fri, 22 Jan 2021 12:09:40 -0500 Subject: [PATCH 0110/1791] introduce overrideable bool instead of copying event logic entirely --- osu.Game/Skinning/PausableSkinnableSound.cs | 58 +++++++++---------- .../Drawables/DrawableStoryboardSample.cs | 15 +---- 2 files changed, 30 insertions(+), 43 deletions(-) diff --git a/osu.Game/Skinning/PausableSkinnableSound.cs b/osu.Game/Skinning/PausableSkinnableSound.cs index b48cf5d448..6245bcae02 100644 --- a/osu.Game/Skinning/PausableSkinnableSound.cs +++ b/osu.Game/Skinning/PausableSkinnableSound.cs @@ -18,7 +18,7 @@ namespace osu.Game.Skinning protected bool RequestedPlaying { get; private set; } - private readonly IBindable samplePlaybackDisabled = new Bindable(); + protected virtual bool AllowNonLoopingCutOff => false; public PausableSkinnableSound() { @@ -34,6 +34,8 @@ namespace osu.Game.Skinning { } + private readonly IBindable samplePlaybackDisabled = new Bindable(); + private ScheduledDelegate scheduledStart; [BackgroundDependencyLoader(true)] @@ -43,28 +45,34 @@ namespace osu.Game.Skinning if (samplePlaybackDisabler != null) { samplePlaybackDisabled.BindTo(samplePlaybackDisabler.SamplePlaybackDisabled); - samplePlaybackDisabled.BindValueChanged(SamplePlaybackDisabledChanged); + samplePlaybackDisabled.BindValueChanged(disabled => + { + if (!RequestedPlaying) return; + + // if the sample is non-looping, and non-looping cut off is not allowed, + // let the sample play out to completion (sounds better than abruptly cutting off). + if (!Looping && !AllowNonLoopingCutOff) return; + + cancelPendingStart(); + + if (disabled.NewValue) + base.Stop(); + else + { + // schedule so we don't start playing a sample which is no longer alive. + scheduledStart = Schedule(() => + { + if (RequestedPlaying) + base.Play(); + }); + } + }); } } - protected virtual void SamplePlaybackDisabledChanged(ValueChangedEvent disabled) - { - if (!RequestedPlaying) return; - - // let non-looping samples that have already been started play out to completion (sounds better than abruptly cutting off). - if (!Looping) return; - - CancelPendingStart(); - - if (disabled.NewValue) - base.Stop(); - else - ScheduleStart(); - } - public override void Play(bool restart = true) { - CancelPendingStart(); + cancelPendingStart(); RequestedPlaying = true; if (samplePlaybackDisabled.Value) @@ -75,25 +83,15 @@ namespace osu.Game.Skinning public override void Stop() { - CancelPendingStart(); + cancelPendingStart(); RequestedPlaying = false; base.Stop(); } - protected void CancelPendingStart() + private void cancelPendingStart() { scheduledStart?.Cancel(); scheduledStart = null; } - - protected void ScheduleStart() - { - // schedule so we don't start playing a sample which is no longer alive. - scheduledStart = Schedule(() => - { - if (RequestedPlaying) - base.Play(); - }); - } } } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs index d5e1e19666..ebdf64e7ba 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs @@ -21,6 +21,8 @@ namespace osu.Game.Storyboards.Drawables public override bool RemoveWhenNotAlive => false; + protected override bool AllowNonLoopingCutOff => true; + public DrawableStoryboardSample(StoryboardSampleInfo sampleInfo) : base(sampleInfo) { @@ -31,19 +33,6 @@ namespace osu.Game.Storyboards.Drawables [Resolved] private IBindable> mods { get; set; } - protected override void SamplePlaybackDisabledChanged(ValueChangedEvent disabled) - { - if (!RequestedPlaying) return; - - if (disabled.NewValue) - Stop(); - else - { - CancelPendingStart(); - ScheduleStart(); - } - } - protected override void SkinChanged(ISkinSource skin, bool allowFallback) { base.SkinChanged(skin, allowFallback); From 6379381f957c9194050f9ae5f47d15edba39f802 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 22 Jan 2021 20:46:20 +0300 Subject: [PATCH 0111/1791] Make VotePill background transparent for own comments --- .../Visual/Online/TestSceneVotePill.cs | 17 +++++++++++++++-- osu.Game/Overlays/Comments/VotePill.cs | 14 ++++++++++---- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs b/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs index 9bb29541ec..420f6b1ab6 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs @@ -7,6 +7,7 @@ using osu.Game.Overlays.Comments; using osu.Game.Online.API.Requests.Responses; using osu.Framework.Allocation; using osu.Game.Overlays; +using osu.Framework.Graphics.Shapes; namespace osu.Game.Tests.Visual.Online { @@ -16,13 +17,14 @@ namespace osu.Game.Tests.Visual.Online [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); - private VotePill votePill; + private TestPill votePill; [Test] public void TestUserCommentPill() { AddStep("Log in", logIn); AddStep("User comment", () => addVotePill(getUserComment())); + AddAssert("Background is transparent", () => votePill.Background.Alpha == 0); AddStep("Click", () => votePill.Click()); AddAssert("Not loading", () => !votePill.IsLoading); } @@ -32,6 +34,7 @@ namespace osu.Game.Tests.Visual.Online { AddStep("Log in", logIn); AddStep("Random comment", () => addVotePill(getRandomComment())); + AddAssert("Background is not transparent", () => votePill.Background.Alpha == 1); AddStep("Click", () => votePill.Click()); AddAssert("Loading", () => votePill.IsLoading); } @@ -64,11 +67,21 @@ namespace osu.Game.Tests.Visual.Online private void addVotePill(Comment comment) { Clear(); - Add(votePill = new VotePill(comment) + Add(votePill = new TestPill(comment) { Anchor = Anchor.Centre, Origin = Anchor.Centre, }); } + + private class TestPill : VotePill + { + public new Box Background => base.Background; + + public TestPill(Comment comment) + : base(comment) + { + } + } } } diff --git a/osu.Game/Overlays/Comments/VotePill.cs b/osu.Game/Overlays/Comments/VotePill.cs index aa9723ea85..b6e6aa82c7 100644 --- a/osu.Game/Overlays/Comments/VotePill.cs +++ b/osu.Game/Overlays/Comments/VotePill.cs @@ -36,8 +36,10 @@ namespace osu.Game.Overlays.Comments [Resolved] private OverlayColourProvider colourProvider { get; set; } + protected Box Background { get; private set; } + private readonly Comment comment; - private Box background; + private Box hoverLayer; private CircularContainer borderContainer; private SpriteText sideNumber; @@ -62,8 +64,12 @@ namespace osu.Game.Overlays.Comments AccentColour = borderContainer.BorderColour = sideNumber.Colour = colours.GreenLight; hoverLayer.Colour = Color4.Black.Opacity(0.5f); - if (api.IsLoggedIn && api.LocalUser.Value.Id != comment.UserId) + var ownComment = api.LocalUser.Value.Id == comment.UserId; + + if (api.IsLoggedIn && !ownComment) Action = onAction; + + Background.Alpha = ownComment ? 0 : 1; } protected override void LoadComplete() @@ -71,7 +77,7 @@ namespace osu.Game.Overlays.Comments base.LoadComplete(); isVoted.Value = comment.IsVoted; votesCount.Value = comment.VotesCount; - isVoted.BindValueChanged(voted => background.Colour = voted.NewValue ? AccentColour : colourProvider.Background6, true); + isVoted.BindValueChanged(voted => Background.Colour = voted.NewValue ? AccentColour : colourProvider.Background6, true); votesCount.BindValueChanged(count => votesCounter.Text = $"+{count.NewValue}", true); } @@ -102,7 +108,7 @@ namespace osu.Game.Overlays.Comments Masking = true, Children = new Drawable[] { - background = new Box + Background = new Box { RelativeSizeAxes = Axes.Both }, From 20161aea6a472320f34ff0e8057d08a0cdea527d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 22 Jan 2021 21:47:53 +0300 Subject: [PATCH 0112/1791] Show LoginOverlay if not logged-in when clicking on a pill --- .../Visual/Online/TestSceneVotePill.cs | 32 ++++++++++++++++--- osu.Game/Overlays/Comments/VotePill.cs | 11 ++++++- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs b/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs index 420f6b1ab6..e9e826e62f 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs @@ -8,6 +8,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Framework.Allocation; using osu.Game.Overlays; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Containers; namespace osu.Game.Tests.Visual.Online { @@ -17,11 +18,30 @@ namespace osu.Game.Tests.Visual.Online [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); + [Cached] + private LoginOverlay login; + private TestPill votePill; + private readonly Container pillContainer; + + public TestSceneVotePill() + { + AddRange(new Drawable[] + { + pillContainer = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both + }, + login = new LoginOverlay() + }); + } [Test] public void TestUserCommentPill() { + AddStep("Hide login overlay", () => login.Hide()); AddStep("Log in", logIn); AddStep("User comment", () => addVotePill(getUserComment())); AddAssert("Background is transparent", () => votePill.Background.Alpha == 0); @@ -32,9 +52,10 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestRandomCommentPill() { + AddStep("Hide login overlay", () => login.Hide()); AddStep("Log in", logIn); AddStep("Random comment", () => addVotePill(getRandomComment())); - AddAssert("Background is not transparent", () => votePill.Background.Alpha == 1); + AddAssert("Background is visible", () => votePill.Background.Alpha == 1); AddStep("Click", () => votePill.Click()); AddAssert("Loading", () => votePill.IsLoading); } @@ -42,10 +63,11 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestOfflineRandomCommentPill() { + AddStep("Hide login overlay", () => login.Hide()); AddStep("Log out", API.Logout); AddStep("Random comment", () => addVotePill(getRandomComment())); AddStep("Click", () => votePill.Click()); - AddAssert("Not loading", () => !votePill.IsLoading); + AddAssert("Login overlay is visible", () => login.State.Value == Visibility.Visible); } private void logIn() => API.Login("localUser", "password"); @@ -66,12 +88,12 @@ namespace osu.Game.Tests.Visual.Online private void addVotePill(Comment comment) { - Clear(); - Add(votePill = new TestPill(comment) + pillContainer.Clear(); + pillContainer.Child = votePill = new TestPill(comment) { Anchor = Anchor.Centre, Origin = Anchor.Centre, - }); + }; } private class TestPill : VotePill diff --git a/osu.Game/Overlays/Comments/VotePill.cs b/osu.Game/Overlays/Comments/VotePill.cs index b6e6aa82c7..cf3c470f96 100644 --- a/osu.Game/Overlays/Comments/VotePill.cs +++ b/osu.Game/Overlays/Comments/VotePill.cs @@ -33,6 +33,9 @@ namespace osu.Game.Overlays.Comments [Resolved] private IAPIProvider api { get; set; } + [Resolved(canBeNull: true)] + private LoginOverlay login { get; set; } + [Resolved] private OverlayColourProvider colourProvider { get; set; } @@ -66,7 +69,7 @@ namespace osu.Game.Overlays.Comments var ownComment = api.LocalUser.Value.Id == comment.UserId; - if (api.IsLoggedIn && !ownComment) + if (!ownComment) Action = onAction; Background.Alpha = ownComment ? 0 : 1; @@ -83,6 +86,12 @@ namespace osu.Game.Overlays.Comments private void onAction() { + if (!api.IsLoggedIn) + { + login?.Show(); + return; + } + request = new CommentVoteRequest(comment.Id, isVoted.Value ? CommentVoteAction.UnVote : CommentVoteAction.Vote); request.Success += onSuccess; api.Queue(request); From 3d42cc1f9199715990aa25c234a15885a87ec09a Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 22 Jan 2021 22:27:26 +0300 Subject: [PATCH 0113/1791] Minor refactoring --- .../Visual/Online/TestSceneVotePill.cs | 19 ++----- osu.Game/Overlays/Comments/VotePill.cs | 53 ++++++++----------- 2 files changed, 26 insertions(+), 46 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs b/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs index e9e826e62f..6334c014c8 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs @@ -7,7 +7,6 @@ using osu.Game.Overlays.Comments; using osu.Game.Online.API.Requests.Responses; using osu.Framework.Allocation; using osu.Game.Overlays; -using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Containers; namespace osu.Game.Tests.Visual.Online @@ -21,7 +20,7 @@ namespace osu.Game.Tests.Visual.Online [Cached] private LoginOverlay login; - private TestPill votePill; + private VotePill votePill; private readonly Container pillContainer; public TestSceneVotePill() @@ -44,7 +43,7 @@ namespace osu.Game.Tests.Visual.Online AddStep("Hide login overlay", () => login.Hide()); AddStep("Log in", logIn); AddStep("User comment", () => addVotePill(getUserComment())); - AddAssert("Background is transparent", () => votePill.Background.Alpha == 0); + AddAssert("Is disabled", () => !votePill.Enabled.Value); AddStep("Click", () => votePill.Click()); AddAssert("Not loading", () => !votePill.IsLoading); } @@ -55,7 +54,7 @@ namespace osu.Game.Tests.Visual.Online AddStep("Hide login overlay", () => login.Hide()); AddStep("Log in", logIn); AddStep("Random comment", () => addVotePill(getRandomComment())); - AddAssert("Background is visible", () => votePill.Background.Alpha == 1); + AddAssert("Is enabled", () => votePill.Enabled.Value); AddStep("Click", () => votePill.Click()); AddAssert("Loading", () => votePill.IsLoading); } @@ -89,21 +88,11 @@ namespace osu.Game.Tests.Visual.Online private void addVotePill(Comment comment) { pillContainer.Clear(); - pillContainer.Child = votePill = new TestPill(comment) + pillContainer.Child = votePill = new VotePill(comment) { Anchor = Anchor.Centre, Origin = Anchor.Centre, }; } - - private class TestPill : VotePill - { - public new Box Background => base.Background; - - public TestPill(Comment comment) - : base(comment) - { - } - } } } diff --git a/osu.Game/Overlays/Comments/VotePill.cs b/osu.Game/Overlays/Comments/VotePill.cs index cf3c470f96..04a0508f3d 100644 --- a/osu.Game/Overlays/Comments/VotePill.cs +++ b/osu.Game/Overlays/Comments/VotePill.cs @@ -39,10 +39,9 @@ namespace osu.Game.Overlays.Comments [Resolved] private OverlayColourProvider colourProvider { get; set; } - protected Box Background { get; private set; } - + private bool isOwnComment; private readonly Comment comment; - + private Box background; private Box hoverLayer; private CircularContainer borderContainer; private SpriteText sideNumber; @@ -64,15 +63,14 @@ namespace osu.Game.Overlays.Comments [BackgroundDependencyLoader] private void load(OsuColour colours) { + isOwnComment = api.LocalUser.Value.Id == comment.UserId; + Action = onAction; + AccentColour = borderContainer.BorderColour = sideNumber.Colour = colours.GreenLight; hoverLayer.Colour = Color4.Black.Opacity(0.5f); + background.Alpha = isOwnComment ? 0 : 1; - var ownComment = api.LocalUser.Value.Id == comment.UserId; - - if (!ownComment) - Action = onAction; - - Background.Alpha = ownComment ? 0 : 1; + Enabled.Value = !isOwnComment; } protected override void LoadComplete() @@ -80,7 +78,7 @@ namespace osu.Game.Overlays.Comments base.LoadComplete(); isVoted.Value = comment.IsVoted; votesCount.Value = comment.VotesCount; - isVoted.BindValueChanged(voted => Background.Colour = voted.NewValue ? AccentColour : colourProvider.Background6, true); + isVoted.BindValueChanged(voted => background.Colour = voted.NewValue ? AccentColour : colourProvider.Background6, true); votesCount.BindValueChanged(count => votesCounter.Text = $"+{count.NewValue}", true); } @@ -117,7 +115,7 @@ namespace osu.Game.Overlays.Comments Masking = true, Children = new Drawable[] { - Background = new Box + background = new Box { RelativeSizeAxes = Axes.Both }, @@ -151,55 +149,48 @@ namespace osu.Game.Overlays.Comments protected override void OnLoadStarted() { votesCounter.FadeOut(duration, Easing.OutQuint); - updateDisplay(); + updateDisplay(false); } protected override void OnLoadFinished() { votesCounter.FadeIn(duration, Easing.OutQuint); - - if (IsHovered) - onHoverAction(); + updateDisplay(IsHovered); } protected override bool OnHover(HoverEvent e) { - onHoverAction(); + if (!isOwnComment && !IsLoading) + updateDisplay(true); + return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { - updateDisplay(); + if (!isOwnComment && !IsLoading) + updateDisplay(false); + base.OnHoverLost(e); } - private void updateDisplay() + private void updateDisplay(bool isHovered) { - if (Action == null) - return; - if (isVoted.Value) { - hoverLayer.FadeTo(IsHovered ? 1 : 0); + hoverLayer.FadeTo(isHovered ? 1 : 0); sideNumber.Hide(); } else - sideNumber.FadeTo(IsHovered ? 1 : 0); + sideNumber.FadeTo(isHovered ? 1 : 0); - borderContainer.BorderThickness = IsHovered ? 3 : 0; - } - - private void onHoverAction() - { - if (!IsLoading) - updateDisplay(); + borderContainer.BorderThickness = isHovered ? 3 : 0; } protected override void Dispose(bool isDisposing) { - base.Dispose(isDisposing); request?.Cancel(); + base.Dispose(isDisposing); } } } From b692abd3c2574cacd5d41033e979f431069187e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 22 Jan 2021 20:17:21 +0100 Subject: [PATCH 0114/1791] Simplify condition from two to one operand --- osu.Game/Skinning/PausableSkinnableSound.cs | 13 ++++++++----- .../Drawables/DrawableStoryboardSample.cs | 6 +++++- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/osu.Game/Skinning/PausableSkinnableSound.cs b/osu.Game/Skinning/PausableSkinnableSound.cs index 6245bcae02..d8149e76c0 100644 --- a/osu.Game/Skinning/PausableSkinnableSound.cs +++ b/osu.Game/Skinning/PausableSkinnableSound.cs @@ -18,7 +18,13 @@ namespace osu.Game.Skinning protected bool RequestedPlaying { get; private set; } - protected virtual bool AllowNonLoopingCutOff => false; + /// + /// Whether this is affected by + /// a higher-level 's state changes. + /// By default only looping samples are started/stopped on sample disable + /// to prevent one-time samples from cutting off abruptly. + /// + protected virtual bool AffectedBySamplePlaybackDisable => Looping; public PausableSkinnableSound() { @@ -48,10 +54,7 @@ namespace osu.Game.Skinning samplePlaybackDisabled.BindValueChanged(disabled => { if (!RequestedPlaying) return; - - // if the sample is non-looping, and non-looping cut off is not allowed, - // let the sample play out to completion (sounds better than abruptly cutting off). - if (!Looping && !AllowNonLoopingCutOff) return; + if (!AffectedBySamplePlaybackDisable) return; cancelPendingStart(); diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs index ebdf64e7ba..5a800a71fd 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs @@ -21,7 +21,11 @@ namespace osu.Game.Storyboards.Drawables public override bool RemoveWhenNotAlive => false; - protected override bool AllowNonLoopingCutOff => true; + /// + /// Contrary to , all s are affected + /// by sample disables, as they are oftentimes longer-running sound effects. This also matches stable behaviour. + /// + protected override bool AffectedBySamplePlaybackDisable => true; public DrawableStoryboardSample(StoryboardSampleInfo sampleInfo) : base(sampleInfo) From e9d10bb6e7bf9d9e48d25263cdc7c8f696b9a27a Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 22 Jan 2021 22:49:49 +0300 Subject: [PATCH 0115/1791] Revert "Minor refactoring" This reverts commit 3d42cc1f9199715990aa25c234a15885a87ec09a. --- .../Visual/Online/TestSceneVotePill.cs | 19 +++++-- osu.Game/Overlays/Comments/VotePill.cs | 53 +++++++++++-------- 2 files changed, 46 insertions(+), 26 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs b/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs index 6334c014c8..e9e826e62f 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneVotePill.cs @@ -7,6 +7,7 @@ using osu.Game.Overlays.Comments; using osu.Game.Online.API.Requests.Responses; using osu.Framework.Allocation; using osu.Game.Overlays; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Containers; namespace osu.Game.Tests.Visual.Online @@ -20,7 +21,7 @@ namespace osu.Game.Tests.Visual.Online [Cached] private LoginOverlay login; - private VotePill votePill; + private TestPill votePill; private readonly Container pillContainer; public TestSceneVotePill() @@ -43,7 +44,7 @@ namespace osu.Game.Tests.Visual.Online AddStep("Hide login overlay", () => login.Hide()); AddStep("Log in", logIn); AddStep("User comment", () => addVotePill(getUserComment())); - AddAssert("Is disabled", () => !votePill.Enabled.Value); + AddAssert("Background is transparent", () => votePill.Background.Alpha == 0); AddStep("Click", () => votePill.Click()); AddAssert("Not loading", () => !votePill.IsLoading); } @@ -54,7 +55,7 @@ namespace osu.Game.Tests.Visual.Online AddStep("Hide login overlay", () => login.Hide()); AddStep("Log in", logIn); AddStep("Random comment", () => addVotePill(getRandomComment())); - AddAssert("Is enabled", () => votePill.Enabled.Value); + AddAssert("Background is visible", () => votePill.Background.Alpha == 1); AddStep("Click", () => votePill.Click()); AddAssert("Loading", () => votePill.IsLoading); } @@ -88,11 +89,21 @@ namespace osu.Game.Tests.Visual.Online private void addVotePill(Comment comment) { pillContainer.Clear(); - pillContainer.Child = votePill = new VotePill(comment) + pillContainer.Child = votePill = new TestPill(comment) { Anchor = Anchor.Centre, Origin = Anchor.Centre, }; } + + private class TestPill : VotePill + { + public new Box Background => base.Background; + + public TestPill(Comment comment) + : base(comment) + { + } + } } } diff --git a/osu.Game/Overlays/Comments/VotePill.cs b/osu.Game/Overlays/Comments/VotePill.cs index 04a0508f3d..cf3c470f96 100644 --- a/osu.Game/Overlays/Comments/VotePill.cs +++ b/osu.Game/Overlays/Comments/VotePill.cs @@ -39,9 +39,10 @@ namespace osu.Game.Overlays.Comments [Resolved] private OverlayColourProvider colourProvider { get; set; } - private bool isOwnComment; + protected Box Background { get; private set; } + private readonly Comment comment; - private Box background; + private Box hoverLayer; private CircularContainer borderContainer; private SpriteText sideNumber; @@ -63,14 +64,15 @@ namespace osu.Game.Overlays.Comments [BackgroundDependencyLoader] private void load(OsuColour colours) { - isOwnComment = api.LocalUser.Value.Id == comment.UserId; - Action = onAction; - AccentColour = borderContainer.BorderColour = sideNumber.Colour = colours.GreenLight; hoverLayer.Colour = Color4.Black.Opacity(0.5f); - background.Alpha = isOwnComment ? 0 : 1; - Enabled.Value = !isOwnComment; + var ownComment = api.LocalUser.Value.Id == comment.UserId; + + if (!ownComment) + Action = onAction; + + Background.Alpha = ownComment ? 0 : 1; } protected override void LoadComplete() @@ -78,7 +80,7 @@ namespace osu.Game.Overlays.Comments base.LoadComplete(); isVoted.Value = comment.IsVoted; votesCount.Value = comment.VotesCount; - isVoted.BindValueChanged(voted => background.Colour = voted.NewValue ? AccentColour : colourProvider.Background6, true); + isVoted.BindValueChanged(voted => Background.Colour = voted.NewValue ? AccentColour : colourProvider.Background6, true); votesCount.BindValueChanged(count => votesCounter.Text = $"+{count.NewValue}", true); } @@ -115,7 +117,7 @@ namespace osu.Game.Overlays.Comments Masking = true, Children = new Drawable[] { - background = new Box + Background = new Box { RelativeSizeAxes = Axes.Both }, @@ -149,48 +151,55 @@ namespace osu.Game.Overlays.Comments protected override void OnLoadStarted() { votesCounter.FadeOut(duration, Easing.OutQuint); - updateDisplay(false); + updateDisplay(); } protected override void OnLoadFinished() { votesCounter.FadeIn(duration, Easing.OutQuint); - updateDisplay(IsHovered); + + if (IsHovered) + onHoverAction(); } protected override bool OnHover(HoverEvent e) { - if (!isOwnComment && !IsLoading) - updateDisplay(true); - + onHoverAction(); return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { - if (!isOwnComment && !IsLoading) - updateDisplay(false); - + updateDisplay(); base.OnHoverLost(e); } - private void updateDisplay(bool isHovered) + private void updateDisplay() { + if (Action == null) + return; + if (isVoted.Value) { - hoverLayer.FadeTo(isHovered ? 1 : 0); + hoverLayer.FadeTo(IsHovered ? 1 : 0); sideNumber.Hide(); } else - sideNumber.FadeTo(isHovered ? 1 : 0); + sideNumber.FadeTo(IsHovered ? 1 : 0); - borderContainer.BorderThickness = isHovered ? 3 : 0; + borderContainer.BorderThickness = IsHovered ? 3 : 0; + } + + private void onHoverAction() + { + if (!IsLoading) + updateDisplay(); } protected override void Dispose(bool isDisposing) { - request?.Cancel(); base.Dispose(isDisposing); + request?.Cancel(); } } } From adcef19ab25953f003bc150654798f82e739a7d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 23 Jan 2021 15:44:30 +0100 Subject: [PATCH 0116/1791] Add coverage for operation tracker with failing tests --- .../NonVisual/OngoingOperationTrackerTest.cs | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 osu.Game.Tests/NonVisual/OngoingOperationTrackerTest.cs diff --git a/osu.Game.Tests/NonVisual/OngoingOperationTrackerTest.cs b/osu.Game.Tests/NonVisual/OngoingOperationTrackerTest.cs new file mode 100644 index 0000000000..b2be83d1f9 --- /dev/null +++ b/osu.Game.Tests/NonVisual/OngoingOperationTrackerTest.cs @@ -0,0 +1,62 @@ +// 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.Bindables; +using osu.Framework.Testing; +using osu.Game.Screens.OnlinePlay; +using osu.Game.Tests.Visual; + +namespace osu.Game.Tests.NonVisual +{ + [HeadlessTest] + public class OngoingOperationTrackerTest : OsuTestScene + { + private OngoingOperationTracker tracker; + private IBindable operationInProgress; + + [SetUpSteps] + public void SetUp() + { + AddStep("create tracker", () => Child = tracker = new OngoingOperationTracker()); + AddStep("bind to operation status", () => operationInProgress = tracker.InProgress.GetBoundCopy()); + } + + [Test] + public void TestOperationTracking() + { + IDisposable firstOperation = null; + IDisposable secondOperation = null; + + AddStep("begin first operation", () => firstOperation = tracker.BeginOperation()); + AddAssert("operation in progress", () => operationInProgress.Value); + + AddStep("cannot start another operation", + () => Assert.Throws(() => tracker.BeginOperation())); + + AddStep("end first operation", () => firstOperation.Dispose()); + AddAssert("operation is ended", () => !operationInProgress.Value); + + AddStep("start second operation", () => secondOperation = tracker.BeginOperation()); + AddAssert("operation in progress", () => operationInProgress.Value); + + AddStep("dispose first operation again", () => firstOperation.Dispose()); + AddAssert("operation in progress", () => operationInProgress.Value); + + AddStep("dispose second operation", () => secondOperation.Dispose()); + AddAssert("operation is ended", () => !operationInProgress.Value); + } + + [Test] + public void TestOperationDisposalAfterTracker() + { + IDisposable operation = null; + + AddStep("begin operation", () => operation = tracker.BeginOperation()); + AddStep("dispose tracker", () => tracker.Expire()); + AddStep("end operation", () => operation.Dispose()); + AddAssert("operation is ended", () => !operationInProgress.Value); + } + } +} From 18b309a195e9aea1890a726dc1c77ed1f2e7afdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 23 Jan 2021 16:02:51 +0100 Subject: [PATCH 0117/1791] Make disposal of tracker operation idempotent --- osu.Game/Screens/OnlinePlay/OngoingOperationTracker.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/OngoingOperationTracker.cs b/osu.Game/Screens/OnlinePlay/OngoingOperationTracker.cs index 5c9e9ce90b..b834d4fa25 100644 --- a/osu.Game/Screens/OnlinePlay/OngoingOperationTracker.cs +++ b/osu.Game/Screens/OnlinePlay/OngoingOperationTracker.cs @@ -49,10 +49,7 @@ namespace osu.Game.Screens.OnlinePlay private void endOperation() { - if (leasedInProgress == null) - throw new InvalidOperationException("Cannot end operation multiple times."); - - leasedInProgress.Return(); + leasedInProgress?.Return(); leasedInProgress = null; } } From 7f89d9117d98f593209bc90271dd099a2febbcfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 23 Jan 2021 16:04:12 +0100 Subject: [PATCH 0118/1791] Make disposal of tracker idempotent for operations --- osu.Game/Screens/OnlinePlay/OngoingOperationTracker.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/OngoingOperationTracker.cs b/osu.Game/Screens/OnlinePlay/OngoingOperationTracker.cs index b834d4fa25..5d171e6e43 100644 --- a/osu.Game/Screens/OnlinePlay/OngoingOperationTracker.cs +++ b/osu.Game/Screens/OnlinePlay/OngoingOperationTracker.cs @@ -52,5 +52,13 @@ namespace osu.Game.Screens.OnlinePlay leasedInProgress?.Return(); leasedInProgress = null; } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + // base call does an UnbindAllBindables(). + // clean up the leased reference here so that it doesn't get returned twice. + leasedInProgress = null; + } } } From d22f557a3bd074ff4ee29128f7d8a3375dece37d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 23 Jan 2021 16:14:58 +0100 Subject: [PATCH 0119/1791] Remove possibility of double-disposal interference --- .../OnlinePlay/OngoingOperationTracker.cs | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/OngoingOperationTracker.cs b/osu.Game/Screens/OnlinePlay/OngoingOperationTracker.cs index 5d171e6e43..060f1d7b91 100644 --- a/osu.Game/Screens/OnlinePlay/OngoingOperationTracker.cs +++ b/osu.Game/Screens/OnlinePlay/OngoingOperationTracker.cs @@ -44,7 +44,7 @@ namespace osu.Game.Screens.OnlinePlay leasedInProgress.Value = true; // for extra safety, marshal the end of operation back to the update thread if necessary. - return new InvokeOnDisposal(() => Scheduler.Add(endOperation, false)); + return new OngoingOperation(() => Scheduler.Add(endOperation, false)); } private void endOperation() @@ -60,5 +60,26 @@ namespace osu.Game.Screens.OnlinePlay // clean up the leased reference here so that it doesn't get returned twice. leasedInProgress = null; } + + private class OngoingOperation : InvokeOnDisposal + { + private bool isDisposed; + + public OngoingOperation(Action action) + : base(action) + { + } + + public override void Dispose() + { + // base class does not check disposal state for performance reasons which aren't relevant here. + // track locally, to avoid interfering with other operations in case of a potential double-disposal. + if (isDisposed) + return; + + base.Dispose(); + isDisposed = true; + } + } } } From c30b700b3a320d070ceebac14e3f43cdd770661c Mon Sep 17 00:00:00 2001 From: yhsphd Date: Sun, 24 Jan 2021 00:26:52 +0900 Subject: [PATCH 0120/1791] "started" for past matches fixes grammar error at 'coming up next' section in schedule screen which displays schedule like "starting an hour ago" for past matches --- osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs b/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs index 88289ad6bd..b3fa9dc91c 100644 --- a/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs +++ b/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs @@ -194,7 +194,7 @@ namespace osu.Game.Tournament.Screens.Schedule { new TournamentSpriteText { - Text = "Starting ", + Text = match.NewValue.Date.Value.CompareTo(DateTimeOffset.Now) > 0 ? "Starting " : "Started ", Font = OsuFont.Torus.With(size: 24, weight: FontWeight.Regular) }, new DrawableDate(match.NewValue.Date.Value) From 899942611fcb085fc09226c84a7bbbd47500450f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 23 Jan 2021 17:01:47 +0100 Subject: [PATCH 0121/1791] Add tests for time display --- .../Screens/TestSceneScheduleScreen.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneScheduleScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneScheduleScreen.cs index b240ef3ae5..0da8d1eb4a 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneScheduleScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneScheduleScreen.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 NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Tournament.Components; @@ -16,5 +18,23 @@ namespace osu.Game.Tournament.Tests.Screens Add(new TourneyVideo("main") { RelativeSizeAxes = Axes.Both }); Add(new ScheduleScreen()); } + + [Test] + public void TestCurrentMatchTime() + { + setMatchDate(TimeSpan.FromDays(-1)); + setMatchDate(TimeSpan.FromSeconds(5)); + setMatchDate(TimeSpan.FromMinutes(4)); + setMatchDate(TimeSpan.FromHours(3)); + } + + private void setMatchDate(TimeSpan relativeTime) + // Humanizer cannot handle negative timespans. + => AddStep($"start time is {relativeTime}", () => + { + var match = CreateSampleMatch(); + match.Date.Value = DateTimeOffset.Now + relativeTime; + Ladder.CurrentMatch.Value = match; + }); } } From a8fa09103cc0573dff3c0a3f2681293f40f45ab7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 23 Jan 2021 17:16:13 +0100 Subject: [PATCH 0122/1791] Update match start text prefix in real time --- .../Screens/Schedule/ScheduleScreen.cs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs b/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs index b3fa9dc91c..c1d8c8ddd3 100644 --- a/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs +++ b/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs @@ -192,12 +192,7 @@ namespace osu.Game.Tournament.Screens.Schedule Origin = Anchor.CentreLeft, Children = new Drawable[] { - new TournamentSpriteText - { - Text = match.NewValue.Date.Value.CompareTo(DateTimeOffset.Now) > 0 ? "Starting " : "Started ", - Font = OsuFont.Torus.With(size: 24, weight: FontWeight.Regular) - }, - new DrawableDate(match.NewValue.Date.Value) + new ScheduleMatchDate(match.NewValue.Date.Value) { Font = OsuFont.Torus.With(size: 24, weight: FontWeight.Regular) } @@ -251,6 +246,18 @@ namespace osu.Game.Tournament.Screens.Schedule } } + public class ScheduleMatchDate : DrawableDate + { + public ScheduleMatchDate(DateTimeOffset date, float textSize = OsuFont.DEFAULT_FONT_SIZE, bool italic = true) + : base(date, textSize, italic) + { + } + + protected override string Format() => Date < DateTimeOffset.Now + ? $"Started {base.Format()}" + : $"Starting {base.Format()}"; + } + public class ScheduleContainer : Container { protected override Container Content => content; From eaa1519710577df734eb51e003777980fa434c81 Mon Sep 17 00:00:00 2001 From: Shivam Date: Sun, 24 Jan 2021 18:41:45 +0100 Subject: [PATCH 0123/1791] Implement native osu!lazer mod icons for tournament --- .../Components/TournamentBeatmapPanel.cs | 98 ++++++++++++++++--- 1 file changed, 86 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs index 477bf4bd63..7fac2bac71 100644 --- a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs +++ b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs @@ -16,6 +16,9 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; using osu.Game.Tournament.Models; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Screens.Play.HUD; using osuTK.Graphics; namespace osu.Game.Tournament.Components @@ -124,21 +127,11 @@ namespace osu.Game.Tournament.Components if (!string.IsNullOrEmpty(mods)) { - AddInternal(new Container + AddInternal(new ModSprite { - RelativeSizeAxes = Axes.Y, - Width = 60, Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, - Margin = new MarginPadding(10), - Child = new Sprite - { - FillMode = FillMode.Fit, - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Texture = textures.Get($"mods/{mods}"), - } + Mod = mods }); } } @@ -192,5 +185,86 @@ namespace osu.Game.Tournament.Components Alpha = 1; } } + + private class ModSprite : Container + { + public string Mod; + + public ModSprite() + { + Margin = new MarginPadding(10); + Width = 60; + RelativeSizeAxes = Axes.Y; + } + + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + var texture = textures.Get($"mods/{Mod}"); + + if (texture != null) + { + Child = new Sprite + { + FillMode = FillMode.Fit, + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Texture = texture + }; + } + else + { + Mod selectedMod = null; + + switch (Mod) + { + case "DT": + selectedMod = new OsuModDoubleTime(); + break; + + case "FL": + selectedMod = new OsuModFlashlight(); + break; + + case "HT": + selectedMod = new OsuModHalfTime(); + break; + + case "HD": + selectedMod = new OsuModHidden(); + break; + + case "HR": + selectedMod = new OsuModHardRock(); + break; + + case "NF": + selectedMod = new OsuModNoFail(); + break; + + case "EZ": + selectedMod = new OsuModEasy(); + break; + } + + if (selectedMod != null) + { + Child = new ModDisplay + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Current = + { + Value = new[] + { + selectedMod + } + } + }; + } + } + } + } } } From 9a5790cd31dbd9a60932fb16790fea3390db7ca8 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sun, 24 Jan 2021 19:18:16 +0100 Subject: [PATCH 0124/1791] Implement StableStorage class. --- osu.Desktop/OsuGameDesktop.cs | 3 +- osu.Game/IO/StableStorage.cs | 64 +++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 osu.Game/IO/StableStorage.cs diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index d1515acafa..0dc659b120 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -18,6 +18,7 @@ using osu.Framework.Screens; using osu.Game.Screens.Menu; using osu.Game.Updater; using osu.Desktop.Windows; +using osu.Game.IO; namespace osu.Desktop { @@ -40,7 +41,7 @@ namespace osu.Desktop { string stablePath = getStableInstallPath(); if (!string.IsNullOrEmpty(stablePath)) - return new DesktopStorage(stablePath, desktopHost); + return new StableStorage(stablePath, desktopHost); } } catch (Exception) diff --git a/osu.Game/IO/StableStorage.cs b/osu.Game/IO/StableStorage.cs new file mode 100644 index 0000000000..a8665b5267 --- /dev/null +++ b/osu.Game/IO/StableStorage.cs @@ -0,0 +1,64 @@ +// 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.IO; +using System.Linq; +using osu.Framework.Platform; + +namespace osu.Game.IO +{ + /// + /// A storage pointing to an osu-stable installation. + /// Provides methods for handling installations with a custom Song folder location. + /// + public class StableStorage : DesktopStorage + { + private const string stable_songs_path = "Songs"; + + private readonly DesktopGameHost host; + private string songs_path; + + public StableStorage(string path, DesktopGameHost host) + : base(path, host) + { + this.host = host; + songs_path = locateSongsDirectory(); + } + + /// + /// Returns a pointing to the osu-stable Songs directory. + /// + public Storage GetSongStorage() + { + if (songs_path.Equals(stable_songs_path, StringComparison.OrdinalIgnoreCase)) + return GetStorageForDirectory(stable_songs_path); + else + return new DesktopStorage(songs_path, host); + } + + private string locateSongsDirectory() + { + var configFile = GetStream(GetFiles(".", "osu!.*.cfg").First()); + var textReader = new StreamReader(configFile); + + var songs_directory_path = stable_songs_path; + + while (!textReader.EndOfStream) + { + var line = textReader.ReadLine(); + + if (line?.StartsWith("BeatmapDirectory", StringComparison.OrdinalIgnoreCase) == true) + { + var directory = line.Split('=')[1].TrimStart(); + if (Path.IsPathFullyQualified(directory) && !directory.Equals(stable_songs_path, StringComparison.OrdinalIgnoreCase)) + songs_directory_path = directory; + + break; + } + } + + return songs_directory_path; + } + } +} From d71ac834280c0873422a974dbc78bf73baa5ce71 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sun, 24 Jan 2021 19:46:10 +0100 Subject: [PATCH 0125/1791] Use StableStorage in ArchiveModelManager. --- osu.Desktop/OsuGameDesktop.cs | 2 +- osu.Game/Database/ArchiveModelManager.cs | 4 ++-- osu.Game/OsuGame.cs | 3 ++- osu.Game/Scoring/ScoreManager.cs | 3 ++- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 0dc659b120..5909b82c8f 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -33,7 +33,7 @@ namespace osu.Desktop noVersionOverlay = args?.Any(a => a == "--no-version-overlay") ?? false; } - public override Storage GetStorageForStableInstall() + public override StableStorage GetStorageForStableInstall() { try { diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 9f69ad035f..7d22c51d0f 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -625,7 +625,7 @@ namespace osu.Game.Database /// /// Set a storage with access to an osu-stable install for import purposes. /// - public Func GetStableStorage { private get; set; } + public Func GetStableStorage { private get; set; } /// /// Denotes whether an osu-stable installation is present to perform automated imports from. @@ -640,7 +640,7 @@ namespace osu.Game.Database /// /// Select paths to import from stable. Default implementation iterates all directories in . /// - protected virtual IEnumerable GetStableImportPaths(Storage stableStoage) => stableStoage.GetDirectories(ImportFromStablePath); + protected virtual IEnumerable GetStableImportPaths(StableStorage stableStoage) => stableStoage.GetDirectories(ImportFromStablePath); /// /// Whether this specified path should be removed after successful import. diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 5acd6bc73d..399bdda491 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -52,6 +52,7 @@ using osu.Game.Updater; using osu.Game.Utils; using LogLevel = osu.Framework.Logging.LogLevel; using osu.Game.Database; +using osu.Game.IO; namespace osu.Game { @@ -88,7 +89,7 @@ namespace osu.Game protected SentryLogger SentryLogger; - public virtual Storage GetStorageForStableInstall() => null; + public virtual StableStorage GetStorageForStableInstall() => null; public float ToolbarOffset => (Toolbar?.Position.Y ?? 0) + (Toolbar?.DrawHeight ?? 0); diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index cf1d123c06..11f31f7d59 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -16,6 +16,7 @@ using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Database; +using osu.Game.IO; using osu.Game.IO.Archives; using osu.Game.Online.API; using osu.Game.Online.API.Requests; @@ -71,7 +72,7 @@ namespace osu.Game.Scoring } } - protected override IEnumerable GetStableImportPaths(Storage stableStorage) + protected override IEnumerable GetStableImportPaths(StableStorage stableStorage) => stableStorage.GetFiles(ImportFromStablePath).Where(p => HandledExtensions.Any(ext => Path.GetExtension(p)?.Equals(ext, StringComparison.OrdinalIgnoreCase) ?? false)); public Score GetScore(ScoreInfo score) => new LegacyDatabasedScore(score, rulesets, beatmaps(), Files.Store); From f0fdad2f838ec4ab18a82841f4483cba66f715f0 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sun, 24 Jan 2021 22:04:46 +0100 Subject: [PATCH 0126/1791] Construct a DesktopStorage pointing to the absolute path of the song directory. --- osu.Game/IO/StableStorage.cs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/osu.Game/IO/StableStorage.cs b/osu.Game/IO/StableStorage.cs index a8665b5267..c7ca37a163 100644 --- a/osu.Game/IO/StableStorage.cs +++ b/osu.Game/IO/StableStorage.cs @@ -29,20 +29,14 @@ namespace osu.Game.IO /// /// Returns a pointing to the osu-stable Songs directory. /// - public Storage GetSongStorage() - { - if (songs_path.Equals(stable_songs_path, StringComparison.OrdinalIgnoreCase)) - return GetStorageForDirectory(stable_songs_path); - else - return new DesktopStorage(songs_path, host); - } + public Storage GetSongStorage() => new DesktopStorage(songs_path, host); private string locateSongsDirectory() { var configFile = GetStream(GetFiles(".", "osu!.*.cfg").First()); var textReader = new StreamReader(configFile); - var songs_directory_path = stable_songs_path; + var songs_directory_path = Path.Combine(BasePath, stable_songs_path); while (!textReader.EndOfStream) { @@ -51,7 +45,7 @@ namespace osu.Game.IO if (line?.StartsWith("BeatmapDirectory", StringComparison.OrdinalIgnoreCase) == true) { var directory = line.Split('=')[1].TrimStart(); - if (Path.IsPathFullyQualified(directory) && !directory.Equals(stable_songs_path, StringComparison.OrdinalIgnoreCase)) + if (Path.IsPathFullyQualified(directory)) songs_directory_path = directory; break; From 51d4da565c87192549f62fa94cf624818809a3d8 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sun, 24 Jan 2021 22:25:49 +0100 Subject: [PATCH 0127/1791] Fix ArchiveModelManagers lookup paths. --- osu.Game/Beatmaps/BeatmapManager.cs | 8 +++++++- osu.Game/Database/ArchiveModelManager.cs | 12 +++++++++--- osu.Game/Scoring/ScoreManager.cs | 3 ++- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 42418e532b..a455f676b3 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -64,7 +64,13 @@ namespace osu.Game.Beatmaps protected override string[] HashableFileTypes => new[] { ".osu" }; - protected override string ImportFromStablePath => "Songs"; + protected override bool CheckStableDirectoryExists(StableStorage stableStorage) => stableStorage.GetSongStorage().ExistsDirectory("."); + + protected override IEnumerable GetStableImportPaths(StableStorage stableStoage) + { + var songStorage = stableStoage.GetSongStorage(); + return songStorage.GetDirectories(".").Select(path => songStorage.GetFullPath(path)); + } private readonly RulesetStore rulesets; private readonly BeatmapStore beatmaps; diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 7d22c51d0f..516f70c700 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -637,10 +637,16 @@ namespace osu.Game.Database /// protected virtual string ImportFromStablePath => null; + /// + /// Checks for the existence of an osu-stable directory. + /// + protected virtual bool CheckStableDirectoryExists(StableStorage stableStorage) => stableStorage.ExistsDirectory(ImportFromStablePath); + /// /// Select paths to import from stable. Default implementation iterates all directories in . /// - protected virtual IEnumerable GetStableImportPaths(StableStorage stableStoage) => stableStoage.GetDirectories(ImportFromStablePath); + protected virtual IEnumerable GetStableImportPaths(StableStorage stableStoage) => stableStoage.GetDirectories(ImportFromStablePath) + .Select(path => stableStoage.GetFullPath(path)); /// /// Whether this specified path should be removed after successful import. @@ -662,14 +668,14 @@ namespace osu.Game.Database return Task.CompletedTask; } - if (!stable.ExistsDirectory(ImportFromStablePath)) + if (!CheckStableDirectoryExists(stable)) { // This handles situations like when the user does not have a Skins folder Logger.Log($"No {ImportFromStablePath} folder available in osu!stable installation", LoggingTarget.Information, LogLevel.Error); return Task.CompletedTask; } - return Task.Run(async () => await Import(GetStableImportPaths(GetStableStorage()).Select(f => stable.GetFullPath(f)).ToArray())); + return Task.Run(async () => await Import(GetStableImportPaths(stable).ToArray())); } #endregion diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 11f31f7d59..6aa0a30a75 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -73,7 +73,8 @@ namespace osu.Game.Scoring } protected override IEnumerable GetStableImportPaths(StableStorage stableStorage) - => stableStorage.GetFiles(ImportFromStablePath).Where(p => HandledExtensions.Any(ext => Path.GetExtension(p)?.Equals(ext, StringComparison.OrdinalIgnoreCase) ?? false)); + => stableStorage.GetFiles(ImportFromStablePath).Where(p => HandledExtensions.Any(ext => Path.GetExtension(p)?.Equals(ext, StringComparison.OrdinalIgnoreCase) ?? false)) + .Select(path => stableStorage.GetFullPath(path)); public Score GetScore(ScoreInfo score) => new LegacyDatabasedScore(score, rulesets, beatmaps(), Files.Store); From d38db6eace2c6b00f5a3b92fe0f603302184f993 Mon Sep 17 00:00:00 2001 From: Shivam Date: Sun, 24 Jan 2021 23:29:05 +0100 Subject: [PATCH 0128/1791] Change ModSprite to use ruleset's mods directly. --- .../Components/TournamentBeatmapPanel.cs | 66 +++++-------------- 1 file changed, 16 insertions(+), 50 deletions(-) diff --git a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs index 7fac2bac71..8fc52f8b4b 100644 --- a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs +++ b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs @@ -15,10 +15,9 @@ using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; -using osu.Game.Tournament.Models; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets; using osu.Game.Screens.Play.HUD; +using osu.Game.Tournament.Models; using osuTK.Graphics; namespace osu.Game.Tournament.Components @@ -190,6 +189,12 @@ namespace osu.Game.Tournament.Components { public string Mod; + [Resolved] + private LadderInfo ladderInfo { get; set; } + + [Resolved] + private RulesetStore rulesets { get; set; } + public ModSprite() { Margin = new MarginPadding(10); @@ -198,7 +203,7 @@ namespace osu.Game.Tournament.Components } [BackgroundDependencyLoader] - private void load(TextureStore textures) + private void load(TextureStore textures, IBindable ruleset) { var texture = textures.Get($"mods/{Mod}"); @@ -215,54 +220,15 @@ namespace osu.Game.Tournament.Components } else { - Mod selectedMod = null; - - switch (Mod) + Child = new ModDisplay { - case "DT": - selectedMod = new OsuModDoubleTime(); - break; - - case "FL": - selectedMod = new OsuModFlashlight(); - break; - - case "HT": - selectedMod = new OsuModHalfTime(); - break; - - case "HD": - selectedMod = new OsuModHidden(); - break; - - case "HR": - selectedMod = new OsuModHardRock(); - break; - - case "NF": - selectedMod = new OsuModNoFail(); - break; - - case "EZ": - selectedMod = new OsuModEasy(); - break; - } - - if (selectedMod != null) - { - Child = new ModDisplay + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Current = { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Current = - { - Value = new[] - { - selectedMod - } - } - }; - } + Value = rulesets.GetRuleset(ladderInfo.Ruleset.Value.ID ?? 0).CreateInstance().GetAllMods().Where(mod => mod.Acronym == Mod).ToArray() + } + }; } } } From c6d46129ad25567e2a3c9736de085e3669c78557 Mon Sep 17 00:00:00 2001 From: Shivam Date: Sun, 24 Jan 2021 23:33:02 +0100 Subject: [PATCH 0129/1791] Remove unneccessary ruleset parameter --- osu.Game.Tournament/Components/TournamentBeatmapPanel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs index 8fc52f8b4b..92ac123097 100644 --- a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs +++ b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs @@ -203,7 +203,7 @@ namespace osu.Game.Tournament.Components } [BackgroundDependencyLoader] - private void load(TextureStore textures, IBindable ruleset) + private void load(TextureStore textures) { var texture = textures.Get($"mods/{Mod}"); From 304264046b0feba5236722652a81bd70ffe46628 Mon Sep 17 00:00:00 2001 From: Mysfit Date: Sun, 24 Jan 2021 17:46:54 -0500 Subject: [PATCH 0130/1791] Added tests. --- .../TestSceneStoryboardSamplePlayback.cs | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs new file mode 100644 index 0000000000..1544f8fd35 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs @@ -0,0 +1,74 @@ +// 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.Testing; +using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; +using osu.Game.Storyboards; +using osu.Game.Storyboards.Drawables; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneStoryboardSamplePlayback : PlayerTestScene + { + private Storyboard storyboard; + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + config.Set(OsuSetting.ShowStoryboard, true); + + storyboard = new Storyboard(); + var backgroundLayer = storyboard.GetLayer("Background"); + backgroundLayer.Add(new StoryboardSampleInfo("Intro/welcome.mp3", time: -7000, volume: 20)); + backgroundLayer.Add(new StoryboardSampleInfo("Intro/welcome.mp3", time: -5000, volume: 20)); + backgroundLayer.Add(new StoryboardSampleInfo("Intro/welcome.mp3", time: 0, volume: 20)); + } + + [Test] + public void TestStoryboardSamplesStopDuringPause() + { + checkForFirstSamplePlayback(); + + AddStep("player paused", () => Player.Pause()); + AddAssert("player is currently paused", () => Player.GameplayClockContainer.IsPaused.Value); + AddAssert("all storyboard samples stopped immediately", () => allStoryboardSamples.All(sound => !sound.IsPlaying)); + + AddStep("player resume", () => Player.Resume()); + AddUntilStep("any storyboard samples playing after resume", () => allStoryboardSamples.Any(sound => sound.IsPlaying)); + } + + [Test] + public void TestStoryboardSamplesStopOnSkip() + { + checkForFirstSamplePlayback(); + + AddStep("skip intro", () => InputManager.Key(osuTK.Input.Key.Space)); + AddAssert("all storyboard samples stopped immediately", () => allStoryboardSamples.All(sound => !sound.IsPlaying)); + + AddUntilStep("any storyboard samples playing after skip", () => allStoryboardSamples.Any(sound => sound.IsPlaying)); + } + + private void checkForFirstSamplePlayback() + { + AddUntilStep("storyboard loaded", () => Player.Beatmap.Value.StoryboardLoaded); + AddUntilStep("any storyboard samples playing", () => allStoryboardSamples.Any(sound => sound.IsPlaying)); + } + + private IEnumerable allStoryboardSamples => Player.ChildrenOfType(); + + protected override bool AllowFail => false; + + protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); + protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(true, false); + + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) => + new ClockBackedTestWorkingBeatmap(beatmap, storyboard ?? this.storyboard, Clock, Audio); + } +} From bb8113fb5136b1c235693899ea11b58c649f289c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Jan 2021 14:47:47 +0900 Subject: [PATCH 0131/1791] Fix mod select footer not animating correctly on first reveal --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index b93602116b..1258ba719d 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -216,9 +216,9 @@ namespace osu.Game.Overlays.Mods }, new Drawable[] { - // Footer new Container { + Name = "Footer content", RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Origin = Anchor.TopCentre, @@ -237,10 +237,9 @@ namespace osu.Game.Overlays.Mods Anchor = Anchor.BottomCentre, AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, + RelativePositionAxes = Axes.X, Width = content_width, Spacing = new Vector2(footer_button_spacing, footer_button_spacing / 2), - LayoutDuration = 100, - LayoutEasing = Easing.OutQuint, Padding = new MarginPadding { Vertical = 15, @@ -354,7 +353,7 @@ namespace osu.Game.Overlays.Mods { base.PopOut(); - footerContainer.MoveToX(footerContainer.DrawSize.X, WaveContainer.DISAPPEAR_DURATION, Easing.InSine); + footerContainer.MoveToX(content_width, WaveContainer.DISAPPEAR_DURATION, Easing.InSine); footerContainer.FadeOut(WaveContainer.DISAPPEAR_DURATION, Easing.InSine); foreach (var section in ModSectionsContainer.Children) From 366f074f86eab5f0a665fe70843720b03fc9fb5b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Jan 2021 16:53:38 +0900 Subject: [PATCH 0132/1791] Better describe test steps to discern on failures --- .../NonVisual/OngoingOperationTrackerTest.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/NonVisual/OngoingOperationTrackerTest.cs b/osu.Game.Tests/NonVisual/OngoingOperationTrackerTest.cs index b2be83d1f9..eef9582af9 100644 --- a/osu.Game.Tests/NonVisual/OngoingOperationTrackerTest.cs +++ b/osu.Game.Tests/NonVisual/OngoingOperationTrackerTest.cs @@ -30,22 +30,22 @@ namespace osu.Game.Tests.NonVisual IDisposable secondOperation = null; AddStep("begin first operation", () => firstOperation = tracker.BeginOperation()); - AddAssert("operation in progress", () => operationInProgress.Value); + AddAssert("first operation in progress", () => operationInProgress.Value); AddStep("cannot start another operation", () => Assert.Throws(() => tracker.BeginOperation())); AddStep("end first operation", () => firstOperation.Dispose()); - AddAssert("operation is ended", () => !operationInProgress.Value); + AddAssert("first operation is ended", () => !operationInProgress.Value); AddStep("start second operation", () => secondOperation = tracker.BeginOperation()); - AddAssert("operation in progress", () => operationInProgress.Value); + AddAssert("second operation in progress", () => operationInProgress.Value); AddStep("dispose first operation again", () => firstOperation.Dispose()); - AddAssert("operation in progress", () => operationInProgress.Value); + AddAssert("second operation still in progress", () => operationInProgress.Value); AddStep("dispose second operation", () => secondOperation.Dispose()); - AddAssert("operation is ended", () => !operationInProgress.Value); + AddAssert("second operation is ended", () => !operationInProgress.Value); } [Test] From 10e8b7082e5affb83670333a6115af16c34ee9c8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Jan 2021 16:53:58 +0900 Subject: [PATCH 0133/1791] Rework logic to avoid custom disposal early return handling --- .../OnlinePlay/OngoingOperationTracker.cs | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/OngoingOperationTracker.cs b/osu.Game/Screens/OnlinePlay/OngoingOperationTracker.cs index 060f1d7b91..b7ee84eb9e 100644 --- a/osu.Game/Screens/OnlinePlay/OngoingOperationTracker.cs +++ b/osu.Game/Screens/OnlinePlay/OngoingOperationTracker.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -43,42 +42,45 @@ namespace osu.Game.Screens.OnlinePlay leasedInProgress = inProgress.BeginLease(true); leasedInProgress.Value = true; - // for extra safety, marshal the end of operation back to the update thread if necessary. - return new OngoingOperation(() => Scheduler.Add(endOperation, false)); + return new OngoingOperation(this, leasedInProgress); } - private void endOperation() + private void endOperationWithKnownLease(LeasedBindable lease) { - leasedInProgress?.Return(); - leasedInProgress = null; + if (lease != leasedInProgress) + return; + + // for extra safety, marshal the end of operation back to the update thread if necessary. + Scheduler.Add(() => + { + leasedInProgress?.Return(); + leasedInProgress = null; + }, false); } protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); + // base call does an UnbindAllBindables(). // clean up the leased reference here so that it doesn't get returned twice. leasedInProgress = null; } - private class OngoingOperation : InvokeOnDisposal + private class OngoingOperation : IDisposable { - private bool isDisposed; + private readonly OngoingOperationTracker tracker; + private readonly LeasedBindable lease; - public OngoingOperation(Action action) - : base(action) + public OngoingOperation(OngoingOperationTracker tracker, LeasedBindable lease) { + this.tracker = tracker; + this.lease = lease; } - public override void Dispose() + public void Dispose() { - // base class does not check disposal state for performance reasons which aren't relevant here. - // track locally, to avoid interfering with other operations in case of a potential double-disposal. - if (isDisposed) - return; - - base.Dispose(); - isDisposed = true; + tracker.endOperationWithKnownLease(lease); } } } From c05ae3497afa8c5a7dc551496e189620ccbd11ad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Jan 2021 17:02:24 +0900 Subject: [PATCH 0134/1791] Make connect/disconnect private --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index aa2305c991..7dc4919d23 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -25,6 +25,8 @@ namespace osu.Game.Online.Multiplayer private readonly Bindable isConnected = new Bindable(); private readonly IBindable apiState = new Bindable(); + private readonly SemaphoreSlim connectionLock = new SemaphoreSlim(1); + [Resolved] private IAPIProvider api { get; set; } = null!; @@ -52,20 +54,16 @@ namespace osu.Game.Online.Multiplayer { case APIState.Failing: case APIState.Offline: - Task.Run(Disconnect).CatchUnobservedExceptions(); + Task.Run(() => disconnect(true)).CatchUnobservedExceptions(); break; case APIState.Online: - Task.Run(Connect).CatchUnobservedExceptions(); + Task.Run(connect).CatchUnobservedExceptions(); break; } } - private readonly SemaphoreSlim connectionLock = new SemaphoreSlim(1); - - public Task Disconnect() => disconnect(true); - - protected async Task Connect() + private async Task connect() { cancelExistingConnect(); @@ -233,7 +231,7 @@ namespace osu.Game.Online.Multiplayer // make sure a disconnect wasn't triggered (and this is still the active connection). if (!cancellationToken.IsCancellationRequested) - Task.Run(Connect, default).CatchUnobservedExceptions(); + Task.Run(connect, default).CatchUnobservedExceptions(); return Task.CompletedTask; }; From 994fb2667dc22d06d0fc7aca26387ef03ec04859 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Jan 2021 17:11:04 +0900 Subject: [PATCH 0135/1791] Call DisposeAsync instead of StopAsync --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 7dc4919d23..ffed2b57fe 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -188,7 +188,7 @@ namespace osu.Game.Online.Multiplayer try { if (connection != null) - await connection.StopAsync(); + await connection.DisposeAsync(); } finally { From 0f09a7feb9ee12c05c1d53464db9e6194e8818d0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Jan 2021 17:17:04 +0900 Subject: [PATCH 0136/1791] Avoid semaphore potentially getting held forever --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index ffed2b57fe..391658f0d0 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -67,7 +67,7 @@ namespace osu.Game.Online.Multiplayer { cancelExistingConnect(); - await connectionLock.WaitAsync(); + await connectionLock.WaitAsync(10000); try { @@ -183,7 +183,7 @@ namespace osu.Game.Online.Multiplayer cancelExistingConnect(); if (takeLock) - await connectionLock.WaitAsync(); + await connectionLock.WaitAsync(10000); try { @@ -237,5 +237,12 @@ namespace osu.Game.Online.Multiplayer }; return newConnection; } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + cancelExistingConnect(); + } } } From 91ce3df3a944244d73e18630f3dd26e3709df282 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Jan 2021 17:44:01 +0900 Subject: [PATCH 0137/1791] Bind MultiplayerGameplayLeaderboard to player updates later in load process --- osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs index d4ce542a67..a3d27c4e71 100644 --- a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs @@ -53,8 +53,6 @@ namespace osu.Game.Screens.Play.HUD [BackgroundDependencyLoader] private void load(OsuConfigManager config, IAPIProvider api) { - streamingClient.OnNewFrames += handleIncomingFrames; - foreach (var userId in playingUsers) { streamingClient.WatchUser(userId); @@ -90,6 +88,9 @@ namespace osu.Game.Screens.Play.HUD playingUsers.BindTo(multiplayerClient.CurrentMatchPlayingUserIds); playingUsers.BindCollectionChanged(usersChanged); + + // this leaderboard should be guaranteed to be completely loaded before the gameplay starts (is a prerequisite in MultiplayerPlayer). + streamingClient.OnNewFrames += handleIncomingFrames; } private void usersChanged(object sender, NotifyCollectionChangedEventArgs e) From 4ac362ee1acc899e1593ef548b981962e00916ec Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Jan 2021 18:29:00 +0900 Subject: [PATCH 0138/1791] Move cloning local to editor --- osu.Game/Beatmaps/WorkingBeatmap.cs | 2 -- osu.Game/Screens/Edit/Editor.cs | 4 ++++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index d25adca92b..30382c444f 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -111,8 +111,6 @@ namespace osu.Game.Beatmaps // Convert IBeatmap converted = converter.Convert(cancellationSource.Token); - converted.ControlPointInfo = converted.ControlPointInfo.CreateCopy(); - // Apply conversion mods to the result foreach (var mod in mods.OfType()) { diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index b7ebf0c0a4..0e04d1ea12 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -131,6 +131,10 @@ namespace osu.Game.Screens.Edit try { playableBeatmap = Beatmap.Value.GetPlayableBeatmap(Beatmap.Value.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. + playableBeatmap.ControlPointInfo = playableBeatmap.ControlPointInfo.CreateCopy(); } catch (Exception e) { From b489e92c9e1042e0d9254158f7da75a339142436 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Jan 2021 18:43:36 +0900 Subject: [PATCH 0139/1791] Fix TimelineParts not using correct beatmap --- .../Timelines/Summary/Parts/BookmarkPart.cs | 3 +-- .../Timelines/Summary/Parts/BreakPart.cs | 5 ++--- .../Summary/Parts/ControlPointPart.cs | 5 ++--- .../Timelines/Summary/Parts/MarkerPart.cs | 8 ++------ .../Timelines/Summary/Parts/TimelinePart.cs | 18 +++++++++++------- .../Timeline/TimelineControlPointDisplay.cs | 5 ++--- 6 files changed, 20 insertions(+), 24 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs index 103e39e78a..8298cf4773 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations; @@ -13,7 +12,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts /// public class BookmarkPart : TimelinePart { - protected override void LoadBeatmap(WorkingBeatmap beatmap) + protected override void LoadBeatmap(EditorBeatmap beatmap) { base.LoadBeatmap(beatmap); foreach (int bookmark in beatmap.BeatmapInfo.Bookmarks) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs index ceccbffc9c..e8a4b5c8c7 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Game.Beatmaps; using osu.Game.Beatmaps.Timing; using osu.Game.Graphics; using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations; @@ -14,10 +13,10 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts /// public class BreakPart : TimelinePart { - protected override void LoadBeatmap(WorkingBeatmap beatmap) + protected override void LoadBeatmap(EditorBeatmap beatmap) { base.LoadBeatmap(beatmap); - foreach (var breakPeriod in beatmap.Beatmap.Breaks) + foreach (var breakPeriod in beatmap.Breaks) Add(new BreakVisualisation(breakPeriod)); } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs index e76ab71e54..70afc1e308 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs @@ -4,7 +4,6 @@ using System.Collections.Specialized; using System.Linq; using osu.Framework.Bindables; -using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts @@ -16,12 +15,12 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { private readonly IBindableList controlPointGroups = new BindableList(); - protected override void LoadBeatmap(WorkingBeatmap beatmap) + protected override void LoadBeatmap(EditorBeatmap beatmap) { base.LoadBeatmap(beatmap); controlPointGroups.UnbindAll(); - controlPointGroups.BindTo(beatmap.Beatmap.ControlPointInfo.Groups); + controlPointGroups.BindTo(beatmap.ControlPointInfo.Groups); controlPointGroups.BindCollectionChanged((sender, args) => { switch (args.Action) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs index 5a2214509c..d551333616 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs @@ -2,15 +2,14 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osuTK; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Threading; -using osu.Game.Beatmaps; using osu.Game.Graphics; +using osuTK; namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { @@ -54,9 +53,6 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts scheduledSeek?.Cancel(); scheduledSeek = Schedule(() => { - if (Beatmap.Value == null) - return; - float markerPos = Math.Clamp(ToLocalSpace(screenPosition).X, 0, DrawWidth); editorClock.SeekSmoothlyTo(markerPos / DrawWidth * editorClock.TrackLength); }); @@ -68,7 +64,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts marker.X = (float)editorClock.CurrentTime; } - protected override void LoadBeatmap(WorkingBeatmap beatmap) + protected override void LoadBeatmap(EditorBeatmap beatmap) { // block base call so we don't clear our marker (can be reused on beatmap change). } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs index 5b8f7c747b..5aba81aa7d 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs @@ -21,7 +21,10 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts /// public class TimelinePart : Container where T : Drawable { - protected readonly IBindable Beatmap = new Bindable(); + private readonly IBindable beatmap = new Bindable(); + + [Resolved] + protected EditorBeatmap EditorBeatmap { get; private set; } protected readonly IBindable Track = new Bindable(); @@ -33,10 +36,9 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { AddInternal(this.content = content ?? new Container { RelativeSizeAxes = Axes.Both }); - Beatmap.ValueChanged += b => + beatmap.ValueChanged += b => { updateRelativeChildSize(); - LoadBeatmap(b.NewValue); }; Track.ValueChanged += _ => updateRelativeChildSize(); @@ -45,24 +47,26 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts [BackgroundDependencyLoader] private void load(IBindable beatmap, EditorClock clock) { - Beatmap.BindTo(beatmap); + this.beatmap.BindTo(beatmap); + LoadBeatmap(EditorBeatmap); + Track.BindTo(clock.Track); } private void updateRelativeChildSize() { // the track may not be loaded completely (only has a length once it is). - if (!Beatmap.Value.Track.IsLoaded) + if (!beatmap.Value.Track.IsLoaded) { content.RelativeChildSize = Vector2.One; Schedule(updateRelativeChildSize); return; } - content.RelativeChildSize = new Vector2((float)Math.Max(1, Beatmap.Value.Track.Length), 1); + content.RelativeChildSize = new Vector2((float)Math.Max(1, beatmap.Value.Track.Length), 1); } - protected virtual void LoadBeatmap(WorkingBeatmap beatmap) + protected virtual void LoadBeatmap(EditorBeatmap beatmap) { content.Clear(); } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs index 13191df13c..18600bcdee 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs @@ -5,7 +5,6 @@ using System.Collections.Specialized; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts; @@ -23,12 +22,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline RelativeSizeAxes = Axes.Both; } - protected override void LoadBeatmap(WorkingBeatmap beatmap) + protected override void LoadBeatmap(EditorBeatmap beatmap) { base.LoadBeatmap(beatmap); controlPointGroups.UnbindAll(); - controlPointGroups.BindTo(beatmap.Beatmap.ControlPointInfo.Groups); + controlPointGroups.BindTo(beatmap.ControlPointInfo.Groups); controlPointGroups.BindCollectionChanged((sender, args) => { switch (args.Action) From f3061a8e837e4a59522d9a4faeac1b0e9f98aa00 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Jan 2021 18:47:41 +0900 Subject: [PATCH 0140/1791] Update squirrel to fix incorrect desktop icon creation on install --- osu.Desktop/osu.Desktop.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 4554f8b83a..e201b250d4 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -25,7 +25,7 @@ - + From 439f03e3b3d78dbdbc8ba3d6db559c13043f1e86 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Jan 2021 19:25:38 +0900 Subject: [PATCH 0141/1791] Fix failing test due to missing dependency --- .../Visual/Editing/TestSceneEditorSummaryTimeline.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSummaryTimeline.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSummaryTimeline.cs index 3adc1bd425..94a9fd7b35 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSummaryTimeline.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSummaryTimeline.cs @@ -5,6 +5,8 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Beatmaps; +using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components.Timelines.Summary; using osuTK; @@ -13,6 +15,9 @@ namespace osu.Game.Tests.Visual.Editing [TestFixture] public class TestSceneEditorSummaryTimeline : EditorClockTestScene { + [Cached(typeof(EditorBeatmap))] + private readonly EditorBeatmap editorBeatmap = new EditorBeatmap(new OsuBeatmap()); + [BackgroundDependencyLoader] private void load() { From 964976f604e9071e929f90cdd934ab104cf20bdb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 25 Jan 2021 20:41:51 +0900 Subject: [PATCH 0142/1791] Use a task chain and fix potential misordering of events --- .../Online/Multiplayer/MultiplayerClient.cs | 11 +- .../Multiplayer/StatefulMultiplayerClient.cs | 123 +++++------------- .../Multiplayer/TestMultiplayerClient.cs | 2 + osu.Game/Utils/TaskChain.cs | 30 +++++ 4 files changed, 70 insertions(+), 96 deletions(-) create mode 100644 osu.Game/Utils/TaskChain.cs diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 5d18521eac..8573adc94a 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -134,17 +134,12 @@ namespace osu.Game.Online.Multiplayer return connection.InvokeAsync(nameof(IMultiplayerServer.JoinRoom), roomId); } - public override async Task LeaveRoom() + protected override Task LeaveRoomInternal() { if (!isConnected.Value) - { - // even if not connected, make sure the local room state can be cleaned up. - await base.LeaveRoom(); - return; - } + return Task.FromCanceled(new CancellationToken(true)); - await base.LeaveRoom(); - await connection.InvokeAsync(nameof(IMultiplayerServer.LeaveRoom)); + return connection.InvokeAsync(nameof(IMultiplayerServer.LeaveRoom)); } public override Task TransferHost(int userId) diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index f2b5a44fcf..94122aeff5 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -108,67 +108,38 @@ namespace osu.Game.Online.Multiplayer }); } - private readonly object joinOrLeaveTaskLock = new object(); - private Task? joinOrLeaveTask; + private readonly TaskChain joinOrLeaveTaskChain = new TaskChain(); /// /// Joins the for a given API . /// /// The API . - public async Task JoinRoom(Room room) + public async Task JoinRoom(Room room) => await joinOrLeaveTaskChain.Add(async () => { - Task? lastTask; - Task newTask; + if (Room != null) + throw new InvalidOperationException("Cannot join a multiplayer room while already in one."); - lock (joinOrLeaveTaskLock) + Debug.Assert(room.RoomID.Value != null); + + // Join the server-side room. + var joinedRoom = await JoinRoom(room.RoomID.Value.Value); + Debug.Assert(joinedRoom != null); + + // Populate users. + Debug.Assert(joinedRoom.Users != null); + await Task.WhenAll(joinedRoom.Users.Select(PopulateUser)); + + // Update the stored room (must be done on update thread for thread-safety). + await scheduleAsync(() => { - lastTask = joinOrLeaveTask; - joinOrLeaveTask = newTask = Task.Run(async () => - { - if (lastTask != null) - await lastTask; + Room = joinedRoom; + apiRoom = room; + playlistItemId = room.Playlist.SingleOrDefault()?.ID ?? 0; + }); - // Should be thread-safe since joinOrLeaveTask is locked on in both JoinRoom() and LeaveRoom(). - if (Room != null) - throw new InvalidOperationException("Cannot join a multiplayer room while already in one."); - - Debug.Assert(room.RoomID.Value != null); - - // Join the server-side room. - var joinedRoom = await JoinRoom(room.RoomID.Value.Value); - Debug.Assert(joinedRoom != null); - - // Populate users. - Debug.Assert(joinedRoom.Users != null); - await Task.WhenAll(joinedRoom.Users.Select(PopulateUser)); - - // Update the stored room (must be done on update thread for thread-safety). - await scheduleAsync(() => - { - Room = joinedRoom; - apiRoom = room; - playlistItemId = room.Playlist.SingleOrDefault()?.ID ?? 0; - }); - - // Update room settings. - await updateLocalRoomSettings(joinedRoom.Settings); - }); - } - - try - { - await newTask; - } - finally - { - // The task will be awaited in the future, so reset it so that the user doesn't get into a permanently faulted state if anything fails. - lock (joinOrLeaveTaskLock) - { - if (joinOrLeaveTask == newTask) - joinOrLeaveTask = null; - } - } - } + // Update room settings. + await updateLocalRoomSettings(joinedRoom.Settings); + }); /// /// Joins the with a given ID. @@ -177,48 +148,24 @@ namespace osu.Game.Online.Multiplayer /// The joined . protected abstract Task JoinRoom(long roomId); - public virtual async Task LeaveRoom() + public async Task LeaveRoom() => await joinOrLeaveTaskChain.Add(async () => { - Task? lastTask; - Task newTask; + if (Room == null) + return; - lock (joinOrLeaveTaskLock) + await scheduleAsync(() => { - lastTask = joinOrLeaveTask; - joinOrLeaveTask = newTask = Task.Run(async () => - { - if (lastTask != null) - await lastTask; + apiRoom = null; + Room = null; + CurrentMatchPlayingUserIds.Clear(); - // Should be thread-safe since joinOrLeaveTask is locked on in both JoinRoom() and LeaveRoom(). - if (Room == null) - return; + RoomUpdated?.Invoke(); + }); - await scheduleAsync(() => - { - apiRoom = null; - Room = null; - CurrentMatchPlayingUserIds.Clear(); + await LeaveRoomInternal(); + }); - RoomUpdated?.Invoke(); - }); - }); - } - - try - { - await newTask; - } - finally - { - // The task will be awaited in the future, so reset it so that the user doesn't get into a permanently faulted state if anything fails. - lock (joinOrLeaveTaskLock) - { - if (joinOrLeaveTask == newTask) - joinOrLeaveTask = null; - } - } - } + protected abstract Task LeaveRoomInternal(); /// /// Change the current settings. diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 7fbc770351..a79183fdab 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -98,6 +98,8 @@ namespace osu.Game.Tests.Visual.Multiplayer return Task.FromResult(room); } + protected override Task LeaveRoomInternal() => Task.CompletedTask; + public override Task TransferHost(int userId) => ((IMultiplayerClient)this).HostChanged(userId); public override async Task ChangeSettings(MultiplayerRoomSettings settings) diff --git a/osu.Game/Utils/TaskChain.cs b/osu.Game/Utils/TaskChain.cs new file mode 100644 index 0000000000..b397b0c45b --- /dev/null +++ b/osu.Game/Utils/TaskChain.cs @@ -0,0 +1,30 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using System; +using System.Threading.Tasks; + +namespace osu.Game.Utils +{ + /// + /// A chain of s that run sequentially. + /// + public class TaskChain + { + private readonly object currentTaskLock = new object(); + private Task? currentTask; + + public Task Add(Func taskFunc) + { + lock (currentTaskLock) + { + currentTask = currentTask == null + ? taskFunc() + : currentTask.ContinueWith(_ => taskFunc()).Unwrap(); + return currentTask; + } + } + } +} From bb44fcfe31c78f77321714c466724847d27492ce Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 25 Jan 2021 20:58:02 +0900 Subject: [PATCH 0143/1791] Prevent some data races --- .../Multiplayer/StatefulMultiplayerClient.cs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index 94122aeff5..0e736ed7c6 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -6,6 +6,7 @@ using System; using System.Diagnostics; using System.Linq; +using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -148,12 +149,15 @@ namespace osu.Game.Online.Multiplayer /// The joined . protected abstract Task JoinRoom(long roomId); - public async Task LeaveRoom() => await joinOrLeaveTaskChain.Add(async () => + public Task LeaveRoom() { if (Room == null) - return; + return Task.FromCanceled(new CancellationToken(true)); - await scheduleAsync(() => + // Leaving rooms is expected to occur instantaneously whilst the operation is finalised in the background. + // However a few members need to be reset immediately to prevent other components from entering invalid states whilst the operation hasn't yet completed. + // For example, if a room was left and the user immediately pressed the "create room" button, then the user could be taken into the lobby if the value of Room is not reset in time. + var scheduledReset = scheduleAsync(() => { apiRoom = null; Room = null; @@ -162,8 +166,12 @@ namespace osu.Game.Online.Multiplayer RoomUpdated?.Invoke(); }); - await LeaveRoomInternal(); - }); + return joinOrLeaveTaskChain.Add(async () => + { + await scheduledReset; + await LeaveRoomInternal(); + }); + } protected abstract Task LeaveRoomInternal(); From c17774e23c6b770d07cd2d9ea059ca1bcc4dff1a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 25 Jan 2021 20:58:05 +0900 Subject: [PATCH 0144/1791] Add xmldoc --- osu.Game/Utils/TaskChain.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Utils/TaskChain.cs b/osu.Game/Utils/TaskChain.cs index b397b0c45b..64d523bd3d 100644 --- a/osu.Game/Utils/TaskChain.cs +++ b/osu.Game/Utils/TaskChain.cs @@ -16,6 +16,11 @@ namespace osu.Game.Utils private readonly object currentTaskLock = new object(); private Task? currentTask; + /// + /// Adds a new task to the end of this . + /// + /// The task creation function. + /// The awaitable . public Task Add(Func taskFunc) { lock (currentTaskLock) From f89eb7d75db7982d3cdc6200b4737c630094eb87 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 25 Jan 2021 13:22:37 +0100 Subject: [PATCH 0145/1791] Split and rename TournamentModDisplay component --- .../Components/TournamentBeatmapPanel.cs | 64 +++---------------- .../Components/TournamentModDisplay.cs | 56 ++++++++++++++++ 2 files changed, 64 insertions(+), 56 deletions(-) create mode 100644 osu.Game.Tournament/Components/TournamentModDisplay.cs diff --git a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs index 92ac123097..2ed99d2fb5 100644 --- a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs +++ b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs @@ -9,14 +9,11 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; -using osu.Game.Rulesets; -using osu.Game.Screens.Play.HUD; using osu.Game.Tournament.Models; using osuTK.Graphics; @@ -25,7 +22,7 @@ namespace osu.Game.Tournament.Components public class TournamentBeatmapPanel : CompositeDrawable { public readonly BeatmapInfo Beatmap; - private readonly string mods; + private readonly string mod; private const float horizontal_padding = 10; private const float vertical_padding = 10; @@ -40,7 +37,7 @@ namespace osu.Game.Tournament.Components if (beatmap == null) throw new ArgumentNullException(nameof(beatmap)); Beatmap = beatmap; - this.mods = mods; + this.mod = mods; Width = 400; Height = HEIGHT; } @@ -124,13 +121,16 @@ namespace osu.Game.Tournament.Components }, }); - if (!string.IsNullOrEmpty(mods)) + if (!string.IsNullOrEmpty(mod)) { - AddInternal(new ModSprite + AddInternal(new TournamentModDisplay { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, - Mod = mods + Margin = new MarginPadding(10), + Width = 60, + RelativeSizeAxes = Axes.Y, + ModAcronym = mod }); } } @@ -184,53 +184,5 @@ namespace osu.Game.Tournament.Components Alpha = 1; } } - - private class ModSprite : Container - { - public string Mod; - - [Resolved] - private LadderInfo ladderInfo { get; set; } - - [Resolved] - private RulesetStore rulesets { get; set; } - - public ModSprite() - { - Margin = new MarginPadding(10); - Width = 60; - RelativeSizeAxes = Axes.Y; - } - - [BackgroundDependencyLoader] - private void load(TextureStore textures) - { - var texture = textures.Get($"mods/{Mod}"); - - if (texture != null) - { - Child = new Sprite - { - FillMode = FillMode.Fit, - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Texture = texture - }; - } - else - { - Child = new ModDisplay - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Current = - { - Value = rulesets.GetRuleset(ladderInfo.Ruleset.Value.ID ?? 0).CreateInstance().GetAllMods().Where(mod => mod.Acronym == Mod).ToArray() - } - }; - } - } - } } } diff --git a/osu.Game.Tournament/Components/TournamentModDisplay.cs b/osu.Game.Tournament/Components/TournamentModDisplay.cs new file mode 100644 index 0000000000..a22969c20d --- /dev/null +++ b/osu.Game.Tournament/Components/TournamentModDisplay.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 System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Game.Rulesets; +using osu.Game.Rulesets.UI; +using osu.Game.Tournament.Models; +using osuTK; + +namespace osu.Game.Tournament.Components +{ + public class TournamentModDisplay : CompositeDrawable + { + public string ModAcronym; + + [Resolved] + private LadderInfo ladderInfo { get; set; } + + [Resolved] + private RulesetStore rulesets { get; set; } + + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + var texture = textures.Get($"mods/{ModAcronym}"); + + if (texture != null) + { + AddInternal(new Sprite + { + FillMode = FillMode.Fit, + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Texture = texture + }); + } + else + { + var mod = rulesets.GetRuleset(ladderInfo.Ruleset.Value.ID ?? 0).CreateInstance().GetAllMods().FirstOrDefault(mod => mod.Acronym == ModAcronym); + + AddInternal(new ModIcon(mod) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(0.5f) + }); + } + } + } +} From 74310da7cf550af33367a4dd0e3d49eebacf32a5 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 25 Jan 2021 13:24:43 +0100 Subject: [PATCH 0146/1791] Change parameter to be singular mod instead of plural --- 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 2ed99d2fb5..e02709a045 100644 --- a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs +++ b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs @@ -32,12 +32,12 @@ namespace osu.Game.Tournament.Components private readonly Bindable currentMatch = new Bindable(); private Box flash; - public TournamentBeatmapPanel(BeatmapInfo beatmap, string mods = null) + public TournamentBeatmapPanel(BeatmapInfo beatmap, string mod = null) { if (beatmap == null) throw new ArgumentNullException(nameof(beatmap)); Beatmap = beatmap; - this.mod = mods; + this.mod = mod; Width = 400; Height = HEIGHT; } From ca08a19c409e4a454a0d6a66d7ce57d2ea845228 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 25 Jan 2021 13:28:46 +0100 Subject: [PATCH 0147/1791] Rename mod to modIcon --- osu.Game.Tournament/Components/TournamentModDisplay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/Components/TournamentModDisplay.cs b/osu.Game.Tournament/Components/TournamentModDisplay.cs index a22969c20d..827b3d6a69 100644 --- a/osu.Game.Tournament/Components/TournamentModDisplay.cs +++ b/osu.Game.Tournament/Components/TournamentModDisplay.cs @@ -42,9 +42,9 @@ namespace osu.Game.Tournament.Components } else { - var mod = rulesets.GetRuleset(ladderInfo.Ruleset.Value.ID ?? 0).CreateInstance().GetAllMods().FirstOrDefault(mod => mod.Acronym == ModAcronym); + var modIcon = rulesets.GetRuleset(ladderInfo.Ruleset.Value.ID ?? 0).CreateInstance().GetAllMods().FirstOrDefault(mod => mod.Acronym == ModAcronym); - AddInternal(new ModIcon(mod) + AddInternal(new ModIcon(modIcon) { Anchor = Anchor.Centre, Origin = Anchor.Centre, From 07bd9013585ebae924cf44c501b079bb9d9b01b0 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 25 Jan 2021 14:20:58 +0100 Subject: [PATCH 0148/1791] Add visual test for Tournament Mod Display --- .../TestSceneTournamentModDisplay.cs | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs diff --git a/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs b/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs new file mode 100644 index 0000000000..9689ecd4ae --- /dev/null +++ b/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs @@ -0,0 +1,66 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Rulesets; +using osu.Game.Tournament.Components; + +namespace osu.Game.Tournament.Tests.Components +{ + public class TestSceneTournamentModDisplay : TournamentTestScene + { + [Resolved] + private IAPIProvider api { get; set; } + + [Resolved] + private RulesetStore rulesets { get; set; } + + private FillFlowContainer fillFlow; + + private BeatmapInfo beatmap; + + [BackgroundDependencyLoader] + private void load() + { + var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = 490154 }); + req.Success += success; + api.Queue(req); + + Add(fillFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Direction = FillDirection.Full, + Spacing = new osuTK.Vector2(10) + }); + } + + [Test] + public void TestModDisplay() + { + AddUntilStep("beatmap is available", () => beatmap != null); + AddStep("add maps with available mods for ruleset", () => displayForRuleset(Ladder.Ruleset.Value.ID ?? 0)); + } + + private void displayForRuleset(int rulesetId) + { + fillFlow.Clear(); + var mods = rulesets.GetRuleset(rulesetId).CreateInstance().GetAllMods(); + + foreach (var mod in mods) + { + fillFlow.Add(new TournamentBeatmapPanel(beatmap, mod.Acronym)); + } + } + + private void success(APIBeatmap apiBeatmap) => beatmap = apiBeatmap.ToBeatmap(rulesets); + } +} From 5e0ccb6c91d3e1d55968882b80d3ba0eca1971a0 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 25 Jan 2021 14:21:22 +0100 Subject: [PATCH 0149/1791] Remove unncessary test step --- .../TestSceneTournamentModDisplay.cs | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs b/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs index 9689ecd4ae..b4d9fa4222 100644 --- a/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs +++ b/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.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 NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -43,24 +42,19 @@ namespace osu.Game.Tournament.Tests.Components }); } - [Test] - public void TestModDisplay() + private void success(APIBeatmap apiBeatmap) { - AddUntilStep("beatmap is available", () => beatmap != null); - AddStep("add maps with available mods for ruleset", () => displayForRuleset(Ladder.Ruleset.Value.ID ?? 0)); - } - - private void displayForRuleset(int rulesetId) - { - fillFlow.Clear(); - var mods = rulesets.GetRuleset(rulesetId).CreateInstance().GetAllMods(); + beatmap = apiBeatmap.ToBeatmap(rulesets); + var mods = rulesets.GetRuleset(Ladder.Ruleset.Value.ID ?? 0).CreateInstance().GetAllMods(); foreach (var mod in mods) { - fillFlow.Add(new TournamentBeatmapPanel(beatmap, mod.Acronym)); + fillFlow.Add(new TournamentBeatmapPanel(beatmap, mod.Acronym) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }); } } - - private void success(APIBeatmap apiBeatmap) => beatmap = apiBeatmap.ToBeatmap(rulesets); } } From 6a85f5ca8bf2a9506ed55d9a60f870b820326e2b Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 25 Jan 2021 14:21:53 +0100 Subject: [PATCH 0150/1791] Add null checks to prevent nullrefexception in automated test --- osu.Game.Tournament/Components/TournamentModDisplay.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Components/TournamentModDisplay.cs b/osu.Game.Tournament/Components/TournamentModDisplay.cs index 827b3d6a69..e91c27345e 100644 --- a/osu.Game.Tournament/Components/TournamentModDisplay.cs +++ b/osu.Game.Tournament/Components/TournamentModDisplay.cs @@ -42,7 +42,15 @@ namespace osu.Game.Tournament.Components } else { - var modIcon = rulesets.GetRuleset(ladderInfo.Ruleset.Value.ID ?? 0).CreateInstance().GetAllMods().FirstOrDefault(mod => mod.Acronym == ModAcronym); + var ruleset = rulesets.AvailableRulesets.FirstOrDefault(r => r == ladderInfo.Ruleset.Value); + + if (ruleset == null) + return; + + var modIcon = ruleset.CreateInstance().GetAllMods().FirstOrDefault(mod => mod.Acronym == ModAcronym); + + if (modIcon == null) + return; AddInternal(new ModIcon(modIcon) { From a741d91aed91f338ffecd3a4596b551c8804b52e Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 25 Jan 2021 14:57:35 +0100 Subject: [PATCH 0151/1791] use null propragtor for Ruleset.Value and rulset instead of null checks --- .../Components/TournamentModDisplay.cs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tournament/Components/TournamentModDisplay.cs b/osu.Game.Tournament/Components/TournamentModDisplay.cs index e91c27345e..369a58858e 100644 --- a/osu.Game.Tournament/Components/TournamentModDisplay.cs +++ b/osu.Game.Tournament/Components/TournamentModDisplay.cs @@ -18,14 +18,11 @@ namespace osu.Game.Tournament.Components { public string ModAcronym; - [Resolved] - private LadderInfo ladderInfo { get; set; } - [Resolved] private RulesetStore rulesets { get; set; } [BackgroundDependencyLoader] - private void load(TextureStore textures) + private void load(TextureStore textures, LadderInfo ladderInfo) { var texture = textures.Get($"mods/{ModAcronym}"); @@ -42,12 +39,8 @@ namespace osu.Game.Tournament.Components } else { - var ruleset = rulesets.AvailableRulesets.FirstOrDefault(r => r == ladderInfo.Ruleset.Value); - - if (ruleset == null) - return; - - var modIcon = ruleset.CreateInstance().GetAllMods().FirstOrDefault(mod => mod.Acronym == ModAcronym); + var ruleset = rulesets.GetRuleset(ladderInfo.Ruleset.Value?.ID ?? 0); + var modIcon = ruleset?.CreateInstance().GetAllMods().FirstOrDefault(mod => mod.Acronym == ModAcronym); if (modIcon == null) return; From b036f0165a077bc49cb0fd9d7e8713fb6a18fd89 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 25 Jan 2021 15:47:31 +0100 Subject: [PATCH 0152/1791] move value set to constructor and make private readonly --- .../Components/TournamentBeatmapPanel.cs | 3 +-- .../Components/TournamentModDisplay.cs | 11 ++++++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs index e02709a045..8cc4566c08 100644 --- a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs +++ b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs @@ -123,14 +123,13 @@ namespace osu.Game.Tournament.Components if (!string.IsNullOrEmpty(mod)) { - AddInternal(new TournamentModDisplay + AddInternal(new TournamentModDisplay(mod) { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, Margin = new MarginPadding(10), Width = 60, RelativeSizeAxes = Axes.Y, - ModAcronym = mod }); } } diff --git a/osu.Game.Tournament/Components/TournamentModDisplay.cs b/osu.Game.Tournament/Components/TournamentModDisplay.cs index 369a58858e..3df8550667 100644 --- a/osu.Game.Tournament/Components/TournamentModDisplay.cs +++ b/osu.Game.Tournament/Components/TournamentModDisplay.cs @@ -16,15 +16,20 @@ namespace osu.Game.Tournament.Components { public class TournamentModDisplay : CompositeDrawable { - public string ModAcronym; + private readonly string modAcronym; [Resolved] private RulesetStore rulesets { get; set; } + public TournamentModDisplay(string mod) + { + modAcronym = mod; + } + [BackgroundDependencyLoader] private void load(TextureStore textures, LadderInfo ladderInfo) { - var texture = textures.Get($"mods/{ModAcronym}"); + var texture = textures.Get($"mods/{modAcronym}"); if (texture != null) { @@ -40,7 +45,7 @@ namespace osu.Game.Tournament.Components else { var ruleset = rulesets.GetRuleset(ladderInfo.Ruleset.Value?.ID ?? 0); - var modIcon = ruleset?.CreateInstance().GetAllMods().FirstOrDefault(mod => mod.Acronym == ModAcronym); + var modIcon = ruleset?.CreateInstance().GetAllMods().FirstOrDefault(mod => mod.Acronym == modAcronym); if (modIcon == null) return; From a4a7f0c5787a1fd0a5470a8af72c1ac5211e7d1b Mon Sep 17 00:00:00 2001 From: Lucas A Date: Mon, 25 Jan 2021 19:05:16 +0100 Subject: [PATCH 0153/1791] Address CI inspections. --- osu.Game/IO/StableStorage.cs | 12 ++++++------ osu.Game/OsuGame.cs | 1 - 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/osu.Game/IO/StableStorage.cs b/osu.Game/IO/StableStorage.cs index c7ca37a163..85af92621b 100644 --- a/osu.Game/IO/StableStorage.cs +++ b/osu.Game/IO/StableStorage.cs @@ -17,26 +17,26 @@ namespace osu.Game.IO private const string stable_songs_path = "Songs"; private readonly DesktopGameHost host; - private string songs_path; + private readonly string songsPath; public StableStorage(string path, DesktopGameHost host) : base(path, host) { this.host = host; - songs_path = locateSongsDirectory(); + songsPath = locateSongsDirectory(); } /// /// Returns a pointing to the osu-stable Songs directory. /// - public Storage GetSongStorage() => new DesktopStorage(songs_path, host); + public Storage GetSongStorage() => new DesktopStorage(songsPath, host); private string locateSongsDirectory() { var configFile = GetStream(GetFiles(".", "osu!.*.cfg").First()); var textReader = new StreamReader(configFile); - var songs_directory_path = Path.Combine(BasePath, stable_songs_path); + var songsDirectoryPath = Path.Combine(BasePath, stable_songs_path); while (!textReader.EndOfStream) { @@ -46,13 +46,13 @@ namespace osu.Game.IO { var directory = line.Split('=')[1].TrimStart(); if (Path.IsPathFullyQualified(directory)) - songs_directory_path = directory; + songsDirectoryPath = directory; break; } } - return songs_directory_path; + return songsDirectoryPath; } } } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 399bdda491..78c4d4ccad 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -28,7 +28,6 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; -using osu.Framework.Platform; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Collections; From 9efce5717f40e2ade16c4dee2cb89501d6c27c0d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 25 Jan 2021 22:11:50 +0300 Subject: [PATCH 0154/1791] Fix beatmap listing placeholder disappearing on second time display --- osu.Game/Overlays/BeatmapListingOverlay.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 2f7f21e403..b65eaad0a2 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -176,6 +176,9 @@ namespace osu.Game.Overlays loadingLayer.Hide(); lastFetchDisplayedTime = Time.Current; + if (content == currentContent) + return; + var lastContent = currentContent; if (lastContent != null) From 9312de7c2387f4534090304550f3910c54b98e39 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 25 Jan 2021 23:40:26 +0300 Subject: [PATCH 0155/1791] Move online beatmap listing overlay to separate test scene --- ...ingOverlay.cs => TestSceneOnlineBeatmapListingOverlay.cs} | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) rename osu.Game.Tests/Visual/Online/{TestSceneBeatmapListingOverlay.cs => TestSceneOnlineBeatmapListingOverlay.cs} (80%) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneOnlineBeatmapListingOverlay.cs similarity index 80% rename from osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs rename to osu.Game.Tests/Visual/Online/TestSceneOnlineBeatmapListingOverlay.cs index 6cb1687d1f..fe1701a554 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneOnlineBeatmapListingOverlay.cs @@ -6,13 +6,14 @@ using NUnit.Framework; namespace osu.Game.Tests.Visual.Online { - public class TestSceneBeatmapListingOverlay : OsuTestScene + [Description("uses online API")] + public class TestSceneOnlineBeatmapListingOverlay : OsuTestScene { protected override bool UseOnlineAPI => true; private readonly BeatmapListingOverlay overlay; - public TestSceneBeatmapListingOverlay() + public TestSceneOnlineBeatmapListingOverlay() { Add(overlay = new BeatmapListingOverlay()); } From 75d6dbdbb7290c215505e408f15781b96128d929 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 25 Jan 2021 22:18:23 +0300 Subject: [PATCH 0156/1791] Fix beatmap listing placeholder potentially getting disposed --- osu.Game/Overlays/BeatmapListingOverlay.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 0c9c995dd6..2f7f21e403 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -180,18 +180,24 @@ namespace osu.Game.Overlays if (lastContent != null) { - lastContent.FadeOut(100, Easing.OutQuint).Expire(); + lastContent.FadeOut(100, Easing.OutQuint); // Consider the case when the new content is smaller than the last content. // If the auto-size computation is delayed until fade out completes, the background remain high for too long making the resulting transition to the smaller height look weird. // At the same time, if the last content's height is bypassed immediately, there is a period where the new content is at Alpha = 0 when the auto-sized height will be 0. // To resolve both of these issues, the bypass is delayed until a point when the content transitions (fade-in and fade-out) overlap and it looks good to do so. - lastContent.Delay(25).Schedule(() => lastContent.BypassAutoSizeAxes = Axes.Y).Then().Schedule(() => panelTarget.Remove(lastContent)); + lastContent.Delay(25).Schedule(() => lastContent.BypassAutoSizeAxes = Axes.Y).Then().Schedule(() => + { + panelTarget.Remove(lastContent); + + // the content may be reused again (e.g. notFoundContent), clear Y-axis bypass for displaying back properly. + lastContent.BypassAutoSizeAxes = Axes.None; + }); } if (!content.IsAlive) panelTarget.Add(content); - content.FadeIn(200, Easing.OutQuint); + content.FadeInFromZero(200, Easing.OutQuint); currentContent = content; } From c317d6016967a321c70d02ee81d012129e8b5767 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 25 Jan 2021 23:41:05 +0300 Subject: [PATCH 0157/1791] Add offline test scene for beatmap listing overlay --- .../Online/TestSceneBeatmapListingOverlay.cs | 81 +++++++++++++++++++ .../API/Requests/Responses/APIBeatmapSet.cs | 2 +- osu.Game/Overlays/BeatmapListingOverlay.cs | 2 +- 3 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs new file mode 100644 index 0000000000..1349264bf9 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -0,0 +1,81 @@ +// 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.Graphics.Containers; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays; +using osu.Game.Overlays.BeatmapListing; +using osu.Game.Rulesets; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneBeatmapListingOverlay : OsuTestScene + { + private readonly List setsForResponse = new List(); + + private BeatmapListingOverlay overlay; + + [BackgroundDependencyLoader] + private void load() + { + Child = overlay = new BeatmapListingOverlay { State = { Value = Visibility.Visible } }; + + ((DummyAPIAccess)API).HandleRequest = req => + { + if (req is SearchBeatmapSetsRequest searchBeatmapSetsRequest) + { + searchBeatmapSetsRequest.TriggerSuccess(new SearchBeatmapSetsResponse + { + BeatmapSets = setsForResponse, + }); + } + }; + } + + [Test] + public void TestNoBeatmapsPlaceholder() + { + AddStep("fetch for 0 beatmaps", () => fetchFor()); + AddUntilStep("placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + + AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet)); + AddUntilStep("placeholder hidden", () => !overlay.ChildrenOfType().Any()); + + AddStep("fetch for 0 beatmaps", () => fetchFor()); + AddUntilStep("placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + + // fetch once more to ensure nothing happens in displaying placeholder again when it already is present. + AddStep("fetch for 0 beatmaps again", () => fetchFor()); + AddUntilStep("placeholder shown", () => overlay.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + } + + private void fetchFor(params BeatmapSetInfo[] beatmaps) + { + setsForResponse.Clear(); + setsForResponse.AddRange(beatmaps.Select(b => new TestAPIBeatmapSet(b))); + + // trigger arbitrary change for fetching. + overlay.ChildrenOfType().Single().Query.TriggerChange(); + } + + private class TestAPIBeatmapSet : APIBeatmapSet + { + private readonly BeatmapSetInfo beatmapSet; + + public TestAPIBeatmapSet(BeatmapSetInfo beatmapSet) + { + this.beatmapSet = beatmapSet; + } + + public override BeatmapSetInfo ToBeatmapSet(RulesetStore rulesets) => beatmapSet; + } + } +} diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs index bd1800e9f7..45d9c9405f 100644 --- a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs +++ b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs @@ -81,7 +81,7 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"beatmaps")] private IEnumerable beatmaps { get; set; } - public BeatmapSetInfo ToBeatmapSet(RulesetStore rulesets) + public virtual BeatmapSetInfo ToBeatmapSet(RulesetStore rulesets) { var beatmapSet = new BeatmapSetInfo { diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index b65eaad0a2..c5cc0a9c85 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -211,7 +211,7 @@ namespace osu.Game.Overlays base.Dispose(isDisposing); } - private class NotFoundDrawable : CompositeDrawable + public class NotFoundDrawable : CompositeDrawable { public NotFoundDrawable() { From 0d8d0d685219e162e385f0bb506735d7fedcb2e0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 26 Jan 2021 01:03:29 +0300 Subject: [PATCH 0158/1791] Apply alternative way of fixing --- osu.Game/Overlays/BeatmapListingOverlay.cs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 2f7f21e403..fc90968ec4 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -178,21 +178,18 @@ namespace osu.Game.Overlays var lastContent = currentContent; - if (lastContent != null) + // "not found" placeholder is reused, only remove without disposing through expire. + if (lastContent == notFoundContent) + lastContent.FadeOut(100, Easing.OutQuint).Schedule(() => panelTarget.Remove(lastContent)); + else if (lastContent != null) { - lastContent.FadeOut(100, Easing.OutQuint); + lastContent.FadeOut(100, Easing.OutQuint).Expire(); // Consider the case when the new content is smaller than the last content. // If the auto-size computation is delayed until fade out completes, the background remain high for too long making the resulting transition to the smaller height look weird. // At the same time, if the last content's height is bypassed immediately, there is a period where the new content is at Alpha = 0 when the auto-sized height will be 0. // To resolve both of these issues, the bypass is delayed until a point when the content transitions (fade-in and fade-out) overlap and it looks good to do so. - lastContent.Delay(25).Schedule(() => lastContent.BypassAutoSizeAxes = Axes.Y).Then().Schedule(() => - { - panelTarget.Remove(lastContent); - - // the content may be reused again (e.g. notFoundContent), clear Y-axis bypass for displaying back properly. - lastContent.BypassAutoSizeAxes = Axes.None; - }); + lastContent.Delay(25).Schedule(() => lastContent.BypassAutoSizeAxes = Axes.Y).Then().Schedule(() => panelTarget.Remove(lastContent)); } if (!content.IsAlive) From 3307e8357ff6f919ff649c6e8036b12c5d4ffb77 Mon Sep 17 00:00:00 2001 From: Mysfit Date: Tue, 26 Jan 2021 00:36:32 -0500 Subject: [PATCH 0159/1791] DrawableStoryboardSample event method override for SamplePlaybackDisabledChanged --- osu.Game/Skinning/PausableSkinnableSound.cs | 46 +++++++++---------- .../Drawables/DrawableStoryboardSample.cs | 14 +++--- 2 files changed, 29 insertions(+), 31 deletions(-) diff --git a/osu.Game/Skinning/PausableSkinnableSound.cs b/osu.Game/Skinning/PausableSkinnableSound.cs index d8149e76c0..e2794938ad 100644 --- a/osu.Game/Skinning/PausableSkinnableSound.cs +++ b/osu.Game/Skinning/PausableSkinnableSound.cs @@ -18,14 +18,6 @@ namespace osu.Game.Skinning protected bool RequestedPlaying { get; private set; } - /// - /// Whether this is affected by - /// a higher-level 's state changes. - /// By default only looping samples are started/stopped on sample disable - /// to prevent one-time samples from cutting off abruptly. - /// - protected virtual bool AffectedBySamplePlaybackDisable => Looping; - public PausableSkinnableSound() { } @@ -51,24 +43,28 @@ namespace osu.Game.Skinning if (samplePlaybackDisabler != null) { samplePlaybackDisabled.BindTo(samplePlaybackDisabler.SamplePlaybackDisabled); - samplePlaybackDisabled.BindValueChanged(disabled => + samplePlaybackDisabled.BindValueChanged(SamplePlaybackDisabledChanged); + } + } + + protected virtual void SamplePlaybackDisabledChanged(ValueChangedEvent disabled) + { + if (!RequestedPlaying) return; + + // let non-looping samples that have already been started play out to completion (sounds better than abruptly cutting off). + if (!Looping) return; + + cancelPendingStart(); + + if (disabled.NewValue) + base.Stop(); + else + { + // schedule so we don't start playing a sample which is no longer alive. + scheduledStart = Schedule(() => { - if (!RequestedPlaying) return; - if (!AffectedBySamplePlaybackDisable) return; - - cancelPendingStart(); - - if (disabled.NewValue) - base.Stop(); - else - { - // schedule so we don't start playing a sample which is no longer alive. - scheduledStart = Schedule(() => - { - if (RequestedPlaying) - base.Play(); - }); - } + if (RequestedPlaying) + base.Play(); }); } } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs index 5a800a71fd..db8428f062 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs @@ -21,12 +21,6 @@ namespace osu.Game.Storyboards.Drawables public override bool RemoveWhenNotAlive => false; - /// - /// Contrary to , all s are affected - /// by sample disables, as they are oftentimes longer-running sound effects. This also matches stable behaviour. - /// - protected override bool AffectedBySamplePlaybackDisable => true; - public DrawableStoryboardSample(StoryboardSampleInfo sampleInfo) : base(sampleInfo) { @@ -48,6 +42,14 @@ namespace osu.Game.Storyboards.Drawables } } + protected override void SamplePlaybackDisabledChanged(ValueChangedEvent disabled) + { + if (!RequestedPlaying) return; + + if (disabled.NewValue) + Stop(); + } + protected override void Update() { base.Update(); From ca0242debef0c77bde87d7f4fecdc84f3b65a8b5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Jan 2021 15:42:48 +0900 Subject: [PATCH 0160/1791] Tidy up logic to correctly expire in cases where expiry is expected --- osu.Game/Overlays/BeatmapListingOverlay.cs | 27 +++++++++++++--------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index fc90968ec4..de566c92cb 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -178,24 +178,29 @@ namespace osu.Game.Overlays var lastContent = currentContent; - // "not found" placeholder is reused, only remove without disposing through expire. - if (lastContent == notFoundContent) - lastContent.FadeOut(100, Easing.OutQuint).Schedule(() => panelTarget.Remove(lastContent)); - else if (lastContent != null) + if (lastContent != null) { - lastContent.FadeOut(100, Easing.OutQuint).Expire(); + var transform = lastContent.FadeOut(100, Easing.OutQuint); - // Consider the case when the new content is smaller than the last content. - // If the auto-size computation is delayed until fade out completes, the background remain high for too long making the resulting transition to the smaller height look weird. - // At the same time, if the last content's height is bypassed immediately, there is a period where the new content is at Alpha = 0 when the auto-sized height will be 0. - // To resolve both of these issues, the bypass is delayed until a point when the content transitions (fade-in and fade-out) overlap and it looks good to do so. - lastContent.Delay(25).Schedule(() => lastContent.BypassAutoSizeAxes = Axes.Y).Then().Schedule(() => panelTarget.Remove(lastContent)); + if (lastContent == notFoundContent) + { + // not found display may be used multiple times, so don't expire/dispose it. + transform.Schedule(() => panelTarget.Remove(lastContent)); + } + else + { + // Consider the case when the new content is smaller than the last content. + // If the auto-size computation is delayed until fade out completes, the background remain high for too long making the resulting transition to the smaller height look weird. + // At the same time, if the last content's height is bypassed immediately, there is a period where the new content is at Alpha = 0 when the auto-sized height will be 0. + // To resolve both of these issues, the bypass is delayed until a point when the content transitions (fade-in and fade-out) overlap and it looks good to do so. + lastContent.Delay(25).Schedule(() => lastContent.BypassAutoSizeAxes = Axes.Y).Then().Schedule(() => lastContent.Expire()); + } } if (!content.IsAlive) panelTarget.Add(content); - content.FadeInFromZero(200, Easing.OutQuint); + content.FadeInFromZero(200, Easing.OutQuint); currentContent = content; } From 60ae87ec383645e61194bf51f19e94d55a342023 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Jan 2021 16:25:49 +0900 Subject: [PATCH 0161/1791] Add MessagePack package --- osu.Game/osu.Game.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 2b8f81532d..3e971d9d4f 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -22,6 +22,7 @@ + From e4fc6041635c4aebfbb791c6e671324ad9156abf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Jan 2021 16:26:03 +0900 Subject: [PATCH 0162/1791] Setup all multiplayer model classes for MessagePack support --- osu.Game/Online/API/APIMod.cs | 7 ++++++- osu.Game/Online/Multiplayer/MultiplayerRoom.cs | 10 +++++++++- osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs | 7 +++++++ osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs | 8 +++++++- osu.Game/Online/Rooms/BeatmapAvailability.cs | 6 +++++- 5 files changed, 34 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/API/APIMod.cs b/osu.Game/Online/API/APIMod.cs index c8b76b9685..69ce3825ee 100644 --- a/osu.Game/Online/API/APIMod.cs +++ b/osu.Game/Online/API/APIMod.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using Humanizer; +using MessagePack; using Newtonsoft.Json; using osu.Framework.Bindables; using osu.Game.Configuration; @@ -13,16 +14,20 @@ using osu.Game.Rulesets.Mods; namespace osu.Game.Online.API { + [MessagePackObject] public class APIMod : IMod { [JsonProperty("acronym")] + [Key(0)] public string Acronym { get; set; } [JsonProperty("settings")] + [Key(1)] public Dictionary Settings { get; set; } = new Dictionary(); [JsonConstructor] - private APIMod() + [SerializationConstructor] + public APIMod() { } diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoom.cs b/osu.Game/Online/Multiplayer/MultiplayerRoom.cs index 12fcf25ace..c5fa6253ed 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerRoom.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerRoom.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; +using MessagePack; using Newtonsoft.Json; namespace osu.Game.Online.Multiplayer @@ -13,35 +14,42 @@ namespace osu.Game.Online.Multiplayer /// A multiplayer room. /// [Serializable] + [MessagePackObject] public class MultiplayerRoom { /// /// The ID of the room, used for database persistence. /// + [Key(0)] public readonly long RoomID; /// /// The current state of the room (ie. whether it is in progress or otherwise). /// + [Key(1)] public MultiplayerRoomState State { get; set; } /// /// All currently enforced game settings for this room. /// + [Key(2)] public MultiplayerRoomSettings Settings { get; set; } = new MultiplayerRoomSettings(); /// /// All users currently in this room. /// + [Key(3)] public List Users { get; set; } = new List(); /// /// The host of this room, in control of changing room settings. /// + [Key(4)] public MultiplayerRoomUser? Host { get; set; } [JsonConstructor] - public MultiplayerRoom(in long roomId) + [SerializationConstructor] + public MultiplayerRoom(long roomId) { RoomID = roomId; } diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs index 857b38ea60..0ead5db84c 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs @@ -7,22 +7,29 @@ using System; using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; +using MessagePack; using osu.Game.Online.API; namespace osu.Game.Online.Multiplayer { [Serializable] + [MessagePackObject] public class MultiplayerRoomSettings : IEquatable { + [Key(0)] public int BeatmapID { get; set; } + [Key(1)] public int RulesetID { get; set; } + [Key(2)] public string BeatmapChecksum { get; set; } = string.Empty; + [Key(3)] public string Name { get; set; } = "Unnamed room"; [NotNull] + [Key(4)] public IEnumerable Mods { get; set; } = Enumerable.Empty(); public bool Equals(MultiplayerRoomSettings other) diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs b/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs index 2590acbc81..b300be9f60 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs @@ -4,6 +4,7 @@ #nullable enable using System; +using MessagePack; using Newtonsoft.Json; using osu.Game.Online.Rooms; using osu.Game.Users; @@ -11,21 +12,26 @@ using osu.Game.Users; namespace osu.Game.Online.Multiplayer { [Serializable] + [MessagePackObject] public class MultiplayerRoomUser : IEquatable { + [Key(0)] public readonly int UserID; + [Key(1)] public MultiplayerUserState State { get; set; } = MultiplayerUserState.Idle; /// /// The availability state of the current beatmap. /// + [Key(2)] public BeatmapAvailability BeatmapAvailability { get; set; } = BeatmapAvailability.LocallyAvailable(); + [IgnoreMember] public User? User { get; set; } [JsonConstructor] - public MultiplayerRoomUser(in int userId) + public MultiplayerRoomUser(int userId) { UserID = userId; } diff --git a/osu.Game/Online/Rooms/BeatmapAvailability.cs b/osu.Game/Online/Rooms/BeatmapAvailability.cs index e7dbc5f436..38bd236718 100644 --- a/osu.Game/Online/Rooms/BeatmapAvailability.cs +++ b/osu.Game/Online/Rooms/BeatmapAvailability.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using MessagePack; using Newtonsoft.Json; namespace osu.Game.Online.Rooms @@ -9,20 +10,23 @@ namespace osu.Game.Online.Rooms /// /// The local availability information about a certain beatmap for the client. /// + [MessagePackObject] public class BeatmapAvailability : IEquatable { /// /// The beatmap's availability state. /// + [Key(0)] public readonly DownloadState State; /// /// The beatmap's downloading progress, null when not in state. /// + [Key(1)] public readonly double? DownloadProgress; [JsonConstructor] - private BeatmapAvailability(DownloadState state, double? downloadProgress = null) + public BeatmapAvailability(DownloadState state, double? downloadProgress = null) { State = state; DownloadProgress = downloadProgress; From 9537090d28bb29994e5a2902c4091e69bc15856b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Jan 2021 16:39:35 +0900 Subject: [PATCH 0163/1791] Setup all spectator model classes for MessagePack --- osu.Game/Online/Spectator/FrameDataBundle.cs | 4 ++++ osu.Game/Online/Spectator/FrameHeader.cs | 10 +++++++++- osu.Game/Online/Spectator/SpectatorState.cs | 5 +++++ osu.Game/Replays/Legacy/LegacyReplayFrame.cs | 13 +++++++++++++ osu.Game/Rulesets/Replays/ReplayFrame.cs | 4 ++++ 5 files changed, 35 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Spectator/FrameDataBundle.cs b/osu.Game/Online/Spectator/FrameDataBundle.cs index a8d0434324..0e59cdf4ce 100644 --- a/osu.Game/Online/Spectator/FrameDataBundle.cs +++ b/osu.Game/Online/Spectator/FrameDataBundle.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; +using MessagePack; using Newtonsoft.Json; using osu.Game.Replays.Legacy; using osu.Game.Scoring; @@ -12,10 +13,13 @@ using osu.Game.Scoring; namespace osu.Game.Online.Spectator { [Serializable] + [MessagePackObject] public class FrameDataBundle { + [Key(0)] public FrameHeader Header { get; set; } + [Key(1)] public IEnumerable Frames { get; set; } public FrameDataBundle(ScoreInfo score, IEnumerable frames) diff --git a/osu.Game/Online/Spectator/FrameHeader.cs b/osu.Game/Online/Spectator/FrameHeader.cs index 135b356eda..adfcbcd95a 100644 --- a/osu.Game/Online/Spectator/FrameHeader.cs +++ b/osu.Game/Online/Spectator/FrameHeader.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; +using MessagePack; using Newtonsoft.Json; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; @@ -12,31 +13,37 @@ using osu.Game.Scoring; namespace osu.Game.Online.Spectator { [Serializable] + [MessagePackObject] public class FrameHeader { /// /// The current accuracy of the score. /// + [Key(0)] public double Accuracy { get; set; } /// /// The current combo of the score. /// + [Key(1)] public int Combo { get; set; } /// /// The maximum combo achieved up to the current point in time. /// + [Key(2)] public int MaxCombo { get; set; } /// /// Cumulative hit statistics. /// + [Key(3)] public Dictionary Statistics { get; set; } /// /// The time at which this frame was received by the server. /// + [Key(4)] public DateTimeOffset ReceivedTime { get; set; } /// @@ -54,7 +61,8 @@ namespace osu.Game.Online.Spectator } [JsonConstructor] - public FrameHeader(int combo, int maxCombo, double accuracy, Dictionary statistics, DateTimeOffset receivedTime) + [SerializationConstructor] + public FrameHeader(double accuracy, int combo, int maxCombo, Dictionary statistics, DateTimeOffset receivedTime) { Combo = combo; MaxCombo = maxCombo; diff --git a/osu.Game/Online/Spectator/SpectatorState.cs b/osu.Game/Online/Spectator/SpectatorState.cs index 101ce3d5d5..96a875bc14 100644 --- a/osu.Game/Online/Spectator/SpectatorState.cs +++ b/osu.Game/Online/Spectator/SpectatorState.cs @@ -5,18 +5,23 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +using MessagePack; using osu.Game.Online.API; namespace osu.Game.Online.Spectator { [Serializable] + [MessagePackObject] public class SpectatorState : IEquatable { + [Key(0)] public int? BeatmapID { get; set; } + [Key(1)] public int? RulesetID { get; set; } [NotNull] + [Key(2)] public IEnumerable Mods { get; set; } = Enumerable.Empty(); public bool Equals(SpectatorState other) => BeatmapID == other?.BeatmapID && Mods.SequenceEqual(other?.Mods) && RulesetID == other?.RulesetID; diff --git a/osu.Game/Replays/Legacy/LegacyReplayFrame.cs b/osu.Game/Replays/Legacy/LegacyReplayFrame.cs index 74bacae9e1..ab9ccda9b9 100644 --- a/osu.Game/Replays/Legacy/LegacyReplayFrame.cs +++ b/osu.Game/Replays/Legacy/LegacyReplayFrame.cs @@ -1,38 +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 MessagePack; using Newtonsoft.Json; using osu.Game.Rulesets.Replays; using osuTK; namespace osu.Game.Replays.Legacy { + [MessagePackObject] public class LegacyReplayFrame : ReplayFrame { [JsonIgnore] + [IgnoreMember] public Vector2 Position => new Vector2(MouseX ?? 0, MouseY ?? 0); + [Key(1)] public float? MouseX; + + [Key(2)] public float? MouseY; [JsonIgnore] + [IgnoreMember] public bool MouseLeft => MouseLeft1 || MouseLeft2; [JsonIgnore] + [IgnoreMember] public bool MouseRight => MouseRight1 || MouseRight2; [JsonIgnore] + [IgnoreMember] public bool MouseLeft1 => ButtonState.HasFlag(ReplayButtonState.Left1); [JsonIgnore] + [IgnoreMember] public bool MouseRight1 => ButtonState.HasFlag(ReplayButtonState.Right1); [JsonIgnore] + [IgnoreMember] public bool MouseLeft2 => ButtonState.HasFlag(ReplayButtonState.Left2); [JsonIgnore] + [IgnoreMember] public bool MouseRight2 => ButtonState.HasFlag(ReplayButtonState.Right2); + [Key(3)] public ReplayButtonState ButtonState; public LegacyReplayFrame(double time, float? mouseX, float? mouseY, ReplayButtonState buttonState) diff --git a/osu.Game/Rulesets/Replays/ReplayFrame.cs b/osu.Game/Rulesets/Replays/ReplayFrame.cs index 85e068ae79..7de53211a2 100644 --- a/osu.Game/Rulesets/Replays/ReplayFrame.cs +++ b/osu.Game/Rulesets/Replays/ReplayFrame.cs @@ -1,10 +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 MessagePack; + namespace osu.Game.Rulesets.Replays { + [MessagePackObject] public class ReplayFrame { + [Key(0)] public double Time; public ReplayFrame() From 20cfa991bffbe1cb4255b6cc45cda39b16cc28f9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Jan 2021 17:41:21 +0900 Subject: [PATCH 0164/1791] Switch clients to MessagePack mode --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 2 +- osu.Game/Online/Spectator/SpectatorStreamingClient.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 50dc8f661c..3221456e75 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -70,7 +70,7 @@ namespace osu.Game.Online.Multiplayer { options.Headers.Add("Authorization", $"Bearer {api.AccessToken}"); }) - .AddNewtonsoftJsonProtocol(options => { options.PayloadSerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; }) + .AddMessagePackProtocol() .Build(); // this is kind of SILLY diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 344b73f3d9..cc866b7ad9 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -121,7 +121,7 @@ namespace osu.Game.Online.Spectator { options.Headers.Add("Authorization", $"Bearer {api.AccessToken}"); }) - .AddNewtonsoftJsonProtocol(options => { options.PayloadSerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; }) + .AddMessagePackProtocol() .Build(); // until strong typed client support is added, each method must be manually bound (see https://github.com/dotnet/aspnetcore/issues/15198) From 15885c17af58234c63ea154178f2156854e95bd5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Jan 2021 18:07:43 +0900 Subject: [PATCH 0165/1791] Remove unused usings --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 1 - osu.Game/Online/Spectator/SpectatorStreamingClient.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 3221456e75..0d779232d0 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -9,7 +9,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR.Client; using Microsoft.Extensions.DependencyInjection; -using Newtonsoft.Json; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Logging; diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index cc866b7ad9..dac2131035 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -9,7 +9,6 @@ using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.AspNetCore.SignalR.Client; using Microsoft.Extensions.DependencyInjection; -using Newtonsoft.Json; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; From b573c96c079f9065ee4690131f4677eeeb2998be Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Jan 2021 18:59:42 +0900 Subject: [PATCH 0166/1791] Move disconnect logic inside connection loop to ensure previous connection is disposed --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 391658f0d0..cbb91c0832 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -71,14 +71,15 @@ namespace osu.Game.Online.Multiplayer try { - await disconnect(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. var cancellationToken = connectCancelSource.Token; while (api.State.Value == APIState.Online) { + // ensure any previous connection was disposed. + await disconnect(false); + cancellationToken.ThrowIfCancellationRequested(); Logger.Log("Multiplayer client connecting...", LoggingTarget.Network); From a5f3418e561efcf05800d4d2d61f9182091467a9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Jan 2021 19:11:19 +0900 Subject: [PATCH 0167/1791] Avoid tooltip display --- .../Components/TournamentModDisplay.cs | 2 +- osu.Game/Overlays/Mods/ModButton.cs | 16 +++------------- osu.Game/Rulesets/UI/ModIcon.cs | 14 ++++++++++++-- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tournament/Components/TournamentModDisplay.cs b/osu.Game.Tournament/Components/TournamentModDisplay.cs index 3df8550667..fa9ee7edff 100644 --- a/osu.Game.Tournament/Components/TournamentModDisplay.cs +++ b/osu.Game.Tournament/Components/TournamentModDisplay.cs @@ -50,7 +50,7 @@ namespace osu.Game.Tournament.Components if (modIcon == null) return; - AddInternal(new ModIcon(modIcon) + AddInternal(new ModIcon(modIcon, false) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs index ab8efdabcc..8e0d1f5bbd 100644 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ b/osu.Game/Overlays/Mods/ModButton.cs @@ -236,13 +236,13 @@ namespace osu.Game.Overlays.Mods { iconsContainer.AddRange(new[] { - backgroundIcon = new PassThroughTooltipModIcon(Mods[1]) + backgroundIcon = new ModIcon(Mods[1], false) { Origin = Anchor.BottomRight, Anchor = Anchor.BottomRight, Position = new Vector2(1.5f), }, - foregroundIcon = new PassThroughTooltipModIcon(Mods[0]) + foregroundIcon = new ModIcon(Mods[0], false) { Origin = Anchor.BottomRight, Anchor = Anchor.BottomRight, @@ -252,7 +252,7 @@ namespace osu.Game.Overlays.Mods } else { - iconsContainer.Add(foregroundIcon = new PassThroughTooltipModIcon(Mod) + iconsContainer.Add(foregroundIcon = new ModIcon(Mod, false) { Origin = Anchor.Centre, Anchor = Anchor.Centre, @@ -297,15 +297,5 @@ namespace osu.Game.Overlays.Mods Mod = mod; } - - private class PassThroughTooltipModIcon : ModIcon - { - public override string TooltipText => null; - - public PassThroughTooltipModIcon(Mod mod) - : base(mod) - { - } - } } } diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index 8ea6c74349..04a2e052fa 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -16,6 +16,9 @@ using osu.Framework.Bindables; namespace osu.Game.Rulesets.UI { + /// + /// Display the specified mod at a fixed size. + /// public class ModIcon : Container, IHasTooltip { public readonly BindableBool Selected = new BindableBool(); @@ -28,9 +31,10 @@ namespace osu.Game.Rulesets.UI private readonly ModType type; - public virtual string TooltipText => mod.IconTooltip; + public virtual string TooltipText => showTooltip ? mod.IconTooltip : null; private Mod mod; + private readonly bool showTooltip; public Mod Mod { @@ -42,9 +46,15 @@ namespace osu.Game.Rulesets.UI } } - public ModIcon(Mod mod) + /// + /// Construct a new instance. + /// + /// The mod to be displayed + /// Whether a tooltip describing the mod should display on hover. + public ModIcon(Mod mod, bool showTooltip = true) { this.mod = mod ?? throw new ArgumentNullException(nameof(mod)); + this.showTooltip = showTooltip; type = mod.Type; From 64a3c712aa8be74bb2d32fddeb55fc364a3cb0fb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Jan 2021 19:15:19 +0900 Subject: [PATCH 0168/1791] Rename class and add xmldoc --- osu.Game.Tournament/Components/TournamentBeatmapPanel.cs | 2 +- .../{TournamentModDisplay.cs => TournamentModIcon.cs} | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) rename osu.Game.Tournament/Components/{TournamentModDisplay.cs => TournamentModIcon.cs} (86%) diff --git a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs index 8cc4566c08..d1197b1a61 100644 --- a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs +++ b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs @@ -123,7 +123,7 @@ namespace osu.Game.Tournament.Components if (!string.IsNullOrEmpty(mod)) { - AddInternal(new TournamentModDisplay(mod) + AddInternal(new TournamentModIcon(mod) { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, diff --git a/osu.Game.Tournament/Components/TournamentModDisplay.cs b/osu.Game.Tournament/Components/TournamentModIcon.cs similarity index 86% rename from osu.Game.Tournament/Components/TournamentModDisplay.cs rename to osu.Game.Tournament/Components/TournamentModIcon.cs index fa9ee7edff..b53ecc02f8 100644 --- a/osu.Game.Tournament/Components/TournamentModDisplay.cs +++ b/osu.Game.Tournament/Components/TournamentModIcon.cs @@ -14,16 +14,19 @@ using osuTK; namespace osu.Game.Tournament.Components { - public class TournamentModDisplay : CompositeDrawable + /// + /// Mod icon displayed in tournament usages, allowing user overridden graphics. + /// + public class TournamentModIcon : CompositeDrawable { private readonly string modAcronym; [Resolved] private RulesetStore rulesets { get; set; } - public TournamentModDisplay(string mod) + public TournamentModIcon(string modAcronym) { - modAcronym = mod; + this.modAcronym = modAcronym; } [BackgroundDependencyLoader] From 81ab82fafe13df7aa513c5d64f3bb205feac6102 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Jan 2021 19:16:38 +0900 Subject: [PATCH 0169/1791] Tidy up nesting --- .../Components/TournamentModIcon.cs | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tournament/Components/TournamentModIcon.cs b/osu.Game.Tournament/Components/TournamentModIcon.cs index b53ecc02f8..43ac92d285 100644 --- a/osu.Game.Tournament/Components/TournamentModIcon.cs +++ b/osu.Game.Tournament/Components/TournamentModIcon.cs @@ -32,9 +32,9 @@ namespace osu.Game.Tournament.Components [BackgroundDependencyLoader] private void load(TextureStore textures, LadderInfo ladderInfo) { - var texture = textures.Get($"mods/{modAcronym}"); + var customTexture = textures.Get($"mods/{modAcronym}"); - if (texture != null) + if (customTexture != null) { AddInternal(new Sprite { @@ -42,24 +42,24 @@ namespace osu.Game.Tournament.Components RelativeSizeAxes = Axes.Both, Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, - Texture = texture + Texture = customTexture }); + + return; } - else + + var ruleset = rulesets.GetRuleset(ladderInfo.Ruleset.Value?.ID ?? 0); + var modIcon = ruleset?.CreateInstance().GetAllMods().FirstOrDefault(mod => mod.Acronym == modAcronym); + + if (modIcon == null) + return; + + AddInternal(new ModIcon(modIcon, false) { - var ruleset = rulesets.GetRuleset(ladderInfo.Ruleset.Value?.ID ?? 0); - var modIcon = ruleset?.CreateInstance().GetAllMods().FirstOrDefault(mod => mod.Acronym == modAcronym); - - if (modIcon == null) - return; - - AddInternal(new ModIcon(modIcon, false) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(0.5f) - }); - } + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(0.5f) + }); } } } From 8c3b0a316737eb359c4d00e03001ab7167ed0633 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 26 Jan 2021 22:47:37 +0900 Subject: [PATCH 0170/1791] Fix TaskChain performing the action in-line, add test --- osu.Game.Tests/NonVisual/TaskChainTest.cs | 83 +++++++++++++++++++++++ osu.Game/Utils/TaskChain.cs | 28 ++++++-- 2 files changed, 106 insertions(+), 5 deletions(-) create mode 100644 osu.Game.Tests/NonVisual/TaskChainTest.cs diff --git a/osu.Game.Tests/NonVisual/TaskChainTest.cs b/osu.Game.Tests/NonVisual/TaskChainTest.cs new file mode 100644 index 0000000000..d561fb4c1b --- /dev/null +++ b/osu.Game.Tests/NonVisual/TaskChainTest.cs @@ -0,0 +1,83 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Threading; +using System.Threading.Tasks; +using NUnit.Framework; +using osu.Game.Utils; + +namespace osu.Game.Tests.NonVisual +{ + [TestFixture] + public class TaskChainTest + { + private TaskChain taskChain; + private int currentTask; + + [SetUp] + public void Setup() + { + taskChain = new TaskChain(); + currentTask = 0; + } + + [Test] + public async Task TestChainedTasksRunSequentially() + { + var task1 = addTask(); + var task2 = addTask(); + var task3 = addTask(); + + task3.mutex.Set(); + task2.mutex.Set(); + task1.mutex.Set(); + + await Task.WhenAll(task1.task, task2.task, task3.task); + + Assert.That(task1.task.Result, Is.EqualTo(1)); + Assert.That(task2.task.Result, Is.EqualTo(2)); + Assert.That(task3.task.Result, Is.EqualTo(3)); + } + + [Test] + public async Task TestChainedTaskWithIntermediateCancelRunsInSequence() + { + var task1 = addTask(); + var task2 = addTask(); + var task3 = addTask(); + + // Cancel task2, allow task3 to complete. + task2.cancellation.Cancel(); + task2.mutex.Set(); + task3.mutex.Set(); + + // Allow task3 to potentially complete. + Thread.Sleep(1000); + + // Allow task1 to complete. + task1.mutex.Set(); + + // Wait on both tasks. + await Task.WhenAll(task1.task, task3.task); + + Assert.That(task1.task.Result, Is.EqualTo(1)); + Assert.That(task2.task.IsCompleted, Is.False); + Assert.That(task3.task.Result, Is.EqualTo(2)); + } + + private (Task task, ManualResetEventSlim mutex, CancellationTokenSource cancellation) addTask() + { + var mutex = new ManualResetEventSlim(false); + var cancellationSource = new CancellationTokenSource(); + var completionSource = new TaskCompletionSource(); + + taskChain.Add(() => + { + mutex.Wait(CancellationToken.None); + completionSource.SetResult(Interlocked.Increment(ref currentTask)); + }, cancellationSource.Token); + + return (completionSource.Task, mutex, cancellationSource); + } + } +} diff --git a/osu.Game/Utils/TaskChain.cs b/osu.Game/Utils/TaskChain.cs index 64d523bd3d..2bc2c00e28 100644 --- a/osu.Game/Utils/TaskChain.cs +++ b/osu.Game/Utils/TaskChain.cs @@ -4,6 +4,7 @@ #nullable enable using System; +using System.Threading; using System.Threading.Tasks; namespace osu.Game.Utils @@ -19,15 +20,32 @@ namespace osu.Game.Utils /// /// Adds a new task to the end of this . /// - /// The task creation function. + /// The action to be executed. + /// The for this task. Does not affect further tasks in the chain. /// The awaitable . - public Task Add(Func taskFunc) + public Task Add(Action action, CancellationToken cancellationToken = default) { lock (currentTaskLock) { - currentTask = currentTask == null - ? taskFunc() - : currentTask.ContinueWith(_ => taskFunc()).Unwrap(); + // Note: Attaching the cancellation token to the continuation could lead to re-ordering of tasks in the chain. + // Therefore, the cancellation token is not used to cancel the continuation but only the run of each task. + if (currentTask == null) + { + currentTask = Task.Run(() => + { + cancellationToken.ThrowIfCancellationRequested(); + action(); + }, CancellationToken.None); + } + else + { + currentTask = currentTask.ContinueWith(_ => + { + cancellationToken.ThrowIfCancellationRequested(); + action(); + }, CancellationToken.None); + } + return currentTask; } } From 085115cba538483ed2bbbfdac848a70a6ffdabb8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 26 Jan 2021 22:49:01 +0900 Subject: [PATCH 0171/1791] Make threading even more thread safe --- osu.Game/Extensions/TaskExtensions.cs | 4 +- .../Multiplayer/StatefulMultiplayerClient.cs | 82 ++++++++++++------- 2 files changed, 55 insertions(+), 31 deletions(-) diff --git a/osu.Game/Extensions/TaskExtensions.cs b/osu.Game/Extensions/TaskExtensions.cs index 4138c2757a..24f0188cf0 100644 --- a/osu.Game/Extensions/TaskExtensions.cs +++ b/osu.Game/Extensions/TaskExtensions.cs @@ -21,9 +21,9 @@ namespace osu.Game.Extensions /// Whether errors should be logged as errors visible to users, or as debug messages. /// Logging as debug will essentially silence the errors on non-release builds. /// - public static void CatchUnobservedExceptions(this Task task, bool logAsError = false) + public static Task CatchUnobservedExceptions(this Task task, bool logAsError = false) { - task.ContinueWith(t => + return task.ContinueWith(t => { Exception? exception = t.Exception?.AsSingular(); if (logAsError) diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index 0e736ed7c6..3d8ab4b4c7 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -110,37 +110,49 @@ namespace osu.Game.Online.Multiplayer } private readonly TaskChain joinOrLeaveTaskChain = new TaskChain(); + private CancellationTokenSource? joinCancellationSource; /// /// Joins the for a given API . /// /// The API . - public async Task JoinRoom(Room room) => await joinOrLeaveTaskChain.Add(async () => + public async Task JoinRoom(Room room) { - if (Room != null) - throw new InvalidOperationException("Cannot join a multiplayer room while already in one."); + var cancellationSource = new CancellationTokenSource(); - Debug.Assert(room.RoomID.Value != null); - - // Join the server-side room. - var joinedRoom = await JoinRoom(room.RoomID.Value.Value); - Debug.Assert(joinedRoom != null); - - // Populate users. - Debug.Assert(joinedRoom.Users != null); - await Task.WhenAll(joinedRoom.Users.Select(PopulateUser)); - - // Update the stored room (must be done on update thread for thread-safety). await scheduleAsync(() => { - Room = joinedRoom; - apiRoom = room; - playlistItemId = room.Playlist.SingleOrDefault()?.ID ?? 0; - }); + joinCancellationSource?.Cancel(); + joinCancellationSource = cancellationSource; + }, CancellationToken.None); - // Update room settings. - await updateLocalRoomSettings(joinedRoom.Settings); - }); + await joinOrLeaveTaskChain.Add(async () => + { + if (Room != null) + throw new InvalidOperationException("Cannot join a multiplayer room while already in one."); + + Debug.Assert(room.RoomID.Value != null); + + // Join the server-side room. + var joinedRoom = await JoinRoom(room.RoomID.Value.Value); + Debug.Assert(joinedRoom != null); + + // Populate users. + Debug.Assert(joinedRoom.Users != null); + await Task.WhenAll(joinedRoom.Users.Select(PopulateUser)); + + // Update the stored room (must be done on update thread for thread-safety). + await scheduleAsync(() => + { + Room = joinedRoom; + apiRoom = room; + playlistItemId = room.Playlist.SingleOrDefault()?.ID ?? 0; + }, cancellationSource.Token); + + // Update room settings. + await updateLocalRoomSettings(joinedRoom.Settings, cancellationSource.Token); + }, cancellationSource.Token); + } /// /// Joins the with a given ID. @@ -151,14 +163,15 @@ namespace osu.Game.Online.Multiplayer public Task LeaveRoom() { - if (Room == null) - return Task.FromCanceled(new CancellationToken(true)); - // Leaving rooms is expected to occur instantaneously whilst the operation is finalised in the background. // However a few members need to be reset immediately to prevent other components from entering invalid states whilst the operation hasn't yet completed. // For example, if a room was left and the user immediately pressed the "create room" button, then the user could be taken into the lobby if the value of Room is not reset in time. var scheduledReset = scheduleAsync(() => { + // The join may have not completed yet, so certain tasks that either update the room or reference the room should be cancelled. + // This includes the setting of Room itself along with the initial update of the room settings on join. + joinCancellationSource?.Cancel(); + apiRoom = null; Room = null; CurrentMatchPlayingUserIds.Clear(); @@ -169,7 +182,7 @@ namespace osu.Game.Online.Multiplayer return joinOrLeaveTaskChain.Add(async () => { await scheduledReset; - await LeaveRoomInternal(); + await LeaveRoomInternal().CatchUnobservedExceptions(); }); } @@ -455,7 +468,8 @@ namespace osu.Game.Online.Multiplayer /// This updates both the joined and the respective API . /// /// The new to update from. - private Task updateLocalRoomSettings(MultiplayerRoomSettings settings) => scheduleAsync(() => + /// The to cancel the update. + private Task updateLocalRoomSettings(MultiplayerRoomSettings settings, CancellationToken cancellationToken = default) => scheduleAsync(() => { if (Room == null) return; @@ -473,10 +487,17 @@ namespace osu.Game.Online.Multiplayer RoomUpdated?.Invoke(); var req = new GetBeatmapSetRequest(settings.BeatmapID, BeatmapSetLookupType.BeatmapId); - req.Success += res => updatePlaylist(settings, res); + + req.Success += res => + { + if (cancellationToken.IsCancellationRequested) + return; + + updatePlaylist(settings, res); + }; api.Queue(req); - }); + }, cancellationToken); private void updatePlaylist(MultiplayerRoomSettings settings, APIBeatmapSet onlineSet) { @@ -524,12 +545,15 @@ namespace osu.Game.Online.Multiplayer CurrentMatchPlayingUserIds.Remove(userId); } - private Task scheduleAsync(Action action) + private Task scheduleAsync(Action action, CancellationToken cancellationToken = default) { var tcs = new TaskCompletionSource(); Scheduler.Add(() => { + if (cancellationToken.IsCancellationRequested) + return; + try { action(); From 248989b3ebe9de5a1b24341774670be6533825d3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 27 Jan 2021 01:20:50 +0900 Subject: [PATCH 0172/1791] wip --- osu.Game.Tests/NonVisual/TaskChainTest.cs | 38 ++++++++++++++++-- .../Multiplayer/StatefulMultiplayerClient.cs | 2 +- osu.Game/Utils/TaskChain.cs | 39 +++++++------------ 3 files changed, 51 insertions(+), 28 deletions(-) diff --git a/osu.Game.Tests/NonVisual/TaskChainTest.cs b/osu.Game.Tests/NonVisual/TaskChainTest.cs index d561fb4c1b..0a56468818 100644 --- a/osu.Game.Tests/NonVisual/TaskChainTest.cs +++ b/osu.Game.Tests/NonVisual/TaskChainTest.cs @@ -4,6 +4,7 @@ using System.Threading; using System.Threading.Tasks; using NUnit.Framework; +using osu.Game.Extensions; using osu.Game.Utils; namespace osu.Game.Tests.NonVisual @@ -13,14 +14,22 @@ namespace osu.Game.Tests.NonVisual { private TaskChain taskChain; private int currentTask; + private CancellationTokenSource globalCancellationToken; [SetUp] public void Setup() { + globalCancellationToken = new CancellationTokenSource(); taskChain = new TaskChain(); currentTask = 0; } + [TearDown] + public void TearDown() + { + globalCancellationToken?.Cancel(); + } + [Test] public async Task TestChainedTasksRunSequentially() { @@ -65,17 +74,40 @@ namespace osu.Game.Tests.NonVisual Assert.That(task3.task.Result, Is.EqualTo(2)); } + [Test] + public async Task TestChainedTaskDoesNotCompleteBeforeChildTasks() + { + var mutex = new ManualResetEventSlim(false); + + var task = taskChain.Add(async () => + { + await Task.Run(() => mutex.Wait(globalCancellationToken.Token)).CatchUnobservedExceptions(); + }); + + // Allow task to potentially complete + Thread.Sleep(1000); + + Assert.That(task.IsCompleted, Is.False); + + // Allow the task to complete. + mutex.Set(); + + await task; + } + private (Task task, ManualResetEventSlim mutex, CancellationTokenSource cancellation) addTask() { var mutex = new ManualResetEventSlim(false); - var cancellationSource = new CancellationTokenSource(); var completionSource = new TaskCompletionSource(); + var cancellationSource = new CancellationTokenSource(); + var token = CancellationTokenSource.CreateLinkedTokenSource(cancellationSource.Token, globalCancellationToken.Token); + taskChain.Add(() => { - mutex.Wait(CancellationToken.None); + mutex.Wait(globalCancellationToken.Token); completionSource.SetResult(Interlocked.Increment(ref currentTask)); - }, cancellationSource.Token); + }, token.Token); return (completionSource.Task, mutex, cancellationSource); } diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index 3d8ab4b4c7..5c6a0d34e0 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -182,7 +182,7 @@ namespace osu.Game.Online.Multiplayer return joinOrLeaveTaskChain.Add(async () => { await scheduledReset; - await LeaveRoomInternal().CatchUnobservedExceptions(); + await LeaveRoomInternal(); }); } diff --git a/osu.Game/Utils/TaskChain.cs b/osu.Game/Utils/TaskChain.cs index 2bc2c00e28..30aea7578f 100644 --- a/osu.Game/Utils/TaskChain.cs +++ b/osu.Game/Utils/TaskChain.cs @@ -14,8 +14,8 @@ namespace osu.Game.Utils /// public class TaskChain { - private readonly object currentTaskLock = new object(); - private Task? currentTask; + private readonly object finalTaskLock = new object(); + private Task? finalTask; /// /// Adds a new task to the end of this . @@ -23,31 +23,22 @@ namespace osu.Game.Utils /// The action to be executed. /// The for this task. Does not affect further tasks in the chain. /// The awaitable . - public Task Add(Action action, CancellationToken cancellationToken = default) + public async Task Add(Action action, CancellationToken cancellationToken = default) { - lock (currentTaskLock) - { - // Note: Attaching the cancellation token to the continuation could lead to re-ordering of tasks in the chain. - // Therefore, the cancellation token is not used to cancel the continuation but only the run of each task. - if (currentTask == null) - { - currentTask = Task.Run(() => - { - cancellationToken.ThrowIfCancellationRequested(); - action(); - }, CancellationToken.None); - } - else - { - currentTask = currentTask.ContinueWith(_ => - { - cancellationToken.ThrowIfCancellationRequested(); - action(); - }, CancellationToken.None); - } + Task? previousTask; + Task currentTask; - return currentTask; + lock (finalTaskLock) + { + previousTask = finalTask; + finalTask = currentTask = new Task(action, cancellationToken); } + + if (previousTask != null) + await previousTask; + + currentTask.Start(); + await currentTask; } } } From 9f9206726a0cd9cbae347ed26264434f37a86738 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Tue, 26 Jan 2021 18:11:54 +0100 Subject: [PATCH 0173/1791] Fix typos. --- osu.Game/Beatmaps/BeatmapManager.cs | 4 ++-- osu.Game/Database/ArchiveModelManager.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index a455f676b3..43b2486ac5 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -66,9 +66,9 @@ namespace osu.Game.Beatmaps protected override bool CheckStableDirectoryExists(StableStorage stableStorage) => stableStorage.GetSongStorage().ExistsDirectory("."); - protected override IEnumerable GetStableImportPaths(StableStorage stableStoage) + protected override IEnumerable GetStableImportPaths(StableStorage stableStorage) { - var songStorage = stableStoage.GetSongStorage(); + var songStorage = stableStorage.GetSongStorage(); return songStorage.GetDirectories(".").Select(path => songStorage.GetFullPath(path)); } diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 516f70c700..99301b6c68 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -645,8 +645,8 @@ namespace osu.Game.Database /// /// Select paths to import from stable. Default implementation iterates all directories in . /// - protected virtual IEnumerable GetStableImportPaths(StableStorage stableStoage) => stableStoage.GetDirectories(ImportFromStablePath) - .Select(path => stableStoage.GetFullPath(path)); + protected virtual IEnumerable GetStableImportPaths(StableStorage stableStorage) => stableStorage.GetDirectories(ImportFromStablePath) + .Select(path => stableStorage.GetFullPath(path)); /// /// Whether this specified path should be removed after successful import. From 043385f91928204cba2a292469eecfd65fd086a9 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Tue, 26 Jan 2021 18:26:01 +0100 Subject: [PATCH 0174/1791] Rename const and fix unintended tabbing. --- osu.Game/Database/ArchiveModelManager.cs | 2 +- osu.Game/IO/StableStorage.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 99301b6c68..ae1608d801 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -646,7 +646,7 @@ namespace osu.Game.Database /// Select paths to import from stable. Default implementation iterates all directories in . /// protected virtual IEnumerable GetStableImportPaths(StableStorage stableStorage) => stableStorage.GetDirectories(ImportFromStablePath) - .Select(path => stableStorage.GetFullPath(path)); + .Select(path => stableStorage.GetFullPath(path)); /// /// Whether this specified path should be removed after successful import. diff --git a/osu.Game/IO/StableStorage.cs b/osu.Game/IO/StableStorage.cs index 85af92621b..88a087087e 100644 --- a/osu.Game/IO/StableStorage.cs +++ b/osu.Game/IO/StableStorage.cs @@ -14,7 +14,7 @@ namespace osu.Game.IO /// public class StableStorage : DesktopStorage { - private const string stable_songs_path = "Songs"; + private const string stable_default_songs_path = "Songs"; private readonly DesktopGameHost host; private readonly string songsPath; @@ -36,7 +36,7 @@ namespace osu.Game.IO var configFile = GetStream(GetFiles(".", "osu!.*.cfg").First()); var textReader = new StreamReader(configFile); - var songsDirectoryPath = Path.Combine(BasePath, stable_songs_path); + var songsDirectoryPath = Path.Combine(BasePath, stable_default_songs_path); while (!textReader.EndOfStream) { From 2a2b6f347e7e8af533e3a9053497fe7ee77898eb Mon Sep 17 00:00:00 2001 From: Lucas A Date: Tue, 26 Jan 2021 19:07:05 +0100 Subject: [PATCH 0175/1791] Use a lazy for delegating Songs directory locating until it is actually used. --- osu.Game/IO/StableStorage.cs | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/osu.Game/IO/StableStorage.cs b/osu.Game/IO/StableStorage.cs index 88a087087e..ebceba6ce0 100644 --- a/osu.Game/IO/StableStorage.cs +++ b/osu.Game/IO/StableStorage.cs @@ -17,38 +17,43 @@ namespace osu.Game.IO private const string stable_default_songs_path = "Songs"; private readonly DesktopGameHost host; - private readonly string songsPath; + private readonly Lazy songsPath; public StableStorage(string path, DesktopGameHost host) : base(path, host) { this.host = host; - songsPath = locateSongsDirectory(); + songsPath = new Lazy(locateSongsDirectory); } /// /// Returns a pointing to the osu-stable Songs directory. /// - public Storage GetSongStorage() => new DesktopStorage(songsPath, host); + public Storage GetSongStorage() => new DesktopStorage(songsPath.Value, host); private string locateSongsDirectory() { - var configFile = GetStream(GetFiles(".", "osu!.*.cfg").First()); - var textReader = new StreamReader(configFile); - var songsDirectoryPath = Path.Combine(BasePath, stable_default_songs_path); - while (!textReader.EndOfStream) + var configFile = GetFiles(".", "osu!.*.cfg").FirstOrDefault(); + + if (configFile == null) + return songsDirectoryPath; + + using (var textReader = new StreamReader(GetStream(configFile))) { - var line = textReader.ReadLine(); + string line; - if (line?.StartsWith("BeatmapDirectory", StringComparison.OrdinalIgnoreCase) == true) + while ((line = textReader.ReadLine()) != null) { - var directory = line.Split('=')[1].TrimStart(); - if (Path.IsPathFullyQualified(directory)) - songsDirectoryPath = directory; + if (line.StartsWith("BeatmapDirectory", StringComparison.OrdinalIgnoreCase)) + { + var directory = line.Split('=')[1].TrimStart(); + if (Path.IsPathFullyQualified(directory)) + songsDirectoryPath = directory; - break; + break; + } } } From 383c40b99268b350e35d955b638781c75e09e682 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Tue, 26 Jan 2021 20:35:42 +0100 Subject: [PATCH 0176/1791] Address remaining reviews suggestions. --- osu.Game/Beatmaps/BeatmapManager.cs | 2 +- osu.Game/Database/ArchiveModelManager.cs | 6 +++--- osu.Game/IO/StableStorage.cs | 5 +++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 43b2486ac5..4825569ee4 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -64,7 +64,7 @@ namespace osu.Game.Beatmaps protected override string[] HashableFileTypes => new[] { ".osu" }; - protected override bool CheckStableDirectoryExists(StableStorage stableStorage) => stableStorage.GetSongStorage().ExistsDirectory("."); + protected override bool StableDirectoryExists(StableStorage stableStorage) => stableStorage.GetSongStorage().ExistsDirectory("."); protected override IEnumerable GetStableImportPaths(StableStorage stableStorage) { diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index ae1608d801..fd94660a4b 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -640,10 +640,10 @@ namespace osu.Game.Database /// /// Checks for the existence of an osu-stable directory. /// - protected virtual bool CheckStableDirectoryExists(StableStorage stableStorage) => stableStorage.ExistsDirectory(ImportFromStablePath); + protected virtual bool StableDirectoryExists(StableStorage stableStorage) => stableStorage.ExistsDirectory(ImportFromStablePath); /// - /// Select paths to import from stable. Default implementation iterates all directories in . + /// Select paths to import from stable where all paths should be absolute. Default implementation iterates all directories in . /// protected virtual IEnumerable GetStableImportPaths(StableStorage stableStorage) => stableStorage.GetDirectories(ImportFromStablePath) .Select(path => stableStorage.GetFullPath(path)); @@ -668,7 +668,7 @@ namespace osu.Game.Database return Task.CompletedTask; } - if (!CheckStableDirectoryExists(stable)) + if (!StableDirectoryExists(stable)) { // This handles situations like when the user does not have a Skins folder Logger.Log($"No {ImportFromStablePath} folder available in osu!stable installation", LoggingTarget.Information, LogLevel.Error); diff --git a/osu.Game/IO/StableStorage.cs b/osu.Game/IO/StableStorage.cs index ebceba6ce0..f86b18c724 100644 --- a/osu.Game/IO/StableStorage.cs +++ b/osu.Game/IO/StableStorage.cs @@ -35,12 +35,13 @@ namespace osu.Game.IO { var songsDirectoryPath = Path.Combine(BasePath, stable_default_songs_path); - var configFile = GetFiles(".", "osu!.*.cfg").FirstOrDefault(); + var configFile = GetFiles(".", "osu!.*.cfg").SingleOrDefault(); if (configFile == null) return songsDirectoryPath; - using (var textReader = new StreamReader(GetStream(configFile))) + using (var stream = GetStream(configFile)) + using (var textReader = new StreamReader(stream)) { string line; From 4d4d97661e7a8bbe924bdf4d2d5a414f017edb3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 26 Jan 2021 21:26:50 +0100 Subject: [PATCH 0177/1791] Fix connection loop always getting a cancelled token --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index cbb91c0832..319a8f7170 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -71,15 +71,16 @@ namespace osu.Game.Online.Multiplayer try { - // 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. - var cancellationToken = connectCancelSource.Token; - while (api.State.Value == APIState.Online) { // ensure any previous connection was disposed. + // this will also create a new cancellation token source. await disconnect(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. + var cancellationToken = connectCancelSource.Token; + cancellationToken.ThrowIfCancellationRequested(); Logger.Log("Multiplayer client connecting...", LoggingTarget.Network); From 690feb1c1e60aa563cdabad181344fda1dc5d62d Mon Sep 17 00:00:00 2001 From: Mysfit Date: Tue, 26 Jan 2021 23:08:51 -0500 Subject: [PATCH 0178/1791] Allow looping storyboard samples to follow the base samplePlaybackDisabled event logic. --- .../Storyboards/Drawables/DrawableStoryboardSample.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs index db8428f062..9041c10640 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs @@ -46,8 +46,15 @@ namespace osu.Game.Storyboards.Drawables { if (!RequestedPlaying) return; - if (disabled.NewValue) - Stop(); + // non-looping storyboard samples should be stopped immediately when sample playback is disabled + if (!Looping) + { + if (disabled.NewValue) + Stop(); + } + else + base.SamplePlaybackDisabledChanged(disabled); + } protected override void Update() From ee89aa159cdc905c1c279080ac3d0333b105cfa4 Mon Sep 17 00:00:00 2001 From: Mysfit Date: Tue, 26 Jan 2021 23:12:26 -0500 Subject: [PATCH 0179/1791] Removed blank line --- osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs index 9041c10640..fcbffda227 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs @@ -54,7 +54,6 @@ namespace osu.Game.Storyboards.Drawables } else base.SamplePlaybackDisabledChanged(disabled); - } protected override void Update() From a800955bb1c9519732f1bf2d72ab3bd0d5840bfb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 27 Jan 2021 19:46:25 +0900 Subject: [PATCH 0180/1791] Add mod validation utilities --- osu.Game.Tests/Mods/ModValidationTest.cs | 64 +++++++++++++ osu.Game.Tests/osu.Game.Tests.csproj | 1 + osu.Game/Utils/ModValidation.cs | 116 +++++++++++++++++++++++ 3 files changed, 181 insertions(+) create mode 100644 osu.Game.Tests/Mods/ModValidationTest.cs create mode 100644 osu.Game/Utils/ModValidation.cs diff --git a/osu.Game.Tests/Mods/ModValidationTest.cs b/osu.Game.Tests/Mods/ModValidationTest.cs new file mode 100644 index 0000000000..c7a7e242e9 --- /dev/null +++ b/osu.Game.Tests/Mods/ModValidationTest.cs @@ -0,0 +1,64 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Moq; +using NUnit.Framework; +using osu.Game.Rulesets.Mods; +using osu.Game.Utils; + +namespace osu.Game.Tests.Mods +{ + [TestFixture] + public class ModValidationTest + { + [Test] + public void TestModIsCompatibleByItself() + { + var mod = new Mock(); + Assert.That(ModValidation.CheckCompatible(new[] { mod.Object })); + } + + [Test] + public void TestIncompatibleThroughTopLevel() + { + var mod1 = new Mock(); + var mod2 = new Mock(); + + mod1.Setup(m => m.IncompatibleMods).Returns(new[] { mod2.Object.GetType() }); + + // Test both orderings. + Assert.That(ModValidation.CheckCompatible(new[] { mod1.Object, mod2.Object }), Is.False); + Assert.That(ModValidation.CheckCompatible(new[] { mod2.Object, mod1.Object }), Is.False); + } + + [Test] + public void TestIncompatibleThroughMultiMod() + { + var mod1 = new Mock(); + + // The nested mod. + var mod2 = new Mock(); + mod2.Setup(m => m.IncompatibleMods).Returns(new[] { mod1.Object.GetType() }); + + var multiMod = new MultiMod(new MultiMod(mod2.Object)); + + // Test both orderings. + Assert.That(ModValidation.CheckCompatible(new[] { multiMod, mod1.Object }), Is.False); + Assert.That(ModValidation.CheckCompatible(new[] { mod1.Object, multiMod }), Is.False); + } + + [Test] + public void TestAllowedThroughMostDerivedType() + { + var mod = new Mock(); + Assert.That(ModValidation.CheckAllowed(new[] { mod.Object }, new[] { mod.Object.GetType() })); + } + + [Test] + public void TestNotAllowedThroughBaseType() + { + var mod = new Mock(); + Assert.That(ModValidation.CheckAllowed(new[] { mod.Object }, new[] { typeof(Mod) }), Is.False); + } + } +} diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index c0c0578391..d29ed94b5f 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -7,6 +7,7 @@ + WinExe diff --git a/osu.Game/Utils/ModValidation.cs b/osu.Game/Utils/ModValidation.cs new file mode 100644 index 0000000000..0c4d58ab2e --- /dev/null +++ b/osu.Game/Utils/ModValidation.cs @@ -0,0 +1,116 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Extensions.TypeExtensions; +using osu.Game.Rulesets.Mods; + +#nullable enable + +namespace osu.Game.Utils +{ + /// + /// A set of utilities to validate combinations. + /// + public static class ModValidation + { + /// + /// Checks that all s are compatible with each-other, and that all appear within a set of allowed types. + /// + /// + /// The allowed types must contain exact types for the respective s to be allowed. + /// + /// The s to check. + /// The set of allowed types. + /// Whether all s are compatible with each-other and appear in the set of allowed types. + public static bool CheckCompatibleAndAllowed(IEnumerable combination, IEnumerable allowedTypes) + { + // Prevent multiple-enumeration. + var combinationList = combination as ICollection ?? combination.ToArray(); + return CheckCompatible(combinationList) && CheckAllowed(combinationList, allowedTypes); + } + + /// + /// Checks that all s in a combination are compatible with each-other. + /// + /// The combination to check. + /// Whether all s in the combination are compatible with each-other. + public static bool CheckCompatible(IEnumerable combination) + { + var incompatibleTypes = new HashSet(); + var incomingTypes = new HashSet(); + + foreach (var mod in combination.SelectMany(flattenMod)) + { + // Add the new mod incompatibilities, checking whether any match the existing mod types. + foreach (var t in mod.IncompatibleMods) + { + if (incomingTypes.Contains(t)) + return false; + + incompatibleTypes.Add(t); + } + + // Add the new mod types, checking whether any match the incompatible types. + foreach (var t in mod.GetType().EnumerateBaseTypes()) + { + if (incomingTypes.Contains(t)) + return false; + + incomingTypes.Add(t); + } + } + + return true; + } + + /// + /// Checks that all s in a combination appear within a set of allowed types. + /// + /// + /// The set of allowed types must contain exact types for the respective s to be allowed. + /// + /// The combination to check. + /// The set of allowed types. + /// Whether all s in the combination are allowed. + public static bool CheckAllowed(IEnumerable combination, IEnumerable allowedTypes) + { + var allowedSet = new HashSet(allowedTypes); + + return combination.SelectMany(flattenMod) + .All(m => allowedSet.Contains(m.GetType())); + } + + /// + /// Determines whether a is in a set of incompatible types. + /// + /// + /// A can be incompatible through its most-declared type or any of its base types. + /// + /// The to test. + /// The set of incompatible types. + /// Whether the given is incompatible. + private static bool isModIncompatible(Mod mod, ICollection incompatibleTypes) + => flattenMod(mod) + .SelectMany(m => m.GetType().EnumerateBaseTypes()) + .Any(incompatibleTypes.Contains); + + /// + /// Flattens a , returning a set of s in-place of any s. + /// + /// The to flatten. + /// A set of singular "flattened" s + private static IEnumerable flattenMod(Mod mod) + { + if (mod is MultiMod multi) + { + foreach (var m in multi.Mods.SelectMany(flattenMod)) + yield return m; + } + else + yield return mod; + } + } +} From fcfb0d52c2d1d605ac3b8510cdffa01a3714e928 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Jan 2021 19:50:16 +0900 Subject: [PATCH 0181/1791] Proposal to use extension method instead of TaskChain class --- osu.Game.Tests/NonVisual/TaskChainTest.cs | 19 ++++++++--- osu.Game/Extensions/TaskExtensions.cs | 39 +++++++++++++++++++++++ 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/NonVisual/TaskChainTest.cs b/osu.Game.Tests/NonVisual/TaskChainTest.cs index 0a56468818..bd4f15a6eb 100644 --- a/osu.Game.Tests/NonVisual/TaskChainTest.cs +++ b/osu.Game.Tests/NonVisual/TaskChainTest.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.Threading; using System.Threading.Tasks; using NUnit.Framework; @@ -12,15 +13,17 @@ namespace osu.Game.Tests.NonVisual [TestFixture] public class TaskChainTest { - private TaskChain taskChain; + private Task taskChain; + private int currentTask; private CancellationTokenSource globalCancellationToken; [SetUp] public void Setup() { + taskChain = Task.CompletedTask; + globalCancellationToken = new CancellationTokenSource(); - taskChain = new TaskChain(); currentTask = 0; } @@ -79,9 +82,15 @@ namespace osu.Game.Tests.NonVisual { var mutex = new ManualResetEventSlim(false); - var task = taskChain.Add(async () => + var task = taskChain.ContinueWithSequential(async () => { - await Task.Run(() => mutex.Wait(globalCancellationToken.Token)).CatchUnobservedExceptions(); + try + { + await Task.Run(() => mutex.Wait(globalCancellationToken.Token)); + } + catch (OperationCanceledException) + { + } }); // Allow task to potentially complete @@ -103,7 +112,7 @@ namespace osu.Game.Tests.NonVisual var cancellationSource = new CancellationTokenSource(); var token = CancellationTokenSource.CreateLinkedTokenSource(cancellationSource.Token, globalCancellationToken.Token); - taskChain.Add(() => + taskChain = taskChain.ContinueWithSequential(() => { mutex.Wait(globalCancellationToken.Token); completionSource.SetResult(Interlocked.Increment(ref currentTask)); diff --git a/osu.Game/Extensions/TaskExtensions.cs b/osu.Game/Extensions/TaskExtensions.cs index 24f0188cf0..fd0274f39e 100644 --- a/osu.Game/Extensions/TaskExtensions.cs +++ b/osu.Game/Extensions/TaskExtensions.cs @@ -4,6 +4,7 @@ #nullable enable using System; +using System.Threading; using System.Threading.Tasks; using osu.Framework.Extensions.ExceptionExtensions; using osu.Framework.Logging; @@ -32,5 +33,43 @@ namespace osu.Game.Extensions Logger.Log($"Error running task: {exception}", LoggingTarget.Runtime, LogLevel.Debug); }, TaskContinuationOptions.NotOnRanToCompletion); } + + public static Task ContinueWithSequential(this Task task, Action continuationFunction, CancellationToken cancellationToken = default) + { + return task.ContinueWithSequential(() => Task.Run(continuationFunction, cancellationToken), cancellationToken); + } + + public static Task ContinueWithSequential(this Task task, Func continuationFunction, CancellationToken cancellationToken = default) + { + var tcs = new TaskCompletionSource(); + + task.ContinueWith(t => + { + if (cancellationToken.IsCancellationRequested) + { + tcs.SetCanceled(); + } + else + { + continuationFunction().ContinueWith(t2 => + { + if (cancellationToken.IsCancellationRequested || t2.IsCanceled) + { + tcs.TrySetCanceled(); + } + else if (t2.IsFaulted) + { + tcs.TrySetException(t2.Exception); + } + else + { + tcs.TrySetResult(true); + } + }, cancellationToken: default); + } + }, cancellationToken: default); + + return tcs.Task; + } } } From f2fa51bf5e31e79c799fcf8fd17cd785280cde94 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 27 Jan 2021 19:59:28 +0900 Subject: [PATCH 0182/1791] Make ModSections overrideable --- osu.Game/Overlays/Mods/ModSection.cs | 18 +++++----- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 33 +++++++++++++++---- .../Mods/Sections/AutomationSection.cs | 19 ----------- .../Mods/Sections/ConversionSection.cs | 19 ----------- .../Sections/DifficultyIncreaseSection.cs | 19 ----------- .../Sections/DifficultyReductionSection.cs | 19 ----------- osu.Game/Overlays/Mods/Sections/FunSection.cs | 19 ----------- 7 files changed, 34 insertions(+), 112 deletions(-) delete mode 100644 osu.Game/Overlays/Mods/Sections/AutomationSection.cs delete mode 100644 osu.Game/Overlays/Mods/Sections/ConversionSection.cs delete mode 100644 osu.Game/Overlays/Mods/Sections/DifficultyIncreaseSection.cs delete mode 100644 osu.Game/Overlays/Mods/Sections/DifficultyReductionSection.cs delete mode 100644 osu.Game/Overlays/Mods/Sections/FunSection.cs diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index 573d1e5355..d70013602e 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -11,26 +11,23 @@ using System; using System.Linq; using System.Collections.Generic; using System.Threading; +using Humanizer; using osu.Framework.Input.Events; using osu.Game.Graphics; namespace osu.Game.Overlays.Mods { - public abstract class ModSection : Container + public class ModSection : Container { private readonly OsuSpriteText headerLabel; public FillFlowContainer ButtonsContainer { get; } public Action Action; - protected abstract Key[] ToggleKeys { get; } - public abstract ModType ModType { get; } - public string Header - { - get => headerLabel.Text; - set => headerLabel.Text = value; - } + public Key[] ToggleKeys; + + public readonly ModType ModType; public IEnumerable SelectedMods => buttons.Select(b => b.SelectedMod).Where(m => m != null); @@ -153,7 +150,7 @@ namespace osu.Game.Overlays.Mods button.Deselect(); } - protected ModSection() + public ModSection(ModType type) { AutoSizeAxes = Axes.Y; RelativeSizeAxes = Axes.X; @@ -168,7 +165,8 @@ namespace osu.Game.Overlays.Mods Origin = Anchor.TopLeft, Anchor = Anchor.TopLeft, Position = new Vector2(0f, 0f), - Font = OsuFont.GetFont(weight: FontWeight.Bold) + Font = OsuFont.GetFont(weight: FontWeight.Bold), + Text = type.Humanize(LetterCasing.Title) }, ButtonsContainer = new FillFlowContainer { diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index b93602116b..5775b08ae7 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -19,7 +19,6 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; -using osu.Game.Overlays.Mods.Sections; using osu.Game.Rulesets.Mods; using osu.Game.Screens; using osuTK; @@ -190,13 +189,31 @@ namespace osu.Game.Overlays.Mods Width = content_width, LayoutDuration = 200, LayoutEasing = Easing.OutQuint, - Children = new ModSection[] + Children = new[] { - new DifficultyReductionSection { Action = modButtonPressed }, - new DifficultyIncreaseSection { Action = modButtonPressed }, - new AutomationSection { Action = modButtonPressed }, - new ConversionSection { Action = modButtonPressed }, - new FunSection { Action = modButtonPressed }, + CreateModSection(ModType.DifficultyReduction).With(s => + { + s.ToggleKeys = new[] { Key.Q, Key.W, Key.E, Key.R, Key.T, Key.Y, Key.U, Key.I, Key.O, Key.P }; + s.Action = modButtonPressed; + }), + CreateModSection(ModType.DifficultyIncrease).With(s => + { + s.ToggleKeys = new[] { Key.A, Key.S, Key.D, Key.F, Key.G, Key.H, Key.J, Key.K, Key.L }; + s.Action = modButtonPressed; + }), + CreateModSection(ModType.Automation).With(s => + { + s.ToggleKeys = new[] { Key.Z, Key.X, Key.C, Key.V, Key.B, Key.N, Key.M }; + s.Action = modButtonPressed; + }), + CreateModSection(ModType.Conversion).With(s => + { + s.Action = modButtonPressed; + }), + CreateModSection(ModType.Fun).With(s => + { + s.Action = modButtonPressed; + }), } }, } @@ -455,6 +472,8 @@ namespace osu.Game.Overlays.Mods private void refreshSelectedMods() => SelectedMods.Value = ModSectionsContainer.Children.SelectMany(s => s.SelectedMods).ToArray(); + protected virtual ModSection CreateModSection(ModType type) => new ModSection(type); + #region Disposal protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Overlays/Mods/Sections/AutomationSection.cs b/osu.Game/Overlays/Mods/Sections/AutomationSection.cs deleted file mode 100644 index a2d7fec15f..0000000000 --- a/osu.Game/Overlays/Mods/Sections/AutomationSection.cs +++ /dev/null @@ -1,19 +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.Game.Rulesets.Mods; -using osuTK.Input; - -namespace osu.Game.Overlays.Mods.Sections -{ - public class AutomationSection : ModSection - { - protected override Key[] ToggleKeys => new[] { Key.Z, Key.X, Key.C, Key.V, Key.B, Key.N, Key.M }; - public override ModType ModType => ModType.Automation; - - public AutomationSection() - { - Header = @"Automation"; - } - } -} diff --git a/osu.Game/Overlays/Mods/Sections/ConversionSection.cs b/osu.Game/Overlays/Mods/Sections/ConversionSection.cs deleted file mode 100644 index 24fd8c30dd..0000000000 --- a/osu.Game/Overlays/Mods/Sections/ConversionSection.cs +++ /dev/null @@ -1,19 +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.Game.Rulesets.Mods; -using osuTK.Input; - -namespace osu.Game.Overlays.Mods.Sections -{ - public class ConversionSection : ModSection - { - protected override Key[] ToggleKeys => null; - public override ModType ModType => ModType.Conversion; - - public ConversionSection() - { - Header = @"Conversion"; - } - } -} diff --git a/osu.Game/Overlays/Mods/Sections/DifficultyIncreaseSection.cs b/osu.Game/Overlays/Mods/Sections/DifficultyIncreaseSection.cs deleted file mode 100644 index 0b7ccd1f25..0000000000 --- a/osu.Game/Overlays/Mods/Sections/DifficultyIncreaseSection.cs +++ /dev/null @@ -1,19 +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.Game.Rulesets.Mods; -using osuTK.Input; - -namespace osu.Game.Overlays.Mods.Sections -{ - public class DifficultyIncreaseSection : ModSection - { - protected override Key[] ToggleKeys => new[] { Key.A, Key.S, Key.D, Key.F, Key.G, Key.H, Key.J, Key.K, Key.L }; - public override ModType ModType => ModType.DifficultyIncrease; - - public DifficultyIncreaseSection() - { - Header = @"Difficulty Increase"; - } - } -} diff --git a/osu.Game/Overlays/Mods/Sections/DifficultyReductionSection.cs b/osu.Game/Overlays/Mods/Sections/DifficultyReductionSection.cs deleted file mode 100644 index 508e92508b..0000000000 --- a/osu.Game/Overlays/Mods/Sections/DifficultyReductionSection.cs +++ /dev/null @@ -1,19 +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.Game.Rulesets.Mods; -using osuTK.Input; - -namespace osu.Game.Overlays.Mods.Sections -{ - public class DifficultyReductionSection : ModSection - { - protected override Key[] ToggleKeys => new[] { Key.Q, Key.W, Key.E, Key.R, Key.T, Key.Y, Key.U, Key.I, Key.O, Key.P }; - public override ModType ModType => ModType.DifficultyReduction; - - public DifficultyReductionSection() - { - Header = @"Difficulty Reduction"; - } - } -} diff --git a/osu.Game/Overlays/Mods/Sections/FunSection.cs b/osu.Game/Overlays/Mods/Sections/FunSection.cs deleted file mode 100644 index af1f5836b1..0000000000 --- a/osu.Game/Overlays/Mods/Sections/FunSection.cs +++ /dev/null @@ -1,19 +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.Game.Rulesets.Mods; -using osuTK.Input; - -namespace osu.Game.Overlays.Mods.Sections -{ - public class FunSection : ModSection - { - protected override Key[] ToggleKeys => null; - public override ModType ModType => ModType.Fun; - - public FunSection() - { - Header = @"Fun"; - } - } -} From a30aecbafeb7d45f87f1061a2ccc71ee757c91cd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Jan 2021 20:01:21 +0900 Subject: [PATCH 0183/1791] Comment and add xmldoc --- osu.Game.Tests/NonVisual/TaskChainTest.cs | 1 - osu.Game/Extensions/TaskExtensions.cs | 32 +++++++++++++++++------ 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/NonVisual/TaskChainTest.cs b/osu.Game.Tests/NonVisual/TaskChainTest.cs index bd4f15a6eb..342f137dfd 100644 --- a/osu.Game.Tests/NonVisual/TaskChainTest.cs +++ b/osu.Game.Tests/NonVisual/TaskChainTest.cs @@ -6,7 +6,6 @@ using System.Threading; using System.Threading.Tasks; using NUnit.Framework; using osu.Game.Extensions; -using osu.Game.Utils; namespace osu.Game.Tests.NonVisual { diff --git a/osu.Game/Extensions/TaskExtensions.cs b/osu.Game/Extensions/TaskExtensions.cs index fd0274f39e..62b249b869 100644 --- a/osu.Game/Extensions/TaskExtensions.cs +++ b/osu.Game/Extensions/TaskExtensions.cs @@ -34,32 +34,46 @@ namespace osu.Game.Extensions }, TaskContinuationOptions.NotOnRanToCompletion); } - public static Task ContinueWithSequential(this Task task, Action continuationFunction, CancellationToken cancellationToken = default) - { - return task.ContinueWithSequential(() => Task.Run(continuationFunction, cancellationToken), cancellationToken); - } + /// + /// Add a continuation to be performed only after the attached task has completed. + /// + /// The previous task to be awaited on. + /// The action to run. + /// An optional cancellation token. Will only cancel the provided action, not the sequence. + /// A task representing the provided action. + public static Task ContinueWithSequential(this Task task, Action action, CancellationToken cancellationToken = default) => + task.ContinueWithSequential(() => Task.Run(action, cancellationToken), cancellationToken); + /// + /// Add a continuation to be performed only after the attached task has completed. + /// + /// The previous task to be awaited on. + /// The continuation to run. Generally should be an async function. + /// An optional cancellation token. Will only cancel the provided action, not the sequence. + /// A task representing the provided action. public static Task ContinueWithSequential(this Task task, Func continuationFunction, CancellationToken cancellationToken = default) { var tcs = new TaskCompletionSource(); task.ContinueWith(t => { + // the previous task has finished execution or been cancelled, so we can run the provided continuation. + if (cancellationToken.IsCancellationRequested) { tcs.SetCanceled(); } else { - continuationFunction().ContinueWith(t2 => + continuationFunction().ContinueWith(continuationTask => { - if (cancellationToken.IsCancellationRequested || t2.IsCanceled) + if (cancellationToken.IsCancellationRequested || continuationTask.IsCanceled) { tcs.TrySetCanceled(); } - else if (t2.IsFaulted) + else if (continuationTask.IsFaulted) { - tcs.TrySetException(t2.Exception); + tcs.TrySetException(continuationTask.Exception); } else { @@ -69,6 +83,8 @@ namespace osu.Game.Extensions } }, cancellationToken: default); + // importantly, we are not returning the continuation itself but rather a task which represents its status in sequential execution order. + // this will not be cancelled or completed until the previous task has also. return tcs.Task; } } From 0ff300628eff78d2d4c983d029137425b3209628 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 27 Jan 2021 20:07:22 +0900 Subject: [PATCH 0184/1791] Fix type not being set --- osu.Game/Overlays/Mods/ModSection.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index d70013602e..df4d05daad 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -152,6 +152,8 @@ namespace osu.Game.Overlays.Mods public ModSection(ModType type) { + ModType = type; + AutoSizeAxes = Axes.Y; RelativeSizeAxes = Axes.X; From 91d34d86f74ba1466323c3ee5f29c6b9541f1640 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 27 Jan 2021 22:02:23 +0900 Subject: [PATCH 0185/1791] Abstractify ModSelectOverlay --- .../TestSceneModSelectOverlay.cs | 2 +- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 23 ++++++------------ .../Overlays/Mods/SoloModSelectOverlay.cs | 24 +++++++++++++++++++ osu.Game/Screens/Select/MatchSongSelect.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 2 +- 5 files changed, 34 insertions(+), 19 deletions(-) create mode 100644 osu.Game/Overlays/Mods/SoloModSelectOverlay.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index bd4010a7f3..b03512ffde 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -265,7 +265,7 @@ namespace osu.Game.Tests.Visual.UserInterface private void checkLabelColor(Func getColour) => AddAssert("check label has expected colour", () => modSelect.MultiplierLabel.Colour.AverageColour == getColour()); - private class TestModSelectOverlay : ModSelectOverlay + private class TestModSelectOverlay : SoloModSelectOverlay { public new Bindable> SelectedMods => base.SelectedMods; diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 5775b08ae7..5709ca3b8d 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -27,7 +27,7 @@ using osuTK.Input; namespace osu.Game.Overlays.Mods { - public class ModSelectOverlay : WaveOverlayContainer + public abstract class ModSelectOverlay : WaveOverlayContainer { private readonly Func isValidMod; public const float HEIGHT = 510; @@ -60,7 +60,7 @@ namespace osu.Game.Overlays.Mods private SampleChannel sampleOn, sampleOff; - public ModSelectOverlay(Func isValidMod = null) + protected ModSelectOverlay(Func isValidMod = null) { this.isValidMod = isValidMod ?? (m => true); @@ -346,19 +346,6 @@ namespace osu.Game.Overlays.Mods refreshSelectedMods(); } - /// - /// Deselect one or more mods. - /// - /// The types of s which should be deselected. - /// Set to true to bypass animations and update selections immediately. - private void deselectTypes(Type[] modTypes, bool immediate = false) - { - if (modTypes.Length == 0) return; - - foreach (var section in ModSectionsContainer.Children) - section.DeselectTypes(modTypes, immediate); - } - protected override void LoadComplete() { base.LoadComplete(); @@ -458,7 +445,7 @@ namespace osu.Game.Overlays.Mods { if (State.Value == Visibility.Visible) sampleOn?.Play(); - deselectTypes(selectedMod.IncompatibleMods, true); + OnModSelected(selectedMod); if (selectedMod.RequiresConfiguration) ModSettingsContainer.Show(); } @@ -470,6 +457,10 @@ namespace osu.Game.Overlays.Mods refreshSelectedMods(); } + protected virtual void OnModSelected(Mod mod) + { + } + private void refreshSelectedMods() => SelectedMods.Value = ModSectionsContainer.Children.SelectMany(s => s.SelectedMods).ToArray(); protected virtual ModSection CreateModSection(ModType type) => new ModSection(type); diff --git a/osu.Game/Overlays/Mods/SoloModSelectOverlay.cs b/osu.Game/Overlays/Mods/SoloModSelectOverlay.cs new file mode 100644 index 0000000000..53d0c9fce9 --- /dev/null +++ b/osu.Game/Overlays/Mods/SoloModSelectOverlay.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 System; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Overlays.Mods +{ + public class SoloModSelectOverlay : ModSelectOverlay + { + public SoloModSelectOverlay(Func isValidMod = null) + : base(isValidMod) + { + } + + protected override void OnModSelected(Mod mod) + { + base.OnModSelected(mod); + + foreach (var section in ModSectionsContainer.Children) + section.DeselectTypes(mod.IncompatibleMods, true); + } + } +} diff --git a/osu.Game/Screens/Select/MatchSongSelect.cs b/osu.Game/Screens/Select/MatchSongSelect.cs index ed47b5d5ac..280f46f9ab 100644 --- a/osu.Game/Screens/Select/MatchSongSelect.cs +++ b/osu.Game/Screens/Select/MatchSongSelect.cs @@ -81,7 +81,7 @@ namespace osu.Game.Screens.Select item.RequiredMods.AddRange(Mods.Value.Select(m => m.CreateCopy())); } - protected override ModSelectOverlay CreateModSelectOverlay() => new ModSelectOverlay(isValidMod); + protected override ModSelectOverlay CreateModSelectOverlay() => new SoloModSelectOverlay(isValidMod); private bool isValidMod(Mod mod) => !(mod is ModAutoplay) && (mod as MultiMod)?.Mods.Any(mm => mm is ModAutoplay) != true; } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 4fca77a176..ff49dd9f7e 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -301,7 +301,7 @@ namespace osu.Game.Screens.Select } } - protected virtual ModSelectOverlay CreateModSelectOverlay() => new ModSelectOverlay(); + protected virtual ModSelectOverlay CreateModSelectOverlay() => new SoloModSelectOverlay(); protected virtual void ApplyFilterToCarousel(FilterCriteria criteria) { From 4019cc38e5e34e8c900bed0e61176fdfa35dbb42 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 27 Jan 2021 22:03:51 +0900 Subject: [PATCH 0186/1791] Allow footer buttons to be customised --- osu.Game/Screens/Select/SongSelect.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index ff49dd9f7e..4af96b7a29 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -263,9 +263,8 @@ namespace osu.Game.Screens.Select if (Footer != null) { - Footer.AddButton(new FooterButtonMods { Current = Mods }, ModSelect); - Footer.AddButton(new FooterButtonRandom { Action = triggerRandom }); - Footer.AddButton(new FooterButtonOptions(), BeatmapOptions); + foreach (var (button, overlay) in CreateFooterButtons()) + Footer.AddButton(button, overlay); BeatmapOptions.AddButton(@"Manage", @"collections", FontAwesome.Solid.Book, colours.Green, () => manageCollectionsDialog?.Show()); BeatmapOptions.AddButton(@"Delete", @"all difficulties", FontAwesome.Solid.Trash, colours.Pink, () => delete(Beatmap.Value.BeatmapSetInfo)); @@ -301,6 +300,13 @@ namespace osu.Game.Screens.Select } } + protected virtual IEnumerable<(FooterButton, OverlayContainer)> CreateFooterButtons() => new (FooterButton, OverlayContainer)[] + { + (new FooterButtonMods { Current = Mods }, ModSelect), + (new FooterButtonRandom { Action = triggerRandom }, null), + (new FooterButtonOptions(), BeatmapOptions) + }; + protected virtual ModSelectOverlay CreateModSelectOverlay() => new SoloModSelectOverlay(); protected virtual void ApplyFilterToCarousel(FilterCriteria criteria) From 45e41aaeacf930ce4b5e3b62f85acbc618ce73da Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 27 Jan 2021 22:15:53 +0900 Subject: [PATCH 0187/1791] Initial implementation of freemod selection overlay --- .../TestSceneFreeModSelectOverlay.cs | 24 +++++ osu.Game/Overlays/Mods/ModButton.cs | 8 +- osu.Game/Overlays/Mods/ModSection.cs | 30 +++--- .../Multiplayer/FreeModSelectOverlay.cs | 101 ++++++++++++++++++ .../Multiplayer/MultiplayerMatchSongSelect.cs | 66 +++++++++++- osu.Game/Screens/Select/Footer.cs | 15 ++- 6 files changed, 216 insertions(+), 28 deletions(-) create mode 100644 osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs create mode 100644 osu.Game/Screens/OnlinePlay/Multiplayer/FreeModSelectOverlay.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs new file mode 100644 index 0000000000..960402df88 --- /dev/null +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.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 NUnit.Framework; +using osu.Framework.Graphics.Containers; +using osu.Game.Overlays.Mods; +using osu.Game.Screens.OnlinePlay.Multiplayer; + +namespace osu.Game.Tests.Visual.Multiplayer +{ + public class TestSceneFreeModSelectOverlay : MultiplayerTestScene + { + private ModSelectOverlay overlay; + + [SetUp] + public new void Setup() => Schedule(() => + { + Child = overlay = new FreeModSelectOverlay + { + State = { Value = Visibility.Visible } + }; + }); + } +} diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs index ab8efdabcc..9ea4f65eb2 100644 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ b/osu.Game/Overlays/Mods/ModButton.cs @@ -174,7 +174,7 @@ namespace osu.Game.Overlays.Mods switch (e.Button) { case MouseButton.Right: - SelectNext(-1); + OnRightClick(e); break; } } @@ -183,10 +183,14 @@ namespace osu.Game.Overlays.Mods protected override bool OnClick(ClickEvent e) { SelectNext(1); - return true; } + protected virtual void OnRightClick(MouseUpEvent e) + { + SelectNext(-1); + } + /// /// Select the next available mod in a specified direction. /// diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index df4d05daad..5de629424b 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Mods { public class ModSection : Container { - private readonly OsuSpriteText headerLabel; + private readonly Drawable header; public FillFlowContainer ButtonsContainer { get; } @@ -47,10 +47,7 @@ namespace osu.Game.Overlays.Mods if (m == null) return new ModButtonEmpty(); - return new ModButton(m) - { - SelectionChanged = Action, - }; + return CreateModButton(m).With(b => b.SelectionChanged = Action); }).ToArray(); modsLoadCts?.Cancel(); @@ -58,7 +55,7 @@ namespace osu.Game.Overlays.Mods if (modContainers.Length == 0) { ModIconsLoaded = true; - headerLabel.Hide(); + header.Hide(); Hide(); return; } @@ -73,7 +70,7 @@ namespace osu.Game.Overlays.Mods buttons = modContainers.OfType().ToArray(); - headerLabel.FadeIn(200); + header.FadeIn(200); this.FadeIn(200); } } @@ -160,16 +157,9 @@ namespace osu.Game.Overlays.Mods Origin = Anchor.TopCentre; Anchor = Anchor.TopCentre; - Children = new Drawable[] + Children = new[] { - headerLabel = new OsuSpriteText - { - Origin = Anchor.TopLeft, - Anchor = Anchor.TopLeft, - Position = new Vector2(0f, 0f), - Font = OsuFont.GetFont(weight: FontWeight.Bold), - Text = type.Humanize(LetterCasing.Title) - }, + header = CreateHeader(type.Humanize(LetterCasing.Title)), ButtonsContainer = new FillFlowContainer { AutoSizeAxes = Axes.Y, @@ -185,5 +175,13 @@ namespace osu.Game.Overlays.Mods }, }; } + + protected virtual ModButton CreateModButton(Mod mod) => new ModButton(mod); + + protected virtual Drawable CreateHeader(string text) => new OsuSpriteText + { + Font = OsuFont.GetFont(weight: FontWeight.Bold), + Text = text + }; } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/FreeModSelectOverlay.cs new file mode 100644 index 0000000000..56e74a8460 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/FreeModSelectOverlay.cs @@ -0,0 +1,101 @@ +// 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.Framework.Input.Events; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Mods; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Screens.OnlinePlay.Multiplayer +{ + public class FreeModSelectOverlay : ModSelectOverlay + { + protected override ModSection CreateModSection(ModType type) => new FreeModSection(type); + + private class FreeModSection : ModSection + { + private HeaderCheckbox checkbox; + + public FreeModSection(ModType type) + : base(type) + { + } + + protected override ModButton CreateModButton(Mod mod) => new FreeModButton(mod); + + protected override Drawable CreateHeader(string text) => new Container + { + AutoSizeAxes = Axes.Y, + Width = 175, + Child = checkbox = new HeaderCheckbox + { + LabelText = text, + Changed = onCheckboxChanged + } + }; + + private void onCheckboxChanged(bool value) + { + foreach (var button in ButtonsContainer.OfType()) + { + if (value) + // Note: Buttons where only part of the group has an implementation are not fully supported. + button.SelectAt(0); + else + button.Deselect(); + } + } + + protected override void Update() + { + base.Update(); + + // If any of the buttons aren't selected, deselect the checkbox. + foreach (var button in ButtonsContainer.OfType()) + { + if (button.Mods.Any(m => m.HasImplementation) && !button.Selected) + checkbox.Current.Value = false; + } + } + } + + private class HeaderCheckbox : OsuCheckbox + { + public Action Changed; + + protected override void OnUserChange(bool value) + { + base.OnUserChange(value); + Changed?.Invoke(value); + } + } + + private class FreeModButton : ModButton + { + public FreeModButton(Mod mod) + : base(mod) + { + } + + protected override bool OnClick(ClickEvent e) + { + onClick(); + return true; + } + + protected override void OnRightClick(MouseUpEvent e) => onClick(); + + private void onClick() + { + if (Selected) + Deselect(); + else + SelectNext(1); + } + } + } +} diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index ebc06d2445..5917ed3f49 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -6,17 +6,23 @@ using System.Linq; using Humanizer; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Logging; using osu.Framework.Screens; using osu.Game.Beatmaps; +using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; using osu.Game.Overlays.Mods; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Select; +using osuTK; namespace osu.Game.Screens.OnlinePlay.Multiplayer { @@ -33,6 +39,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer private StatefulMultiplayerClient client { get; set; } private LoadingLayer loadingLayer; + private FreeModSelectOverlay freeModSelectOverlay; private WorkingBeatmap initialBeatmap; private RulesetInfo initialRuleset; @@ -43,6 +50,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer public MultiplayerMatchSongSelect() { Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING }; + + freeModSelectOverlay = new FreeModSelectOverlay(); } [BackgroundDependencyLoader] @@ -52,6 +61,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer initialBeatmap = Beatmap.Value; initialRuleset = Ruleset.Value; initialMods = Mods.Value.ToList(); + + FooterPanels.Add(freeModSelectOverlay); } protected override bool OnStart() @@ -111,8 +122,61 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea(); - protected override ModSelectOverlay CreateModSelectOverlay() => new ModSelectOverlay(isValidMod); + protected override ModSelectOverlay CreateModSelectOverlay() => new SoloModSelectOverlay(isValidMod); + + protected override IEnumerable<(FooterButton, OverlayContainer)> CreateFooterButtons() + { + var buttons = base.CreateFooterButtons().ToList(); + buttons.Insert(buttons.FindIndex(b => b.Item1 is FooterButtonMods) + 1, (new FooterButtonFreeMods(), freeModSelectOverlay)); + return buttons; + } private bool isValidMod(Mod mod) => !(mod is ModAutoplay) && (mod as MultiMod)?.Mods.Any(mm => mm is ModAutoplay) != true; } + + public class FooterButtonFreeMods : FooterButton, IHasCurrentValue> + { + public Bindable> Current + { + get => modDisplay.Current; + set => modDisplay.Current = value; + } + + private readonly ModDisplay modDisplay; + + public FooterButtonFreeMods() + { + ButtonContentContainer.Add(modDisplay = new ModDisplay + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + DisplayUnrankedText = false, + Scale = new Vector2(0.8f), + ExpansionMode = ExpansionMode.AlwaysContracted, + }); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + SelectedColour = colours.Yellow; + DeselectedColour = SelectedColour.Opacity(0.5f); + Text = @"freemods"; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Current.BindValueChanged(_ => updateModDisplay(), true); + } + + private void updateModDisplay() + { + if (Current.Value?.Count > 0) + modDisplay.FadeIn(); + else + modDisplay.FadeOut(); + } + } } diff --git a/osu.Game/Screens/Select/Footer.cs b/osu.Game/Screens/Select/Footer.cs index 689a11166a..ee13ebda44 100644 --- a/osu.Game/Screens/Select/Footer.cs +++ b/osu.Game/Screens/Select/Footer.cs @@ -28,19 +28,16 @@ namespace osu.Game.Screens.Select private readonly List overlays = new List(); - /// THe button to be added. + /// The button to be added. /// The to be toggled by this button. public void AddButton(FooterButton button, OverlayContainer overlay) { - overlays.Add(overlay); - button.Action = () => showOverlay(overlay); + if (overlay != null) + { + overlays.Add(overlay); + button.Action = () => showOverlay(overlay); + } - AddButton(button); - } - - /// Button to be added. - public void AddButton(FooterButton button) - { button.Hovered = updateModeLight; button.HoverLost = updateModeLight; From 4c256f1fb361f4a64d7cf3fa7a919467c56969b0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 27 Jan 2021 22:23:38 +0900 Subject: [PATCH 0188/1791] Actually populate the playlist item --- .../OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index 5917ed3f49..4054d84540 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -62,6 +62,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer initialRuleset = Ruleset.Value; initialMods = Mods.Value.ToList(); + freeModSelectOverlay.SelectedMods.Value = playlist.FirstOrDefault()?.AllowedMods.Select(m => m.CreateCopy()).ToList(); FooterPanels.Add(freeModSelectOverlay); } @@ -76,6 +77,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer item.RequiredMods.Clear(); item.RequiredMods.AddRange(Mods.Value.Select(m => m.CreateCopy())); + item.AllowedMods.Clear(); + item.AllowedMods.AddRange(freeModSelectOverlay.SelectedMods.Value.Select(m => m.CreateCopy())); + // If the client is already in a room, update via the client. // Otherwise, update the playlist directly in preparation for it to be submitted to the API on match creation. if (client.Room != null) From c408b46a2158e251d82ce99997d65d2c58c7203f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 27 Jan 2021 22:25:14 +0900 Subject: [PATCH 0189/1791] Add AllowedMods to MultiplayerRoomSettings model --- osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs index 857b38ea60..ad624f18ed 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs @@ -25,13 +25,21 @@ namespace osu.Game.Online.Multiplayer [NotNull] public IEnumerable Mods { get; set; } = Enumerable.Empty(); + [NotNull] + public IEnumerable AllowedMods { get; set; } = Enumerable.Empty(); + public bool Equals(MultiplayerRoomSettings other) => BeatmapID == other.BeatmapID && BeatmapChecksum == other.BeatmapChecksum && Mods.SequenceEqual(other.Mods) + && AllowedMods.SequenceEqual(other.AllowedMods) && RulesetID == other.RulesetID && Name.Equals(other.Name, StringComparison.Ordinal); - public override string ToString() => $"Name:{Name} Beatmap:{BeatmapID} ({BeatmapChecksum}) Mods:{string.Join(',', Mods)} Ruleset:{RulesetID}"; + public override string ToString() => $"Name:{Name}" + + $" Beatmap:{BeatmapID} ({BeatmapChecksum})" + + $" Mods:{string.Join(',', Mods)}" + + $" AllowedMods:{string.Join(',', AllowedMods)}" + + $" Ruleset:{RulesetID}"; } } From ff8ee379fb9c1684cdd064a32361f27d8fb6105e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 27 Jan 2021 22:27:31 +0900 Subject: [PATCH 0190/1791] Fix possible nullref --- .../OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index 4054d84540..70d3d128dd 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.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.Collections.Generic; using System.Linq; using Humanizer; @@ -38,8 +39,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer [Resolved] private StatefulMultiplayerClient client { get; set; } + private readonly FreeModSelectOverlay freeModSelectOverlay; private LoadingLayer loadingLayer; - private FreeModSelectOverlay freeModSelectOverlay; private WorkingBeatmap initialBeatmap; private RulesetInfo initialRuleset; @@ -62,7 +63,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer initialRuleset = Ruleset.Value; initialMods = Mods.Value.ToList(); - freeModSelectOverlay.SelectedMods.Value = playlist.FirstOrDefault()?.AllowedMods.Select(m => m.CreateCopy()).ToList(); + freeModSelectOverlay.SelectedMods.Value = playlist.FirstOrDefault()?.AllowedMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty(); FooterPanels.Add(freeModSelectOverlay); } From b79d1c7b81ef900087608be126f96b6e49e6bdf0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 27 Jan 2021 22:33:03 +0900 Subject: [PATCH 0191/1791] Add mods to footer --- .../OnlinePlay/Multiplayer/FreeModSelectOverlay.cs | 5 +++++ .../Multiplayer/MultiplayerMatchSongSelect.cs | 11 +++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/FreeModSelectOverlay.cs index 56e74a8460..10b68ec5a6 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/FreeModSelectOverlay.cs @@ -14,6 +14,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { public class FreeModSelectOverlay : ModSelectOverlay { + public FreeModSelectOverlay(Func isValidMod = null) + : base(isValidMod) + { + } + protected override ModSection CreateModSection(ModType type) => new FreeModSection(type); private class FreeModSection : ModSection diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index 70d3d128dd..86b8f22d34 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -39,6 +39,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer [Resolved] private StatefulMultiplayerClient client { get; set; } + private readonly Bindable> freeMods = new Bindable>(Array.Empty()); + private readonly FreeModSelectOverlay freeModSelectOverlay; private LoadingLayer loadingLayer; @@ -52,7 +54,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING }; - freeModSelectOverlay = new FreeModSelectOverlay(); + freeModSelectOverlay = new FreeModSelectOverlay(isValidMod) { SelectedMods = { BindTarget = freeMods } }; } [BackgroundDependencyLoader] @@ -63,7 +65,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer initialRuleset = Ruleset.Value; initialMods = Mods.Value.ToList(); - freeModSelectOverlay.SelectedMods.Value = playlist.FirstOrDefault()?.AllowedMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty(); + freeMods.Value = playlist.FirstOrDefault()?.AllowedMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty(); + FooterPanels.Add(freeModSelectOverlay); } @@ -79,7 +82,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer item.RequiredMods.AddRange(Mods.Value.Select(m => m.CreateCopy())); item.AllowedMods.Clear(); - item.AllowedMods.AddRange(freeModSelectOverlay.SelectedMods.Value.Select(m => m.CreateCopy())); + item.AllowedMods.AddRange(freeMods.Value.Select(m => m.CreateCopy())); // If the client is already in a room, update via the client. // Otherwise, update the playlist directly in preparation for it to be submitted to the API on match creation. @@ -132,7 +135,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override IEnumerable<(FooterButton, OverlayContainer)> CreateFooterButtons() { var buttons = base.CreateFooterButtons().ToList(); - buttons.Insert(buttons.FindIndex(b => b.Item1 is FooterButtonMods) + 1, (new FooterButtonFreeMods(), freeModSelectOverlay)); + buttons.Insert(buttons.FindIndex(b => b.Item1 is FooterButtonMods) + 1, (new FooterButtonFreeMods { Current = freeMods }, freeModSelectOverlay)); return buttons; } From 45395cb5e8fcf22d495908f7c77a3b5d90624d98 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Jan 2021 23:00:14 +0900 Subject: [PATCH 0192/1791] 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 9ad5946311..f8ce6befd4 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 3e971d9d4f..97f4320c95 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -27,7 +27,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 4732620085..301e7378a6 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -88,7 +88,7 @@ - + From 63f057a525e01e3c58ede431293b124c8b14d70d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 27 Jan 2021 20:45:48 +0300 Subject: [PATCH 0193/1791] Fix dotnet run/publish with runtime specified not working again --- osu.Desktop/osu.Desktop.csproj | 5 +---- osu.Game/osu.Game.csproj | 1 + 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index e201b250d4..cce7907c6c 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -24,16 +24,13 @@ + - - - - diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 97f4320c95..eb541c9de5 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -26,6 +26,7 @@ + From 2c08ce05fa18828248c1fe45aea502059917c0bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 27 Jan 2021 22:01:56 +0100 Subject: [PATCH 0194/1791] Remove game-local enum [Order] attribute In favour of the newly-added framework one. --- .../BeatmapListing/BeatmapSearchFilterRow.cs | 4 +- .../Overlays/BeatmapListing/SearchLanguage.cs | 2 +- .../Overlays/BeatmapSet/Scores/ScoreTable.cs | 4 +- osu.Game/Rulesets/Ruleset.cs | 6 +-- osu.Game/Rulesets/Scoring/HitResult.cs | 2 +- osu.Game/Utils/OrderAttribute.cs | 52 ------------------- 6 files changed, 9 insertions(+), 61 deletions(-) delete mode 100644 osu.Game/Utils/OrderAttribute.cs diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs index b429a5277b..01bcbd3244 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs @@ -12,7 +12,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osuTK; using Humanizer; -using osu.Game.Utils; +using osu.Framework.Extensions.EnumExtensions; namespace osu.Game.Overlays.BeatmapListing { @@ -80,7 +80,7 @@ namespace osu.Game.Overlays.BeatmapListing if (typeof(T).IsEnum) { - foreach (var val in OrderAttributeUtils.GetValuesInOrder()) + foreach (var val in EnumExtensions.GetValuesInOrder()) AddItem(val); } } diff --git a/osu.Game/Overlays/BeatmapListing/SearchLanguage.cs b/osu.Game/Overlays/BeatmapListing/SearchLanguage.cs index eee5d8f7e1..015cee8ce3 100644 --- a/osu.Game/Overlays/BeatmapListing/SearchLanguage.cs +++ b/osu.Game/Overlays/BeatmapListing/SearchLanguage.cs @@ -1,7 +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.Game.Utils; +using osu.Framework.Utils; namespace osu.Game.Overlays.BeatmapListing { diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 324299ccba..ddd1dfa6cd 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions; +using osu.Framework.Extensions.EnumExtensions; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -15,7 +16,6 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Users.Drawables; -using osu.Game.Utils; using osuTK; using osuTK.Graphics; @@ -105,7 +105,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores var ruleset = scores.First().Ruleset.CreateInstance(); - foreach (var result in OrderAttributeUtils.GetValuesInOrder()) + foreach (var result in EnumExtensions.GetValuesInOrder()) { if (!allScoreStatistics.Contains(result)) continue; diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index b3b3d11ab3..dbc2bd4d01 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -24,9 +24,9 @@ using osu.Game.Skinning; using osu.Game.Users; using JetBrains.Annotations; using osu.Framework.Extensions; +using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Testing; using osu.Game.Screens.Ranking.Statistics; -using osu.Game.Utils; namespace osu.Game.Rulesets { @@ -272,7 +272,7 @@ namespace osu.Game.Rulesets var validResults = GetValidHitResults(); // enumerate over ordered list to guarantee return order is stable. - foreach (var result in OrderAttributeUtils.GetValuesInOrder()) + foreach (var result in EnumExtensions.GetValuesInOrder()) { switch (result) { @@ -298,7 +298,7 @@ namespace osu.Game.Rulesets /// /// is implicitly included. Special types like are ignored even when specified. /// - protected virtual IEnumerable GetValidHitResults() => OrderAttributeUtils.GetValuesInOrder(); + protected virtual IEnumerable GetValidHitResults() => EnumExtensions.GetValuesInOrder(); /// /// Get a display friendly name for the specified result type. diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs index 6a3a034fc1..eaa1f95744 100644 --- a/osu.Game/Rulesets/Scoring/HitResult.cs +++ b/osu.Game/Rulesets/Scoring/HitResult.cs @@ -3,7 +3,7 @@ using System.ComponentModel; using System.Diagnostics; -using osu.Game.Utils; +using osu.Framework.Utils; namespace osu.Game.Rulesets.Scoring { diff --git a/osu.Game/Utils/OrderAttribute.cs b/osu.Game/Utils/OrderAttribute.cs deleted file mode 100644 index aded7f9814..0000000000 --- a/osu.Game/Utils/OrderAttribute.cs +++ /dev/null @@ -1,52 +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 System.Linq; - -namespace osu.Game.Utils -{ - public static class OrderAttributeUtils - { - /// - /// Get values of an enum in order. Supports custom ordering via . - /// - public static IEnumerable GetValuesInOrder() - { - var type = typeof(T); - - if (!type.IsEnum) - throw new InvalidOperationException("T must be an enum"); - - IEnumerable items = (T[])Enum.GetValues(type); - - if (Attribute.GetCustomAttribute(type, typeof(HasOrderedElementsAttribute)) == null) - return items; - - return items.OrderBy(i => - { - if (type.GetField(i.ToString()).GetCustomAttributes(typeof(OrderAttribute), false).FirstOrDefault() is OrderAttribute attr) - return attr.Order; - - throw new ArgumentException($"Not all values of {nameof(T)} have {nameof(OrderAttribute)} specified."); - }); - } - } - - [AttributeUsage(AttributeTargets.Field)] - public class OrderAttribute : Attribute - { - public readonly int Order; - - public OrderAttribute(int order) - { - Order = order; - } - } - - [AttributeUsage(AttributeTargets.Enum)] - public class HasOrderedElementsAttribute : Attribute - { - } -} From 90a82f986bcd2ba59f0ebc3ccc95e942a3e5665a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jan 2021 16:20:16 +0900 Subject: [PATCH 0195/1791] Fallback to using json for signalr communication if JIT is unavailable --- .../Online/Multiplayer/MultiplayerClient.cs | 22 +++++++++++++------ .../Spectator/SpectatorStreamingClient.cs | 21 ++++++++++++------ 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 0d779232d0..c10c4dd1b0 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -9,6 +9,8 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR.Client; using Microsoft.Extensions.DependencyInjection; +using Newtonsoft.Json; +using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Logging; @@ -64,13 +66,19 @@ namespace osu.Game.Online.Multiplayer if (connection != null) return; - connection = new HubConnectionBuilder() - .WithUrl(endpoint, options => - { - options.Headers.Add("Authorization", $"Bearer {api.AccessToken}"); - }) - .AddMessagePackProtocol() - .Build(); + var builder = new HubConnectionBuilder() + .WithUrl(endpoint, options => { options.Headers.Add("Authorization", $"Bearer {api.AccessToken}"); }); + + if (RuntimeInfo.SupportsJIT) + builder.AddMessagePackProtocol(); + else + { + // eventuall we will precompile resolvers for messagepack, but this isn't working currently + // see https://github.com/neuecc/MessagePack-CSharp/issues/780#issuecomment-768794308. + builder.AddNewtonsoftJsonProtocol(options => { options.PayloadSerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; }); + } + + connection = builder.Build(); // this is kind of SILLY // https://github.com/dotnet/aspnetcore/issues/15198 diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index dac2131035..7a28c179a8 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -9,6 +9,8 @@ using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.AspNetCore.SignalR.Client; using Microsoft.Extensions.DependencyInjection; +using Newtonsoft.Json; +using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -115,14 +117,19 @@ namespace osu.Game.Online.Spectator if (connection != null) return; - connection = new HubConnectionBuilder() - .WithUrl(endpoint, options => - { - options.Headers.Add("Authorization", $"Bearer {api.AccessToken}"); - }) - .AddMessagePackProtocol() - .Build(); + var builder = new HubConnectionBuilder() + .WithUrl(endpoint, options => { options.Headers.Add("Authorization", $"Bearer {api.AccessToken}"); }); + if (RuntimeInfo.SupportsJIT) + builder.AddMessagePackProtocol(); + else + { + // eventuall we will precompile resolvers for messagepack, but this isn't working currently + // see https://github.com/neuecc/MessagePack-CSharp/issues/780#issuecomment-768794308. + builder.AddNewtonsoftJsonProtocol(options => { options.PayloadSerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; }); + } + + connection = builder.Build(); // until strong typed client support is added, each method must be manually bound (see https://github.com/dotnet/aspnetcore/issues/15198) connection.On(nameof(ISpectatorClient.UserBeganPlaying), ((ISpectatorClient)this).UserBeganPlaying); connection.On(nameof(ISpectatorClient.UserSentFrames), ((ISpectatorClient)this).UserSentFrames); From c3d40440170e98e026bfceaf3dbd25901704e7ca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jan 2021 16:53:56 +0900 Subject: [PATCH 0196/1791] Avoid using Dapper to fix iOS compatibility of beatmap lookup cache --- ...BeatmapManager_BeatmapOnlineLookupQueue.cs | 32 ++++++++++++------- osu.Game/osu.Game.csproj | 1 - 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs b/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs index e90ccbb805..ea91f2d2e0 100644 --- a/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs +++ b/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs @@ -7,7 +7,6 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using Dapper; using Microsoft.Data.Sqlite; using osu.Framework.Development; using osu.Framework.IO.Network; @@ -154,20 +153,31 @@ namespace osu.Game.Beatmaps { using (var db = new SqliteConnection(storage.GetDatabaseConnectionString("online"))) { - var found = db.QuerySingleOrDefault( - "SELECT * FROM osu_beatmaps WHERE checksum = @MD5Hash OR beatmap_id = @OnlineBeatmapID OR filename = @Path", beatmap); + db.Open(); - if (found != null) + using (var cmd = db.CreateCommand()) { - var status = (BeatmapSetOnlineStatus)found.approved; + cmd.CommandText = "SELECT beatmapset_id, beatmap_id, approved FROM osu_beatmaps WHERE checksum = @MD5Hash OR beatmap_id = @OnlineBeatmapID OR filename = @Path"; - beatmap.Status = status; - beatmap.BeatmapSet.Status = status; - beatmap.BeatmapSet.OnlineBeatmapSetID = found.beatmapset_id; - beatmap.OnlineBeatmapID = found.beatmap_id; + cmd.Parameters.Add(new SqliteParameter("@MD5Hash", beatmap.MD5Hash)); + cmd.Parameters.Add(new SqliteParameter("@OnlineBeatmapID", beatmap.OnlineBeatmapID)); + cmd.Parameters.Add(new SqliteParameter("@Path", beatmap.Path)); - LogForModel(set, $"Cached local retrieval for {beatmap}."); - return true; + using (var reader = cmd.ExecuteReader()) + { + if (reader.Read()) + { + var status = (BeatmapSetOnlineStatus)reader.GetByte(2); + + beatmap.Status = status; + beatmap.BeatmapSet.Status = status; + beatmap.BeatmapSet.OnlineBeatmapSetID = reader.GetInt32(0); + beatmap.OnlineBeatmapID = reader.GetInt32(1); + + LogForModel(set, $"Cached local retrieval for {beatmap}."); + return true; + } + } } } } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 97f4320c95..bfc5ff302e 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -18,7 +18,6 @@ - From a616688a47a1d2d1c952eb8fe173cd88f8a64c37 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jan 2021 23:55:03 +0900 Subject: [PATCH 0197/1791] 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 f8ce6befd4..7060e88026 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 97f4320c95..a18eef15fc 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -27,7 +27,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 301e7378a6..48dc01f5de 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -88,7 +88,7 @@ - + From 386f9f78423f205490c817cab468d09a32d12339 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 28 Jan 2021 22:36:07 +0100 Subject: [PATCH 0198/1791] Fix typos in comments --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 2 +- osu.Game/Online/Spectator/SpectatorStreamingClient.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index c10c4dd1b0..b13d4fa899 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -73,7 +73,7 @@ namespace osu.Game.Online.Multiplayer builder.AddMessagePackProtocol(); else { - // eventuall we will precompile resolvers for messagepack, but this isn't working currently + // eventually we will precompile resolvers for messagepack, but this isn't working currently // see https://github.com/neuecc/MessagePack-CSharp/issues/780#issuecomment-768794308. builder.AddNewtonsoftJsonProtocol(options => { options.PayloadSerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; }); } diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 7a28c179a8..b95e3f1297 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -124,7 +124,7 @@ namespace osu.Game.Online.Spectator builder.AddMessagePackProtocol(); else { - // eventuall we will precompile resolvers for messagepack, but this isn't working currently + // eventually we will precompile resolvers for messagepack, but this isn't working currently // see https://github.com/neuecc/MessagePack-CSharp/issues/780#issuecomment-768794308. builder.AddNewtonsoftJsonProtocol(options => { options.PayloadSerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; }); } From da4c207a73b4beb25a59237a2e43ce674ed9ec99 Mon Sep 17 00:00:00 2001 From: Corentin PALLARD Date: Fri, 29 Jan 2021 02:53:26 +0100 Subject: [PATCH 0199/1791] Fix the ctb auto mod speedup in some occasions --- osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs index 32e8ab5da7..ae6868aea5 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs @@ -45,6 +45,9 @@ namespace osu.Game.Rulesets.Catch.Replays float positionChange = Math.Abs(lastPosition - h.EffectiveX); double timeAvailable = h.StartTime - lastTime; + if (timeAvailable < 0) + return; + // So we can either make it there without a dash or not. // If positionChange is 0, we don't need to move, so speedRequired should also be 0 (could be NaN if timeAvailable is 0 too) // The case where positionChange > 0 and timeAvailable == 0 results in PositiveInfinity which provides expected beheaviour. From d168de0ae32f0877d225efc13942b96a57a9bba2 Mon Sep 17 00:00:00 2001 From: Corentin PALLARD Date: Fri, 29 Jan 2021 03:03:23 +0100 Subject: [PATCH 0200/1791] Formatting --- osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs index ae6868aea5..64ded8e94f 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs @@ -46,7 +46,9 @@ namespace osu.Game.Rulesets.Catch.Replays double timeAvailable = h.StartTime - lastTime; if (timeAvailable < 0) + { return; + } // So we can either make it there without a dash or not. // If positionChange is 0, we don't need to move, so speedRequired should also be 0 (could be NaN if timeAvailable is 0 too) From 449f883be15956273c5271eef5236dbd95776792 Mon Sep 17 00:00:00 2001 From: Firmatorenio Date: Fri, 29 Jan 2021 11:48:51 +0600 Subject: [PATCH 0201/1791] add SV multiplier adjustment to TaikoModDifficultyAdjust --- .../Mods/TaikoModDifficultyAdjust.cs | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs index 56a73ad7df..1d1773fcbb 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs @@ -1,11 +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.Linq; +using osu.Framework.Bindables; +using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Taiko.Mods { public class TaikoModDifficultyAdjust : ModDifficultyAdjust { + [SettingSource("Slider Velocity", "Adjust a beatmap's set SV", LAST_SETTING_ORDER + 1)] + public BindableNumber SliderVelocity { get; } = new BindableFloat + { + Precision = 0.05f, + MinValue = 0.25f, + MaxValue = 4, + Default = 1, + Value = 1, + }; + + public override string SettingDescription + { + get + { + string sliderVelocity = SliderVelocity.IsDefault ? string.Empty : $"SV {SliderVelocity.Value:N1}"; + + return string.Join(", ", new[] + { + base.SettingDescription, + sliderVelocity + }.Where(s => !string.IsNullOrEmpty(s))); + } + } + + protected override void ApplySettings(BeatmapDifficulty difficulty) + { + base.ApplySettings(difficulty); + + ApplySetting(SliderVelocity, sv => difficulty.SliderMultiplier *= sv); + } } } From 1ec305e10d4ba70ee06a9f21bb3087e0d2e245e9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Jan 2021 16:06:57 +0900 Subject: [PATCH 0202/1791] Update TaskChain to use ContinueWithSequential internally It turns out we may still want to use TaskChain for its locking behaviour, so I've made it internally use the refactored version I implemented, while keeping the general structure. --- osu.Game/Utils/TaskChain.cs | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/osu.Game/Utils/TaskChain.cs b/osu.Game/Utils/TaskChain.cs index 30aea7578f..df28faf9fb 100644 --- a/osu.Game/Utils/TaskChain.cs +++ b/osu.Game/Utils/TaskChain.cs @@ -6,6 +6,7 @@ using System; using System.Threading; using System.Threading.Tasks; +using osu.Game.Extensions; namespace osu.Game.Utils { @@ -14,8 +15,9 @@ namespace osu.Game.Utils /// public class TaskChain { - private readonly object finalTaskLock = new object(); - private Task? finalTask; + private readonly object taskLock = new object(); + + private Task lastTaskInChain = Task.CompletedTask; /// /// Adds a new task to the end of this . @@ -23,22 +25,22 @@ namespace osu.Game.Utils /// The action to be executed. /// The for this task. Does not affect further tasks in the chain. /// The awaitable . - public async Task Add(Action action, CancellationToken cancellationToken = default) + public Task Add(Action action, CancellationToken cancellationToken = default) { - Task? previousTask; - Task currentTask; + lock (taskLock) + return lastTaskInChain = lastTaskInChain.ContinueWithSequential(action, cancellationToken); + } - lock (finalTaskLock) - { - previousTask = finalTask; - finalTask = currentTask = new Task(action, cancellationToken); - } - - if (previousTask != null) - await previousTask; - - currentTask.Start(); - await currentTask; + /// + /// Adds a new task to the end of this . + /// + /// The task to be executed. + /// The for this task. Does not affect further tasks in the chain. + /// The awaitable . + public Task Add(Func task, CancellationToken cancellationToken = default) + { + lock (taskLock) + return lastTaskInChain = lastTaskInChain.ContinueWithSequential(task, cancellationToken); } } } From c3aec3bfe43da7c71c243f2188b39bf24c8233cc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Jan 2021 16:20:25 +0900 Subject: [PATCH 0203/1791] Revert test changes to test original class/scope Importantly, this removes the call to CatchUnobservedExceptions(), which was outright incorrect (awaiting on the wrong task as a result) in the original test code. --- osu.Game.Tests/NonVisual/TaskChainTest.cs | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/NonVisual/TaskChainTest.cs b/osu.Game.Tests/NonVisual/TaskChainTest.cs index 342f137dfd..d83eaafe20 100644 --- a/osu.Game.Tests/NonVisual/TaskChainTest.cs +++ b/osu.Game.Tests/NonVisual/TaskChainTest.cs @@ -1,28 +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 System.Threading; using System.Threading.Tasks; using NUnit.Framework; -using osu.Game.Extensions; +using osu.Game.Utils; namespace osu.Game.Tests.NonVisual { [TestFixture] public class TaskChainTest { - private Task taskChain; - + private TaskChain taskChain; private int currentTask; private CancellationTokenSource globalCancellationToken; [SetUp] public void Setup() { - taskChain = Task.CompletedTask; - globalCancellationToken = new CancellationTokenSource(); + taskChain = new TaskChain(); currentTask = 0; } @@ -81,16 +78,7 @@ namespace osu.Game.Tests.NonVisual { var mutex = new ManualResetEventSlim(false); - var task = taskChain.ContinueWithSequential(async () => - { - try - { - await Task.Run(() => mutex.Wait(globalCancellationToken.Token)); - } - catch (OperationCanceledException) - { - } - }); + var task = taskChain.Add(async () => await Task.Run(() => mutex.Wait(globalCancellationToken.Token))); // Allow task to potentially complete Thread.Sleep(1000); @@ -111,7 +99,7 @@ namespace osu.Game.Tests.NonVisual var cancellationSource = new CancellationTokenSource(); var token = CancellationTokenSource.CreateLinkedTokenSource(cancellationSource.Token, globalCancellationToken.Token); - taskChain = taskChain.ContinueWithSequential(() => + taskChain.Add(() => { mutex.Wait(globalCancellationToken.Token); completionSource.SetResult(Interlocked.Increment(ref currentTask)); From a61444690ea8324719e4f04f1cb6e02b3706bb19 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Jan 2021 16:32:28 +0900 Subject: [PATCH 0204/1791] Remove all usage of CatchUnobservedExceptions This should no longer be required with the recent framework side change that stops a game from crashing on unobserved exceptions (https://github.com/ppy/osu-framework/pull/4171). --- osu.Game/Extensions/TaskExtensions.cs | 36 ------------------- .../Multiplayer/StatefulMultiplayerClient.cs | 3 +- .../OnlinePlay/Multiplayer/Multiplayer.cs | 3 +- .../Multiplayer/MultiplayerMatchSubScreen.cs | 8 +---- .../Multiplayer/MultiplayerRoomManager.cs | 3 +- .../Participants/ParticipantPanel.cs | 3 +- 6 files changed, 5 insertions(+), 51 deletions(-) delete mode 100644 osu.Game/Extensions/TaskExtensions.cs diff --git a/osu.Game/Extensions/TaskExtensions.cs b/osu.Game/Extensions/TaskExtensions.cs deleted file mode 100644 index 4138c2757a..0000000000 --- a/osu.Game/Extensions/TaskExtensions.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable enable - -using System; -using System.Threading.Tasks; -using osu.Framework.Extensions.ExceptionExtensions; -using osu.Framework.Logging; - -namespace osu.Game.Extensions -{ - public static class TaskExtensions - { - /// - /// Denote a task which is to be run without local error handling logic, where failure is not catastrophic. - /// Avoids unobserved exceptions from being fired. - /// - /// The task. - /// - /// Whether errors should be logged as errors visible to users, or as debug messages. - /// Logging as debug will essentially silence the errors on non-release builds. - /// - public static void CatchUnobservedExceptions(this Task task, bool logAsError = false) - { - task.ContinueWith(t => - { - Exception? exception = t.Exception?.AsSingular(); - if (logAsError) - Logger.Error(exception, $"Error running task: {exception?.Message ?? "(unknown)"}", LoggingTarget.Runtime, true); - else - Logger.Log($"Error running task: {exception}", LoggingTarget.Runtime, LogLevel.Debug); - }, TaskContinuationOptions.NotOnRanToCompletion); - } - } -} diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index f0e11b2b8b..48194d1f0f 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -15,7 +15,6 @@ using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Database; -using osu.Game.Extensions; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; @@ -104,7 +103,7 @@ namespace osu.Game.Online.Multiplayer if (!connected.NewValue && Room != null) { Logger.Log("Connection to multiplayer server was lost.", LoggingTarget.Runtime, LogLevel.Important); - LeaveRoom().CatchUnobservedExceptions(); + LeaveRoom(); } }); } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs index 76f5c74433..ae22e1fcec 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs @@ -4,7 +4,6 @@ using osu.Framework.Allocation; using osu.Framework.Logging; using osu.Framework.Screens; -using osu.Game.Extensions; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; @@ -23,7 +22,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer base.OnResuming(last); if (client.Room != null) - client.ChangeState(MultiplayerUserState.Idle).CatchUnobservedExceptions(true); + client.ChangeState(MultiplayerUserState.Idle); } protected override void UpdatePollingRate(bool isIdle) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 7c4b6d18ec..c071637b9b 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -11,7 +11,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Screens; -using osu.Game.Extensions; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Components; @@ -237,7 +236,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer // accessing Exception here silences any potential errors from the antecedent task if (t.Exception != null) { - t.CatchUnobservedExceptions(true); // will run immediately. // gameplay was not started due to an exception; unblock button. endOperation(); } @@ -248,11 +246,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer } client.ToggleReady() - .ContinueWith(t => - { - t.CatchUnobservedExceptions(true); // will run immediately. - endOperation(); - }); + .ContinueWith(t => endOperation()); void endOperation() { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs index 61d8896732..65d112a032 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs @@ -9,7 +9,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.ExceptionExtensions; using osu.Framework.Logging; -using osu.Game.Extensions; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; using osu.Game.Online.Rooms.RoomStatuses; @@ -69,7 +68,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer base.PartRoom(); - multiplayerClient.LeaveRoom().CatchUnobservedExceptions(); + multiplayerClient.LeaveRoom(); // Todo: This is not the way to do this. Basically when we're the only participant and the room closes, there's no way to know if this is actually the case. // This is delayed one frame because upon exiting the match subscreen, multiplayer updates the polling rate and messes with polling. diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index f99655e305..b5533f49cc 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; -using osu.Game.Extensions; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -176,7 +175,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants if (Room.Host?.UserID != api.LocalUser.Value.Id) return; - Client.TransferHost(targetUser).CatchUnobservedExceptions(true); + Client.TransferHost(targetUser); }) }; } From 37ef5c70729c6f99cf51cc6841eb4833a728e51c Mon Sep 17 00:00:00 2001 From: Firmatorenio Date: Fri, 29 Jan 2021 15:04:55 +0600 Subject: [PATCH 0205/1791] rename SliderVelocity to ScrollSpeed --- .../Mods/TaikoModDifficultyAdjust.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs index 1d1773fcbb..4006652bd5 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs @@ -11,8 +11,8 @@ namespace osu.Game.Rulesets.Taiko.Mods { public class TaikoModDifficultyAdjust : ModDifficultyAdjust { - [SettingSource("Slider Velocity", "Adjust a beatmap's set SV", LAST_SETTING_ORDER + 1)] - public BindableNumber SliderVelocity { get; } = new BindableFloat + [SettingSource("Scroll Speed", "Adjust a beatmap's set scroll speed", LAST_SETTING_ORDER + 1)] + public BindableNumber ScrollSpeed { get; } = new BindableFloat { Precision = 0.05f, MinValue = 0.25f, @@ -25,12 +25,12 @@ namespace osu.Game.Rulesets.Taiko.Mods { get { - string sliderVelocity = SliderVelocity.IsDefault ? string.Empty : $"SV {SliderVelocity.Value:N1}"; + string scrollSpeed = ScrollSpeed.IsDefault ? string.Empty : $"Scroll x{ScrollSpeed.Value:N1}"; return string.Join(", ", new[] { base.SettingDescription, - sliderVelocity + scrollSpeed }.Where(s => !string.IsNullOrEmpty(s))); } } @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Taiko.Mods { base.ApplySettings(difficulty); - ApplySetting(SliderVelocity, sv => difficulty.SliderMultiplier *= sv); + ApplySetting(ScrollSpeed, scroll => difficulty.SliderMultiplier *= scroll); } } } From ab9a3e6dd05bd4a85ab5d1be0e7acc726148e029 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 29 Jan 2021 18:21:22 +0900 Subject: [PATCH 0206/1791] Pass allowed mods and consume on server callback --- osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index f0e11b2b8b..e5b07ddfb4 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -192,7 +192,8 @@ namespace osu.Game.Online.Multiplayer BeatmapID = item.GetOr(existingPlaylistItem).BeatmapID, BeatmapChecksum = item.GetOr(existingPlaylistItem).Beatmap.Value.MD5Hash, RulesetID = item.GetOr(existingPlaylistItem).RulesetID, - Mods = item.HasValue ? item.Value.AsNonNull().RequiredMods.Select(m => new APIMod(m)).ToList() : Room.Settings.Mods + Mods = item.HasValue ? item.Value.AsNonNull().RequiredMods.Select(m => new APIMod(m)).ToList() : Room.Settings.Mods, + AllowedMods = item.HasValue ? item.Value.AsNonNull().AllowedMods.Select(m => new APIMod(m)).ToList() : Room.Settings.AllowedMods }); } @@ -502,6 +503,7 @@ namespace osu.Game.Online.Multiplayer var ruleset = rulesets.GetRuleset(settings.RulesetID).CreateInstance(); var mods = settings.Mods.Select(m => m.ToMod(ruleset)); + var allowedMods = settings.AllowedMods.Select(m => m.ToMod(ruleset)); PlaylistItem playlistItem = new PlaylistItem { @@ -511,6 +513,7 @@ namespace osu.Game.Online.Multiplayer }; playlistItem.RequiredMods.AddRange(mods); + playlistItem.AllowedMods.AddRange(allowedMods); apiRoom.Playlist.Clear(); // Clearing should be unnecessary, but here for sanity. apiRoom.Playlist.Add(playlistItem); From 18e6afbec06a2111275ba6415eb7d132f52f9ce2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Jan 2021 19:16:10 +0900 Subject: [PATCH 0207/1791] Ensure the item is present before trying to select it --- .../Overlays/Settings/Sections/SkinSection.cs | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 56c677d5b5..7c8309fd56 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -121,7 +121,22 @@ namespace osu.Game.Overlays.Settings.Sections }); } - private void updateSelectedSkinFromConfig() => dropdownBindable.Value = skinDropdown.Items.Single(s => s.ID == configBindable.Value); + private void updateSelectedSkinFromConfig() + { + int id = configBindable.Value; + + var skin = skinDropdown.Items.FirstOrDefault(s => s.ID == id); + + if (skin == null) + { + // there may be a thread race condition where an item is selected that hasn't yet been added to the dropdown. + // to avoid adding complexity, let's just ensure the item is added so we can perform the selection. + skin = skins.Query(s => s.ID == id); + addItem(skin); + } + + dropdownBindable.Value = skin; + } private void updateItems() { @@ -134,14 +149,14 @@ namespace osu.Game.Overlays.Settings.Sections private void itemUpdated(ValueChangedEvent> weakItem) { if (weakItem.NewValue.TryGetTarget(out var item)) - { - Schedule(() => - { - List newDropdownItems = skinDropdown.Items.Where(i => !i.Equals(item)).Append(item).ToList(); - sortUserSkins(newDropdownItems); - skinDropdown.Items = newDropdownItems; - }); - } + Schedule(() => addItem(item)); + } + + private void addItem(SkinInfo item) + { + List newDropdownItems = skinDropdown.Items.Where(i => !i.Equals(item)).Append(item).ToList(); + sortUserSkins(newDropdownItems); + skinDropdown.Items = newDropdownItems; } private void itemRemoved(ValueChangedEvent> weakItem) From 16f3d1815f27a344ca2e9a662f960f97ef4db666 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Jan 2021 19:53:56 +0900 Subject: [PATCH 0208/1791] Fix SQLite exception thrown is a beatmap lookup is attempted without an OnlineBeatmapID present MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It turns out the SQLite API isn't smart enough to handle nullables directly, so we need to help it out a bit. Stops the following from being thrown: ``` System.InvalidOperationException: Value must be set. at Microsoft.Data.Sqlite.SqliteParameter.Bind(sqlite3_stmt stmt) = 3 at at Microsoft.Data.Sqlite.SqliteParameterCollection.Bind(sqlite3_stmt stmt) = 3 at at Microsoft.Data.Sqlite.SqliteCommand.ExecuteReader(CommandBehavior behavior) at Microsoft.Data.Sqlite.SqliteCommand.ExecuteReader() at osu.Game.Beatmaps.BeatmapManager.BeatmapOnlineLookupQueue.checkLocalCache(BeatmapSetInfo set, BeatmapInfo beatmap) in /Users/dean/Projects/osu/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs:line 166 = 166 ``` --- osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs b/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs index ea91f2d2e0..7c4b344c9e 100644 --- a/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs +++ b/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs @@ -160,7 +160,7 @@ namespace osu.Game.Beatmaps cmd.CommandText = "SELECT beatmapset_id, beatmap_id, approved FROM osu_beatmaps WHERE checksum = @MD5Hash OR beatmap_id = @OnlineBeatmapID OR filename = @Path"; cmd.Parameters.Add(new SqliteParameter("@MD5Hash", beatmap.MD5Hash)); - cmd.Parameters.Add(new SqliteParameter("@OnlineBeatmapID", beatmap.OnlineBeatmapID)); + cmd.Parameters.Add(new SqliteParameter("@OnlineBeatmapID", beatmap.OnlineBeatmapID ?? (object)DBNull.Value)); cmd.Parameters.Add(new SqliteParameter("@Path", beatmap.Path)); using (var reader = cmd.ExecuteReader()) From f25809d35f9fd084f7486bab8066dc0da6b59347 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Jan 2021 19:55:51 +0900 Subject: [PATCH 0209/1791] Ensure spinners only handle input during their hittable time While this was already being enforced inside of `CheckForResult`, the internal tracking values of rotation were still being incremented as long as the `DrawableSpinner` was present. This resulted in incorrect SPM values being displayed if a user was to start spinning before the object's `StartTime`. Kind of annoying to write a test for (there's no setup for spinners yet) but am willing to do so if that is deemed necessary. Closes https://github.com/ppy/osu/issues/11600. --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 56aedebed3..43012563ae 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -18,6 +18,7 @@ using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Skinning; using osu.Game.Rulesets.Osu.Skinning.Default; using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Screens.Ranking; using osu.Game.Skinning; @@ -242,6 +243,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.Update(); + HandleUserInput = Time.Current >= HitObject.StartTime && Time.Current <= HitObject.EndTime; + if (HandleUserInput) RotationTracker.Tracking = !Result.HasResult && (OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false); @@ -255,6 +258,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (!SpmCounter.IsPresent && RotationTracker.Tracking) SpmCounter.FadeIn(HitObject.TimeFadeIn); + SpmCounter.SetRotation(Result.RateAdjustedRotation); updateBonusScore(); From 5a306dfc2b261e5926817eb03352a11ec846c1ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Jan 2021 20:22:25 +0900 Subject: [PATCH 0210/1791] Fix unused using --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 43012563ae..ab8156f4f2 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -18,7 +18,6 @@ using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Skinning; using osu.Game.Rulesets.Osu.Skinning.Default; using osu.Game.Rulesets.Scoring; -using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Screens.Ranking; using osu.Game.Skinning; From d521bfc251195a4eb5560d9685181bf079602d46 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 30 Jan 2021 02:35:11 +0900 Subject: [PATCH 0211/1791] Don't directly update HandleUserInput (as it is used by mods) --- .../Objects/Drawables/DrawableSpinner.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index ab8156f4f2..c58f703bef 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -242,10 +242,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.Update(); - HandleUserInput = Time.Current >= HitObject.StartTime && Time.Current <= HitObject.EndTime; - if (HandleUserInput) - RotationTracker.Tracking = !Result.HasResult && (OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false); + { + bool isValidSpinningTime = Time.Current >= HitObject.StartTime && Time.Current <= HitObject.EndTime; + bool correctButtonPressed = (OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false); + + RotationTracker.Tracking = !Result.HasResult + && correctButtonPressed + && isValidSpinningTime; + } if (spinningSample != null && spinnerFrequencyModulate) spinningSample.Frequency.Value = spinning_sample_modulated_base_frequency + Progress; From ae08ef25437efd3e6aa6661a8d1d627cff338a19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 29 Jan 2021 20:32:45 +0100 Subject: [PATCH 0212/1791] Reset SPM counter state on DHO application --- .../Skinning/Default/SpinnerSpmCounter.cs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerSpmCounter.cs b/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerSpmCounter.cs index e5952ecf97..69355f624b 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerSpmCounter.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerSpmCounter.cs @@ -4,16 +4,21 @@ 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.Utils; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.Osu.Skinning.Default { public class SpinnerSpmCounter : Container { + [Resolved] + private DrawableHitObject drawableSpinner { get; set; } + private readonly OsuSpriteText spmText; public SpinnerSpmCounter() @@ -38,6 +43,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default }; } + protected override void LoadComplete() + { + base.LoadComplete(); + drawableSpinner.HitObjectApplied += resetState; + } + private double spm; public double SpinsPerMinute @@ -82,5 +93,19 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default records.Enqueue(new RotationRecord { Rotation = currentRotation, Time = Time.Current }); } + + private void resetState(DrawableHitObject hitObject) + { + SpinsPerMinute = 0; + records.Clear(); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (drawableSpinner != null) + drawableSpinner.HitObjectApplied -= resetState; + } } } From c3ba92f057349d56c79906bd96ab0de8b67e0fa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 30 Jan 2021 16:13:50 +0100 Subject: [PATCH 0213/1791] Set canceled result in scheduleAsync Was holding up the task completion source, and in consequence, potentially the entire task chain. --- osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index 4bec327884..de51a4b117 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -551,7 +551,10 @@ namespace osu.Game.Online.Multiplayer Scheduler.Add(() => { if (cancellationToken.IsCancellationRequested) + { + tcs.SetCanceled(); return; + } try { From 0aaa62efc2d9f2d1e8f6d46632661b0e6ef8db03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 30 Jan 2021 20:55:56 +0100 Subject: [PATCH 0214/1791] Add failing test case --- .../NonVisual/OngoingOperationTrackerTest.cs | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/osu.Game.Tests/NonVisual/OngoingOperationTrackerTest.cs b/osu.Game.Tests/NonVisual/OngoingOperationTrackerTest.cs index eef9582af9..10216c3339 100644 --- a/osu.Game.Tests/NonVisual/OngoingOperationTrackerTest.cs +++ b/osu.Game.Tests/NonVisual/OngoingOperationTrackerTest.cs @@ -3,8 +3,12 @@ using System; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Screens; using osu.Framework.Testing; +using osu.Game.Screens; using osu.Game.Screens.OnlinePlay; using osu.Game.Tests.Visual; @@ -58,5 +62,45 @@ namespace osu.Game.Tests.NonVisual AddStep("end operation", () => operation.Dispose()); AddAssert("operation is ended", () => !operationInProgress.Value); } + + [Test] + public void TestOperationDisposalAfterScreenExit() + { + TestScreenWithTracker screen = null; + OsuScreenStack stack; + IDisposable operation = null; + + AddStep("create screen with tracker", () => + { + Child = stack = new OsuScreenStack + { + RelativeSizeAxes = Axes.Both + }; + + stack.Push(screen = new TestScreenWithTracker()); + }); + AddUntilStep("wait for loaded", () => screen.IsLoaded); + + AddStep("begin operation", () => operation = screen.OngoingOperationTracker.BeginOperation()); + AddAssert("operation in progress", () => screen.OngoingOperationTracker.InProgress.Value); + + AddStep("dispose after screen exit", () => + { + screen.Exit(); + operation.Dispose(); + }); + AddAssert("operation ended", () => !screen.OngoingOperationTracker.InProgress.Value); + } + + private class TestScreenWithTracker : OsuScreen + { + public OngoingOperationTracker OngoingOperationTracker { get; private set; } + + [BackgroundDependencyLoader] + private void load() + { + InternalChild = OngoingOperationTracker = new OngoingOperationTracker(); + } + } } } From 96f56d1c942505797f20cf1c166b5452c60ea592 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 30 Jan 2021 21:00:13 +0100 Subject: [PATCH 0215/1791] Return tracker lease via UnbindAll() Improves reliability by being fail-safe in case of multiple returns, which can happen if the operation tracker is part of a screen being exited (as is the case with its current primary usage in multiplayer). --- .../Screens/OnlinePlay/OngoingOperationTracker.cs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/OngoingOperationTracker.cs b/osu.Game/Screens/OnlinePlay/OngoingOperationTracker.cs index b7ee84eb9e..9e88fabb3d 100644 --- a/osu.Game/Screens/OnlinePlay/OngoingOperationTracker.cs +++ b/osu.Game/Screens/OnlinePlay/OngoingOperationTracker.cs @@ -53,20 +53,15 @@ namespace osu.Game.Screens.OnlinePlay // for extra safety, marshal the end of operation back to the update thread if necessary. Scheduler.Add(() => { - leasedInProgress?.Return(); + // UnbindAll() is purposefully used instead of Return() - the two do roughly the same thing, with one difference: + // the former won't throw if the lease has already been returned before. + // this matters because framework can unbind the lease via the internal UnbindAllBindables(), which is not always detectable + // (it is in the case of disposal, but not in the case of screen exit - at least not cleanly). + leasedInProgress?.UnbindAll(); leasedInProgress = null; }, false); } - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - // base call does an UnbindAllBindables(). - // clean up the leased reference here so that it doesn't get returned twice. - leasedInProgress = null; - } - private class OngoingOperation : IDisposable { private readonly OngoingOperationTracker tracker; From 5f320cd4264ad444fe5cb3f7aa7bef74d530e932 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 30 Jan 2021 21:03:09 +0100 Subject: [PATCH 0216/1791] Move lease check inside schedule Theoretically safer due to avoiding a potential data race (change in `leasedInProgress` between the time of the check and start of schedule execution). --- osu.Game/Screens/OnlinePlay/OngoingOperationTracker.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/OngoingOperationTracker.cs b/osu.Game/Screens/OnlinePlay/OngoingOperationTracker.cs index 9e88fabb3d..aabeafe460 100644 --- a/osu.Game/Screens/OnlinePlay/OngoingOperationTracker.cs +++ b/osu.Game/Screens/OnlinePlay/OngoingOperationTracker.cs @@ -47,12 +47,12 @@ namespace osu.Game.Screens.OnlinePlay private void endOperationWithKnownLease(LeasedBindable lease) { - if (lease != leasedInProgress) - return; - // for extra safety, marshal the end of operation back to the update thread if necessary. Scheduler.Add(() => { + if (lease != leasedInProgress) + return; + // UnbindAll() is purposefully used instead of Return() - the two do roughly the same thing, with one difference: // the former won't throw if the lease has already been returned before. // this matters because framework can unbind the lease via the internal UnbindAllBindables(), which is not always detectable From 90ba8ae234bbec60aab5b42f9cd625eb8e2d2f02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 30 Jan 2021 23:39:01 +0100 Subject: [PATCH 0217/1791] Don't part room if join task was cancelled --- .../Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs index 65d112a032..1e57847f04 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs @@ -87,7 +87,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { if (t.IsCompletedSuccessfully) Schedule(() => onSuccess?.Invoke(room)); - else + else if (t.IsFaulted) { const string message = "Failed to join multiplayer room."; From d7e5a212134e371838ee342ecb476bdcc0347c6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 31 Jan 2021 15:23:53 +0100 Subject: [PATCH 0218/1791] Add failing test case --- .../Formats/LegacyStoryboardDecoderTest.cs | 20 +++++++++++++++++++ osu.Game.Tests/Resources/animation-types.osb | 9 +++++++++ 2 files changed, 29 insertions(+) create mode 100644 osu.Game.Tests/Resources/animation-types.osb diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs index 7bee580863..bcde899789 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs @@ -129,5 +129,25 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(3456, ((StoryboardSprite)background.Elements.Single()).InitialPosition.X); } } + + [Test] + public void TestDecodeOutOfRangeLoopAnimationType() + { + var decoder = new LegacyStoryboardDecoder(); + + using (var resStream = TestResources.OpenResource("animation-types.osb")) + using (var stream = new LineBufferedReader(resStream)) + { + var storyboard = decoder.Decode(stream); + + StoryboardLayer foreground = storyboard.Layers.Single(l => l.Depth == 0); + Assert.AreEqual(AnimationLoopType.LoopForever, ((StoryboardAnimation)foreground.Elements[0]).LoopType); + Assert.AreEqual(AnimationLoopType.LoopOnce, ((StoryboardAnimation)foreground.Elements[1]).LoopType); + Assert.AreEqual(AnimationLoopType.LoopForever, ((StoryboardAnimation)foreground.Elements[2]).LoopType); + Assert.AreEqual(AnimationLoopType.LoopOnce, ((StoryboardAnimation)foreground.Elements[3]).LoopType); + Assert.AreEqual(AnimationLoopType.LoopForever, ((StoryboardAnimation)foreground.Elements[4]).LoopType); + Assert.AreEqual(AnimationLoopType.LoopForever, ((StoryboardAnimation)foreground.Elements[5]).LoopType); + } + } } } diff --git a/osu.Game.Tests/Resources/animation-types.osb b/osu.Game.Tests/Resources/animation-types.osb new file mode 100644 index 0000000000..82233b7d30 --- /dev/null +++ b/osu.Game.Tests/Resources/animation-types.osb @@ -0,0 +1,9 @@ +osu file format v14 + +[Events] +Animation,Foreground,Centre,"forever-string.png",330,240,10,108,LoopForever +Animation,Foreground,Centre,"once-string.png",330,240,10,108,LoopOnce +Animation,Foreground,Centre,"forever-number.png",330,240,10,108,0 +Animation,Foreground,Centre,"once-number.png",330,240,10,108,1 +Animation,Foreground,Centre,"undefined-number.png",330,240,10,108,16 +Animation,Foreground,Centre,"omitted.png",330,240,10,108 From b9a49d5589dfd91e651ad84c0872401e27ab192e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 31 Jan 2021 15:33:07 +0100 Subject: [PATCH 0219/1791] Coerce undefined animation loop types to Forever --- osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index 9a244c8bb2..b9bf6823b5 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -139,7 +139,7 @@ namespace osu.Game.Beatmaps.Formats // this is random as hell but taken straight from osu-stable. frameDelay = Math.Round(0.015 * frameDelay) * 1.186 * (1000 / 60f); - var loopType = split.Length > 8 ? (AnimationLoopType)Enum.Parse(typeof(AnimationLoopType), split[8]) : AnimationLoopType.LoopForever; + var loopType = split.Length > 8 ? parseAnimationLoopType(split[8]) : AnimationLoopType.LoopForever; storyboardSprite = new StoryboardAnimation(path, origin, new Vector2(x, y), frameCount, frameDelay, loopType); storyboard.GetLayer(layer).Add(storyboardSprite); break; @@ -341,6 +341,12 @@ namespace osu.Game.Beatmaps.Formats } } + private AnimationLoopType parseAnimationLoopType(string value) + { + var parsed = (AnimationLoopType)Enum.Parse(typeof(AnimationLoopType), value); + return Enum.IsDefined(typeof(AnimationLoopType), parsed) ? parsed : AnimationLoopType.LoopForever; + } + private void handleVariables(string line) { var pair = SplitKeyVal(line, '='); From 81b052b866af6422434f96638dabace022618f95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 31 Jan 2021 20:18:12 +0100 Subject: [PATCH 0220/1791] Add failing test cases --- .../Rulesets/Mods/ModTimeRampTest.cs | 82 +++++++++++++++++++ osu.Game/Rulesets/Mods/ModTimeRamp.cs | 4 +- 2 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Tests/Rulesets/Mods/ModTimeRampTest.cs diff --git a/osu.Game.Tests/Rulesets/Mods/ModTimeRampTest.cs b/osu.Game.Tests/Rulesets/Mods/ModTimeRampTest.cs new file mode 100644 index 0000000000..894648b8b3 --- /dev/null +++ b/osu.Game.Tests/Rulesets/Mods/ModTimeRampTest.cs @@ -0,0 +1,82 @@ +// 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.Audio.Track; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Objects; + +namespace osu.Game.Tests.Rulesets.Mods +{ + [TestFixture] + public class ModTimeRampTest + { + private const double start_time = 1000; + private const double duration = 9000; + + private TrackVirtual track; + private IBeatmap beatmap; + + [SetUp] + public void SetUp() + { + track = new TrackVirtual(20_000); + beatmap = new Beatmap + { + HitObjects = + { + new Spinner + { + StartTime = start_time, + Duration = duration + } + } + }; + } + + [TestCase(0, 1)] + [TestCase(start_time, 1)] + [TestCase(start_time + duration * ModTimeRamp.FINAL_RATE_PROGRESS / 2, 1.25)] + [TestCase(start_time + duration * ModTimeRamp.FINAL_RATE_PROGRESS, 1.5)] + [TestCase(start_time + duration, 1.5)] + [TestCase(15000, 1.5)] + public void TestModWindUp(double time, double expectedRate) + { + var mod = new ModWindUp(); + mod.ApplyToBeatmap(beatmap); + mod.ApplyToTrack(track); + + seekTrackAndUpdateMod(mod, time); + + Assert.That(mod.SpeedChange.Value, Is.EqualTo(expectedRate)); + } + + [TestCase(0, 1)] + [TestCase(start_time, 1)] + [TestCase(start_time + duration * ModTimeRamp.FINAL_RATE_PROGRESS / 2, 0.75)] + [TestCase(start_time + duration * ModTimeRamp.FINAL_RATE_PROGRESS, 0.5)] + [TestCase(start_time + duration, 0.5)] + [TestCase(15000, 0.5)] + public void TestModWindDown(double time, double expectedRate) + { + var mod = new ModWindDown + { + FinalRate = { Value = 0.5 } + }; + mod.ApplyToBeatmap(beatmap); + mod.ApplyToTrack(track); + + seekTrackAndUpdateMod(mod, time); + + Assert.That(mod.SpeedChange.Value, Is.EqualTo(expectedRate)); + } + + private void seekTrackAndUpdateMod(ModTimeRamp mod, double time) + { + track.Seek(time); + // update the mod via a fake playfield to re-calculate the current rate. + mod.Update(null); + } + } +} diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index 4d43ae73d3..c691339a81 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mods /// /// The point in the beatmap at which the final ramping rate should be reached. /// - private const double final_rate_progress = 0.75f; + public const double FINAL_RATE_PROGRESS = 0.75f; [SettingSource("Initial rate", "The starting speed of the track")] public abstract BindableNumber InitialRate { get; } @@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Mods SpeedChange.SetDefault(); beginRampTime = beatmap.HitObjects.FirstOrDefault()?.StartTime ?? 0; - finalRateTime = final_rate_progress * (lastObject?.GetEndTime() ?? 0); + finalRateTime = FINAL_RATE_PROGRESS * (lastObject?.GetEndTime() ?? 0); } public virtual void Update(Playfield playfield) From 547b3d8bed86d1398da54ea3d6ffcca8f883763b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 31 Jan 2021 20:34:56 +0100 Subject: [PATCH 0221/1791] Fix speed change calculation in time ramp mods --- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index c691339a81..a28a0351a6 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -66,17 +66,18 @@ namespace osu.Game.Rulesets.Mods public virtual void ApplyToBeatmap(IBeatmap beatmap) { - HitObject lastObject = beatmap.HitObjects.LastOrDefault(); - SpeedChange.SetDefault(); - beginRampTime = beatmap.HitObjects.FirstOrDefault()?.StartTime ?? 0; - finalRateTime = FINAL_RATE_PROGRESS * (lastObject?.GetEndTime() ?? 0); + double firstObjectStart = beatmap.HitObjects.FirstOrDefault()?.StartTime ?? 0; + double lastObjectEnd = beatmap.HitObjects.LastOrDefault()?.GetEndTime() ?? 0; + + beginRampTime = firstObjectStart; + finalRateTime = firstObjectStart + FINAL_RATE_PROGRESS * (lastObjectEnd - firstObjectStart); } public virtual void Update(Playfield playfield) { - applyRateAdjustment((track.CurrentTime - beginRampTime) / finalRateTime); + applyRateAdjustment((track.CurrentTime - beginRampTime) / (finalRateTime - beginRampTime)); } /// From a0de1cbfd07447789c4093d67c10e782e6463725 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 31 Jan 2021 21:09:41 +0100 Subject: [PATCH 0222/1791] Handle no-duration single-object edge case --- .../Rulesets/Mods/ModTimeRampTest.cs | 55 +++++++++++++++---- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 2 +- 2 files changed, 44 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Rulesets/Mods/ModTimeRampTest.cs b/osu.Game.Tests/Rulesets/Mods/ModTimeRampTest.cs index 894648b8b3..4b9f2181dc 100644 --- a/osu.Game.Tests/Rulesets/Mods/ModTimeRampTest.cs +++ b/osu.Game.Tests/Rulesets/Mods/ModTimeRampTest.cs @@ -16,23 +16,11 @@ namespace osu.Game.Tests.Rulesets.Mods private const double duration = 9000; private TrackVirtual track; - private IBeatmap beatmap; [SetUp] public void SetUp() { track = new TrackVirtual(20_000); - beatmap = new Beatmap - { - HitObjects = - { - new Spinner - { - StartTime = start_time, - Duration = duration - } - } - }; } [TestCase(0, 1)] @@ -43,6 +31,7 @@ namespace osu.Game.Tests.Rulesets.Mods [TestCase(15000, 1.5)] public void TestModWindUp(double time, double expectedRate) { + var beatmap = createSingleSpinnerBeatmap(); var mod = new ModWindUp(); mod.ApplyToBeatmap(beatmap); mod.ApplyToTrack(track); @@ -60,6 +49,7 @@ namespace osu.Game.Tests.Rulesets.Mods [TestCase(15000, 0.5)] public void TestModWindDown(double time, double expectedRate) { + var beatmap = createSingleSpinnerBeatmap(); var mod = new ModWindDown { FinalRate = { Value = 0.5 } @@ -72,11 +62,52 @@ namespace osu.Game.Tests.Rulesets.Mods Assert.That(mod.SpeedChange.Value, Is.EqualTo(expectedRate)); } + [TestCase(0, 1)] + [TestCase(start_time, 1)] + [TestCase(2 * start_time, 1.5)] + public void TestZeroDurationMap(double time, double expectedRate) + { + var beatmap = createSingleObjectBeatmap(); + var mod = new ModWindUp(); + mod.ApplyToBeatmap(beatmap); + mod.ApplyToTrack(track); + + seekTrackAndUpdateMod(mod, time); + + Assert.That(mod.SpeedChange.Value, Is.EqualTo(expectedRate)); + } + private void seekTrackAndUpdateMod(ModTimeRamp mod, double time) { track.Seek(time); // update the mod via a fake playfield to re-calculate the current rate. mod.Update(null); } + + private static Beatmap createSingleSpinnerBeatmap() + { + return new Beatmap + { + HitObjects = + { + new Spinner + { + StartTime = start_time, + Duration = duration + } + } + }; + } + + private static Beatmap createSingleObjectBeatmap() + { + return new Beatmap + { + HitObjects = + { + new HitCircle { StartTime = start_time } + } + }; + } } } diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index a28a0351a6..b6916c838e 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -77,7 +77,7 @@ namespace osu.Game.Rulesets.Mods public virtual void Update(Playfield playfield) { - applyRateAdjustment((track.CurrentTime - beginRampTime) / (finalRateTime - beginRampTime)); + applyRateAdjustment((track.CurrentTime - beginRampTime) / Math.Max(1, finalRateTime - beginRampTime)); } /// From 39d46d21e6e2453121e513a2025cd6c5a2bd9ba9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 31 Jan 2021 23:44:27 +0300 Subject: [PATCH 0223/1791] Add failing test case --- .../Online/TestSceneStandAloneChatDisplay.cs | 116 ++++++++++++------ 1 file changed, 80 insertions(+), 36 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs index 492abdd88d..02ef024128 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs @@ -8,6 +8,7 @@ using osu.Game.Users; using osuTK; using System; using System.Linq; +using NUnit.Framework; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Containers; using osu.Game.Overlays.Chat; @@ -16,8 +17,6 @@ namespace osu.Game.Tests.Visual.Online { public class TestSceneStandAloneChatDisplay : OsuTestScene { - private readonly Channel testChannel = new Channel(); - private readonly User admin = new User { Username = "HappyStick", @@ -46,78 +45,84 @@ namespace osu.Game.Tests.Visual.Online [Cached] private ChannelManager channelManager = new ChannelManager(); - private readonly TestStandAloneChatDisplay chatDisplay; - private readonly TestStandAloneChatDisplay chatDisplay2; + private TestStandAloneChatDisplay chatDisplay; + private TestStandAloneChatDisplay chatDisplay2; + private int messageIdSequence; + + private Channel testChannel; public TestSceneStandAloneChatDisplay() { Add(channelManager); - - Add(chatDisplay = new TestStandAloneChatDisplay - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Margin = new MarginPadding(20), - Size = new Vector2(400, 80) - }); - - Add(chatDisplay2 = new TestStandAloneChatDisplay(true) - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Margin = new MarginPadding(20), - Size = new Vector2(400, 150) - }); } - protected override void LoadComplete() + [SetUp] + public void SetUp() => Schedule(() => { - base.LoadComplete(); + messageIdSequence = 0; + channelManager.CurrentChannel.Value = testChannel = new Channel(); - channelManager.CurrentChannel.Value = testChannel; + Children = new[] + { + chatDisplay = new TestStandAloneChatDisplay + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding(20), + Size = new Vector2(400, 80), + Channel = { Value = testChannel }, + }, + chatDisplay2 = new TestStandAloneChatDisplay(true) + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Margin = new MarginPadding(20), + Size = new Vector2(400, 150), + Channel = { Value = testChannel }, + } + }; + }); - chatDisplay.Channel.Value = testChannel; - chatDisplay2.Channel.Value = testChannel; - - int sequence = 0; - - AddStep("message from admin", () => testChannel.AddNewMessages(new Message(sequence++) + [Test] + public void TestManyMessages() + { + AddStep("message from admin", () => testChannel.AddNewMessages(new Message(messageIdSequence++) { Sender = admin, Content = "I am a wang!" })); - AddStep("message from team red", () => testChannel.AddNewMessages(new Message(sequence++) + AddStep("message from team red", () => testChannel.AddNewMessages(new Message(messageIdSequence++) { Sender = redUser, Content = "I am team red." })); - AddStep("message from team red", () => testChannel.AddNewMessages(new Message(sequence++) + AddStep("message from team red", () => testChannel.AddNewMessages(new Message(messageIdSequence++) { Sender = redUser, Content = "I plan to win!" })); - AddStep("message from team blue", () => testChannel.AddNewMessages(new Message(sequence++) + AddStep("message from team blue", () => testChannel.AddNewMessages(new Message(messageIdSequence++) { Sender = blueUser, Content = "Not on my watch. Prepare to eat saaaaaaaaaand. Lots and lots of saaaaaaand." })); - AddStep("message from admin", () => testChannel.AddNewMessages(new Message(sequence++) + AddStep("message from admin", () => testChannel.AddNewMessages(new Message(messageIdSequence++) { Sender = admin, Content = "Okay okay, calm down guys. Let's do this!" })); - AddStep("message from long username", () => testChannel.AddNewMessages(new Message(sequence++) + AddStep("message from long username", () => testChannel.AddNewMessages(new Message(messageIdSequence++) { Sender = longUsernameUser, Content = "Hi guys, my new username is lit!" })); - AddStep("message with new date", () => testChannel.AddNewMessages(new Message(sequence++) + AddStep("message with new date", () => testChannel.AddNewMessages(new Message(messageIdSequence++) { Sender = longUsernameUser, Content = "Message from the future!", @@ -131,7 +136,7 @@ namespace osu.Game.Tests.Visual.Online { for (int i = 0; i < messages_per_call; i++) { - testChannel.AddNewMessages(new Message(sequence++) + testChannel.AddNewMessages(new Message(messageIdSequence++) { Sender = longUsernameUser, Content = "Many messages! " + Guid.NewGuid(), @@ -156,6 +161,45 @@ namespace osu.Game.Tests.Visual.Online AddUntilStep("ensure still scrolled to bottom", () => chatDisplay.ScrolledToBottom); } + /// + /// Tests that when a message gets wrapped by the chat display getting contracted while scrolled to bottom, the chat will still keep scrolling down. + /// + [Test] + public void TestMessageWrappingKeepsAutoScrolling() + { + AddStep("fill chat", () => + { + for (int i = 0; i < 10; i++) + { + testChannel.AddNewMessages(new Message(messageIdSequence++) + { + Sender = longUsernameUser, + Content = $"some stuff {Guid.NewGuid()}", + }); + } + }); + + AddAssert("ensure scrolled to bottom", () => chatDisplay.ScrolledToBottom); + + // send message with short words for text wrapping to occur when contracting chat. + AddStep("send lorem ipsum", () => testChannel.AddNewMessages(new Message(messageIdSequence++) + { + Sender = longUsernameUser, + Content = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce et bibendum velit.", + })); + + AddStep("contract chat", () => chatDisplay.Width -= 100); + AddUntilStep("ensure still scrolled to bottom", () => chatDisplay.ScrolledToBottom); + + AddStep("send another message", () => testChannel.AddNewMessages(new Message(messageIdSequence++) + { + Sender = admin, + Content = "As we were saying...", + })); + + AddUntilStep("ensure still scrolled to bottom", () => chatDisplay.ScrolledToBottom); + } + private class TestStandAloneChatDisplay : StandAloneChatDisplay { public TestStandAloneChatDisplay(bool textbox = false) From e806e5bcd1580f1c85026e53d289d7cf933e4767 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 31 Jan 2021 23:37:52 +0300 Subject: [PATCH 0224/1791] Improve robustness of chat auto-scrolling logic Fix auto-scrolling state changing by old messages removal logic --- osu.Game/Overlays/Chat/DrawableChannel.cs | 47 ++++++++++++++++------- 1 file changed, 33 insertions(+), 14 deletions(-) diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 5926d11c03..f1aa387c17 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -24,7 +24,7 @@ namespace osu.Game.Overlays.Chat { public readonly Channel Channel; protected FillFlowContainer ChatLineFlow; - private OsuScrollContainer scroll; + private ChannelScrollContainer scroll; private bool scrollbarVisible = true; @@ -56,7 +56,7 @@ namespace osu.Game.Overlays.Chat { RelativeSizeAxes = Axes.Both, Masking = true, - Child = scroll = new OsuScrollContainer + Child = scroll = new ChannelScrollContainer { ScrollbarVisible = scrollbarVisible, RelativeSizeAxes = Axes.Both, @@ -80,12 +80,6 @@ namespace osu.Game.Overlays.Chat Channel.PendingMessageResolved += pendingMessageResolved; } - protected override void LoadComplete() - { - base.LoadComplete(); - scrollToEnd(); - } - protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); @@ -113,8 +107,6 @@ namespace osu.Game.Overlays.Chat ChatLineFlow.Clear(); } - bool shouldScrollToEnd = scroll.IsScrolledToEnd(10) || !chatLines.Any() || newMessages.Any(m => m is LocalMessage); - // Add up to last Channel.MAX_HISTORY messages var displayMessages = newMessages.Skip(Math.Max(0, newMessages.Count() - Channel.MAX_HISTORY)); @@ -153,8 +145,10 @@ namespace osu.Game.Overlays.Chat } } - if (shouldScrollToEnd) - scrollToEnd(); + // due to the scroll adjusts from old messages removal above, a scroll-to-end must be enforced, + // to avoid making the container think the user has scrolled back up and unwantedly disable auto-scrolling. + if (scroll.ShouldAutoScroll || newMessages.Any(m => m is LocalMessage)) + ScheduleAfterChildren(() => scroll.ScrollToEnd()); }); private void pendingMessageResolved(Message existing, Message updated) => Schedule(() => @@ -178,8 +172,6 @@ namespace osu.Game.Overlays.Chat private IEnumerable chatLines => ChatLineFlow.Children.OfType(); - private void scrollToEnd() => ScheduleAfterChildren(() => scroll.ScrollToEnd()); - public class DaySeparator : Container { public float TextSize @@ -243,5 +235,32 @@ namespace osu.Game.Overlays.Chat }; } } + + /// + /// An with functionality to automatically scrolls whenever the maximum scrollable distance increases. + /// + private class ChannelScrollContainer : OsuScrollContainer + { + private const float auto_scroll_leniency = 10f; + + private float? lastExtent; + + /// + /// Whether this should automatically scroll to end on the next call to . + /// + public bool ShouldAutoScroll { get; private set; } = true; + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + if ((lastExtent == null || ScrollableExtent > lastExtent) && ShouldAutoScroll) + ScrollToEnd(); + else + ShouldAutoScroll = IsScrolledToEnd(auto_scroll_leniency); + + lastExtent = ScrollableExtent; + } + } } } From 230b347c1efbfc7baa7da6af8676356e47d7a4d2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Feb 2021 12:18:11 +0900 Subject: [PATCH 0225/1791] Move ModSelectOverlay.IsValidMod to a property --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 29 ++++++++++++++----- .../Overlays/Mods/SoloModSelectOverlay.cs | 6 ---- .../Multiplayer/FreeModSelectOverlay.cs | 5 ---- .../Multiplayer/MultiplayerMatchSongSelect.cs | 11 +++++-- osu.Game/Screens/Select/MatchSongSelect.cs | 5 +++- 5 files changed, 34 insertions(+), 22 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 5709ca3b8d..c21b9ba409 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -29,7 +30,6 @@ namespace osu.Game.Overlays.Mods { public abstract class ModSelectOverlay : WaveOverlayContainer { - private readonly Func isValidMod; public const float HEIGHT = 510; protected readonly TriangleButton DeselectAllButton; @@ -46,6 +46,20 @@ namespace osu.Game.Overlays.Mods protected readonly ModSettingsContainer ModSettingsContainer; + [NotNull] + private Func isValidMod = m => true; + + [NotNull] + public Func IsValidMod + { + get => isValidMod; + set + { + isValidMod = value ?? throw new ArgumentNullException(nameof(value)); + updateAvailableMods(); + } + } + public readonly Bindable> SelectedMods = new Bindable>(Array.Empty()); private Bindable>> availableMods; @@ -60,10 +74,8 @@ namespace osu.Game.Overlays.Mods private SampleChannel sampleOn, sampleOff; - protected ModSelectOverlay(Func isValidMod = null) + protected ModSelectOverlay() { - this.isValidMod = isValidMod ?? (m => true); - Waves.FirstWaveColour = Color4Extensions.FromHex(@"19b0e2"); Waves.SecondWaveColour = Color4Extensions.FromHex(@"2280a2"); Waves.ThirdWaveColour = Color4Extensions.FromHex(@"005774"); @@ -350,7 +362,7 @@ namespace osu.Game.Overlays.Mods { base.LoadComplete(); - availableMods.BindValueChanged(availableModsChanged, true); + availableMods.BindValueChanged(_ => updateAvailableMods(), true); SelectedMods.BindValueChanged(selectedModsChanged, true); } @@ -405,12 +417,13 @@ namespace osu.Game.Overlays.Mods public override bool OnPressed(GlobalAction action) => false; // handled by back button - private void availableModsChanged(ValueChangedEvent>> mods) + private void updateAvailableMods() { - if (mods.NewValue == null) return; + if (availableMods.Value == null) + return; foreach (var section in ModSectionsContainer.Children) - section.Mods = mods.NewValue[section.ModType].Where(isValidMod); + section.Mods = availableMods.Value[section.ModType].Where(IsValidMod); } private void selectedModsChanged(ValueChangedEvent> mods) diff --git a/osu.Game/Overlays/Mods/SoloModSelectOverlay.cs b/osu.Game/Overlays/Mods/SoloModSelectOverlay.cs index 53d0c9fce9..d039ad1f98 100644 --- a/osu.Game/Overlays/Mods/SoloModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/SoloModSelectOverlay.cs @@ -1,18 +1,12 @@ // 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.Mods; namespace osu.Game.Overlays.Mods { public class SoloModSelectOverlay : ModSelectOverlay { - public SoloModSelectOverlay(Func isValidMod = null) - : base(isValidMod) - { - } - protected override void OnModSelected(Mod mod) { base.OnModSelected(mod); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/FreeModSelectOverlay.cs index 10b68ec5a6..56e74a8460 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/FreeModSelectOverlay.cs @@ -14,11 +14,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { public class FreeModSelectOverlay : ModSelectOverlay { - public FreeModSelectOverlay(Func isValidMod = null) - : base(isValidMod) - { - } - protected override ModSection CreateModSection(ModType type) => new FreeModSection(type); private class FreeModSection : ModSection diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index 86b8f22d34..d36ebeec0f 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -54,7 +54,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING }; - freeModSelectOverlay = new FreeModSelectOverlay(isValidMod) { SelectedMods = { BindTarget = freeMods } }; + freeModSelectOverlay = new FreeModSelectOverlay + { + SelectedMods = { BindTarget = freeMods }, + IsValidMod = isValidMod, + }; } [BackgroundDependencyLoader] @@ -130,7 +134,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea(); - protected override ModSelectOverlay CreateModSelectOverlay() => new SoloModSelectOverlay(isValidMod); + protected override ModSelectOverlay CreateModSelectOverlay() => new SoloModSelectOverlay + { + IsValidMod = isValidMod + }; protected override IEnumerable<(FooterButton, OverlayContainer)> CreateFooterButtons() { diff --git a/osu.Game/Screens/Select/MatchSongSelect.cs b/osu.Game/Screens/Select/MatchSongSelect.cs index 280f46f9ab..98e02a9294 100644 --- a/osu.Game/Screens/Select/MatchSongSelect.cs +++ b/osu.Game/Screens/Select/MatchSongSelect.cs @@ -81,7 +81,10 @@ namespace osu.Game.Screens.Select item.RequiredMods.AddRange(Mods.Value.Select(m => m.CreateCopy())); } - protected override ModSelectOverlay CreateModSelectOverlay() => new SoloModSelectOverlay(isValidMod); + protected override ModSelectOverlay CreateModSelectOverlay() => new SoloModSelectOverlay + { + IsValidMod = isValidMod + }; private bool isValidMod(Mod mod) => !(mod is ModAutoplay) && (mod as MultiMod)?.Mods.Any(mm => mm is ModAutoplay) != true; } From 797a8102876808174924486af644de927d7123f4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Feb 2021 13:24:56 +0900 Subject: [PATCH 0226/1791] Allow unstacking mods --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 30 +++++++++++++++++-- .../Multiplayer/FreeModSelectOverlay.cs | 5 ++++ osu.Game/Utils/ModValidation.cs | 17 +++++++---- 3 files changed, 45 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index c21b9ba409..db9460c494 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -22,6 +22,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Rulesets.Mods; using osu.Game.Screens; +using osu.Game.Utils; using osuTK; using osuTK.Graphics; using osuTK.Input; @@ -46,9 +47,27 @@ namespace osu.Game.Overlays.Mods protected readonly ModSettingsContainer ModSettingsContainer; + private bool stacked = true; + + /// + /// Whether mod icons should be stacked, or appear as individual buttons. + /// + public bool Stacked + { + get => stacked; + set + { + stacked = value; + updateAvailableMods(); + } + } + [NotNull] private Func isValidMod = m => true; + /// + /// A function that checks whether a given mod is valid. + /// [NotNull] public Func IsValidMod { @@ -419,11 +438,18 @@ namespace osu.Game.Overlays.Mods private void updateAvailableMods() { - if (availableMods.Value == null) + if (availableMods?.Value == null) return; foreach (var section in ModSectionsContainer.Children) - section.Mods = availableMods.Value[section.ModType].Where(IsValidMod); + { + IEnumerable modEnumeration = availableMods.Value[section.ModType]; + + if (!stacked) + modEnumeration = ModValidation.FlattenMods(modEnumeration); + + section.Mods = modEnumeration.Where(IsValidMod); + } } private void selectedModsChanged(ValueChangedEvent> mods) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/FreeModSelectOverlay.cs index 56e74a8460..8334df1e44 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/FreeModSelectOverlay.cs @@ -14,6 +14,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { public class FreeModSelectOverlay : ModSelectOverlay { + public FreeModSelectOverlay() + { + Stacked = false; + } + protected override ModSection CreateModSection(ModType type) => new FreeModSection(type); private class FreeModSection : ModSection diff --git a/osu.Game/Utils/ModValidation.cs b/osu.Game/Utils/ModValidation.cs index 0c4d58ab2e..3597396ec4 100644 --- a/osu.Game/Utils/ModValidation.cs +++ b/osu.Game/Utils/ModValidation.cs @@ -42,7 +42,7 @@ namespace osu.Game.Utils var incompatibleTypes = new HashSet(); var incomingTypes = new HashSet(); - foreach (var mod in combination.SelectMany(flattenMod)) + foreach (var mod in combination.SelectMany(FlattenMod)) { // Add the new mod incompatibilities, checking whether any match the existing mod types. foreach (var t in mod.IncompatibleMods) @@ -79,7 +79,7 @@ namespace osu.Game.Utils { var allowedSet = new HashSet(allowedTypes); - return combination.SelectMany(flattenMod) + return combination.SelectMany(FlattenMod) .All(m => allowedSet.Contains(m.GetType())); } @@ -93,20 +93,27 @@ namespace osu.Game.Utils /// The set of incompatible types. /// Whether the given is incompatible. private static bool isModIncompatible(Mod mod, ICollection incompatibleTypes) - => flattenMod(mod) + => FlattenMod(mod) .SelectMany(m => m.GetType().EnumerateBaseTypes()) .Any(incompatibleTypes.Contains); + /// + /// Flattens a set of s, returning a new set with all s removed. + /// + /// The set of s to flatten. + /// The new set, containing all s in recursively with all s removed. + public static IEnumerable FlattenMods(IEnumerable mods) => mods.SelectMany(FlattenMod); + /// /// Flattens a , returning a set of s in-place of any s. /// /// The to flatten. /// A set of singular "flattened" s - private static IEnumerable flattenMod(Mod mod) + public static IEnumerable FlattenMod(Mod mod) { if (mod is MultiMod multi) { - foreach (var m in multi.Mods.SelectMany(flattenMod)) + foreach (var m in multi.Mods.SelectMany(FlattenMod)) yield return m; } else From e02e3cf19a9ad9ace7d21e949ec30e6c9269771e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Feb 2021 13:35:48 +0900 Subject: [PATCH 0227/1791] Disallow selecting DT/HT/WU/WD as allowable freemods --- .../OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index d36ebeec0f..98e3ca3358 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -57,7 +57,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer freeModSelectOverlay = new FreeModSelectOverlay { SelectedMods = { BindTarget = freeMods }, - IsValidMod = isValidMod, + IsValidMod = isValidFreeMod, }; } @@ -147,6 +147,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer } private bool isValidMod(Mod mod) => !(mod is ModAutoplay) && (mod as MultiMod)?.Mods.Any(mm => mm is ModAutoplay) != true; + + private bool isValidFreeMod(Mod mod) => isValidMod(mod) && !(mod is ModRateAdjust) && !(mod is ModTimeRamp); } public class FooterButtonFreeMods : FooterButton, IHasCurrentValue> From 4ae10b1e1c88f4d6b6a3fe5ec136abf1e3ead32b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Feb 2021 13:40:59 +0900 Subject: [PATCH 0228/1791] Add initial UI for selecting extra mods --- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 29 ++++++-- .../Multiplayer/MultiplayerMatchSubScreen.cs | 69 +++++++++++++++++-- 2 files changed, 90 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 2449563c73..231fb58836 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -2,6 +2,7 @@ // 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.Audio; @@ -29,6 +30,11 @@ namespace osu.Game.Screens.OnlinePlay.Match [Resolved(typeof(Room), nameof(Room.Playlist))] protected BindableList Playlist { get; private set; } + /// + /// Any mods applied by/to the local user. + /// + protected readonly Bindable> ExtraMods = new Bindable>(Array.Empty()); + [Resolved] private MusicController music { get; set; } @@ -55,6 +61,8 @@ namespace osu.Game.Screens.OnlinePlay.Match managerUpdated = beatmapManager.ItemUpdated.GetBoundCopy(); managerUpdated.BindValueChanged(beatmapUpdated); + + ExtraMods.BindValueChanged(_ => updateMods()); } public override void OnEntering(IScreen last) @@ -95,12 +103,17 @@ namespace osu.Game.Screens.OnlinePlay.Match { updateWorkingBeatmap(); - var item = SelectedItem.Value; + if (SelectedItem.Value == null) + return; - Mods.Value = item?.RequiredMods?.ToArray() ?? Array.Empty(); + // Remove any extra mods that are no longer allowed. + ExtraMods.Value = ExtraMods.Value + .Where(m => SelectedItem.Value.AllowedMods.Any(a => m.GetType() == a.GetType())) + .ToList(); - if (item?.Ruleset != null) - Ruleset.Value = item.Ruleset.Value; + updateMods(); + + Ruleset.Value = SelectedItem.Value.Ruleset.Value; } private void beatmapUpdated(ValueChangedEvent> weakSet) => Schedule(updateWorkingBeatmap); @@ -115,6 +128,14 @@ namespace osu.Game.Screens.OnlinePlay.Match Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap); } + private void updateMods() + { + if (SelectedItem.Value == null) + return; + + Mods.Value = ExtraMods.Value.Concat(SelectedItem.Value.RequiredMods).ToList(); + } + private void beginHandlingTrack() { Beatmap.BindValueChanged(applyLoopingToTrack, true); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 7c4b6d18ec..95dda1a051 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -14,12 +14,15 @@ using osu.Framework.Screens; using osu.Game.Extensions; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; +using osu.Game.Overlays.Mods; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Match; using osu.Game.Screens.OnlinePlay.Match.Components; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; using osu.Game.Screens.OnlinePlay.Multiplayer.Participants; +using osu.Game.Screens.Play.HUD; using osu.Game.Users; +using osuTK; using ParticipantsList = osu.Game.Screens.OnlinePlay.Multiplayer.Participants.ParticipantsList; namespace osu.Game.Screens.OnlinePlay.Multiplayer @@ -37,7 +40,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer [Resolved] private OngoingOperationTracker ongoingOperationTracker { get; set; } + private ModSelectOverlay extraModSelectOverlay; private MultiplayerMatchSettingsOverlay settingsOverlay; + private Drawable extraModsSection; private IBindable isConnected; @@ -129,10 +134,39 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Padding = new MarginPadding { Horizontal = 5 }, - Children = new Drawable[] + Spacing = new Vector2(0, 10), + Children = new[] { - new OverlinedHeader("Beatmap"), - new BeatmapSelectionControl { RelativeSizeAxes = Axes.X } + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new OverlinedHeader("Beatmap"), + new BeatmapSelectionControl { RelativeSizeAxes = Axes.X } + } + }, + extraModsSection = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new OverlinedHeader("Extra mods"), + new ModDisplay + { + DisplayUnrankedText = false, + Current = ExtraMods + }, + new PurpleTriangleButton + { + RelativeSizeAxes = Axes.X, + Text = "Select", + Action = () => extraModSelectOverlay.Show() + } + } + } } } } @@ -174,6 +208,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer new Dimension(GridSizeMode.AutoSize), } }, + extraModSelectOverlay = new SoloModSelectOverlay + { + SelectedMods = { BindTarget = ExtraMods }, + Stacked = false, + IsValidMod = _ => false + }, settingsOverlay = new MultiplayerMatchSettingsOverlay { RelativeSizeAxes = Axes.Both, @@ -219,10 +259,31 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer return true; } + if (extraModSelectOverlay.State.Value == Visibility.Visible) + { + extraModSelectOverlay.Hide(); + return true; + } + return base.OnBackButton(); } - private void onPlaylistChanged(object sender, NotifyCollectionChangedEventArgs e) => SelectedItem.Value = Playlist.FirstOrDefault(); + private void onPlaylistChanged(object sender, NotifyCollectionChangedEventArgs e) + { + SelectedItem.Value = Playlist.FirstOrDefault(); + + if (SelectedItem.Value?.AllowedMods.Any() != true) + { + extraModsSection.Hide(); + extraModSelectOverlay.Hide(); + extraModSelectOverlay.IsValidMod = _ => false; + } + else + { + extraModsSection.Show(); + extraModSelectOverlay.IsValidMod = m => SelectedItem.Value.AllowedMods.Any(a => a.GetType() == m.GetType()); + } + } private void onReadyClick() { From b846146f163a0dfe87d8d5e02f00730387b8de04 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Feb 2021 13:58:44 +0900 Subject: [PATCH 0229/1791] Update mods when resuming room subscreen --- osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 231fb58836..3c4c6ce040 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -81,6 +81,7 @@ namespace osu.Game.Screens.OnlinePlay.Match { base.OnResuming(last); beginHandlingTrack(); + updateMods(); } public override bool OnExiting(IScreen next) From 426569c2a93244a7a31688a0d96529d752858ebc Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Feb 2021 14:57:39 +0900 Subject: [PATCH 0230/1791] Move common song select implementation for online play --- .../Multiplayer/MultiplayerMatchSongSelect.cs | 91 +------------ .../OnlinePlay/OnlinePlaySongSelect.cs | 124 ++++++++++++++++++ osu.Game/Screens/Select/MatchSongSelect.cs | 34 +---- 3 files changed, 130 insertions(+), 119 deletions(-) create mode 100644 osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index 98e3ca3358..e892570066 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -1,25 +1,18 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; -using System.Linq; -using Humanizer; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using osu.Framework.Logging; using osu.Framework.Screens; -using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; -using osu.Game.Overlays.Mods; -using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Select; @@ -27,67 +20,21 @@ using osuTK; namespace osu.Game.Screens.OnlinePlay.Multiplayer { - public class MultiplayerMatchSongSelect : SongSelect, IOnlinePlaySubScreen + public class MultiplayerMatchSongSelect : OnlinePlaySongSelect { - public string ShortTitle => "song selection"; - - public override string Title => ShortTitle.Humanize(); - - [Resolved(typeof(Room), nameof(Room.Playlist))] - private BindableList playlist { get; set; } - [Resolved] private StatefulMultiplayerClient client { get; set; } - private readonly Bindable> freeMods = new Bindable>(Array.Empty()); - - private readonly FreeModSelectOverlay freeModSelectOverlay; private LoadingLayer loadingLayer; - private WorkingBeatmap initialBeatmap; - private RulesetInfo initialRuleset; - private IReadOnlyList initialMods; - - private bool itemSelected; - - public MultiplayerMatchSongSelect() - { - Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING }; - - freeModSelectOverlay = new FreeModSelectOverlay - { - SelectedMods = { BindTarget = freeMods }, - IsValidMod = isValidFreeMod, - }; - } - [BackgroundDependencyLoader] private void load() { AddInternal(loadingLayer = new LoadingLayer(true)); - initialBeatmap = Beatmap.Value; - initialRuleset = Ruleset.Value; - initialMods = Mods.Value.ToList(); - - freeMods.Value = playlist.FirstOrDefault()?.AllowedMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty(); - - FooterPanels.Add(freeModSelectOverlay); } - protected override bool OnStart() + protected override void OnSetItem(PlaylistItem item) { - itemSelected = true; - var item = new PlaylistItem(); - - item.Beatmap.Value = Beatmap.Value.BeatmapInfo; - item.Ruleset.Value = Ruleset.Value; - - item.RequiredMods.Clear(); - item.RequiredMods.AddRange(Mods.Value.Select(m => m.CreateCopy())); - - item.AllowedMods.Clear(); - item.AllowedMods.AddRange(freeMods.Value.Select(m => m.CreateCopy())); - // If the client is already in a room, update via the client. // Otherwise, update the playlist directly in preparation for it to be submitted to the API on match creation. if (client.Room != null) @@ -112,43 +59,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer } else { - playlist.Clear(); - playlist.Add(item); + Playlist.Clear(); + Playlist.Add(item); this.Exit(); } - - return true; - } - - public override bool OnExiting(IScreen next) - { - if (!itemSelected) - { - Beatmap.Value = initialBeatmap; - Ruleset.Value = initialRuleset; - Mods.Value = initialMods; - } - - return base.OnExiting(next); } protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea(); - - protected override ModSelectOverlay CreateModSelectOverlay() => new SoloModSelectOverlay - { - IsValidMod = isValidMod - }; - - protected override IEnumerable<(FooterButton, OverlayContainer)> CreateFooterButtons() - { - var buttons = base.CreateFooterButtons().ToList(); - buttons.Insert(buttons.FindIndex(b => b.Item1 is FooterButtonMods) + 1, (new FooterButtonFreeMods { Current = freeMods }, freeModSelectOverlay)); - return buttons; - } - - private bool isValidMod(Mod mod) => !(mod is ModAutoplay) && (mod as MultiMod)?.Mods.Any(mm => mm is ModAutoplay) != true; - - private bool isValidFreeMod(Mod mod) => isValidMod(mod) && !(mod is ModRateAdjust) && !(mod is ModTimeRamp); } public class FooterButtonFreeMods : FooterButton, IHasCurrentValue> diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs new file mode 100644 index 0000000000..7c64e00dc4 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs @@ -0,0 +1,124 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using Humanizer; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Screens; +using osu.Game.Beatmaps; +using osu.Game.Online.Rooms; +using osu.Game.Overlays.Mods; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Screens.OnlinePlay.Multiplayer; +using osu.Game.Screens.Select; + +namespace osu.Game.Screens.OnlinePlay +{ + public abstract class OnlinePlaySongSelect : SongSelect, IOnlinePlaySubScreen + { + public string ShortTitle => "song selection"; + + public override string Title => ShortTitle.Humanize(); + + public override bool AllowEditing => false; + + [Resolved(typeof(Room), nameof(Room.Playlist))] + protected BindableList Playlist { get; private set; } + + private readonly Bindable> freeMods = new Bindable>(Array.Empty()); + private readonly FreeModSelectOverlay freeModSelectOverlay; + + private WorkingBeatmap initialBeatmap; + private RulesetInfo initialRuleset; + private IReadOnlyList initialMods; + private bool itemSelected; + + protected OnlinePlaySongSelect() + { + Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING }; + + freeModSelectOverlay = new FreeModSelectOverlay + { + SelectedMods = { BindTarget = freeMods }, + IsValidMod = IsValidFreeMod, + }; + } + + [BackgroundDependencyLoader] + private void load() + { + initialBeatmap = Beatmap.Value; + initialRuleset = Ruleset.Value; + initialMods = Mods.Value.ToList(); + + freeMods.Value = Playlist.FirstOrDefault()?.AllowedMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty(); + FooterPanels.Add(freeModSelectOverlay); + } + + protected sealed override bool OnStart() + { + itemSelected = true; + + var item = new PlaylistItem(); + + item.Beatmap.Value = Beatmap.Value.BeatmapInfo; + item.Ruleset.Value = Ruleset.Value; + + item.RequiredMods.Clear(); + item.RequiredMods.AddRange(Mods.Value.Select(m => m.CreateCopy())); + + item.AllowedMods.Clear(); + item.AllowedMods.AddRange(freeMods.Value.Select(m => m.CreateCopy())); + + OnSetItem(item); + return true; + } + + protected abstract void OnSetItem(PlaylistItem item); + + public override bool OnBackButton() + { + if (freeModSelectOverlay.State.Value == Visibility.Visible) + { + freeModSelectOverlay.Hide(); + return true; + } + + return base.OnBackButton(); + } + + public override bool OnExiting(IScreen next) + { + if (!itemSelected) + { + Beatmap.Value = initialBeatmap; + Ruleset.Value = initialRuleset; + Mods.Value = initialMods; + } + + return base.OnExiting(next); + } + + protected override ModSelectOverlay CreateModSelectOverlay() => new SoloModSelectOverlay + { + IsValidMod = IsValidMod + }; + + protected override IEnumerable<(FooterButton, OverlayContainer)> CreateFooterButtons() + { + var buttons = base.CreateFooterButtons().ToList(); + buttons.Insert(buttons.FindIndex(b => b.Item1 is FooterButtonMods) + 1, (new FooterButtonFreeMods { Current = freeMods }, freeModSelectOverlay)); + return buttons; + } + + protected virtual bool IsValidMod(Mod mod) => !(mod is ModAutoplay) && (mod as MultiMod)?.Mods.Any(mm => mm is ModAutoplay) != true; + + protected virtual bool IsValidFreeMod(Mod mod) => IsValidMod(mod); + } +} diff --git a/osu.Game/Screens/Select/MatchSongSelect.cs b/osu.Game/Screens/Select/MatchSongSelect.cs index 98e02a9294..23fe9620fe 100644 --- a/osu.Game/Screens/Select/MatchSongSelect.cs +++ b/osu.Game/Screens/Select/MatchSongSelect.cs @@ -1,48 +1,27 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Linq; -using Humanizer; using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Online.Rooms; -using osu.Game.Overlays.Mods; -using osu.Game.Rulesets.Mods; using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Components; namespace osu.Game.Screens.Select { - public class MatchSongSelect : SongSelect, IOnlinePlaySubScreen + public class MatchSongSelect : OnlinePlaySongSelect { - public Action Selected; - - public string ShortTitle => "song selection"; - public override string Title => ShortTitle.Humanize(); - - public override bool AllowEditing => false; - - [Resolved(typeof(Room), nameof(Room.Playlist))] - protected BindableList Playlist { get; private set; } - [Resolved] private BeatmapManager beatmaps { get; set; } - public MatchSongSelect() - { - Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING }; - } - protected override BeatmapDetailArea CreateBeatmapDetailArea() => new MatchBeatmapDetailArea { CreateNewItem = createNewItem }; - protected override bool OnStart() + protected override void OnSetItem(PlaylistItem item) { switch (Playlist.Count) { @@ -56,8 +35,6 @@ namespace osu.Game.Screens.Select } this.Exit(); - - return true; } private void createNewItem() @@ -80,12 +57,5 @@ namespace osu.Game.Screens.Select item.RequiredMods.Clear(); item.RequiredMods.AddRange(Mods.Value.Select(m => m.CreateCopy())); } - - protected override ModSelectOverlay CreateModSelectOverlay() => new SoloModSelectOverlay - { - IsValidMod = isValidMod - }; - - private bool isValidMod(Mod mod) => !(mod is ModAutoplay) && (mod as MultiMod)?.Mods.Any(mm => mm is ModAutoplay) != true; } } From b43e529964b31ac40d5230aa41e68876985579d7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Feb 2021 15:06:50 +0900 Subject: [PATCH 0231/1791] Fix allowed mods being copied into required mods --- .../OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index e892570066..0c22813e56 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.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; using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; @@ -33,6 +35,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer AddInternal(loadingLayer = new LoadingLayer(true)); } + protected override void LoadComplete() + { + base.LoadComplete(); + + Mods.Value = Playlist.FirstOrDefault()?.RequiredMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty(); + } + protected override void OnSetItem(PlaylistItem item) { // If the client is already in a room, update via the client. From 0909c73ead3d4c45495c8bb5d55dfac16386038a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Feb 2021 15:07:56 +0900 Subject: [PATCH 0232/1791] Once again disallow DT/etc as allowable mods --- .../OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index 0c22813e56..80bb7c7ac2 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -75,6 +75,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer } protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea(); + + protected override bool IsValidFreeMod(Mod mod) => base.IsValidFreeMod(mod) && !(mod is ModTimeRamp) && !(mod is ModRateAdjust); } public class FooterButtonFreeMods : FooterButton, IHasCurrentValue> From 05982f42ab8a991d9056a7ad38f947c0edc4c4cd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 1 Feb 2021 16:43:53 +0900 Subject: [PATCH 0233/1791] Add more comprehensive commenting and simplify base call logic We can call the base method regardless for better safety. Worst case it's just going to run `Stop()` twice anyway. --- .../Drawables/DrawableStoryboardSample.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs index fcbffda227..7b16009859 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs @@ -46,14 +46,15 @@ namespace osu.Game.Storyboards.Drawables { if (!RequestedPlaying) return; - // non-looping storyboard samples should be stopped immediately when sample playback is disabled - if (!Looping) + if (!Looping && disabled.NewValue) { - if (disabled.NewValue) - Stop(); + // the default behaviour for sample disabling is to allow one-shot samples to play out. + // storyboards regularly have long running samples that can cause this behaviour to lead to unintended results. + // for this reason, we immediately stop such samples. + Stop(); } - else - base.SamplePlaybackDisabledChanged(disabled); + + base.SamplePlaybackDisabledChanged(disabled); } protected override void Update() From 49e62c3a4b7118c1f3c81c6af357534bd541d8ee Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 1 Feb 2021 11:02:08 +0300 Subject: [PATCH 0234/1791] Apply documentation changes Co-authored-by: Dean Herbert --- osu.Game/Overlays/Chat/DrawableChannel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index f1aa387c17..4c8513b1d5 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -237,7 +237,7 @@ namespace osu.Game.Overlays.Chat } /// - /// An with functionality to automatically scrolls whenever the maximum scrollable distance increases. + /// An with functionality to automatically scroll whenever the maximum scrollable distance increases. /// private class ChannelScrollContainer : OsuScrollContainer { @@ -246,7 +246,7 @@ namespace osu.Game.Overlays.Chat private float? lastExtent; /// - /// Whether this should automatically scroll to end on the next call to . + /// Whether this container should automatically scroll to end on the next call to . /// public bool ShouldAutoScroll { get; private set; } = true; From fabb0eeb29a35c7e0d212d9c7be9f1aad8e90c35 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 1 Feb 2021 17:27:14 +0900 Subject: [PATCH 0235/1791] Add signalr messagepack key attribute --- osu.Game/Online/Rooms/BeatmapAvailability.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Online/Rooms/BeatmapAvailability.cs b/osu.Game/Online/Rooms/BeatmapAvailability.cs index 4ce797e583..2adeb9b959 100644 --- a/osu.Game/Online/Rooms/BeatmapAvailability.cs +++ b/osu.Game/Online/Rooms/BeatmapAvailability.cs @@ -22,6 +22,7 @@ namespace osu.Game.Online.Rooms /// /// The beatmap's downloading progress, null when not in state. /// + [Key(1)] public readonly float? DownloadProgress; [JsonConstructor] From 1d8de2f718916b907b24bf1b411ebb627258cbd7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 1 Feb 2021 17:32:54 +0900 Subject: [PATCH 0236/1791] Rename class to better match purpose --- ... TestSceneMultiplayerBeatmapAvailabilityTracker.cs} | 10 +++++----- ...er.cs => MultiplayerBeatmapAvailablilityTracker.cs} | 7 +++---- osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs | 4 ++-- 3 files changed, 10 insertions(+), 11 deletions(-) rename osu.Game.Tests/Online/{TestSceneMultiplayerBeatmapTracker.cs => TestSceneMultiplayerBeatmapAvailabilityTracker.cs} (94%) rename osu.Game/Online/Rooms/{MultiplayerBeatmapTracker.cs => MultiplayerBeatmapAvailablilityTracker.cs} (93%) diff --git a/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs b/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapAvailabilityTracker.cs similarity index 94% rename from osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs rename to osu.Game.Tests/Online/TestSceneMultiplayerBeatmapAvailabilityTracker.cs index 9caecc198a..3c3793670a 100644 --- a/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapTracker.cs +++ b/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapAvailabilityTracker.cs @@ -28,7 +28,7 @@ using osu.Game.Tests.Visual; namespace osu.Game.Tests.Online { [HeadlessTest] - public class TestSceneMultiplayerBeatmapTracker : OsuTestScene + public class TestSceneMultiplayerBeatmapAvailabilityTracker : OsuTestScene { private RulesetStore rulesets; private TestBeatmapManager beatmaps; @@ -38,7 +38,7 @@ namespace osu.Game.Tests.Online private BeatmapSetInfo testBeatmapSet; private readonly Bindable selectedItem = new Bindable(); - private MultiplayerBeatmapTracker tracker; + private MultiplayerBeatmapAvailablilityTracker availablilityTracker; [BackgroundDependencyLoader] private void load(AudioManager audio, GameHost host) @@ -67,7 +67,7 @@ namespace osu.Game.Tests.Online Ruleset = { Value = testBeatmapInfo.Ruleset }, }; - Child = tracker = new MultiplayerBeatmapTracker + Child = availablilityTracker = new MultiplayerBeatmapAvailablilityTracker { SelectedItem = { BindTarget = selectedItem, } }; @@ -118,7 +118,7 @@ namespace osu.Game.Tests.Online }); addAvailabilityCheckStep("state still not downloaded", BeatmapAvailability.NotDownloaded); - AddStep("recreate tracker", () => Child = tracker = new MultiplayerBeatmapTracker + AddStep("recreate tracker", () => Child = availablilityTracker = new MultiplayerBeatmapAvailablilityTracker { SelectedItem = { BindTarget = selectedItem } }); @@ -129,7 +129,7 @@ namespace osu.Game.Tests.Online { // In DownloadTrackingComposite, state changes are scheduled one frame later, wait one step. AddWaitStep("wait for potential change", 1); - AddAssert(description, () => tracker.Availability.Value.Equals(expected.Invoke())); + AddAssert(description, () => availablilityTracker.Availability.Value.Equals(expected.Invoke())); } private static BeatmapInfo getTestBeatmapInfo(string archiveFile) diff --git a/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs b/osu.Game/Online/Rooms/MultiplayerBeatmapAvailablilityTracker.cs similarity index 93% rename from osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs rename to osu.Game/Online/Rooms/MultiplayerBeatmapAvailablilityTracker.cs index 28e4872ad3..578c4db2f8 100644 --- a/osu.Game/Online/Rooms/MultiplayerBeatmapTracker.cs +++ b/osu.Game/Online/Rooms/MultiplayerBeatmapAvailablilityTracker.cs @@ -15,7 +15,7 @@ namespace osu.Game.Online.Rooms /// This differs from a regular download tracking composite as this accounts for the /// databased beatmap set's checksum, to disallow from playing with an altered version of the beatmap. /// - public class MultiplayerBeatmapTracker : DownloadTrackingComposite + public class MultiplayerBeatmapAvailablilityTracker : DownloadTrackingComposite { public readonly IBindable SelectedItem = new Bindable(); @@ -26,11 +26,10 @@ namespace osu.Game.Online.Rooms private readonly Bindable availability = new Bindable(); - public MultiplayerBeatmapTracker() + public MultiplayerBeatmapAvailablilityTracker() { State.BindValueChanged(_ => updateAvailability()); - Progress.BindValueChanged(_ => updateAvailability()); - updateAvailability(); + Progress.BindValueChanged(_ => updateAvailability(), true); } protected override void LoadComplete() diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index c049d4be20..f2cb1120cb 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -41,11 +41,11 @@ namespace osu.Game.Screens.OnlinePlay.Match private IBindable> managerUpdated; [Cached] - protected readonly MultiplayerBeatmapTracker BeatmapTracker; + protected readonly MultiplayerBeatmapAvailablilityTracker BeatmapAvailablilityTracker; protected RoomSubScreen() { - InternalChild = BeatmapTracker = new MultiplayerBeatmapTracker + InternalChild = BeatmapAvailablilityTracker = new MultiplayerBeatmapAvailablilityTracker { SelectedItem = { BindTarget = SelectedItem }, }; From ac2a995041590370e31b2b5ef9fb440dcb20d43c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Feb 2021 17:54:56 +0900 Subject: [PATCH 0237/1791] Add user and panel states --- .../TestSceneMultiplayerParticipantsList.cs | 25 ++++++++++++++ .../Online/Multiplayer/IMultiplayerClient.cs | 4 +++ .../Multiplayer/IMultiplayerRoomServer.cs | 4 +++ .../Online/Multiplayer/MultiplayerRoomUser.cs | 7 ++++ .../Multiplayer/StatefulMultiplayerClient.cs | 26 +++++++++++++++ .../Multiplayer/MultiplayerMatchSubScreen.cs | 11 +++++++ .../Participants/ParticipantPanel.cs | 33 ++++++++++++++++++- osu.Game/Screens/Play/HUD/ModDisplay.cs | 14 +++++--- .../Multiplayer/TestMultiplayerClient.cs | 17 ++++++++++ 9 files changed, 136 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs index 968a869532..9aa1f2cf99 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs @@ -8,6 +8,8 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Online.Multiplayer; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.OnlinePlay.Multiplayer.Participants; using osu.Game.Users; using osuTK; @@ -123,5 +125,28 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); } + + [Test] + public void TestUserWithMods() + { + AddStep("add user", () => + { + Client.AddUser(new User + { + Id = 0, + Username = $"User 0", + CurrentModeRank = RNG.Next(1, 100000), + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + }); + + Client.ChangeUserExtraMods(0, new Mod[] + { + new OsuModHardRock(), + new OsuModDifficultyAdjust { ApproachRate = { Value = 1 } } + }); + }); + + AddToggleStep("toggle ready state", v => Client.ChangeUserState(0, v ? MultiplayerUserState.Ready : MultiplayerUserState.Idle)); + } } } diff --git a/osu.Game/Online/Multiplayer/IMultiplayerClient.cs b/osu.Game/Online/Multiplayer/IMultiplayerClient.cs index 19dd473230..37f60ab036 100644 --- a/osu.Game/Online/Multiplayer/IMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/IMultiplayerClient.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 System.Threading.Tasks; +using osu.Game.Online.API; using osu.Game.Online.Rooms; namespace osu.Game.Online.Multiplayer @@ -55,6 +57,8 @@ namespace osu.Game.Online.Multiplayer /// The new beatmap availability state of the user. Task UserBeatmapAvailabilityChanged(int userId, BeatmapAvailability beatmapAvailability); + Task UserExtraModsChanged(int userId, IEnumerable mods); + /// /// Signals that a match is to be started. This will *only* be sent to clients which are to begin loading at this point. /// diff --git a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs index 09816974a7..484acfe957 100644 --- a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs +++ b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.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 System.Threading.Tasks; +using osu.Game.Online.API; using osu.Game.Online.Rooms; namespace osu.Game.Online.Multiplayer @@ -47,6 +49,8 @@ namespace osu.Game.Online.Multiplayer /// The proposed new beatmap availability state. Task ChangeBeatmapAvailability(BeatmapAvailability newBeatmapAvailability); + Task ChangeExtraMods(IEnumerable newMods); + /// /// As the host of a room, start the match. /// diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs b/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs index 2590acbc81..d7f7f9135e 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs @@ -4,7 +4,11 @@ #nullable enable using System; +using System.Collections.Generic; +using System.Linq; +using JetBrains.Annotations; using Newtonsoft.Json; +using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Users; @@ -22,6 +26,9 @@ namespace osu.Game.Online.Multiplayer /// public BeatmapAvailability BeatmapAvailability { get; set; } = BeatmapAvailability.LocallyAvailable(); + [NotNull] + public IEnumerable ExtraMods { get; set; } = Enumerable.Empty(); + public User? User { get; set; } [JsonConstructor] diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index e5b07ddfb4..33dcf1e8b4 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -22,6 +22,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Rooms; using osu.Game.Online.Rooms.RoomStatuses; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; using osu.Game.Users; using osu.Game.Utils; @@ -231,6 +232,10 @@ namespace osu.Game.Online.Multiplayer public abstract Task ChangeBeatmapAvailability(BeatmapAvailability newBeatmapAvailability); + public Task ChangeExtraMods(IEnumerable newMods) => ChangeExtraMods(newMods.Select(m => new APIMod(m)).ToList()); + + public abstract Task ChangeExtraMods(IEnumerable newMods); + public abstract Task StartMatch(); Task IMultiplayerClient.RoomStateChanged(MultiplayerRoomState state) @@ -379,6 +384,27 @@ namespace osu.Game.Online.Multiplayer return Task.CompletedTask; } + public Task UserExtraModsChanged(int userId, IEnumerable mods) + { + if (Room == null) + return Task.CompletedTask; + + Scheduler.Add(() => + { + var user = Room?.Users.SingleOrDefault(u => u.UserID == userId); + + // errors here are not critical - user mods is mostly for display. + if (user == null) + return; + + user.ExtraMods = mods; + + RoomUpdated?.Invoke(); + }, false); + + return Task.CompletedTask; + } + Task IMultiplayerClient.LoadRequested() { if (Room == null) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 95dda1a051..c1025e73f8 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; using System.Linq; @@ -15,6 +16,7 @@ using osu.Game.Extensions; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; using osu.Game.Overlays.Mods; +using osu.Game.Rulesets.Mods; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Match; using osu.Game.Screens.OnlinePlay.Match.Components; @@ -240,6 +242,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer base.LoadComplete(); Playlist.BindCollectionChanged(onPlaylistChanged, true); + ExtraMods.BindValueChanged(onExtraModsChanged); client.LoadRequested += onLoadRequested; @@ -285,6 +288,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer } } + private void onExtraModsChanged(ValueChangedEvent> extraMods) + { + if (client.Room == null) + return; + + client.ChangeExtraMods(extraMods.NewValue).CatchUnobservedExceptions(); + } + private void onReadyClick() { Debug.Assert(readyClickOperation == null); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index f99655e305..059e9e518d 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -16,6 +17,8 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.Multiplayer; +using osu.Game.Rulesets; +using osu.Game.Screens.Play.HUD; using osu.Game.Users; using osu.Game.Users.Drawables; using osuTK; @@ -30,6 +33,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants [Resolved] private IAPIProvider api { get; set; } + [Resolved] + private RulesetStore rulesets { get; set; } + + private ModDisplay extraModsDisplay; private StateDisplay userStateDisplay; private SpriteIcon crown; @@ -122,11 +129,32 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants } } }, - userStateDisplay = new StateDisplay + new FillFlowContainer { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, Margin = new MarginPadding { Right = 10 }, + Spacing = new Vector2(10), + Children = new Drawable[] + { + extraModsDisplay = new ModDisplay + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(0.5f), + ExpansionMode = ExpansionMode.AlwaysContracted, + DisplayUnrankedText = false, + ExpandOnAppear = false + }, + userStateDisplay = new StateDisplay + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AlwaysPresent = true + } + } } } } @@ -143,7 +171,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants const double fade_time = 50; + var ruleset = rulesets.GetRuleset(Room.Settings.RulesetID).CreateInstance(); + userStateDisplay.Status = User.State; + extraModsDisplay.Current.Value = User.ExtraMods.Select(m => m.ToMod(ruleset)).ToList(); if (Room.Host?.Equals(User) == true) crown.FadeIn(fade_time); diff --git a/osu.Game/Screens/Play/HUD/ModDisplay.cs b/osu.Game/Screens/Play/HUD/ModDisplay.cs index 68d019bf71..2d5b07f056 100644 --- a/osu.Game/Screens/Play/HUD/ModDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ModDisplay.cs @@ -26,6 +26,8 @@ namespace osu.Game.Screens.Play.HUD public ExpansionMode ExpansionMode = ExpansionMode.ExpandOnHover; + public bool ExpandOnAppear = true; + private readonly Bindable> current = new Bindable>(); public Bindable> Current @@ -108,10 +110,14 @@ namespace osu.Game.Screens.Play.HUD else unrankedText.Hide(); - expand(); - - using (iconsContainer.BeginDelayedSequence(1200)) - contract(); + if (ExpandOnAppear) + { + expand(); + using (iconsContainer.BeginDelayedSequence(1200)) + contract(); + } + else + iconsContainer.TransformSpacingTo(new Vector2(-25, 0)); } private void expand() diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 7fbc770351..e699e7fb34 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -3,6 +3,7 @@ #nullable enable +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; @@ -11,6 +12,7 @@ using osu.Framework.Bindables; using osu.Game.Online.API; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; +using osu.Game.Rulesets.Mods; using osu.Game.Users; namespace osu.Game.Tests.Visual.Multiplayer @@ -122,6 +124,21 @@ namespace osu.Game.Tests.Visual.Multiplayer return Task.CompletedTask; } + public void ChangeUserExtraMods(int userId, IEnumerable newMods) + => ChangeUserExtraMods(userId, newMods.Select(m => new APIMod(m)).ToList()); + + public void ChangeUserExtraMods(int userId, IEnumerable newMods) + { + Debug.Assert(Room != null); + ((IMultiplayerClient)this).UserExtraModsChanged(userId, newMods.ToList()); + } + + public override Task ChangeExtraMods(IEnumerable newMods) + { + ChangeUserExtraMods(api.LocalUser.Value.Id, newMods); + return Task.CompletedTask; + } + public override Task StartMatch() { Debug.Assert(Room != null); From f53896360715341641dfc7971cb04f66d19fd91d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Feb 2021 17:57:32 +0900 Subject: [PATCH 0238/1791] Extra mods -> user mods --- .../TestSceneMultiplayerParticipantsList.cs | 2 +- .../Online/Multiplayer/IMultiplayerClient.cs | 2 +- .../Multiplayer/IMultiplayerRoomServer.cs | 2 +- .../Online/Multiplayer/MultiplayerClient.cs | 10 ++++++ .../Online/Multiplayer/MultiplayerRoomUser.cs | 2 +- .../Multiplayer/StatefulMultiplayerClient.cs | 8 ++--- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 12 +++---- .../Multiplayer/MultiplayerMatchSubScreen.cs | 34 +++++++++---------- .../Participants/ParticipantPanel.cs | 6 ++-- .../Multiplayer/TestMultiplayerClient.cs | 12 +++---- 10 files changed, 50 insertions(+), 40 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs index 9aa1f2cf99..8caba5d9c8 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs @@ -139,7 +139,7 @@ namespace osu.Game.Tests.Visual.Multiplayer CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", }); - Client.ChangeUserExtraMods(0, new Mod[] + Client.ChangeUserMods(0, new Mod[] { new OsuModHardRock(), new OsuModDifficultyAdjust { ApproachRate = { Value = 1 } } diff --git a/osu.Game/Online/Multiplayer/IMultiplayerClient.cs b/osu.Game/Online/Multiplayer/IMultiplayerClient.cs index 37f60ab036..f22b0e4e28 100644 --- a/osu.Game/Online/Multiplayer/IMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/IMultiplayerClient.cs @@ -57,7 +57,7 @@ namespace osu.Game.Online.Multiplayer /// The new beatmap availability state of the user. Task UserBeatmapAvailabilityChanged(int userId, BeatmapAvailability beatmapAvailability); - Task UserExtraModsChanged(int userId, IEnumerable mods); + Task UserModsChanged(int userId, IEnumerable mods); /// /// Signals that a match is to be started. This will *only* be sent to clients which are to begin loading at this point. diff --git a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs index 484acfe957..71555ae23d 100644 --- a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs +++ b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs @@ -49,7 +49,7 @@ namespace osu.Game.Online.Multiplayer /// The proposed new beatmap availability state. Task ChangeBeatmapAvailability(BeatmapAvailability newBeatmapAvailability); - Task ChangeExtraMods(IEnumerable newMods); + Task ChangeUserMods(IEnumerable newMods); /// /// As the host of a room, start the match. diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 50dc8f661c..ecf314c1e5 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -4,6 +4,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; @@ -84,6 +85,7 @@ namespace osu.Game.Online.Multiplayer connection.On(nameof(IMultiplayerClient.LoadRequested), ((IMultiplayerClient)this).LoadRequested); connection.On(nameof(IMultiplayerClient.MatchStarted), ((IMultiplayerClient)this).MatchStarted); connection.On(nameof(IMultiplayerClient.ResultsReady), ((IMultiplayerClient)this).ResultsReady); + connection.On>(nameof(IMultiplayerClient.UserModsChanged), ((IMultiplayerClient)this).UserModsChanged); connection.Closed += async ex => { @@ -182,6 +184,14 @@ namespace osu.Game.Online.Multiplayer return connection.InvokeAsync(nameof(IMultiplayerServer.ChangeBeatmapAvailability), newBeatmapAvailability); } + public override Task ChangeUserMods(IEnumerable newMods) + { + if (!isConnected.Value) + return Task.CompletedTask; + + return connection.InvokeAsync(nameof(IMultiplayerServer.ChangeUserMods), newMods); + } + public override Task StartMatch() { if (!isConnected.Value) diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs b/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs index d7f7f9135e..4c9643bfce 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs @@ -27,7 +27,7 @@ namespace osu.Game.Online.Multiplayer public BeatmapAvailability BeatmapAvailability { get; set; } = BeatmapAvailability.LocallyAvailable(); [NotNull] - public IEnumerable ExtraMods { get; set; } = Enumerable.Empty(); + public IEnumerable UserMods { get; set; } = Enumerable.Empty(); public User? User { get; set; } diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index 33dcf1e8b4..a0e903e89a 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -232,9 +232,9 @@ namespace osu.Game.Online.Multiplayer public abstract Task ChangeBeatmapAvailability(BeatmapAvailability newBeatmapAvailability); - public Task ChangeExtraMods(IEnumerable newMods) => ChangeExtraMods(newMods.Select(m => new APIMod(m)).ToList()); + public Task ChangeUserMods(IEnumerable newMods) => ChangeUserMods(newMods.Select(m => new APIMod(m)).ToList()); - public abstract Task ChangeExtraMods(IEnumerable newMods); + public abstract Task ChangeUserMods(IEnumerable newMods); public abstract Task StartMatch(); @@ -384,7 +384,7 @@ namespace osu.Game.Online.Multiplayer return Task.CompletedTask; } - public Task UserExtraModsChanged(int userId, IEnumerable mods) + public Task UserModsChanged(int userId, IEnumerable mods) { if (Room == null) return Task.CompletedTask; @@ -397,7 +397,7 @@ namespace osu.Game.Online.Multiplayer if (user == null) return; - user.ExtraMods = mods; + user.UserMods = mods; RoomUpdated?.Invoke(); }, false); diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 3c4c6ce040..6367aa54a7 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -33,7 +33,7 @@ namespace osu.Game.Screens.OnlinePlay.Match /// /// Any mods applied by/to the local user. /// - protected readonly Bindable> ExtraMods = new Bindable>(Array.Empty()); + protected readonly Bindable> UserMods = new Bindable>(Array.Empty()); [Resolved] private MusicController music { get; set; } @@ -62,7 +62,7 @@ namespace osu.Game.Screens.OnlinePlay.Match managerUpdated = beatmapManager.ItemUpdated.GetBoundCopy(); managerUpdated.BindValueChanged(beatmapUpdated); - ExtraMods.BindValueChanged(_ => updateMods()); + UserMods.BindValueChanged(_ => updateMods()); } public override void OnEntering(IScreen last) @@ -108,9 +108,9 @@ namespace osu.Game.Screens.OnlinePlay.Match return; // Remove any extra mods that are no longer allowed. - ExtraMods.Value = ExtraMods.Value - .Where(m => SelectedItem.Value.AllowedMods.Any(a => m.GetType() == a.GetType())) - .ToList(); + UserMods.Value = UserMods.Value + .Where(m => SelectedItem.Value.AllowedMods.Any(a => m.GetType() == a.GetType())) + .ToList(); updateMods(); @@ -134,7 +134,7 @@ namespace osu.Game.Screens.OnlinePlay.Match if (SelectedItem.Value == null) return; - Mods.Value = ExtraMods.Value.Concat(SelectedItem.Value.RequiredMods).ToList(); + Mods.Value = UserMods.Value.Concat(SelectedItem.Value.RequiredMods).ToList(); } private void beginHandlingTrack() diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index c1025e73f8..a31a3e51ee 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -42,9 +42,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer [Resolved] private OngoingOperationTracker ongoingOperationTracker { get; set; } - private ModSelectOverlay extraModSelectOverlay; + private ModSelectOverlay userModsSelectOverlay; private MultiplayerMatchSettingsOverlay settingsOverlay; - private Drawable extraModsSection; + private Drawable userModsSection; private IBindable isConnected; @@ -149,7 +149,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer new BeatmapSelectionControl { RelativeSizeAxes = Axes.X } } }, - extraModsSection = new FillFlowContainer + userModsSection = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -159,13 +159,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer new ModDisplay { DisplayUnrankedText = false, - Current = ExtraMods + Current = UserMods }, new PurpleTriangleButton { RelativeSizeAxes = Axes.X, Text = "Select", - Action = () => extraModSelectOverlay.Show() + Action = () => userModsSelectOverlay.Show() } } } @@ -210,9 +210,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer new Dimension(GridSizeMode.AutoSize), } }, - extraModSelectOverlay = new SoloModSelectOverlay + userModsSelectOverlay = new SoloModSelectOverlay { - SelectedMods = { BindTarget = ExtraMods }, + SelectedMods = { BindTarget = UserMods }, Stacked = false, IsValidMod = _ => false }, @@ -242,7 +242,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer base.LoadComplete(); Playlist.BindCollectionChanged(onPlaylistChanged, true); - ExtraMods.BindValueChanged(onExtraModsChanged); + UserMods.BindValueChanged(onUserModsChanged); client.LoadRequested += onLoadRequested; @@ -262,9 +262,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer return true; } - if (extraModSelectOverlay.State.Value == Visibility.Visible) + if (userModsSelectOverlay.State.Value == Visibility.Visible) { - extraModSelectOverlay.Hide(); + userModsSelectOverlay.Hide(); return true; } @@ -277,23 +277,23 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer if (SelectedItem.Value?.AllowedMods.Any() != true) { - extraModsSection.Hide(); - extraModSelectOverlay.Hide(); - extraModSelectOverlay.IsValidMod = _ => false; + userModsSection.Hide(); + userModsSelectOverlay.Hide(); + userModsSelectOverlay.IsValidMod = _ => false; } else { - extraModsSection.Show(); - extraModSelectOverlay.IsValidMod = m => SelectedItem.Value.AllowedMods.Any(a => a.GetType() == m.GetType()); + userModsSection.Show(); + userModsSelectOverlay.IsValidMod = m => SelectedItem.Value.AllowedMods.Any(a => a.GetType() == m.GetType()); } } - private void onExtraModsChanged(ValueChangedEvent> extraMods) + private void onUserModsChanged(ValueChangedEvent> mods) { if (client.Room == null) return; - client.ChangeExtraMods(extraMods.NewValue).CatchUnobservedExceptions(); + client.ChangeUserMods(mods.NewValue).CatchUnobservedExceptions(); } private void onReadyClick() diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index 059e9e518d..a782da4c39 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs @@ -36,7 +36,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants [Resolved] private RulesetStore rulesets { get; set; } - private ModDisplay extraModsDisplay; + private ModDisplay userModsDisplay; private StateDisplay userStateDisplay; private SpriteIcon crown; @@ -139,7 +139,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants Spacing = new Vector2(10), Children = new Drawable[] { - extraModsDisplay = new ModDisplay + userModsDisplay = new ModDisplay { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -174,7 +174,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants var ruleset = rulesets.GetRuleset(Room.Settings.RulesetID).CreateInstance(); userStateDisplay.Status = User.State; - extraModsDisplay.Current.Value = User.ExtraMods.Select(m => m.ToMod(ruleset)).ToList(); + userModsDisplay.Current.Value = User.UserMods.Select(m => m.ToMod(ruleset)).ToList(); if (Room.Host?.Equals(User) == true) crown.FadeIn(fade_time); diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index e699e7fb34..d0d41e56c3 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -124,18 +124,18 @@ namespace osu.Game.Tests.Visual.Multiplayer return Task.CompletedTask; } - public void ChangeUserExtraMods(int userId, IEnumerable newMods) - => ChangeUserExtraMods(userId, newMods.Select(m => new APIMod(m)).ToList()); + public void ChangeUserMods(int userId, IEnumerable newMods) + => ChangeUserMods(userId, newMods.Select(m => new APIMod(m)).ToList()); - public void ChangeUserExtraMods(int userId, IEnumerable newMods) + public void ChangeUserMods(int userId, IEnumerable newMods) { Debug.Assert(Room != null); - ((IMultiplayerClient)this).UserExtraModsChanged(userId, newMods.ToList()); + ((IMultiplayerClient)this).UserModsChanged(userId, newMods.ToList()); } - public override Task ChangeExtraMods(IEnumerable newMods) + public override Task ChangeUserMods(IEnumerable newMods) { - ChangeUserExtraMods(api.LocalUser.Value.Id, newMods); + ChangeUserMods(api.LocalUser.Value.Id, newMods); return Task.CompletedTask; } From 3cd30d284eb01689fab740342244ad33e37d66f7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Feb 2021 18:08:49 +0900 Subject: [PATCH 0239/1791] Renamespace --- .../TestSceneFreeModSelectOverlay.cs | 2 +- .../OnlinePlay/Match/FooterButtonFreeMods.cs | 63 +++++++++++++++++++ .../FreeModSelectOverlay.cs | 2 +- .../Multiplayer/MultiplayerMatchSongSelect.cs | 54 ---------------- .../OnlinePlay/OnlinePlaySongSelect.cs | 2 +- 5 files changed, 66 insertions(+), 57 deletions(-) create mode 100644 osu.Game/Screens/OnlinePlay/Match/FooterButtonFreeMods.cs rename osu.Game/Screens/OnlinePlay/{Multiplayer => Match}/FreeModSelectOverlay.cs (98%) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs index 960402df88..b1700d5b6e 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs @@ -4,7 +4,7 @@ using NUnit.Framework; using osu.Framework.Graphics.Containers; using osu.Game.Overlays.Mods; -using osu.Game.Screens.OnlinePlay.Multiplayer; +using osu.Game.Screens.OnlinePlay.Match; namespace osu.Game.Tests.Visual.Multiplayer { diff --git a/osu.Game/Screens/OnlinePlay/Match/FooterButtonFreeMods.cs b/osu.Game/Screens/OnlinePlay/Match/FooterButtonFreeMods.cs new file mode 100644 index 0000000000..ca2db877c3 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Match/FooterButtonFreeMods.cs @@ -0,0 +1,63 @@ +// 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.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics; +using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Play.HUD; +using osu.Game.Screens.Select; +using osuTK; + +namespace osu.Game.Screens.OnlinePlay.Match +{ + public class FooterButtonFreeMods : FooterButton, IHasCurrentValue> + { + public Bindable> Current + { + get => modDisplay.Current; + set => modDisplay.Current = value; + } + + private readonly ModDisplay modDisplay; + + public FooterButtonFreeMods() + { + ButtonContentContainer.Add(modDisplay = new ModDisplay + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + DisplayUnrankedText = false, + Scale = new Vector2(0.8f), + ExpansionMode = ExpansionMode.AlwaysContracted, + }); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + SelectedColour = colours.Yellow; + DeselectedColour = SelectedColour.Opacity(0.5f); + Text = @"freemods"; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Current.BindValueChanged(_ => updateModDisplay(), true); + } + + private void updateModDisplay() + { + if (Current.Value?.Count > 0) + modDisplay.FadeIn(); + else + modDisplay.FadeOut(); + } + } +} diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/Match/FreeModSelectOverlay.cs similarity index 98% rename from osu.Game/Screens/OnlinePlay/Multiplayer/FreeModSelectOverlay.cs rename to osu.Game/Screens/OnlinePlay/Match/FreeModSelectOverlay.cs index 8334df1e44..aba86a3d72 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Match/FreeModSelectOverlay.cs @@ -10,7 +10,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Mods; using osu.Game.Rulesets.Mods; -namespace osu.Game.Screens.OnlinePlay.Multiplayer +namespace osu.Game.Screens.OnlinePlay.Match { public class FreeModSelectOverlay : ModSelectOverlay { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index 80bb7c7ac2..e6b7656986 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -2,23 +2,15 @@ // 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.Bindables; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.UserInterface; using osu.Framework.Logging; using osu.Framework.Screens; -using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; using osu.Game.Rulesets.Mods; -using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Select; -using osuTK; namespace osu.Game.Screens.OnlinePlay.Multiplayer { @@ -78,50 +70,4 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override bool IsValidFreeMod(Mod mod) => base.IsValidFreeMod(mod) && !(mod is ModTimeRamp) && !(mod is ModRateAdjust); } - - public class FooterButtonFreeMods : FooterButton, IHasCurrentValue> - { - public Bindable> Current - { - get => modDisplay.Current; - set => modDisplay.Current = value; - } - - private readonly ModDisplay modDisplay; - - public FooterButtonFreeMods() - { - ButtonContentContainer.Add(modDisplay = new ModDisplay - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - DisplayUnrankedText = false, - Scale = new Vector2(0.8f), - ExpansionMode = ExpansionMode.AlwaysContracted, - }); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - SelectedColour = colours.Yellow; - DeselectedColour = SelectedColour.Opacity(0.5f); - Text = @"freemods"; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - Current.BindValueChanged(_ => updateModDisplay(), true); - } - - private void updateModDisplay() - { - if (Current.Value?.Count > 0) - modDisplay.FadeIn(); - else - modDisplay.FadeOut(); - } - } } diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs index 7c64e00dc4..b75bf93204 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs @@ -15,7 +15,7 @@ using osu.Game.Online.Rooms; using osu.Game.Overlays.Mods; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; -using osu.Game.Screens.OnlinePlay.Multiplayer; +using osu.Game.Screens.OnlinePlay.Match; using osu.Game.Screens.Select; namespace osu.Game.Screens.OnlinePlay From 3e74f8fd9e260c48b4240a98bd7bfd2eef2c0598 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Feb 2021 18:11:20 +0900 Subject: [PATCH 0240/1791] Disable customisation of freemods, move stacking to property --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 28 ++++++++----------- .../OnlinePlay/Match/FreeModSelectOverlay.cs | 7 ++--- .../Multiplayer/MultiplayerMatchSubScreen.cs | 8 ++++-- 3 files changed, 21 insertions(+), 22 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index db9460c494..56246a031d 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -39,6 +39,16 @@ namespace osu.Game.Overlays.Mods protected readonly OsuSpriteText MultiplierLabel; + /// + /// Whether to allow customisation of mod settings. + /// + protected virtual bool AllowCustomisation => true; + + /// + /// Whether mod icons should be stacked, or appear as individual buttons. + /// + protected virtual bool Stacked => true; + protected override bool BlockNonPositionalInput => false; protected override bool DimMainContent => false; @@ -47,21 +57,6 @@ namespace osu.Game.Overlays.Mods protected readonly ModSettingsContainer ModSettingsContainer; - private bool stacked = true; - - /// - /// Whether mod icons should be stacked, or appear as individual buttons. - /// - public bool Stacked - { - get => stacked; - set - { - stacked = value; - updateAvailableMods(); - } - } - [NotNull] private Func isValidMod = m => true; @@ -307,6 +302,7 @@ namespace osu.Game.Overlays.Mods CustomiseButton = new TriangleButton { Width = 180, + Alpha = AllowCustomisation ? 1 : 0, Text = "Customisation", Action = () => ModSettingsContainer.ToggleVisibility(), Enabled = { Value = false }, @@ -445,7 +441,7 @@ namespace osu.Game.Overlays.Mods { IEnumerable modEnumeration = availableMods.Value[section.ModType]; - if (!stacked) + if (!Stacked) modEnumeration = ModValidation.FlattenMods(modEnumeration); section.Mods = modEnumeration.Where(IsValidMod); diff --git a/osu.Game/Screens/OnlinePlay/Match/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/Match/FreeModSelectOverlay.cs index aba86a3d72..0d62cc3d37 100644 --- a/osu.Game/Screens/OnlinePlay/Match/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Match/FreeModSelectOverlay.cs @@ -14,10 +14,9 @@ namespace osu.Game.Screens.OnlinePlay.Match { public class FreeModSelectOverlay : ModSelectOverlay { - public FreeModSelectOverlay() - { - Stacked = false; - } + protected override bool AllowCustomisation => false; + + protected override bool Stacked => false; protected override ModSection CreateModSection(ModType type) => new FreeModSection(type); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index a31a3e51ee..22a58add70 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -210,10 +210,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer new Dimension(GridSizeMode.AutoSize), } }, - userModsSelectOverlay = new SoloModSelectOverlay + userModsSelectOverlay = new UserModSelectOverlay { SelectedMods = { BindTarget = UserMods }, - Stacked = false, IsValidMod = _ => false }, settingsOverlay = new MultiplayerMatchSettingsOverlay @@ -352,5 +351,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer if (client != null) client.LoadRequested -= onLoadRequested; } + + private class UserModSelectOverlay : SoloModSelectOverlay + { + protected override bool Stacked => false; + } } } From e134af82f52373cd3b360ab31139271c5d91007a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Feb 2021 18:16:38 +0900 Subject: [PATCH 0241/1791] Stack freemods for the local user --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 12 +++++++++++- .../Multiplayer/MultiplayerMatchSubScreen.cs | 7 +------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 56246a031d..2e69f15ff5 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -444,10 +444,20 @@ namespace osu.Game.Overlays.Mods if (!Stacked) modEnumeration = ModValidation.FlattenMods(modEnumeration); - section.Mods = modEnumeration.Where(IsValidMod); + section.Mods = modEnumeration.Select(validModOrNull).Where(m => m != null); } } + [CanBeNull] + private Mod validModOrNull([NotNull] Mod mod) + { + if (!(mod is MultiMod multi)) + return IsValidMod(mod) ? mod : null; + + var validSubset = multi.Mods.Select(validModOrNull).Where(m => m != null).ToArray(); + return validSubset.Length == 0 ? null : new MultiMod(validSubset); + } + private void selectedModsChanged(ValueChangedEvent> mods) { foreach (var section in ModSectionsContainer.Children) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 22a58add70..659551abfd 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -210,7 +210,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer new Dimension(GridSizeMode.AutoSize), } }, - userModsSelectOverlay = new UserModSelectOverlay + userModsSelectOverlay = new SoloModSelectOverlay { SelectedMods = { BindTarget = UserMods }, IsValidMod = _ => false @@ -351,10 +351,5 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer if (client != null) client.LoadRequested -= onLoadRequested; } - - private class UserModSelectOverlay : SoloModSelectOverlay - { - protected override bool Stacked => false; - } } } From 51cb2887172fb330a8b5ff40961e458c08ef7024 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Feb 2021 18:18:59 +0900 Subject: [PATCH 0242/1791] Reduce mod selection height --- .../Multiplayer/MultiplayerMatchSubScreen.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 659551abfd..87ae723c63 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -210,10 +210,17 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer new Dimension(GridSizeMode.AutoSize), } }, - userModsSelectOverlay = new SoloModSelectOverlay + new Container { - SelectedMods = { BindTarget = UserMods }, - IsValidMod = _ => false + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.Both, + Height = 0.5f, + Child = userModsSelectOverlay = new SoloModSelectOverlay + { + SelectedMods = { BindTarget = UserMods }, + IsValidMod = _ => false + } }, settingsOverlay = new MultiplayerMatchSettingsOverlay { From 3a906a89fce84a1dd7195af89aa7808276c1c70a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Feb 2021 18:25:09 +0900 Subject: [PATCH 0243/1791] Pin mod position in participant panels --- .../TestSceneMultiplayerParticipantsList.cs | 6 +++- .../Participants/ParticipantPanel.cs | 33 ++++++++----------- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs index 8caba5d9c8..768dc6512c 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs @@ -146,7 +146,11 @@ namespace osu.Game.Tests.Visual.Multiplayer }); }); - AddToggleStep("toggle ready state", v => Client.ChangeUserState(0, v ? MultiplayerUserState.Ready : MultiplayerUserState.Idle)); + for (var i = MultiplayerUserState.Idle; i < MultiplayerUserState.Results; i++) + { + var state = i; + AddStep($"set state: {state}", () => Client.ChangeUserState(0, state)); + } } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index a782da4c39..f6a60c8f57 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs @@ -129,32 +129,25 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants } } }, - new FillFlowContainer + new Container { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Margin = new MarginPadding { Right = 10 }, - Spacing = new Vector2(10), - Children = new Drawable[] + Margin = new MarginPadding { Right = 70 }, + Child = userModsDisplay = new ModDisplay { - userModsDisplay = new ModDisplay - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(0.5f), - ExpansionMode = ExpansionMode.AlwaysContracted, - DisplayUnrankedText = false, - ExpandOnAppear = false - }, - userStateDisplay = new StateDisplay - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AlwaysPresent = true - } + Scale = new Vector2(0.5f), + ExpansionMode = ExpansionMode.AlwaysContracted, + DisplayUnrankedText = false, + ExpandOnAppear = false } + }, + userStateDisplay = new StateDisplay + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Margin = new MarginPadding { Right = 10 }, } } } From 89a42d60fbc8f33c6cd28d2baa617b33c57b1312 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Feb 2021 18:50:32 +0900 Subject: [PATCH 0244/1791] General cleanup --- osu.Game.Tests/Mods/ModValidationTest.cs | 14 ++++----- .../TestSceneFreeModSelectOverlay.cs | 5 +-- .../Online/Multiplayer/IMultiplayerClient.cs | 5 +++ .../Multiplayer/IMultiplayerRoomServer.cs | 4 +++ .../Online/Multiplayer/MultiplayerRoomUser.cs | 3 ++ .../Multiplayer/StatefulMultiplayerClient.cs | 6 +++- osu.Game/Overlays/Mods/ModButton.cs | 8 ++--- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 12 +++++-- .../OnlinePlay/Match/FreeModSelectOverlay.cs | 31 ++----------------- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 2 +- .../Multiplayer/MultiplayerMatchSongSelect.cs | 2 +- .../OnlinePlay/OnlinePlaySongSelect.cs | 18 +++++++++-- osu.Game/Screens/Play/HUD/ModDisplay.cs | 3 ++ osu.Game/Screens/Select/MatchSongSelect.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 4 +++ .../Utils/{ModValidation.cs => ModUtils.cs} | 24 +++----------- 16 files changed, 71 insertions(+), 72 deletions(-) rename osu.Game/Utils/{ModValidation.cs => ModUtils.cs} (79%) diff --git a/osu.Game.Tests/Mods/ModValidationTest.cs b/osu.Game.Tests/Mods/ModValidationTest.cs index c7a7e242e9..991adc221e 100644 --- a/osu.Game.Tests/Mods/ModValidationTest.cs +++ b/osu.Game.Tests/Mods/ModValidationTest.cs @@ -15,7 +15,7 @@ namespace osu.Game.Tests.Mods public void TestModIsCompatibleByItself() { var mod = new Mock(); - Assert.That(ModValidation.CheckCompatible(new[] { mod.Object })); + Assert.That(ModUtils.CheckCompatibleSet(new[] { mod.Object })); } [Test] @@ -27,8 +27,8 @@ namespace osu.Game.Tests.Mods mod1.Setup(m => m.IncompatibleMods).Returns(new[] { mod2.Object.GetType() }); // Test both orderings. - Assert.That(ModValidation.CheckCompatible(new[] { mod1.Object, mod2.Object }), Is.False); - Assert.That(ModValidation.CheckCompatible(new[] { mod2.Object, mod1.Object }), Is.False); + Assert.That(ModUtils.CheckCompatibleSet(new[] { mod1.Object, mod2.Object }), Is.False); + Assert.That(ModUtils.CheckCompatibleSet(new[] { mod2.Object, mod1.Object }), Is.False); } [Test] @@ -43,22 +43,22 @@ namespace osu.Game.Tests.Mods var multiMod = new MultiMod(new MultiMod(mod2.Object)); // Test both orderings. - Assert.That(ModValidation.CheckCompatible(new[] { multiMod, mod1.Object }), Is.False); - Assert.That(ModValidation.CheckCompatible(new[] { mod1.Object, multiMod }), Is.False); + Assert.That(ModUtils.CheckCompatibleSet(new[] { multiMod, mod1.Object }), Is.False); + Assert.That(ModUtils.CheckCompatibleSet(new[] { mod1.Object, multiMod }), Is.False); } [Test] public void TestAllowedThroughMostDerivedType() { var mod = new Mock(); - Assert.That(ModValidation.CheckAllowed(new[] { mod.Object }, new[] { mod.Object.GetType() })); + Assert.That(ModUtils.CheckAllowed(new[] { mod.Object }, new[] { mod.Object.GetType() })); } [Test] public void TestNotAllowedThroughBaseType() { var mod = new Mock(); - Assert.That(ModValidation.CheckAllowed(new[] { mod.Object }, new[] { typeof(Mod) }), Is.False); + Assert.That(ModUtils.CheckAllowed(new[] { mod.Object }, new[] { typeof(Mod) }), Is.False); } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs index b1700d5b6e..e3342eb6a0 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs @@ -3,19 +3,16 @@ using NUnit.Framework; using osu.Framework.Graphics.Containers; -using osu.Game.Overlays.Mods; using osu.Game.Screens.OnlinePlay.Match; namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneFreeModSelectOverlay : MultiplayerTestScene { - private ModSelectOverlay overlay; - [SetUp] public new void Setup() => Schedule(() => { - Child = overlay = new FreeModSelectOverlay + Child = new FreeModSelectOverlay { State = { Value = Visibility.Visible } }; diff --git a/osu.Game/Online/Multiplayer/IMultiplayerClient.cs b/osu.Game/Online/Multiplayer/IMultiplayerClient.cs index f22b0e4e28..6d7b9d24d6 100644 --- a/osu.Game/Online/Multiplayer/IMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/IMultiplayerClient.cs @@ -57,6 +57,11 @@ namespace osu.Game.Online.Multiplayer /// The new beatmap availability state of the user. Task UserBeatmapAvailabilityChanged(int userId, BeatmapAvailability beatmapAvailability); + /// + /// Signals that a user in this room changed their local mods. + /// + /// The ID of the user whose mods have changed. + /// The user's new local mods. Task UserModsChanged(int userId, IEnumerable mods); /// diff --git a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs index 71555ae23d..3527ce6314 100644 --- a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs +++ b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs @@ -49,6 +49,10 @@ namespace osu.Game.Online.Multiplayer /// The proposed new beatmap availability state. Task ChangeBeatmapAvailability(BeatmapAvailability newBeatmapAvailability); + /// + /// Change the local user's mods in the currently joined room. + /// + /// The proposed new mods, excluding any required by the room itself. Task ChangeUserMods(IEnumerable newMods); /// diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs b/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs index 4c9643bfce..7de24826dd 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs @@ -26,6 +26,9 @@ namespace osu.Game.Online.Multiplayer /// public BeatmapAvailability BeatmapAvailability { get; set; } = BeatmapAvailability.LocallyAvailable(); + /// + /// Any mods applicable only to the local user. + /// [NotNull] public IEnumerable UserMods { get; set; } = Enumerable.Empty(); diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index a0e903e89a..597bee2764 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -232,6 +232,10 @@ namespace osu.Game.Online.Multiplayer public abstract Task ChangeBeatmapAvailability(BeatmapAvailability newBeatmapAvailability); + /// + /// Change the local user's mods in the currently joined room. + /// + /// The proposed new mods, excluding any required by the room itself. public Task ChangeUserMods(IEnumerable newMods) => ChangeUserMods(newMods.Select(m => new APIMod(m)).ToList()); public abstract Task ChangeUserMods(IEnumerable newMods); @@ -393,7 +397,7 @@ namespace osu.Game.Online.Multiplayer { var user = Room?.Users.SingleOrDefault(u => u.UserID == userId); - // errors here are not critical - user mods is mostly for display. + // errors here are not critical - user mods are mostly for display. if (user == null) return; diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs index 9ea4f65eb2..ab8efdabcc 100644 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ b/osu.Game/Overlays/Mods/ModButton.cs @@ -174,7 +174,7 @@ namespace osu.Game.Overlays.Mods switch (e.Button) { case MouseButton.Right: - OnRightClick(e); + SelectNext(-1); break; } } @@ -183,12 +183,8 @@ namespace osu.Game.Overlays.Mods protected override bool OnClick(ClickEvent e) { SelectNext(1); - return true; - } - protected virtual void OnRightClick(MouseUpEvent e) - { - SelectNext(-1); + return true; } /// diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 2e69f15ff5..2950dc7489 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -61,7 +61,7 @@ namespace osu.Game.Overlays.Mods private Func isValidMod = m => true; /// - /// A function that checks whether a given mod is valid. + /// A function that checks whether a given mod is selectable. /// [NotNull] public Func IsValidMod @@ -442,12 +442,20 @@ namespace osu.Game.Overlays.Mods IEnumerable modEnumeration = availableMods.Value[section.ModType]; if (!Stacked) - modEnumeration = ModValidation.FlattenMods(modEnumeration); + modEnumeration = ModUtils.FlattenMods(modEnumeration); section.Mods = modEnumeration.Select(validModOrNull).Where(m => m != null); } } + /// + /// Returns a valid form of a given if possible, or null otherwise. + /// + /// + /// This is a recursive process during which any invalid mods are culled while preserving structures where possible. + /// + /// The to check. + /// A valid form of if exists, or null otherwise. [CanBeNull] private Mod validModOrNull([NotNull] Mod mod) { diff --git a/osu.Game/Screens/OnlinePlay/Match/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/Match/FreeModSelectOverlay.cs index 0d62cc3d37..3cdf25fad0 100644 --- a/osu.Game/Screens/OnlinePlay/Match/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Match/FreeModSelectOverlay.cs @@ -5,13 +5,15 @@ using System; using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Mods; using osu.Game.Rulesets.Mods; namespace osu.Game.Screens.OnlinePlay.Match { + /// + /// A used for free-mod selection in online play. + /// public class FreeModSelectOverlay : ModSelectOverlay { protected override bool AllowCustomisation => false; @@ -29,8 +31,6 @@ namespace osu.Game.Screens.OnlinePlay.Match { } - protected override ModButton CreateModButton(Mod mod) => new FreeModButton(mod); - protected override Drawable CreateHeader(string text) => new Container { AutoSizeAxes = Axes.Y, @@ -47,7 +47,6 @@ namespace osu.Game.Screens.OnlinePlay.Match foreach (var button in ButtonsContainer.OfType()) { if (value) - // Note: Buttons where only part of the group has an implementation are not fully supported. button.SelectAt(0); else button.Deselect(); @@ -77,29 +76,5 @@ namespace osu.Game.Screens.OnlinePlay.Match Changed?.Invoke(value); } } - - private class FreeModButton : ModButton - { - public FreeModButton(Mod mod) - : base(mod) - { - } - - protected override bool OnClick(ClickEvent e) - { - onClick(); - return true; - } - - protected override void OnRightClick(MouseUpEvent e) => onClick(); - - private void onClick() - { - if (Selected) - Deselect(); - else - SelectNext(1); - } - } } } diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 6367aa54a7..f4136c895f 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -107,7 +107,7 @@ namespace osu.Game.Screens.OnlinePlay.Match if (SelectedItem.Value == null) return; - // Remove any extra mods that are no longer allowed. + // Remove any user mods that are no longer allowed. UserMods.Value = UserMods.Value .Where(m => SelectedItem.Value.AllowedMods.Any(a => m.GetType() == a.GetType())) .ToList(); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index e6b7656986..08c00e8372 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -34,7 +34,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer Mods.Value = Playlist.FirstOrDefault()?.RequiredMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty(); } - protected override void OnSetItem(PlaylistItem item) + protected override void SelectItem(PlaylistItem item) { // If the client is already in a room, update via the client. // Otherwise, update the playlist directly in preparation for it to be submitted to the API on match creation. diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs index b75bf93204..46be5591a8 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs @@ -76,11 +76,15 @@ namespace osu.Game.Screens.OnlinePlay item.AllowedMods.Clear(); item.AllowedMods.AddRange(freeMods.Value.Select(m => m.CreateCopy())); - OnSetItem(item); + SelectItem(item); return true; } - protected abstract void OnSetItem(PlaylistItem item); + /// + /// Invoked when the user has requested a selection of a beatmap. + /// + /// The resultant . This item has not yet been added to the 's. + protected abstract void SelectItem(PlaylistItem item); public override bool OnBackButton() { @@ -117,8 +121,18 @@ namespace osu.Game.Screens.OnlinePlay return buttons; } + /// + /// Checks whether a given is valid for global selection. + /// + /// The to check. + /// Whether is a valid mod for online play. protected virtual bool IsValidMod(Mod mod) => !(mod is ModAutoplay) && (mod as MultiMod)?.Mods.Any(mm => mm is ModAutoplay) != true; + /// + /// Checks whether a given is valid for per-player free-mod selection. + /// + /// The to check. + /// Whether is a selectable free-mod. protected virtual bool IsValidFreeMod(Mod mod) => IsValidMod(mod); } } diff --git a/osu.Game/Screens/Play/HUD/ModDisplay.cs b/osu.Game/Screens/Play/HUD/ModDisplay.cs index 2d5b07f056..ce1a8a3205 100644 --- a/osu.Game/Screens/Play/HUD/ModDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ModDisplay.cs @@ -26,6 +26,9 @@ namespace osu.Game.Screens.Play.HUD public ExpansionMode ExpansionMode = ExpansionMode.ExpandOnHover; + /// + /// Whether the mods should initially appear expanded, before potentially contracting into their final expansion state (depending on ). + /// public bool ExpandOnAppear = true; private readonly Bindable> current = new Bindable>(); diff --git a/osu.Game/Screens/Select/MatchSongSelect.cs b/osu.Game/Screens/Select/MatchSongSelect.cs index 23fe9620fe..1de3e0e989 100644 --- a/osu.Game/Screens/Select/MatchSongSelect.cs +++ b/osu.Game/Screens/Select/MatchSongSelect.cs @@ -21,7 +21,7 @@ namespace osu.Game.Screens.Select CreateNewItem = createNewItem }; - protected override void OnSetItem(PlaylistItem item) + protected override void SelectItem(PlaylistItem item) { switch (Playlist.Count) { diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 4af96b7a29..ed6e0a1028 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -300,6 +300,10 @@ namespace osu.Game.Screens.Select } } + /// + /// Creates the buttons to be displayed in the footer. + /// + /// A set of and an optional which the button opens when pressed. protected virtual IEnumerable<(FooterButton, OverlayContainer)> CreateFooterButtons() => new (FooterButton, OverlayContainer)[] { (new FooterButtonMods { Current = Mods }, ModSelect), diff --git a/osu.Game/Utils/ModValidation.cs b/osu.Game/Utils/ModUtils.cs similarity index 79% rename from osu.Game/Utils/ModValidation.cs rename to osu.Game/Utils/ModUtils.cs index 3597396ec4..808dba2900 100644 --- a/osu.Game/Utils/ModValidation.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -12,9 +12,9 @@ using osu.Game.Rulesets.Mods; namespace osu.Game.Utils { /// - /// A set of utilities to validate combinations. + /// A set of utilities to handle combinations. /// - public static class ModValidation + public static class ModUtils { /// /// Checks that all s are compatible with each-other, and that all appear within a set of allowed types. @@ -25,11 +25,11 @@ namespace osu.Game.Utils /// The s to check. /// The set of allowed types. /// Whether all s are compatible with each-other and appear in the set of allowed types. - public static bool CheckCompatibleAndAllowed(IEnumerable combination, IEnumerable allowedTypes) + public static bool CheckCompatibleSetAndAllowed(IEnumerable combination, IEnumerable allowedTypes) { // Prevent multiple-enumeration. var combinationList = combination as ICollection ?? combination.ToArray(); - return CheckCompatible(combinationList) && CheckAllowed(combinationList, allowedTypes); + return CheckCompatibleSet(combinationList) && CheckAllowed(combinationList, allowedTypes); } /// @@ -37,7 +37,7 @@ namespace osu.Game.Utils /// /// The combination to check. /// Whether all s in the combination are compatible with each-other. - public static bool CheckCompatible(IEnumerable combination) + public static bool CheckCompatibleSet(IEnumerable combination) { var incompatibleTypes = new HashSet(); var incomingTypes = new HashSet(); @@ -83,20 +83,6 @@ namespace osu.Game.Utils .All(m => allowedSet.Contains(m.GetType())); } - /// - /// Determines whether a is in a set of incompatible types. - /// - /// - /// A can be incompatible through its most-declared type or any of its base types. - /// - /// The to test. - /// The set of incompatible types. - /// Whether the given is incompatible. - private static bool isModIncompatible(Mod mod, ICollection incompatibleTypes) - => FlattenMod(mod) - .SelectMany(m => m.GetType().EnumerateBaseTypes()) - .Any(incompatibleTypes.Contains); - /// /// Flattens a set of s, returning a new set with all s removed. /// From ee92ec0a5c2e6120f45b36f14b9a678ecab78e8a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Feb 2021 18:54:47 +0900 Subject: [PATCH 0245/1791] Disallow local user mod customisation --- .../OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 87ae723c63..f09c7168b3 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -216,7 +216,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer Origin = Anchor.BottomLeft, RelativeSizeAxes = Axes.Both, Height = 0.5f, - Child = userModsSelectOverlay = new SoloModSelectOverlay + Child = userModsSelectOverlay = new UserModSelectOverlay { SelectedMods = { BindTarget = UserMods }, IsValidMod = _ => false @@ -358,5 +358,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer if (client != null) client.LoadRequested -= onLoadRequested; } + + private class UserModSelectOverlay : ModSelectOverlay + { + protected override bool AllowCustomisation => false; + } } } From 76ebb3811a03ea45fb5279fab26c9951fe199ba9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Feb 2021 19:05:02 +0900 Subject: [PATCH 0246/1791] Fix mod icons potentially having incorrect colours --- osu.Game/Rulesets/UI/ModIcon.cs | 49 +++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index 8ea6c74349..76fcb8e080 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -26,8 +26,6 @@ namespace osu.Game.Rulesets.UI private const float size = 80; - private readonly ModType type; - public virtual string TooltipText => mod.IconTooltip; private Mod mod; @@ -38,16 +36,22 @@ namespace osu.Game.Rulesets.UI set { mod = value; - updateMod(value); + + if (LoadState >= LoadState.Ready) + updateMod(value); } } + [Resolved] + private OsuColour colours { get; set; } + + private Color4 backgroundColour; + private Color4 highlightedColour; + public ModIcon(Mod mod) { this.mod = mod ?? throw new ArgumentNullException(nameof(mod)); - type = mod.Type; - Size = new Vector2(size); Children = new Drawable[] @@ -79,10 +83,20 @@ namespace osu.Game.Rulesets.UI Icon = FontAwesome.Solid.Question }, }; + } + [BackgroundDependencyLoader] + private void load() + { updateMod(mod); } + protected override void LoadComplete() + { + base.LoadComplete(); + Selected.BindValueChanged(_ => updateColour(), true); + } + private void updateMod(Mod value) { modAcronym.Text = value.Acronym; @@ -92,20 +106,14 @@ namespace osu.Game.Rulesets.UI { modIcon.FadeOut(); modAcronym.FadeIn(); - return; + } + else + { + modIcon.FadeIn(); + modAcronym.FadeOut(); } - modIcon.FadeIn(); - modAcronym.FadeOut(); - } - - private Color4 backgroundColour; - private Color4 highlightedColour; - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - switch (type) + switch (value.Type) { default: case ModType.DifficultyIncrease: @@ -139,12 +147,13 @@ namespace osu.Game.Rulesets.UI modIcon.Colour = colours.Yellow; break; } + + updateColour(); } - protected override void LoadComplete() + private void updateColour() { - base.LoadComplete(); - Selected.BindValueChanged(selected => background.Colour = selected.NewValue ? highlightedColour : backgroundColour, true); + background.Colour = Selected.Value ? highlightedColour : backgroundColour; } } } From e5ca9b1e500c06e1cc084bcf4f8d1cc8994a3474 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Feb 2021 19:28:33 +0900 Subject: [PATCH 0247/1791] Remove usage of removed method --- .../Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 3322c8ede1..9e1e9f3d69 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -298,7 +298,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer if (client.Room == null) return; - client.ChangeUserMods(mods.NewValue).CatchUnobservedExceptions(); + client.ChangeUserMods(mods.NewValue); } private void onReadyClick() From b9832c1b2d2e55b06170e67c64c05e2f0cf016fc Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Feb 2021 19:37:19 +0900 Subject: [PATCH 0248/1791] Add ModUtils class for validating mod usages --- .../osu.Game.Tests.Android.csproj | 4 + osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj | 1 + osu.Game.Tests/Mods/ModUtilsTest.cs | 64 ++++++++++ osu.Game.Tests/osu.Game.Tests.csproj | 1 + osu.Game/Utils/ModUtils.cs | 109 ++++++++++++++++++ 5 files changed, 179 insertions(+) create mode 100644 osu.Game.Tests/Mods/ModUtilsTest.cs create mode 100644 osu.Game/Utils/ModUtils.cs diff --git a/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj b/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj index c44ed69c4d..19e36a63f1 100644 --- a/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj +++ b/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj @@ -69,5 +69,9 @@ osu.Game + + + + \ 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 ca68369ebb..67b2298f4c 100644 --- a/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj +++ b/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj @@ -45,6 +45,7 @@ + \ No newline at end of file diff --git a/osu.Game.Tests/Mods/ModUtilsTest.cs b/osu.Game.Tests/Mods/ModUtilsTest.cs new file mode 100644 index 0000000000..fdb441343a --- /dev/null +++ b/osu.Game.Tests/Mods/ModUtilsTest.cs @@ -0,0 +1,64 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Moq; +using NUnit.Framework; +using osu.Game.Rulesets.Mods; +using osu.Game.Utils; + +namespace osu.Game.Tests.Mods +{ + [TestFixture] + public class ModUtilsTest + { + [Test] + public void TestModIsCompatibleByItself() + { + var mod = new Mock(); + Assert.That(ModUtils.CheckCompatibleSet(new[] { mod.Object })); + } + + [Test] + public void TestIncompatibleThroughTopLevel() + { + var mod1 = new Mock(); + var mod2 = new Mock(); + + mod1.Setup(m => m.IncompatibleMods).Returns(new[] { mod2.Object.GetType() }); + + // Test both orderings. + Assert.That(ModUtils.CheckCompatibleSet(new[] { mod1.Object, mod2.Object }), Is.False); + Assert.That(ModUtils.CheckCompatibleSet(new[] { mod2.Object, mod1.Object }), Is.False); + } + + [Test] + public void TestIncompatibleThroughMultiMod() + { + var mod1 = new Mock(); + + // The nested mod. + var mod2 = new Mock(); + mod2.Setup(m => m.IncompatibleMods).Returns(new[] { mod1.Object.GetType() }); + + var multiMod = new MultiMod(new MultiMod(mod2.Object)); + + // Test both orderings. + Assert.That(ModUtils.CheckCompatibleSet(new[] { multiMod, mod1.Object }), Is.False); + Assert.That(ModUtils.CheckCompatibleSet(new[] { mod1.Object, multiMod }), Is.False); + } + + [Test] + public void TestAllowedThroughMostDerivedType() + { + var mod = new Mock(); + Assert.That(ModUtils.CheckAllowed(new[] { mod.Object }, new[] { mod.Object.GetType() })); + } + + [Test] + public void TestNotAllowedThroughBaseType() + { + var mod = new Mock(); + Assert.That(ModUtils.CheckAllowed(new[] { mod.Object }, new[] { typeof(Mod) }), Is.False); + } + } +} diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index c0c0578391..d29ed94b5f 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -7,6 +7,7 @@ + WinExe diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs new file mode 100644 index 0000000000..808dba2900 --- /dev/null +++ b/osu.Game/Utils/ModUtils.cs @@ -0,0 +1,109 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Extensions.TypeExtensions; +using osu.Game.Rulesets.Mods; + +#nullable enable + +namespace osu.Game.Utils +{ + /// + /// A set of utilities to handle combinations. + /// + public static class ModUtils + { + /// + /// Checks that all s are compatible with each-other, and that all appear within a set of allowed types. + /// + /// + /// The allowed types must contain exact types for the respective s to be allowed. + /// + /// The s to check. + /// The set of allowed types. + /// Whether all s are compatible with each-other and appear in the set of allowed types. + public static bool CheckCompatibleSetAndAllowed(IEnumerable combination, IEnumerable allowedTypes) + { + // Prevent multiple-enumeration. + var combinationList = combination as ICollection ?? combination.ToArray(); + return CheckCompatibleSet(combinationList) && CheckAllowed(combinationList, allowedTypes); + } + + /// + /// Checks that all s in a combination are compatible with each-other. + /// + /// The combination to check. + /// Whether all s in the combination are compatible with each-other. + public static bool CheckCompatibleSet(IEnumerable combination) + { + var incompatibleTypes = new HashSet(); + var incomingTypes = new HashSet(); + + foreach (var mod in combination.SelectMany(FlattenMod)) + { + // Add the new mod incompatibilities, checking whether any match the existing mod types. + foreach (var t in mod.IncompatibleMods) + { + if (incomingTypes.Contains(t)) + return false; + + incompatibleTypes.Add(t); + } + + // Add the new mod types, checking whether any match the incompatible types. + foreach (var t in mod.GetType().EnumerateBaseTypes()) + { + if (incomingTypes.Contains(t)) + return false; + + incomingTypes.Add(t); + } + } + + return true; + } + + /// + /// Checks that all s in a combination appear within a set of allowed types. + /// + /// + /// The set of allowed types must contain exact types for the respective s to be allowed. + /// + /// The combination to check. + /// The set of allowed types. + /// Whether all s in the combination are allowed. + public static bool CheckAllowed(IEnumerable combination, IEnumerable allowedTypes) + { + var allowedSet = new HashSet(allowedTypes); + + return combination.SelectMany(FlattenMod) + .All(m => allowedSet.Contains(m.GetType())); + } + + /// + /// Flattens a set of s, returning a new set with all s removed. + /// + /// The set of s to flatten. + /// The new set, containing all s in recursively with all s removed. + public static IEnumerable FlattenMods(IEnumerable mods) => mods.SelectMany(FlattenMod); + + /// + /// Flattens a , returning a set of s in-place of any s. + /// + /// The to flatten. + /// A set of singular "flattened" s + public static IEnumerable FlattenMod(Mod mod) + { + if (mod is MultiMod multi) + { + foreach (var m in multi.Mods.SelectMany(FlattenMod)) + yield return m; + } + else + yield return mod; + } + } +} From 97247b7a673fa552b9f8f5b66aa87e9dfe2293c0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Feb 2021 19:59:18 +0900 Subject: [PATCH 0249/1791] Fix unset key --- osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs index a25c332b47..d0e19d9f37 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs @@ -33,6 +33,7 @@ namespace osu.Game.Online.Multiplayer public IEnumerable Mods { get; set; } = Enumerable.Empty(); [NotNull] + [Key(5)] public IEnumerable AllowedMods { get; set; } = Enumerable.Empty(); public bool Equals(MultiplayerRoomSettings other) From 97e3023df9cb680feedadd089b2eb326078a2e39 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Feb 2021 20:16:58 +0900 Subject: [PATCH 0250/1791] Renamespace/rename MatchSongSelect -> PlaylistsSongSelect --- .../Visual/Multiplayer/TestSceneMatchSongSelect.cs | 8 ++++---- .../OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs | 3 +-- .../Playlists/PlaylistsSongSelect.cs} | 6 +++--- 3 files changed, 8 insertions(+), 9 deletions(-) rename osu.Game/Screens/{Select/MatchSongSelect.cs => OnlinePlay/Playlists/PlaylistsSongSelect.cs} (91%) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs index e0fd7d9874..86429ac50e 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs @@ -19,7 +19,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.OnlinePlay.Components; -using osu.Game.Screens.Select; +using osu.Game.Screens.OnlinePlay.Playlists; namespace osu.Game.Tests.Visual.Multiplayer { @@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private RulesetStore rulesets; - private TestMatchSongSelect songSelect; + private TestPlaylistsSongSelect songSelect; [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) @@ -89,7 +89,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Beatmap.SetDefault(); }); - AddStep("create song select", () => LoadScreen(songSelect = new TestMatchSongSelect())); + AddStep("create song select", () => LoadScreen(songSelect = new TestPlaylistsSongSelect())); AddUntilStep("wait for present", () => songSelect.IsCurrentScreen()); } @@ -176,7 +176,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("item has rate 1.5", () => Precision.AlmostEquals(1.5, ((OsuModDoubleTime)Room.Playlist.First().RequiredMods[0]).SpeedChange.Value)); } - private class TestMatchSongSelect : MatchSongSelect + private class TestPlaylistsSongSelect : PlaylistsSongSelect { public new MatchBeatmapDetailArea BeatmapDetails => (MatchBeatmapDetailArea)base.BeatmapDetails; } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index 22580f0537..cedde373b3 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -13,7 +13,6 @@ using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Match; using osu.Game.Screens.OnlinePlay.Match.Components; -using osu.Game.Screens.Select; using osu.Game.Users; using Footer = osu.Game.Screens.OnlinePlay.Match.Components.Footer; @@ -188,7 +187,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists settingsOverlay = new PlaylistsMatchSettingsOverlay { RelativeSizeAxes = Axes.Both, - EditPlaylist = () => this.Push(new MatchSongSelect()), + EditPlaylist = () => this.Push(new PlaylistsSongSelect()), State = { Value = roomId.Value == null ? Visibility.Visible : Visibility.Hidden } } }; diff --git a/osu.Game/Screens/Select/MatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs similarity index 91% rename from osu.Game/Screens/Select/MatchSongSelect.cs rename to osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs index 1de3e0e989..0e8db6dfe5 100644 --- a/osu.Game/Screens/Select/MatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs @@ -6,12 +6,12 @@ using osu.Framework.Allocation; using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Online.Rooms; -using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Components; +using osu.Game.Screens.Select; -namespace osu.Game.Screens.Select +namespace osu.Game.Screens.OnlinePlay.Playlists { - public class MatchSongSelect : OnlinePlaySongSelect + public class PlaylistsSongSelect : OnlinePlaySongSelect { [Resolved] private BeatmapManager beatmaps { get; set; } From ead8262257ed0749b9b81670aa76b534aa73874f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 1 Feb 2021 20:20:10 +0900 Subject: [PATCH 0251/1791] Add function to check for (and return) invalid mods --- osu.Game/Utils/ModUtils.cs | 42 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs index 808dba2900..9e638d4f2f 100644 --- a/osu.Game/Utils/ModUtils.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -83,6 +83,48 @@ namespace osu.Game.Utils .All(m => allowedSet.Contains(m.GetType())); } + /// + /// Check the provided combination of mods are valid for a local gameplay session. + /// + /// The mods to check. + /// Invalid mods, if any where found. Can be null if all mods were valid. + /// Whether the input mods were all valid. If false, will contain all invalid entries. + public static bool CheckValidForGameplay(IEnumerable mods, out Mod[]? invalidMods) + { + mods = mods.ToArray(); + + List? foundInvalid = null; + + void addInvalid(Mod mod) + { + foundInvalid ??= new List(); + foundInvalid.Add(mod); + } + + foreach (var mod in mods) + { + bool valid = mod.Type != ModType.System + && mod.HasImplementation + && !(mod is MultiMod); + + if (!valid) + { + // if this mod was found as invalid, we can exclude it before potentially excluding more incompatible types. + addInvalid(mod); + continue; + } + + foreach (var type in mod.IncompatibleMods) + { + foreach (var invalid in mods.Where(m => type.IsInstanceOfType(m))) + addInvalid(invalid); + } + } + + invalidMods = foundInvalid?.ToArray(); + return foundInvalid == null; + } + /// /// Flattens a set of s, returning a new set with all s removed. /// From 425dc8a210d69c853c58f4d2a10ce0347914261f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 1 Feb 2021 20:20:19 +0900 Subject: [PATCH 0252/1791] Ensure mods are always in a valid state at a game level --- osu.Game/OsuGame.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 5acd6bc73d..a00cd5e6a0 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -468,6 +468,12 @@ namespace osu.Game private void modsChanged(ValueChangedEvent> mods) { updateModDefaults(); + + if (!ModUtils.CheckValidForGameplay(mods.NewValue, out var invalid)) + { + // ensure we always have a valid set of mods. + SelectedMods.Value = mods.NewValue.Except(invalid).ToArray(); + } } private void updateModDefaults() From 286726feb0cb93c4f3d54e60d690979bba35c005 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 1 Feb 2021 17:44:30 +0000 Subject: [PATCH 0253/1791] Bump SharpCompress from 0.26.0 to 0.27.1 Bumps [SharpCompress](https://github.com/adamhathcock/sharpcompress) from 0.26.0 to 0.27.1. - [Release notes](https://github.com/adamhathcock/sharpcompress/releases) - [Commits](https://github.com/adamhathcock/sharpcompress/compare/0.26...0.27.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 1552dff17d..d0aeb10c9c 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -30,7 +30,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 48dc01f5de..b40e6a3346 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -89,7 +89,7 @@ - + From 0560676236e2b73eb25726b10a07927912b2b201 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 1 Feb 2021 18:09:05 +0000 Subject: [PATCH 0254/1791] Bump ppy.osu.Framework.NativeLibs from 2020.923.0 to 2021.115.0 Bumps [ppy.osu.Framework.NativeLibs](https://github.com/ppy/osu-framework) from 2020.923.0 to 2021.115.0. - [Release notes](https://github.com/ppy/osu-framework/releases) - [Commits](https://github.com/ppy/osu-framework/compare/2020.923.0...2021.115.0) Signed-off-by: dependabot-preview[bot] --- osu.iOS.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.iOS.props b/osu.iOS.props index b40e6a3346..dc3527c687 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -93,6 +93,6 @@ - + From 57213e630806a6e25973fa19588009217a0ad9ea Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 1 Feb 2021 18:09:07 +0000 Subject: [PATCH 0255/1791] Bump DiscordRichPresence from 1.0.169 to 1.0.175 Bumps [DiscordRichPresence](https://github.com/Lachee/discord-rpc-csharp) from 1.0.169 to 1.0.175. - [Release notes](https://github.com/Lachee/discord-rpc-csharp/releases) - [Commits](https://github.com/Lachee/discord-rpc-csharp/commits) Signed-off-by: dependabot-preview[bot] --- osu.Desktop/osu.Desktop.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index cce7907c6c..3e0f0cb7f6 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -30,7 +30,7 @@ - + From 15fcabb1282c8c36c4f005e6acba36a7d239fb11 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 1 Feb 2021 22:04:44 +0300 Subject: [PATCH 0256/1791] Add documentation to auto-scroll leniency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Overlays/Chat/DrawableChannel.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 4c8513b1d5..0cd5ecc05a 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -241,6 +241,11 @@ namespace osu.Game.Overlays.Chat /// private class ChannelScrollContainer : OsuScrollContainer { + /// + /// The chat will be automatically scrolled to end if and only if + /// the distance between the current scroll position and the end of the scroll + /// is less than this value. + /// private const float auto_scroll_leniency = 10f; private float? lastExtent; From 5c28c030c86a270cd6435955ac82de4daa8a196e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 1 Feb 2021 22:08:55 +0300 Subject: [PATCH 0257/1791] Unconditionally set "autoscroll" state --- osu.Game/Overlays/Chat/DrawableChannel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 0cd5ecc05a..db6a27bf8c 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -261,8 +261,8 @@ namespace osu.Game.Overlays.Chat if ((lastExtent == null || ScrollableExtent > lastExtent) && ShouldAutoScroll) ScrollToEnd(); - else - ShouldAutoScroll = IsScrolledToEnd(auto_scroll_leniency); + + ShouldAutoScroll = IsScrolledToEnd(auto_scroll_leniency); lastExtent = ScrollableExtent; } From 216b0d89a729cff6ad311ebb440f32dcb1e174e6 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 1 Feb 2021 19:16:51 +0000 Subject: [PATCH 0258/1791] Bump Sentry from 2.1.8 to 3.0.1 Bumps [Sentry](https://github.com/getsentry/sentry-dotnet) from 2.1.8 to 3.0.1. - [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/2.1.8...3.0.1) 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 d0aeb10c9c..e2b506e187 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -29,7 +29,7 @@ - + From dcb1626e4d66649304d9307cdc63329f523192f9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 1 Feb 2021 22:38:42 +0300 Subject: [PATCH 0259/1791] Remove no longer necessary field --- osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs index 02ef024128..8ea05784e9 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs @@ -46,7 +46,6 @@ namespace osu.Game.Tests.Visual.Online private ChannelManager channelManager = new ChannelManager(); private TestStandAloneChatDisplay chatDisplay; - private TestStandAloneChatDisplay chatDisplay2; private int messageIdSequence; private Channel testChannel; @@ -72,7 +71,7 @@ namespace osu.Game.Tests.Visual.Online Size = new Vector2(400, 80), Channel = { Value = testChannel }, }, - chatDisplay2 = new TestStandAloneChatDisplay(true) + new TestStandAloneChatDisplay(true) { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, From f166c4c4148553e381f694cbdb7cca3fb65d7cf1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 11:04:09 +0900 Subject: [PATCH 0260/1791] Rename test --- ...tSceneMatchSongSelect.cs => TestScenePlaylistsSongSelect.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename osu.Game.Tests/Visual/Multiplayer/{TestSceneMatchSongSelect.cs => TestScenePlaylistsSongSelect.cs} (99%) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs similarity index 99% rename from osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs rename to osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs index 86429ac50e..2f7e59f800 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs @@ -23,7 +23,7 @@ using osu.Game.Screens.OnlinePlay.Playlists; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneMatchSongSelect : RoomTestScene + public class TestScenePlaylistsSongSelect : RoomTestScene { [Resolved] private BeatmapManager beatmapManager { get; set; } From 4cf52077b6dbb51bfb48f4589ed88f47cab886ca Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 11:11:28 +0900 Subject: [PATCH 0261/1791] Make checkbox also respond to all mods selected --- osu.Game/Screens/OnlinePlay/Match/FreeModSelectOverlay.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Match/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/Match/FreeModSelectOverlay.cs index 3cdf25fad0..9a3c87f1ff 100644 --- a/osu.Game/Screens/OnlinePlay/Match/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Match/FreeModSelectOverlay.cs @@ -57,12 +57,8 @@ namespace osu.Game.Screens.OnlinePlay.Match { base.Update(); - // If any of the buttons aren't selected, deselect the checkbox. - foreach (var button in ButtonsContainer.OfType()) - { - if (button.Mods.Any(m => m.HasImplementation) && !button.Selected) - checkbox.Current.Value = false; - } + var validButtons = ButtonsContainer.OfType().Where(b => b.Mod.HasImplementation); + checkbox.Current.Value = validButtons.All(b => b.Selected); } } From b54f65c28279ffcc4d53b9eebdac6c1b73ac4d30 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 12:48:15 +0900 Subject: [PATCH 0262/1791] Exclude more mods from multiplayer --- .../OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs | 2 +- osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index 08c00e8372..f7f0402555 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -68,6 +68,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea(); - protected override bool IsValidFreeMod(Mod mod) => base.IsValidFreeMod(mod) && !(mod is ModTimeRamp) && !(mod is ModRateAdjust); + protected override bool IsValidFreeMod(Mod mod) => base.IsValidFreeMod(mod) && !(mod is ModTimeRamp) && !(mod is ModRateAdjust) && !mod.RequiresConfiguration; } } diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs index 46be5591a8..755dbdb55e 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs @@ -126,7 +126,7 @@ namespace osu.Game.Screens.OnlinePlay /// /// The to check. /// Whether is a valid mod for online play. - protected virtual bool IsValidMod(Mod mod) => !(mod is ModAutoplay) && (mod as MultiMod)?.Mods.Any(mm => mm is ModAutoplay) != true; + protected virtual bool IsValidMod(Mod mod) => mod.HasImplementation && !(mod is ModAutoplay) && (mod as MultiMod)?.Mods.Any(mm => mm is ModAutoplay) != true; /// /// Checks whether a given is valid for per-player free-mod selection. From 7a14e14e67db572f8ec1c8154d6e2e0111a0429b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 12:49:49 +0900 Subject: [PATCH 0263/1791] Refactor condition This won't make any noticeable difference, but is the more correct way to handle MultiMod because flattening works through infinite recursion levels. --- osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs index 755dbdb55e..b0062720e0 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs @@ -17,6 +17,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens.OnlinePlay.Match; using osu.Game.Screens.Select; +using osu.Game.Utils; namespace osu.Game.Screens.OnlinePlay { @@ -126,7 +127,7 @@ namespace osu.Game.Screens.OnlinePlay /// /// The to check. /// Whether is a valid mod for online play. - protected virtual bool IsValidMod(Mod mod) => mod.HasImplementation && !(mod is ModAutoplay) && (mod as MultiMod)?.Mods.Any(mm => mm is ModAutoplay) != true; + protected virtual bool IsValidMod(Mod mod) => mod.HasImplementation && !ModUtils.FlattenMod(mod).Any(m => m is ModAutoplay); /// /// Checks whether a given is valid for per-player free-mod selection. From 0d5353008c1d3e9520d7d12bf50d6f23b85d9712 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Feb 2021 13:34:34 +0900 Subject: [PATCH 0264/1791] Update sentry sdk usage --- osu.Game/Utils/SentryLogger.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Utils/SentryLogger.cs b/osu.Game/Utils/SentryLogger.cs index e8e41cdbbe..be9d01cde6 100644 --- a/osu.Game/Utils/SentryLogger.cs +++ b/osu.Game/Utils/SentryLogger.cs @@ -23,7 +23,7 @@ namespace osu.Game.Utils var options = new SentryOptions { - Dsn = new Dsn("https://5e342cd55f294edebdc9ad604d28bbd3@sentry.io/1255255"), + Dsn = "https://5e342cd55f294edebdc9ad604d28bbd3@sentry.io/1255255", Release = game.Version }; From 9c3c0895cf2a63d3a7517d53fecf8a0d64c149eb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 13:03:46 +0900 Subject: [PATCH 0265/1791] Hide customise button + multiplier label --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 3 ++- osu.Game/Screens/OnlinePlay/Match/FreeModSelectOverlay.cs | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 25cb75f73a..a7bfac4088 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -37,6 +37,7 @@ namespace osu.Game.Overlays.Mods protected readonly TriangleButton CustomiseButton; protected readonly TriangleButton CloseButton; + protected readonly Drawable MultiplierSection; protected readonly OsuSpriteText MultiplierLabel; /// @@ -316,7 +317,7 @@ namespace osu.Game.Overlays.Mods Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, }, - new FillFlowContainer + MultiplierSection = new FillFlowContainer { AutoSizeAxes = Axes.Both, Spacing = new Vector2(footer_button_spacing / 2, 0), diff --git a/osu.Game/Screens/OnlinePlay/Match/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/Match/FreeModSelectOverlay.cs index 9a3c87f1ff..f22c6603e5 100644 --- a/osu.Game/Screens/OnlinePlay/Match/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Match/FreeModSelectOverlay.cs @@ -20,6 +20,12 @@ namespace osu.Game.Screens.OnlinePlay.Match protected override bool Stacked => false; + public FreeModSelectOverlay() + { + CustomiseButton.Alpha = 0; + MultiplierSection.Alpha = 0; + } + protected override ModSection CreateModSection(ModType type) => new FreeModSection(type); private class FreeModSection : ModSection From 87f9e46b164c540d9f7a531510521087f3840a0c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 13:37:25 +0900 Subject: [PATCH 0266/1791] Add option to select all --- osu.Game/Overlays/Mods/ModSection.cs | 8 +++- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 13 +++--- .../OnlinePlay/Match/FreeModSelectOverlay.cs | 40 +++++++++++++++++++ 3 files changed, 53 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index 5de629424b..993f4ef9d7 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -91,7 +91,13 @@ namespace osu.Game.Overlays.Mods return base.OnKeyDown(e); } - public void DeselectAll() => DeselectTypes(buttons.Select(b => b.SelectedMod?.GetType()).Where(t => t != null)); + public void SelectAll() + { + foreach (var button in buttons.Where(b => !b.Selected)) + button.SelectAt(0); + } + + public void DeselectAll(bool immediate = false) => DeselectTypes(buttons.Select(b => b.SelectedMod?.GetType()).Where(t => t != null), immediate); /// /// Deselect one or more mods in this section. diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index a7bfac4088..087990d3f8 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -33,6 +33,7 @@ namespace osu.Game.Overlays.Mods { public const float HEIGHT = 510; + protected readonly FillFlowContainer FooterContainer; protected readonly TriangleButton DeselectAllButton; protected readonly TriangleButton CustomiseButton; protected readonly TriangleButton CloseButton; @@ -85,8 +86,6 @@ namespace osu.Game.Overlays.Mods private const float content_width = 0.8f; private const float footer_button_spacing = 20; - private readonly FillFlowContainer footerContainer; - private SampleChannel sampleOn, sampleOff; protected ModSelectOverlay() @@ -275,7 +274,7 @@ namespace osu.Game.Overlays.Mods Colour = new Color4(172, 20, 116, 255), Alpha = 0.5f, }, - footerContainer = new FillFlowContainer + FooterContainer = new FillFlowContainer { Origin = Anchor.BottomCentre, Anchor = Anchor.BottomCentre, @@ -385,8 +384,8 @@ namespace osu.Game.Overlays.Mods { base.PopOut(); - footerContainer.MoveToX(content_width, WaveContainer.DISAPPEAR_DURATION, Easing.InSine); - footerContainer.FadeOut(WaveContainer.DISAPPEAR_DURATION, Easing.InSine); + FooterContainer.MoveToX(content_width, WaveContainer.DISAPPEAR_DURATION, Easing.InSine); + FooterContainer.FadeOut(WaveContainer.DISAPPEAR_DURATION, Easing.InSine); foreach (var section in ModSectionsContainer.Children) { @@ -400,8 +399,8 @@ namespace osu.Game.Overlays.Mods { base.PopIn(); - footerContainer.MoveToX(0, WaveContainer.APPEAR_DURATION, Easing.OutQuint); - footerContainer.FadeIn(WaveContainer.APPEAR_DURATION, Easing.OutQuint); + FooterContainer.MoveToX(0, WaveContainer.APPEAR_DURATION, Easing.OutQuint); + FooterContainer.FadeIn(WaveContainer.APPEAR_DURATION, Easing.OutQuint); foreach (var section in ModSectionsContainer.Children) { diff --git a/osu.Game/Screens/OnlinePlay/Match/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/Match/FreeModSelectOverlay.cs index f22c6603e5..9a676930a0 100644 --- a/osu.Game/Screens/OnlinePlay/Match/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Match/FreeModSelectOverlay.cs @@ -24,6 +24,46 @@ namespace osu.Game.Screens.OnlinePlay.Match { CustomiseButton.Alpha = 0; MultiplierSection.Alpha = 0; + DeselectAllButton.Alpha = 0; + + Drawable selectAllButton; + Drawable deselectAllButton; + + FooterContainer.AddRange(new[] + { + selectAllButton = new TriangleButton + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Width = 180, + Text = "Select All", + Action = selectAll, + }, + // Unlike the base mod select overlay, this button deselects mods instantaneously. + deselectAllButton = new TriangleButton + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Width = 180, + Text = "Deselect All", + Action = deselectAll, + }, + }); + + FooterContainer.SetLayoutPosition(selectAllButton, -2); + FooterContainer.SetLayoutPosition(deselectAllButton, -1); + } + + private void selectAll() + { + foreach (var section in ModSectionsContainer.Children) + section.SelectAll(); + } + + private void deselectAll() + { + foreach (var section in ModSectionsContainer.Children) + section.DeselectAll(true); } protected override ModSection CreateModSection(ModType type) => new FreeModSection(type); From 1d3dff8c75988867dce9fd6124fdb08bcb84e344 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 13:41:01 +0900 Subject: [PATCH 0267/1791] Refactor ModDisplay flag usage --- osu.Game/Screens/Play/HUD/ModDisplay.cs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ModDisplay.cs b/osu.Game/Screens/Play/HUD/ModDisplay.cs index ce1a8a3205..052484afc4 100644 --- a/osu.Game/Screens/Play/HUD/ModDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ModDisplay.cs @@ -113,14 +113,10 @@ namespace osu.Game.Screens.Play.HUD else unrankedText.Hide(); - if (ExpandOnAppear) - { - expand(); - using (iconsContainer.BeginDelayedSequence(1200)) - contract(); - } - else - iconsContainer.TransformSpacingTo(new Vector2(-25, 0)); + expand(); + + using (iconsContainer.BeginDelayedSequence(ExpandOnAppear ? 1200 : 0)) + contract(); } private void expand() From 53cfc3bc6ea4ddda9b8faf32a97b954e5a09c4f9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 13:42:45 +0900 Subject: [PATCH 0268/1791] Make ModIcon a bit more safe --- osu.Game/Rulesets/UI/ModIcon.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index 981fe7f4a6..2ff59f4d1a 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.UI { mod = value; - if (LoadState >= LoadState.Ready) + if (IsLoaded) updateMod(value); } } @@ -98,13 +98,15 @@ namespace osu.Game.Rulesets.UI [BackgroundDependencyLoader] private void load() { - updateMod(mod); } protected override void LoadComplete() { base.LoadComplete(); - Selected.BindValueChanged(_ => updateColour(), true); + + Selected.BindValueChanged(_ => updateColour()); + + updateMod(mod); } private void updateMod(Mod value) From 173e20938cd0b87fc47747cc13b8deba60bb9bea Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 13:49:58 +0900 Subject: [PATCH 0269/1791] Revert changes to ModDisplay --- .../Multiplayer/Participants/ParticipantPanel.cs | 1 - osu.Game/Screens/Play/HUD/ModDisplay.cs | 7 +------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index 8b907066a8..8036e5f702 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs @@ -139,7 +139,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants Scale = new Vector2(0.5f), ExpansionMode = ExpansionMode.AlwaysContracted, DisplayUnrankedText = false, - ExpandOnAppear = false } }, userStateDisplay = new StateDisplay diff --git a/osu.Game/Screens/Play/HUD/ModDisplay.cs b/osu.Game/Screens/Play/HUD/ModDisplay.cs index 052484afc4..68d019bf71 100644 --- a/osu.Game/Screens/Play/HUD/ModDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ModDisplay.cs @@ -26,11 +26,6 @@ namespace osu.Game.Screens.Play.HUD public ExpansionMode ExpansionMode = ExpansionMode.ExpandOnHover; - /// - /// Whether the mods should initially appear expanded, before potentially contracting into their final expansion state (depending on ). - /// - public bool ExpandOnAppear = true; - private readonly Bindable> current = new Bindable>(); public Bindable> Current @@ -115,7 +110,7 @@ namespace osu.Game.Screens.Play.HUD expand(); - using (iconsContainer.BeginDelayedSequence(ExpandOnAppear ? 1200 : 0)) + using (iconsContainer.BeginDelayedSequence(1200)) contract(); } From 4194c9308eedc24e5ad6cc6315eb945e0838a7a8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 13:50:05 +0900 Subject: [PATCH 0270/1791] Add xmldoc --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 087990d3f8..b20c2d9d19 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -288,7 +288,7 @@ namespace osu.Game.Overlays.Mods Vertical = 15, Horizontal = OsuScreen.HORIZONTAL_OVERFLOW_PADDING }, - Children = new Drawable[] + Children = new[] { DeselectAllButton = new TriangleButton { @@ -509,6 +509,10 @@ namespace osu.Game.Overlays.Mods refreshSelectedMods(); } + /// + /// Invoked when a new has been selected. + /// + /// The that has been selected. protected virtual void OnModSelected(Mod mod) { } From 0bce9d68335d78f9c9286af6520e330f3d3f5c59 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 13:54:27 +0900 Subject: [PATCH 0271/1791] Clear freemods when ruleset is changed --- osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs index b0062720e0..c58632c500 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs @@ -60,6 +60,13 @@ namespace osu.Game.Screens.OnlinePlay freeMods.Value = Playlist.FirstOrDefault()?.AllowedMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty(); FooterPanels.Add(freeModSelectOverlay); + + Ruleset.BindValueChanged(onRulesetChanged); + } + + private void onRulesetChanged(ValueChangedEvent ruleset) + { + freeMods.Value = Array.Empty(); } protected sealed override bool OnStart() From 80d88024d6bbca6db1f9ca2765340bb765f5ae07 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Feb 2021 14:13:50 +0900 Subject: [PATCH 0272/1791] Add basic test coverage of CheckValidForGameplay function --- osu.Game.Tests/Mods/ModUtilsTest.cs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/osu.Game.Tests/Mods/ModUtilsTest.cs b/osu.Game.Tests/Mods/ModUtilsTest.cs index fdb441343a..88eee5449c 100644 --- a/osu.Game.Tests/Mods/ModUtilsTest.cs +++ b/osu.Game.Tests/Mods/ModUtilsTest.cs @@ -1,9 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Collections.Generic; +using System.Linq; using Moq; using NUnit.Framework; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Utils; namespace osu.Game.Tests.Mods @@ -60,5 +64,29 @@ namespace osu.Game.Tests.Mods var mod = new Mock(); Assert.That(ModUtils.CheckAllowed(new[] { mod.Object }, new[] { typeof(Mod) }), Is.False); } + + // test incompatible pair. + [TestCase(new[] { typeof(OsuModDoubleTime), typeof(OsuModHalfTime) }, new[] { typeof(OsuModDoubleTime), typeof(OsuModHalfTime) })] + // test incompatible pair with derived class. + [TestCase(new[] { typeof(OsuModNightcore), typeof(OsuModHalfTime) }, new[] { typeof(OsuModNightcore), typeof(OsuModHalfTime) })] + // test system mod. + [TestCase(new[] { typeof(OsuModDoubleTime), typeof(OsuModTouchDevice) }, new[] { typeof(OsuModTouchDevice) })] + // test valid. + [TestCase(new[] { typeof(OsuModDoubleTime), typeof(OsuModHardRock) }, null)] + public void TestInvalidModScenarios(Type[] input, Type[] expectedInvalid) + { + List inputMods = new List(); + foreach (var t in input) + inputMods.Add((Mod)Activator.CreateInstance(t)); + + bool isValid = ModUtils.CheckValidForGameplay(inputMods, out var invalid); + + Assert.That(isValid, Is.EqualTo(expectedInvalid == null)); + + if (isValid) + Assert.IsNull(invalid); + else + Assert.That(invalid?.Select(t => t.GetType()), Is.EquivalentTo(expectedInvalid)); + } } } From 1c645601d41491004c96903231c61ce5a163c7b2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Feb 2021 14:14:31 +0900 Subject: [PATCH 0273/1791] Fix typo in xmldoc --- osu.Game/Utils/ModUtils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs index 9e638d4f2f..2146abacb6 100644 --- a/osu.Game/Utils/ModUtils.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -87,7 +87,7 @@ namespace osu.Game.Utils /// Check the provided combination of mods are valid for a local gameplay session. /// /// The mods to check. - /// Invalid mods, if any where found. Can be null if all mods were valid. + /// Invalid mods, if any were found. Can be null if all mods were valid. /// Whether the input mods were all valid. If false, will contain all invalid entries. public static bool CheckValidForGameplay(IEnumerable mods, out Mod[]? invalidMods) { From ed63b571d2185d4b3e86d77a08c0f9ce656f819c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Feb 2021 15:16:26 +0900 Subject: [PATCH 0274/1791] Add "new" override for ScrollToEnd To UserTrackingScrollContainer --- osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs b/osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs index b8ce34b204..be33c231c9 100644 --- a/osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs +++ b/osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs @@ -45,5 +45,11 @@ namespace osu.Game.Graphics.Containers UserScrolling = false; base.ScrollTo(value, animated, distanceDecay); } + + public new void ScrollToEnd(bool animated = true, bool allowDuringDrag = false) + { + UserScrolling = false; + base.ScrollToEnd(animated, allowDuringDrag); + } } } From 398ab9c2c2283a8bad2dd2bf513103a606868e89 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Feb 2021 15:16:10 +0900 Subject: [PATCH 0275/1791] Use UserTrackingScrollContainer instead --- .../Online/TestSceneStandAloneChatDisplay.cs | 2 +- osu.Game/Overlays/Chat/DrawableChannel.cs | 32 ++++++++++++------- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs index 8ea05784e9..e7669262fe 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs @@ -208,7 +208,7 @@ namespace osu.Game.Tests.Visual.Online protected DrawableChannel DrawableChannel => InternalChildren.OfType().First(); - protected OsuScrollContainer ScrollContainer => (OsuScrollContainer)((Container)DrawableChannel.Child).Child; + protected UserTrackingScrollContainer ScrollContainer => (UserTrackingScrollContainer)((Container)DrawableChannel.Child).Child; public FillFlowContainer FillFlow => (FillFlowContainer)ScrollContainer.Child; diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index db6a27bf8c..1d021b331a 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -16,6 +16,7 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics.Sprites; +using osu.Framework.Utils; using osu.Game.Graphics.Sprites; namespace osu.Game.Overlays.Chat @@ -147,8 +148,8 @@ namespace osu.Game.Overlays.Chat // due to the scroll adjusts from old messages removal above, a scroll-to-end must be enforced, // to avoid making the container think the user has scrolled back up and unwantedly disable auto-scrolling. - if (scroll.ShouldAutoScroll || newMessages.Any(m => m is LocalMessage)) - ScheduleAfterChildren(() => scroll.ScrollToEnd()); + if (newMessages.Any(m => m is LocalMessage)) + scroll.ScrollToEnd(); }); private void pendingMessageResolved(Message existing, Message updated) => Schedule(() => @@ -239,7 +240,7 @@ namespace osu.Game.Overlays.Chat /// /// An with functionality to automatically scroll whenever the maximum scrollable distance increases. /// - private class ChannelScrollContainer : OsuScrollContainer + private class ChannelScrollContainer : UserTrackingScrollContainer { /// /// The chat will be automatically scrolled to end if and only if @@ -250,21 +251,30 @@ namespace osu.Game.Overlays.Chat private float? lastExtent; - /// - /// Whether this container should automatically scroll to end on the next call to . - /// - public bool ShouldAutoScroll { get; private set; } = true; + protected override void OnUserScroll(float value, bool animated = true, double? distanceDecay = default) + { + base.OnUserScroll(value, animated, distanceDecay); + lastExtent = null; + } protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); - if ((lastExtent == null || ScrollableExtent > lastExtent) && ShouldAutoScroll) - ScrollToEnd(); + // If the user has scrolled to the bottom of the container, we should resume tracking new content. + bool cancelUserScroll = UserScrolling && IsScrolledToEnd(auto_scroll_leniency); - ShouldAutoScroll = IsScrolledToEnd(auto_scroll_leniency); + // If the user hasn't overridden our behaviour and there has been new content added to the container, we should update our scroll position to track it. + bool requiresScrollUpdate = !UserScrolling && (lastExtent == null || Precision.AlmostBigger(ScrollableExtent, lastExtent.Value)); - lastExtent = ScrollableExtent; + if (cancelUserScroll || requiresScrollUpdate) + { + ScheduleAfterChildren(() => + { + ScrollToEnd(); + lastExtent = ScrollableExtent; + }); + } } } } From bb0753f68d79860b0631938dbbea7b9cd9ab2210 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Feb 2021 15:44:03 +0900 Subject: [PATCH 0276/1791] Use a better method of cancelling user scroll --- .../Containers/UserTrackingScrollContainer.cs | 2 ++ osu.Game/Overlays/Chat/DrawableChannel.cs | 12 ++++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs b/osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs index be33c231c9..17506ce0f5 100644 --- a/osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs +++ b/osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs @@ -25,6 +25,8 @@ namespace osu.Game.Graphics.Containers /// public bool UserScrolling { get; private set; } + public void CancelUserScroll() => UserScrolling = false; + public UserTrackingScrollContainer() { } diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 1d021b331a..de3057e9dc 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -262,17 +262,21 @@ namespace osu.Game.Overlays.Chat base.UpdateAfterChildren(); // If the user has scrolled to the bottom of the container, we should resume tracking new content. - bool cancelUserScroll = UserScrolling && IsScrolledToEnd(auto_scroll_leniency); + if (UserScrolling && IsScrolledToEnd(auto_scroll_leniency)) + CancelUserScroll(); // If the user hasn't overridden our behaviour and there has been new content added to the container, we should update our scroll position to track it. bool requiresScrollUpdate = !UserScrolling && (lastExtent == null || Precision.AlmostBigger(ScrollableExtent, lastExtent.Value)); - if (cancelUserScroll || requiresScrollUpdate) + if (requiresScrollUpdate) { ScheduleAfterChildren(() => { - ScrollToEnd(); - lastExtent = ScrollableExtent; + if (!UserScrolling) + { + ScrollToEnd(); + lastExtent = ScrollableExtent; + } }); } } From 3670bd40c2c8dee50cdbc4ab3a61cf412c603b15 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Feb 2021 15:44:11 +0900 Subject: [PATCH 0277/1791] Add test coverage of user scroll overriding --- .../Online/TestSceneStandAloneChatDisplay.cs | 82 +++++++++++++++---- 1 file changed, 66 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs index e7669262fe..0e1c90f88e 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs @@ -12,10 +12,11 @@ using NUnit.Framework; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Containers; using osu.Game.Overlays.Chat; +using osuTK.Input; namespace osu.Game.Tests.Visual.Online { - public class TestSceneStandAloneChatDisplay : OsuTestScene + public class TestSceneStandAloneChatDisplay : OsuManualInputManagerTestScene { private readonly User admin = new User { @@ -128,7 +129,7 @@ namespace osu.Game.Tests.Visual.Online Timestamp = DateTimeOffset.Now })); - AddUntilStep("ensure still scrolled to bottom", () => chatDisplay.ScrolledToBottom); + checkScrolledToBottom(); const int messages_per_call = 10; AddRepeatStep("add many messages", () => @@ -157,7 +158,7 @@ namespace osu.Game.Tests.Visual.Online return true; }); - AddUntilStep("ensure still scrolled to bottom", () => chatDisplay.ScrolledToBottom); + checkScrolledToBottom(); } /// @@ -165,6 +166,58 @@ namespace osu.Game.Tests.Visual.Online /// [Test] public void TestMessageWrappingKeepsAutoScrolling() + { + fillChat(); + + // send message with short words for text wrapping to occur when contracting chat. + sendMessage(); + + AddStep("contract chat", () => chatDisplay.Width -= 100); + checkScrolledToBottom(); + + AddStep("send another message", () => testChannel.AddNewMessages(new Message(messageIdSequence++) + { + Sender = admin, + Content = "As we were saying...", + })); + + checkScrolledToBottom(); + } + + [Test] + public void TestUserScrollOverride() + { + fillChat(); + + sendMessage(); + checkScrolledToBottom(); + + AddStep("User scroll up", () => + { + InputManager.MoveMouseTo(chatDisplay.ScreenSpaceDrawQuad.Centre); + InputManager.PressButton(MouseButton.Left); + InputManager.MoveMouseTo(chatDisplay.ScreenSpaceDrawQuad.Centre + new Vector2(0, chatDisplay.ScreenSpaceDrawQuad.Height)); + InputManager.ReleaseButton(MouseButton.Left); + }); + + checkNotScrolledToBottom(); + sendMessage(); + checkNotScrolledToBottom(); + + AddRepeatStep("User scroll to bottom", () => + { + InputManager.MoveMouseTo(chatDisplay.ScreenSpaceDrawQuad.Centre); + InputManager.PressButton(MouseButton.Left); + InputManager.MoveMouseTo(chatDisplay.ScreenSpaceDrawQuad.Centre - new Vector2(0, chatDisplay.ScreenSpaceDrawQuad.Height)); + InputManager.ReleaseButton(MouseButton.Left); + }, 5); + + checkScrolledToBottom(); + sendMessage(); + checkScrolledToBottom(); + } + + private void fillChat() { AddStep("fill chat", () => { @@ -178,27 +231,24 @@ namespace osu.Game.Tests.Visual.Online } }); - AddAssert("ensure scrolled to bottom", () => chatDisplay.ScrolledToBottom); + checkScrolledToBottom(); + } - // send message with short words for text wrapping to occur when contracting chat. + private void sendMessage() + { AddStep("send lorem ipsum", () => testChannel.AddNewMessages(new Message(messageIdSequence++) { Sender = longUsernameUser, Content = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce et bibendum velit.", })); - - AddStep("contract chat", () => chatDisplay.Width -= 100); - AddUntilStep("ensure still scrolled to bottom", () => chatDisplay.ScrolledToBottom); - - AddStep("send another message", () => testChannel.AddNewMessages(new Message(messageIdSequence++) - { - Sender = admin, - Content = "As we were saying...", - })); - - AddUntilStep("ensure still scrolled to bottom", () => chatDisplay.ScrolledToBottom); } + private void checkScrolledToBottom() => + AddUntilStep("is scrolled to bottom", () => chatDisplay.ScrolledToBottom); + + private void checkNotScrolledToBottom() => + AddUntilStep("not scrolled to bottom", () => !chatDisplay.ScrolledToBottom); + private class TestStandAloneChatDisplay : StandAloneChatDisplay { public TestStandAloneChatDisplay(bool textbox = false) From b3105fb2920ac3f3c24e144550cbc598bbcf0cf3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Feb 2021 15:46:26 +0900 Subject: [PATCH 0278/1791] Add coverage of local echo messages performing automatic scrolling --- .../Online/TestSceneStandAloneChatDisplay.cs | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs index 0e1c90f88e..ee01eb5f3a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs @@ -217,6 +217,33 @@ namespace osu.Game.Tests.Visual.Online checkScrolledToBottom(); } + [Test] + public void TestLocalEchoMessageResetsScroll() + { + fillChat(); + + sendMessage(); + checkScrolledToBottom(); + + AddStep("User scroll up", () => + { + InputManager.MoveMouseTo(chatDisplay.ScreenSpaceDrawQuad.Centre); + InputManager.PressButton(MouseButton.Left); + InputManager.MoveMouseTo(chatDisplay.ScreenSpaceDrawQuad.Centre + new Vector2(0, chatDisplay.ScreenSpaceDrawQuad.Height)); + InputManager.ReleaseButton(MouseButton.Left); + }); + + checkNotScrolledToBottom(); + sendMessage(); + checkNotScrolledToBottom(); + + sendLocalMessage(); + checkScrolledToBottom(); + + sendMessage(); + checkScrolledToBottom(); + } + private void fillChat() { AddStep("fill chat", () => @@ -243,6 +270,15 @@ namespace osu.Game.Tests.Visual.Online })); } + private void sendLocalMessage() + { + AddStep("send local echo", () => testChannel.AddLocalEcho(new LocalEchoMessage() + { + Sender = longUsernameUser, + Content = "This is a local echo message.", + })); + } + private void checkScrolledToBottom() => AddUntilStep("is scrolled to bottom", () => chatDisplay.ScrolledToBottom); From a76314a8760f98474067decfa23c8ed980def836 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Feb 2021 15:57:17 +0900 Subject: [PATCH 0279/1791] Use Update instead of UpdateAfterChildren (no need for the latter) --- osu.Game/Overlays/Chat/DrawableChannel.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index de3057e9dc..86ce724390 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -257,9 +257,9 @@ namespace osu.Game.Overlays.Chat lastExtent = null; } - protected override void UpdateAfterChildren() + protected override void Update() { - base.UpdateAfterChildren(); + base.Update(); // If the user has scrolled to the bottom of the container, we should resume tracking new content. if (UserScrolling && IsScrolledToEnd(auto_scroll_leniency)) @@ -270,7 +270,8 @@ namespace osu.Game.Overlays.Chat if (requiresScrollUpdate) { - ScheduleAfterChildren(() => + // Schedule required to allow FillFlow to be the correct size. + Schedule(() => { if (!UserScrolling) { From 54c0bdf7d3174ec1b6ee5ba61abf096492d7fa2c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Feb 2021 16:04:42 +0900 Subject: [PATCH 0280/1791] Fix PlaylistLoungeTestScene appearing very narrow --- .../Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs index 008c862cc3..730bbbb397 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs @@ -28,12 +28,7 @@ namespace osu.Game.Tests.Visual.Playlists { base.SetUpSteps(); - AddStep("push screen", () => LoadScreen(loungeScreen = new PlaylistsLoungeSubScreen - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Width = 0.5f, - })); + AddStep("push screen", () => LoadScreen(loungeScreen = new PlaylistsLoungeSubScreen())); AddUntilStep("wait for present", () => loungeScreen.IsCurrentScreen()); } From 3002fef05eebf7c7102ff2e07838347b232d4749 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Feb 2021 16:11:13 +0900 Subject: [PATCH 0281/1791] Remove empty parenthesis --- osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs index ee01eb5f3a..01e67b1681 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs @@ -272,7 +272,7 @@ namespace osu.Game.Tests.Visual.Online private void sendLocalMessage() { - AddStep("send local echo", () => testChannel.AddLocalEcho(new LocalEchoMessage() + AddStep("send local echo", () => testChannel.AddLocalEcho(new LocalEchoMessage { Sender = longUsernameUser, Content = "This is a local echo message.", From 43052991f8120b51b6cce28f68f07f9250b09b47 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Feb 2021 16:18:55 +0900 Subject: [PATCH 0282/1791] Remove unused using statement --- .../Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs index 730bbbb397..618447eae2 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs @@ -4,7 +4,6 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Graphics; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Graphics.Containers; From bdc05af4b7d3fcf43d919c594991f679410ae18d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Feb 2021 16:30:45 +0900 Subject: [PATCH 0283/1791] Make playlist settings area taller to better match screen aspect ratio --- .../OnlinePlay/Playlists/PlaylistsMatchSettingsOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsMatchSettingsOverlay.cs index 01f9920609..ced6d1c5db 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsMatchSettingsOverlay.cs @@ -200,7 +200,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists Child = new GridContainer { RelativeSizeAxes = Axes.X, - Height = 300, + Height = 500, Content = new[] { new Drawable[] From fb52ac8c69792e0101c7f612904cb562b3d1e8c2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Feb 2021 16:57:08 +0900 Subject: [PATCH 0284/1791] Share remove from playlist button design with adjacent download button --- .../Graphics/UserInterface/DownloadButton.cs | 60 +++++++------------ osu.Game/Graphics/UserInterface/GrayButton.cs | 48 +++++++++++++++ .../OnlinePlay/DrawableRoomPlaylistItem.cs | 26 +++++++- 3 files changed, 93 insertions(+), 41 deletions(-) create mode 100644 osu.Game/Graphics/UserInterface/GrayButton.cs diff --git a/osu.Game/Graphics/UserInterface/DownloadButton.cs b/osu.Game/Graphics/UserInterface/DownloadButton.cs index 5168ff646b..7a8db158c1 100644 --- a/osu.Game/Graphics/UserInterface/DownloadButton.cs +++ b/osu.Game/Graphics/UserInterface/DownloadButton.cs @@ -4,54 +4,38 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Game.Online; using osuTK; namespace osu.Game.Graphics.UserInterface { - public class DownloadButton : OsuAnimatedButton + public class DownloadButton : GrayButton { - public readonly Bindable State = new Bindable(); - - private readonly SpriteIcon icon; - private readonly SpriteIcon checkmark; - private readonly Box background; - [Resolved] private OsuColour colours { get; set; } + public readonly Bindable State = new Bindable(); + + private SpriteIcon checkmark; + public DownloadButton() + : base(FontAwesome.Solid.Download) { - Children = new Drawable[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both, - Depth = float.MaxValue - }, - icon = new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(13), - Icon = FontAwesome.Solid.Download, - }, - checkmark = new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - X = 8, - Size = Vector2.Zero, - Icon = FontAwesome.Solid.Check, - } - }; } [BackgroundDependencyLoader] private void load() { + AddInternal(checkmark = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + X = 8, + Size = Vector2.Zero, + Icon = FontAwesome.Solid.Check, + }); + State.BindValueChanged(updateState, true); } @@ -60,27 +44,27 @@ namespace osu.Game.Graphics.UserInterface switch (state.NewValue) { case DownloadState.NotDownloaded: - background.FadeColour(colours.Gray4, 500, Easing.InOutExpo); - icon.MoveToX(0, 500, Easing.InOutExpo); + Background.FadeColour(colours.Gray4, 500, Easing.InOutExpo); + Icon.MoveToX(0, 500, Easing.InOutExpo); checkmark.ScaleTo(Vector2.Zero, 500, Easing.InOutExpo); TooltipText = "Download"; break; case DownloadState.Downloading: - background.FadeColour(colours.Blue, 500, Easing.InOutExpo); - icon.MoveToX(0, 500, Easing.InOutExpo); + Background.FadeColour(colours.Blue, 500, Easing.InOutExpo); + Icon.MoveToX(0, 500, Easing.InOutExpo); checkmark.ScaleTo(Vector2.Zero, 500, Easing.InOutExpo); TooltipText = "Downloading..."; break; case DownloadState.Importing: - background.FadeColour(colours.Yellow, 500, Easing.InOutExpo); + Background.FadeColour(colours.Yellow, 500, Easing.InOutExpo); TooltipText = "Importing"; break; case DownloadState.LocallyAvailable: - background.FadeColour(colours.Green, 500, Easing.InOutExpo); - icon.MoveToX(-8, 500, Easing.InOutExpo); + Background.FadeColour(colours.Green, 500, Easing.InOutExpo); + Icon.MoveToX(-8, 500, Easing.InOutExpo); checkmark.ScaleTo(new Vector2(13), 500, Easing.InOutExpo); break; } diff --git a/osu.Game/Graphics/UserInterface/GrayButton.cs b/osu.Game/Graphics/UserInterface/GrayButton.cs new file mode 100644 index 0000000000..dd05701545 --- /dev/null +++ b/osu.Game/Graphics/UserInterface/GrayButton.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 osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osuTK; + +namespace osu.Game.Graphics.UserInterface +{ + public class GrayButton : OsuAnimatedButton + { + protected SpriteIcon Icon; + protected Box Background; + + private readonly IconUsage icon; + + [Resolved] + private OsuColour colours { get; set; } + + public GrayButton(IconUsage icon) + { + this.icon = icon; + } + + [BackgroundDependencyLoader] + private void load() + { + Children = new Drawable[] + { + Background = new Box + { + Colour = colours.Gray4, + RelativeSizeAxes = Axes.Both, + Depth = float.MaxValue + }, + Icon = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(13), + Icon = icon, + }, + }; + } + } +} diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index f8982582d5..4316a9508e 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -214,7 +214,8 @@ namespace osu.Game.Screens.OnlinePlay Origin = Anchor.CentreRight, Direction = FillDirection.Horizontal, AutoSizeAxes = Axes.Both, - X = -18, + Spacing = new Vector2(5), + X = -10, ChildrenEnumerable = CreateButtons() } } @@ -225,16 +226,35 @@ namespace osu.Game.Screens.OnlinePlay { new PlaylistDownloadButton(Item) { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, Size = new Vector2(50, 30) }, - new IconButton + new PlaylistRemoveButton { - Icon = FontAwesome.Solid.MinusSquare, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(30, 30), Alpha = allowEdit ? 1 : 0, Action = () => RequestDeletion?.Invoke(Model), }, }; + public class PlaylistRemoveButton : GrayButton + { + public PlaylistRemoveButton() + : base(FontAwesome.Solid.MinusSquare) + { + TooltipText = "Remove from playlist"; + } + + [BackgroundDependencyLoader] + private void load() + { + Icon.Scale = new Vector2(0.8f); + } + } + protected override bool OnClick(ClickEvent e) { if (allowSelection) From 6d9ac4d0f01bfefccd38b399ca9158bd4415ce23 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Feb 2021 16:57:27 +0900 Subject: [PATCH 0285/1791] Increase darkness of gradient on buttons to make text readability (slightly) better --- .../TestSceneDrawableRoomPlaylist.cs | 17 ++++++++++++++++- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 14 ++++---------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 874c1694eb..16f6723e2d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -20,6 +20,7 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.OnlinePlay; using osu.Game.Tests.Beatmaps; +using osu.Game.Users; using osuTK; using osuTK.Input; @@ -278,7 +279,21 @@ namespace osu.Game.Tests.Visual.Multiplayer playlist.Items.Add(new PlaylistItem { ID = i, - Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, + Beatmap = + { + Value = i % 2 == 1 + ? new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo + : new BeatmapInfo + { + Metadata = new BeatmapMetadata + { + Artist = "Artist", + Author = new User { Username = "Creator name here" }, + Title = "Long title used to check background colour", + }, + BeatmapSet = new BeatmapSetInfo() + } + }, Ruleset = { Value = new OsuRuleset().RulesetInfo }, RequiredMods = { diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 4316a9508e..844758b262 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -342,20 +342,14 @@ namespace osu.Game.Screens.OnlinePlay new Box { RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientHorizontal(Color4.Black, new Color4(0f, 0f, 0f, 0.9f)), - Width = 0.05f, + Colour = ColourInfo.GradientHorizontal(Color4.Black, new Color4(0f, 0f, 0f, 0.7f)), + Width = 0.4f, }, new Box { RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientHorizontal(new Color4(0f, 0f, 0f, 0.9f), new Color4(0f, 0f, 0f, 0.1f)), - Width = 0.2f, - }, - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientHorizontal(new Color4(0f, 0f, 0f, 0.1f), new Color4(0, 0, 0, 0)), - Width = 0.05f, + Colour = ColourInfo.GradientHorizontal(new Color4(0f, 0f, 0f, 0.7f), new Color4(0, 0, 0, 0.4f)), + Width = 0.4f, }, } } From 40233fb47cba94b9b48eafdf28944036c301e347 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Feb 2021 17:09:59 +0900 Subject: [PATCH 0286/1791] Make font bolder --- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 155 +++++++++--------- 1 file changed, 80 insertions(+), 75 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 844758b262..a7015ba1c4 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -124,102 +124,107 @@ namespace osu.Game.Screens.OnlinePlay modDisplay.Current.Value = requiredMods.ToArray(); } - protected override Drawable CreateContent() => maskingContainer = new Container + protected override Drawable CreateContent() { - RelativeSizeAxes = Axes.X, - Height = 50, - Masking = true, - CornerRadius = 10, - Children = new Drawable[] + Action fontParameters = s => s.Font = OsuFont.Default.With(weight: FontWeight.SemiBold); + + return maskingContainer = new Container { - new Box // A transparent box that forces the border to be drawn if the panel background is opaque + RelativeSizeAxes = Axes.X, + Height = 50, + Masking = true, + CornerRadius = 10, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true - }, - new PanelBackground - { - RelativeSizeAxes = Axes.Both, - Beatmap = { BindTarget = beatmap } - }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = 8 }, - Spacing = new Vector2(8, 0), - Direction = FillDirection.Horizontal, - Children = new Drawable[] + new Box // A transparent box that forces the border to be drawn if the panel background is opaque { - difficultyIconContainer = new Container + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + }, + new PanelBackground + { + RelativeSizeAxes = Axes.Both, + Beatmap = { BindTarget = beatmap } + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = 8 }, + Spacing = new Vector2(8, 0), + Direction = FillDirection.Horizontal, + Children = new Drawable[] { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - }, - new FillFlowContainer - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Children = new Drawable[] + difficultyIconContainer = new Container { - beatmapText = new LinkFlowContainer { AutoSizeAxes = Axes.Both }, - new FillFlowContainer + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + }, + new FillFlowContainer + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(10f, 0), - Children = new Drawable[] + beatmapText = new LinkFlowContainer(fontParameters) { AutoSizeAxes = Axes.Both }, + new FillFlowContainer { - new FillFlowContainer + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10f, 0), + Children = new Drawable[] { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(10f, 0), - Children = new Drawable[] + new FillFlowContainer { - authorText = new LinkFlowContainer { AutoSizeAxes = Axes.Both }, - explicitContentPill = new ExplicitContentBeatmapPill + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10f, 0), + Children = new Drawable[] { - Alpha = 0f, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Margin = new MarginPadding { Top = 3f }, - } + authorText = new LinkFlowContainer(fontParameters) { AutoSizeAxes = Axes.Both }, + explicitContentPill = new ExplicitContentBeatmapPill + { + Alpha = 0f, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding { Top = 3f }, + } + }, }, - }, - new Container - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - Child = modDisplay = new ModDisplay + new Container { - Scale = new Vector2(0.4f), - DisplayUnrankedText = false, - ExpansionMode = ExpansionMode.AlwaysExpanded + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + Child = modDisplay = new ModDisplay + { + Scale = new Vector2(0.4f), + DisplayUnrankedText = false, + ExpansionMode = ExpansionMode.AlwaysExpanded + } } } } } } } + }, + new FillFlowContainer + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(5), + X = -10, + ChildrenEnumerable = CreateButtons() } - }, - new FillFlowContainer - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Direction = FillDirection.Horizontal, - AutoSizeAxes = Axes.Both, - Spacing = new Vector2(5), - X = -10, - ChildrenEnumerable = CreateButtons() } - } - }; + }; + } protected virtual IEnumerable CreateButtons() => new Drawable[] From bc8a4f411159140371596546144fd56e34bc63d2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Feb 2021 17:21:46 +0900 Subject: [PATCH 0287/1791] Update test handling --- .../Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 16f6723e2d..960aad10c6 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -11,8 +11,8 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.UserInterface; using osu.Game.Online.Rooms; using osu.Game.Overlays; using osu.Game.Rulesets; @@ -242,7 +242,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } private void moveToItem(int index, Vector2? offset = null) - => AddStep($"move mouse to item {index}", () => InputManager.MoveMouseTo(playlist.ChildrenOfType>().ElementAt(index), offset)); + => AddStep($"move mouse to item {index}", () => InputManager.MoveMouseTo(playlist.ChildrenOfType().ElementAt(index), offset)); private void moveToDragger(int index, Vector2? offset = null) => AddStep($"move mouse to dragger {index}", () => { @@ -253,7 +253,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private void moveToDeleteButton(int index, Vector2? offset = null) => AddStep($"move mouse to delete button {index}", () => { var item = playlist.ChildrenOfType>().ElementAt(index); - InputManager.MoveMouseTo(item.ChildrenOfType().ElementAt(0), offset); + InputManager.MoveMouseTo(item.ChildrenOfType().ElementAt(0), offset); }); private void assertHandleVisibility(int index, bool visible) @@ -261,7 +261,7 @@ namespace osu.Game.Tests.Visual.Multiplayer () => (playlist.ChildrenOfType.PlaylistItemHandle>().ElementAt(index).Alpha > 0) == visible); private void assertDeleteButtonVisibility(int index, bool visible) - => AddAssert($"delete button {index} {(visible ? "is" : "is not")} visible", () => (playlist.ChildrenOfType().ElementAt(2 + index * 2).Alpha > 0) == visible); + => AddAssert($"delete button {index} {(visible ? "is" : "is not")} visible", () => (playlist.ChildrenOfType().ElementAt(2 + index * 2).Alpha > 0) == visible); private void createPlaylist(bool allowEdit, bool allowSelection) { From 7c29386717cc7f8c96668dc9fb4d970c7b43a17a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 18:01:33 +0900 Subject: [PATCH 0288/1791] Add failing tests --- osu.Game.Tests/Mods/ModUtilsTest.cs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/osu.Game.Tests/Mods/ModUtilsTest.cs b/osu.Game.Tests/Mods/ModUtilsTest.cs index fdb441343a..b602c082bf 100644 --- a/osu.Game.Tests/Mods/ModUtilsTest.cs +++ b/osu.Game.Tests/Mods/ModUtilsTest.cs @@ -47,6 +47,29 @@ namespace osu.Game.Tests.Mods Assert.That(ModUtils.CheckCompatibleSet(new[] { mod1.Object, multiMod }), Is.False); } + [Test] + public void TestCompatibleMods() + { + var mod1 = new Mock(); + var mod2 = new Mock(); + + // Test both orderings. + Assert.That(ModUtils.CheckCompatibleSet(new[] { mod1.Object, mod2.Object }), Is.True); + Assert.That(ModUtils.CheckCompatibleSet(new[] { mod2.Object, mod1.Object }), Is.True); + } + + [Test] + public void TestIncompatibleThroughBaseType() + { + var mod1 = new Mock(); + var mod2 = new Mock(); + mod2.Setup(m => m.IncompatibleMods).Returns(new[] { mod1.Object.GetType().BaseType }); + + // Test both orderings. + Assert.That(ModUtils.CheckCompatibleSet(new[] { mod1.Object, mod2.Object }), Is.False); + Assert.That(ModUtils.CheckCompatibleSet(new[] { mod2.Object, mod1.Object }), Is.False); + } + [Test] public void TestAllowedThroughMostDerivedType() { From 8232d9d2fe667a201aded46f4df1dba44b93ae7e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 18:01:38 +0900 Subject: [PATCH 0289/1791] Fix incorrect implementation --- osu.Game/Utils/ModUtils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs index 808dba2900..34bc0faca4 100644 --- a/osu.Game/Utils/ModUtils.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -56,7 +56,7 @@ namespace osu.Game.Utils // Add the new mod types, checking whether any match the incompatible types. foreach (var t in mod.GetType().EnumerateBaseTypes()) { - if (incomingTypes.Contains(t)) + if (incompatibleTypes.Contains(t)) return false; incomingTypes.Add(t); From d0655c21c6db7e73b006a837b276d2efd0492e27 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 18:18:57 +0900 Subject: [PATCH 0290/1791] Simplify implementation of CheckCompatibleSet --- osu.Game/Utils/ModUtils.cs | 40 ++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs index 34bc0faca4..a9271db1b5 100644 --- a/osu.Game/Utils/ModUtils.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using osu.Framework.Extensions.TypeExtensions; using osu.Game.Rulesets.Mods; #nullable enable @@ -29,7 +28,7 @@ namespace osu.Game.Utils { // Prevent multiple-enumeration. var combinationList = combination as ICollection ?? combination.ToArray(); - return CheckCompatibleSet(combinationList) && CheckAllowed(combinationList, allowedTypes); + return CheckCompatibleSet(combinationList, out _) && CheckAllowed(combinationList, allowedTypes); } /// @@ -38,32 +37,31 @@ namespace osu.Game.Utils /// The combination to check. /// Whether all s in the combination are compatible with each-other. public static bool CheckCompatibleSet(IEnumerable combination) + => CheckCompatibleSet(combination, out _); + + /// + /// Checks that all s in a combination are compatible with each-other. + /// + /// The combination to check. + /// Any invalid mods in the set. + /// Whether all s in the combination are compatible with each-other. + public static bool CheckCompatibleSet(IEnumerable combination, out List? invalidMods) { - var incompatibleTypes = new HashSet(); - var incomingTypes = new HashSet(); + invalidMods = null; - foreach (var mod in combination.SelectMany(FlattenMod)) + foreach (var mod in combination) { - // Add the new mod incompatibilities, checking whether any match the existing mod types. - foreach (var t in mod.IncompatibleMods) + foreach (var type in mod.IncompatibleMods) { - if (incomingTypes.Contains(t)) - return false; - - incompatibleTypes.Add(t); - } - - // Add the new mod types, checking whether any match the incompatible types. - foreach (var t in mod.GetType().EnumerateBaseTypes()) - { - if (incompatibleTypes.Contains(t)) - return false; - - incomingTypes.Add(t); + foreach (var invalid in combination.Where(m => type.IsInstanceOfType(m))) + { + invalidMods ??= new List(); + invalidMods.Add(invalid); + } } } - return true; + return invalidMods == null; } /// From 1df412a03cde9c1ef9363fc1759f8942ea73ec94 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 18:31:08 +0900 Subject: [PATCH 0291/1791] Fix incorrect handling of multi-mod incompatibilities --- osu.Game.Tests/Mods/ModUtilsTest.cs | 25 ++++++++++++++++++++++++- osu.Game/Utils/ModUtils.cs | 1 + 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Mods/ModUtilsTest.cs b/osu.Game.Tests/Mods/ModUtilsTest.cs index b602c082bf..7d3dea7ed5 100644 --- a/osu.Game.Tests/Mods/ModUtilsTest.cs +++ b/osu.Game.Tests/Mods/ModUtilsTest.cs @@ -32,7 +32,7 @@ namespace osu.Game.Tests.Mods } [Test] - public void TestIncompatibleThroughMultiMod() + public void TestMultiModIncompatibleWithTopLevel() { var mod1 = new Mock(); @@ -47,6 +47,21 @@ namespace osu.Game.Tests.Mods Assert.That(ModUtils.CheckCompatibleSet(new[] { mod1.Object, multiMod }), Is.False); } + [Test] + public void TestTopLevelIncompatibleWithMultiMod() + { + // The nested mod. + var mod1 = new Mock(); + var multiMod = new MultiMod(new MultiMod(mod1.Object)); + + var mod2 = new Mock(); + mod2.Setup(m => m.IncompatibleMods).Returns(new[] { typeof(CustomMod1) }); + + // Test both orderings. + Assert.That(ModUtils.CheckCompatibleSet(new Mod[] { multiMod, mod2.Object }), Is.False); + Assert.That(ModUtils.CheckCompatibleSet(new Mod[] { mod2.Object, multiMod }), Is.False); + } + [Test] public void TestCompatibleMods() { @@ -83,5 +98,13 @@ namespace osu.Game.Tests.Mods var mod = new Mock(); Assert.That(ModUtils.CheckAllowed(new[] { mod.Object }, new[] { typeof(Mod) }), Is.False); } + + public abstract class CustomMod1 : Mod + { + } + + public abstract class CustomMod2 : Mod + { + } } } diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs index a9271db1b5..41f7b1b45c 100644 --- a/osu.Game/Utils/ModUtils.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -47,6 +47,7 @@ namespace osu.Game.Utils /// Whether all s in the combination are compatible with each-other. public static bool CheckCompatibleSet(IEnumerable combination, out List? invalidMods) { + combination = FlattenMods(combination); invalidMods = null; foreach (var mod in combination) From 9955e0289869be6a14cb67e762a75e9b49a599b1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 18:33:12 +0900 Subject: [PATCH 0292/1791] Make more tests use the custom mod classes For safety purposes... In implementing the previous tests, I found that using mod.Object.GetType() can lead to bad assertions since the same ModProxy class is used for all mocked classes. --- osu.Game.Tests/Mods/ModUtilsTest.cs | 40 ++++++++++++++--------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/osu.Game.Tests/Mods/ModUtilsTest.cs b/osu.Game.Tests/Mods/ModUtilsTest.cs index 7d3dea7ed5..e4ded602aa 100644 --- a/osu.Game.Tests/Mods/ModUtilsTest.cs +++ b/osu.Game.Tests/Mods/ModUtilsTest.cs @@ -14,37 +14,37 @@ namespace osu.Game.Tests.Mods [Test] public void TestModIsCompatibleByItself() { - var mod = new Mock(); + var mod = new Mock(); Assert.That(ModUtils.CheckCompatibleSet(new[] { mod.Object })); } [Test] public void TestIncompatibleThroughTopLevel() { - var mod1 = new Mock(); - var mod2 = new Mock(); + var mod1 = new Mock(); + var mod2 = new Mock(); mod1.Setup(m => m.IncompatibleMods).Returns(new[] { mod2.Object.GetType() }); // Test both orderings. - Assert.That(ModUtils.CheckCompatibleSet(new[] { mod1.Object, mod2.Object }), Is.False); - Assert.That(ModUtils.CheckCompatibleSet(new[] { mod2.Object, mod1.Object }), Is.False); + Assert.That(ModUtils.CheckCompatibleSet(new Mod[] { mod1.Object, mod2.Object }), Is.False); + Assert.That(ModUtils.CheckCompatibleSet(new Mod[] { mod2.Object, mod1.Object }), Is.False); } [Test] public void TestMultiModIncompatibleWithTopLevel() { - var mod1 = new Mock(); + var mod1 = new Mock(); // The nested mod. - var mod2 = new Mock(); + var mod2 = new Mock(); mod2.Setup(m => m.IncompatibleMods).Returns(new[] { mod1.Object.GetType() }); var multiMod = new MultiMod(new MultiMod(mod2.Object)); // Test both orderings. - Assert.That(ModUtils.CheckCompatibleSet(new[] { multiMod, mod1.Object }), Is.False); - Assert.That(ModUtils.CheckCompatibleSet(new[] { mod1.Object, multiMod }), Is.False); + Assert.That(ModUtils.CheckCompatibleSet(new Mod[] { multiMod, mod1.Object }), Is.False); + Assert.That(ModUtils.CheckCompatibleSet(new Mod[] { mod1.Object, multiMod }), Is.False); } [Test] @@ -65,37 +65,37 @@ namespace osu.Game.Tests.Mods [Test] public void TestCompatibleMods() { - var mod1 = new Mock(); - var mod2 = new Mock(); + var mod1 = new Mock(); + var mod2 = new Mock(); // Test both orderings. - Assert.That(ModUtils.CheckCompatibleSet(new[] { mod1.Object, mod2.Object }), Is.True); - Assert.That(ModUtils.CheckCompatibleSet(new[] { mod2.Object, mod1.Object }), Is.True); + Assert.That(ModUtils.CheckCompatibleSet(new Mod[] { mod1.Object, mod2.Object }), Is.True); + Assert.That(ModUtils.CheckCompatibleSet(new Mod[] { mod2.Object, mod1.Object }), Is.True); } [Test] public void TestIncompatibleThroughBaseType() { - var mod1 = new Mock(); - var mod2 = new Mock(); - mod2.Setup(m => m.IncompatibleMods).Returns(new[] { mod1.Object.GetType().BaseType }); + var mod1 = new Mock(); + var mod2 = new Mock(); + mod2.Setup(m => m.IncompatibleMods).Returns(new[] { typeof(Mod) }); // Test both orderings. - Assert.That(ModUtils.CheckCompatibleSet(new[] { mod1.Object, mod2.Object }), Is.False); - Assert.That(ModUtils.CheckCompatibleSet(new[] { mod2.Object, mod1.Object }), Is.False); + Assert.That(ModUtils.CheckCompatibleSet(new Mod[] { mod1.Object, mod2.Object }), Is.False); + Assert.That(ModUtils.CheckCompatibleSet(new Mod[] { mod2.Object, mod1.Object }), Is.False); } [Test] public void TestAllowedThroughMostDerivedType() { - var mod = new Mock(); + var mod = new Mock(); Assert.That(ModUtils.CheckAllowed(new[] { mod.Object }, new[] { mod.Object.GetType() })); } [Test] public void TestNotAllowedThroughBaseType() { - var mod = new Mock(); + var mod = new Mock(); Assert.That(ModUtils.CheckAllowed(new[] { mod.Object }, new[] { typeof(Mod) }), Is.False); } From 12f52316cd2f198c0d649f41b46ef2975cfbb16d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 18:37:11 +0900 Subject: [PATCH 0293/1791] Prevent multiple enumeration --- osu.Game/Utils/ModUtils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs index 41f7b1b45c..9336add465 100644 --- a/osu.Game/Utils/ModUtils.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -47,7 +47,7 @@ namespace osu.Game.Utils /// Whether all s in the combination are compatible with each-other. public static bool CheckCompatibleSet(IEnumerable combination, out List? invalidMods) { - combination = FlattenMods(combination); + combination = FlattenMods(combination).ToArray(); invalidMods = null; foreach (var mod in combination) From 052cf1abaedec23cf44022aedc3324dacd5a8168 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 18:42:02 +0900 Subject: [PATCH 0294/1791] Reuse existing method --- osu.Game/Utils/ModUtils.cs | 30 ++++++------------------------ 1 file changed, 6 insertions(+), 24 deletions(-) diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs index 05a07f0459..0eb30cbe36 100644 --- a/osu.Game/Utils/ModUtils.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -88,40 +88,22 @@ namespace osu.Game.Utils /// The mods to check. /// Invalid mods, if any were found. Can be null if all mods were valid. /// Whether the input mods were all valid. If false, will contain all invalid entries. - public static bool CheckValidForGameplay(IEnumerable mods, out Mod[]? invalidMods) + public static bool CheckValidForGameplay(IEnumerable mods, out List? invalidMods) { mods = mods.ToArray(); - List? foundInvalid = null; - - void addInvalid(Mod mod) - { - foundInvalid ??= new List(); - foundInvalid.Add(mod); - } + CheckCompatibleSet(mods, out invalidMods); foreach (var mod in mods) { - bool valid = mod.Type != ModType.System - && mod.HasImplementation - && !(mod is MultiMod); - - if (!valid) + if (mod.Type == ModType.System || !mod.HasImplementation || mod is MultiMod) { - // if this mod was found as invalid, we can exclude it before potentially excluding more incompatible types. - addInvalid(mod); - continue; - } - - foreach (var type in mod.IncompatibleMods) - { - foreach (var invalid in mods.Where(m => type.IsInstanceOfType(m))) - addInvalid(invalid); + invalidMods ??= new List(); + invalidMods.Add(mod); } } - invalidMods = foundInvalid?.ToArray(); - return foundInvalid == null; + return invalidMods == null; } /// From 6fdaf025182f29e39243240dff05d85ac6820810 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Feb 2021 17:57:23 +0900 Subject: [PATCH 0295/1791] Hook up room-level max attempts to UI --- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 3 ++ .../PlaylistsMatchSettingsOverlay.cs | 45 +++++++------------ 2 files changed, 18 insertions(+), 30 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index 64792a32f3..b2f3e4a1d9 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -39,6 +39,9 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(typeof(Room))] protected Bindable MaxParticipants { get; private set; } + [Resolved(typeof(Room))] + protected Bindable MaxAttempts { get; private set; } + [Resolved(typeof(Room))] protected Bindable EndDate { get; private set; } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsMatchSettingsOverlay.cs index 01f9920609..bf85ecf13d 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsMatchSettingsOverlay.cs @@ -42,15 +42,13 @@ namespace osu.Game.Screens.OnlinePlay.Playlists public Action EditPlaylist; - public OsuTextBox NameField, MaxParticipantsField; + public OsuTextBox NameField, MaxParticipantsField, MaxAttemptsField; public OsuDropdown DurationField; public RoomAvailabilityPicker AvailabilityPicker; - public GameTypePicker TypePicker; public TriangleButton ApplyButton; public OsuSpriteText ErrorText; - private OsuSpriteText typeLabel; private LoadingLayer loadingLayer; private DrawableRoomPlaylist playlist; private OsuSpriteText playlistLength; @@ -134,6 +132,15 @@ namespace osu.Game.Screens.OnlinePlay.Playlists } } }, + new Section("Allowed attempts (across all playlist items)") + { + Child = MaxAttemptsField = new SettingsNumberTextBox + { + RelativeSizeAxes = Axes.X, + TabbableContentContainer = this, + PlaceholderText = "Unlimited", + }, + }, new Section("Room visibility") { Alpha = disabled_alpha, @@ -142,30 +149,6 @@ namespace osu.Game.Screens.OnlinePlay.Playlists Enabled = { Value = false } }, }, - new Section("Game type") - { - Alpha = disabled_alpha, - Child = new FillFlowContainer - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical, - Spacing = new Vector2(7), - Children = new Drawable[] - { - TypePicker = new GameTypePicker - { - RelativeSizeAxes = Axes.X, - Enabled = { Value = false } - }, - typeLabel = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 14), - Colour = colours.Yellow - }, - }, - }, - }, new Section("Max participants") { Alpha = disabled_alpha, @@ -294,10 +277,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists loadingLayer = new LoadingLayer(true) }; - TypePicker.Current.BindValueChanged(type => typeLabel.Text = type.NewValue?.Name ?? string.Empty, true); RoomName.BindValueChanged(name => NameField.Text = name.NewValue, true); Availability.BindValueChanged(availability => AvailabilityPicker.Current.Value = availability.NewValue, true); - Type.BindValueChanged(type => TypePicker.Current.Value = type.NewValue, true); MaxParticipants.BindValueChanged(count => MaxParticipantsField.Text = count.NewValue?.ToString(), true); Duration.BindValueChanged(duration => DurationField.Current.Value = duration.NewValue ?? TimeSpan.FromMinutes(30), true); @@ -326,13 +307,17 @@ namespace osu.Game.Screens.OnlinePlay.Playlists RoomName.Value = NameField.Text; Availability.Value = AvailabilityPicker.Current.Value; - Type.Value = TypePicker.Current.Value; if (int.TryParse(MaxParticipantsField.Text, out int max)) MaxParticipants.Value = max; else MaxParticipants.Value = null; + if (int.TryParse(MaxAttemptsField.Text, out max)) + MaxAttempts.Value = max; + else + MaxAttempts.Value = null; + Duration.Value = DurationField.Current.Value; manager?.CreateRoom(currentRoom.Value, onSuccess, onError); From 90acdd4361550d251ede8fc93db8edda2c5a38d2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Feb 2021 18:06:52 +0900 Subject: [PATCH 0296/1791] Display the correct error message on score submission failure The server will return a valid error message in most cases here. We may eventually want to add some fallback message for cases an error may occur that isn't of [InvariantException](https://github.com/ppy/osu-web/blob/3169b33ccc4c540be5f20136393ad5f00d635ff9/app/Exceptions/InvariantException.php#L9-L10) type, but I'm not 100% sure how to identify these just yet. --- 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 2c3e7a12e2..7936ab8ecd 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs @@ -65,7 +65,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { failed = true; - Logger.Error(e, "Failed to retrieve a score submission token.\n\nThis may happen if you are running an old or non-official release of osu! (ie. you are self-compiling)."); + Logger.Log($"You are not able to submit a score: {e.Message}", LoggingTarget.Information, LogLevel.Important); Schedule(() => { From 96d20bf6072aa181b6fb5662ddc32024be2d61bf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Feb 2021 18:24:51 +0900 Subject: [PATCH 0297/1791] Reduce height of ModeTypeInfo to match adjacent text sections --- osu.Game/Screens/OnlinePlay/Components/ModeTypeInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Components/ModeTypeInfo.cs b/osu.Game/Screens/OnlinePlay/Components/ModeTypeInfo.cs index 03b27b605c..2026106c42 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ModeTypeInfo.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ModeTypeInfo.cs @@ -12,7 +12,7 @@ namespace osu.Game.Screens.OnlinePlay.Components { public class ModeTypeInfo : OnlinePlayComposite { - private const float height = 30; + private const float height = 28; private const float transition_duration = 100; private Container drawableRuleset; From 9b209d67dc83568751994c1af8a0713f19339979 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Feb 2021 18:44:13 +0900 Subject: [PATCH 0298/1791] Match size of participants text with host display --- osu.Game/Screens/OnlinePlay/Lounge/Components/ParticipantInfo.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/ParticipantInfo.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/ParticipantInfo.cs index 0d5ce65d5a..bc4506b78e 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/ParticipantInfo.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/ParticipantInfo.cs @@ -63,7 +63,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components summary = new OsuSpriteText { Text = "0 participants", - Font = OsuFont.GetFont(size: 14) } }, }, From fc3adaf6123e9f697b85d7c8fa566e111f1eb05b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Feb 2021 18:44:35 +0900 Subject: [PATCH 0299/1791] Show maximum attempt count in room display (when not unlimited) --- .../Components/RoomLocalUserInfo.cs | 50 +++++++++++++++++++ .../OnlinePlay/Lounge/Components/RoomInfo.cs | 39 ++++++--------- 2 files changed, 66 insertions(+), 23 deletions(-) create mode 100644 osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs diff --git a/osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs b/osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs new file mode 100644 index 0000000000..f52e59b0c8 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs @@ -0,0 +1,50 @@ +// 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.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Screens.OnlinePlay.Components +{ + public class RoomLocalUserInfo : OnlinePlayComposite + { + private OsuSpriteText attemptDisplay; + + public RoomLocalUserInfo() + { + AutoSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChild = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + attemptDisplay = new OsuSpriteText + { + Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 14) + }, + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + MaxAttempts.BindValueChanged(attempts => + { + attemptDisplay.Text = attempts.NewValue == null + ? string.Empty + : $"Maximum attempts: {attempts.NewValue:N0}"; + }, true); + } + } +} diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomInfo.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomInfo.cs index 0a17702f2a..a0a7f2dc28 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomInfo.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomInfo.cs @@ -20,41 +20,34 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { AutoSizeAxes = Axes.Y; + RoomLocalUserInfo localUserInfo; RoomStatusInfo statusInfo; ModeTypeInfo typeInfo; ParticipantInfo participantInfo; InternalChild = new FillFlowContainer { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, RelativeSizeAxes = Axes.X, + Spacing = new Vector2(0, 10), AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 4), Children = new Drawable[] { + roomName = new OsuTextFlowContainer(t => t.Font = OsuFont.GetFont(size: 30)) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }, + participantInfo = new ParticipantInfo(), new Container { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Children = new Drawable[] { - new FillFlowContainer - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - roomName = new OsuTextFlowContainer(t => t.Font = OsuFont.GetFont(size: 30)) - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - }, - statusInfo = new RoomStatusInfo(), - } - }, + statusInfo = new RoomStatusInfo(), typeInfo = new ModeTypeInfo { Anchor = Anchor.BottomRight, @@ -62,20 +55,21 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components } } }, - participantInfo = new ParticipantInfo(), + localUserInfo = new RoomLocalUserInfo(), } }; - statusElements.AddRange(new Drawable[] { statusInfo, typeInfo, participantInfo }); + statusElements.AddRange(new Drawable[] + { + statusInfo, typeInfo, participantInfo, localUserInfo + }); } protected override void LoadComplete() { base.LoadComplete(); - if (RoomID.Value == null) statusElements.ForEach(e => e.FadeOut()); - RoomID.BindValueChanged(id => { if (id.NewValue == null) @@ -83,7 +77,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components else statusElements.ForEach(e => e.FadeIn(100)); }, true); - RoomName.BindValueChanged(name => { roomName.Text = name.NewValue ?? "No room selected"; From 0a9861d0abe19879a3d9772c248df7ed2b0c2e74 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 18:51:13 +0900 Subject: [PATCH 0300/1791] Use TestCaseSource and add multi-mod test --- osu.Game.Tests/Mods/ModUtilsTest.cs | 47 +++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Mods/ModUtilsTest.cs b/osu.Game.Tests/Mods/ModUtilsTest.cs index ff1af88bac..fbdb1e2f3d 100644 --- a/osu.Game.Tests/Mods/ModUtilsTest.cs +++ b/osu.Game.Tests/Mods/ModUtilsTest.cs @@ -103,20 +103,43 @@ namespace osu.Game.Tests.Mods Assert.That(ModUtils.CheckAllowed(new[] { mod.Object }, new[] { typeof(Mod) }), Is.False); } - // test incompatible pair. - [TestCase(new[] { typeof(OsuModDoubleTime), typeof(OsuModHalfTime) }, new[] { typeof(OsuModDoubleTime), typeof(OsuModHalfTime) })] - // test incompatible pair with derived class. - [TestCase(new[] { typeof(OsuModNightcore), typeof(OsuModHalfTime) }, new[] { typeof(OsuModNightcore), typeof(OsuModHalfTime) })] - // test system mod. - [TestCase(new[] { typeof(OsuModDoubleTime), typeof(OsuModTouchDevice) }, new[] { typeof(OsuModTouchDevice) })] - // test valid. - [TestCase(new[] { typeof(OsuModDoubleTime), typeof(OsuModHardRock) }, null)] - public void TestInvalidModScenarios(Type[] input, Type[] expectedInvalid) + private static readonly object[] invalid_mod_test_scenarios = { - List inputMods = new List(); - foreach (var t in input) - inputMods.Add((Mod)Activator.CreateInstance(t)); + // incompatible pair. + new object[] + { + new Mod[] { new OsuModDoubleTime(), new OsuModHalfTime() }, + new[] { typeof(OsuModDoubleTime), typeof(OsuModHalfTime) } + }, + // incompatible pair with derived class. + new object[] + { + new Mod[] { new OsuModNightcore(), new OsuModHalfTime() }, + new[] { typeof(OsuModNightcore), typeof(OsuModHalfTime) } + }, + // system mod. + new object[] + { + new Mod[] { new OsuModDoubleTime(), new OsuModTouchDevice() }, + new[] { typeof(OsuModTouchDevice) } + }, + // multi mod. + new object[] + { + new Mod[] { new MultiMod(new OsuModHalfTime()), new OsuModHalfTime() }, + new[] { typeof(MultiMod) } + }, + // valid pair. + new object[] + { + new Mod[] { new OsuModDoubleTime(), new OsuModHardRock() }, + null + } + }; + [TestCaseSource(nameof(invalid_mod_test_scenarios))] + public void TestInvalidModScenarios(Mod[] inputMods, Type[] expectedInvalid) + { bool isValid = ModUtils.CheckValidForGameplay(inputMods, out var invalid); Assert.That(isValid, Is.EqualTo(expectedInvalid == null)); From 180af3c7f8311d0a4477ecb79e7f7403da9dc85a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 19:02:09 +0900 Subject: [PATCH 0301/1791] Add codeanalysis attribute --- osu.Game/Utils/ModUtils.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs index 9336add465..8ac5bde65a 100644 --- a/osu.Game/Utils/ModUtils.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using osu.Game.Rulesets.Mods; @@ -45,7 +46,7 @@ namespace osu.Game.Utils /// The combination to check. /// Any invalid mods in the set. /// Whether all s in the combination are compatible with each-other. - public static bool CheckCompatibleSet(IEnumerable combination, out List? invalidMods) + public static bool CheckCompatibleSet(IEnumerable combination, [NotNullWhen(false)] out List? invalidMods) { combination = FlattenMods(combination).ToArray(); invalidMods = null; From a2e3b1c0e454e81c6836b44ca6f29426040b6ac1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 18:56:57 +0900 Subject: [PATCH 0302/1791] Move Mods reset code to OnlinePlaySongSelect --- .../Multiplayer/MultiplayerMatchSongSelect.cs | 9 --------- osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs | 11 ++++++++++- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index f7f0402555..84e8849726 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Logging; using osu.Framework.Screens; @@ -27,13 +25,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer AddInternal(loadingLayer = new LoadingLayer(true)); } - protected override void LoadComplete() - { - base.LoadComplete(); - - Mods.Value = Playlist.FirstOrDefault()?.RequiredMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty(); - } - protected override void SelectItem(PlaylistItem item) { // If the client is already in a room, update via the client. diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs index c58632c500..1c345b883f 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs @@ -58,8 +58,17 @@ namespace osu.Game.Screens.OnlinePlay initialRuleset = Ruleset.Value; initialMods = Mods.Value.ToList(); - freeMods.Value = Playlist.FirstOrDefault()?.AllowedMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty(); FooterPanels.Add(freeModSelectOverlay); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + // At this point, Mods contains both the required and allowed mods. For selection purposes, it should only contain the required mods. + // Similarly, freeMods is currently empty but should only contain the allowed mods. + Mods.Value = Playlist.FirstOrDefault()?.RequiredMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty(); + freeMods.Value = Playlist.FirstOrDefault()?.AllowedMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty(); Ruleset.BindValueChanged(onRulesetChanged); } From 41593ff09e4ef0ac405cbd0fdcdba0beff9a29b5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Feb 2021 19:14:44 +0900 Subject: [PATCH 0303/1791] Privatise protected property setters --- osu.Game/Graphics/UserInterface/GrayButton.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/GrayButton.cs b/osu.Game/Graphics/UserInterface/GrayButton.cs index dd05701545..88c46f29e0 100644 --- a/osu.Game/Graphics/UserInterface/GrayButton.cs +++ b/osu.Game/Graphics/UserInterface/GrayButton.cs @@ -11,8 +11,8 @@ namespace osu.Game.Graphics.UserInterface { public class GrayButton : OsuAnimatedButton { - protected SpriteIcon Icon; - protected Box Background; + protected SpriteIcon Icon { get; private set; } + protected Box Background { get; private set; } private readonly IconUsage icon; From 8e70a50af0836af6fb821c69a7239c7cad9e1eec Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Feb 2021 19:22:13 +0900 Subject: [PATCH 0304/1791] Remove unused using statement --- osu.Game.Tests/Mods/ModUtilsTest.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Mods/ModUtilsTest.cs b/osu.Game.Tests/Mods/ModUtilsTest.cs index fbdb1e2f3d..7dcaabca3d 100644 --- a/osu.Game.Tests/Mods/ModUtilsTest.cs +++ b/osu.Game.Tests/Mods/ModUtilsTest.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.Linq; using Moq; using NUnit.Framework; From 2dece12a7c6041fb8ff9e5a4896c77b78865de3a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 19:57:42 +0900 Subject: [PATCH 0305/1791] Disable/disallow freemods on incompatible/selected mods --- osu.Game/Overlays/Mods/ModSection.cs | 6 +++--- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 11 ++++++++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index 993f4ef9d7..728c726b82 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -130,13 +130,13 @@ namespace osu.Game.Overlays.Mods /// Updates all buttons with the given list of selected mods. /// /// The new list of selected mods to select. - public void UpdateSelectedMods(IReadOnlyList newSelectedMods) + public void UpdateSelectedButtons(IReadOnlyList newSelectedMods) { foreach (var button in buttons) - updateButtonMods(button, newSelectedMods); + updateButtonSelection(button, newSelectedMods); } - private void updateButtonMods(ModButton button, IReadOnlyList newSelectedMods) + private void updateButtonSelection(ModButton button, IReadOnlyList newSelectedMods) { foreach (var mod in newSelectedMods) { diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index b20c2d9d19..56d6008b00 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -377,7 +377,7 @@ namespace osu.Game.Overlays.Mods base.LoadComplete(); availableMods.BindValueChanged(_ => updateAvailableMods(), true); - SelectedMods.BindValueChanged(selectedModsChanged, true); + SelectedMods.BindValueChanged(_ => updateSelectedButtons(), true); } protected override void PopOut() @@ -445,6 +445,8 @@ namespace osu.Game.Overlays.Mods section.Mods = modEnumeration.Select(validModOrNull).Where(m => m != null); } + + updateSelectedButtons(); } /// @@ -465,10 +467,13 @@ namespace osu.Game.Overlays.Mods return validSubset.Length == 0 ? null : new MultiMod(validSubset); } - private void selectedModsChanged(ValueChangedEvent> mods) + private void updateSelectedButtons() { + // Enumeration below may update the bindable list. + var selectedMods = SelectedMods.Value.ToList(); + foreach (var section in ModSectionsContainer.Children) - section.UpdateSelectedMods(mods.NewValue); + section.UpdateSelectedButtons(selectedMods); updateMods(); } From 3741f05ab335d5baeb755bd4e370310698c1fe7f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 20:11:40 +0900 Subject: [PATCH 0306/1791] Refactor mod sections and make them overridable --- osu.Game/Overlays/Mods/ModSection.cs | 39 +++++++++---------- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 38 ++++++++++++++---- .../Mods/Sections/AutomationSection.cs | 19 --------- .../Mods/Sections/ConversionSection.cs | 19 --------- .../Sections/DifficultyIncreaseSection.cs | 19 --------- .../Sections/DifficultyReductionSection.cs | 19 --------- osu.Game/Overlays/Mods/Sections/FunSection.cs | 19 --------- 7 files changed, 50 insertions(+), 122 deletions(-) delete mode 100644 osu.Game/Overlays/Mods/Sections/AutomationSection.cs delete mode 100644 osu.Game/Overlays/Mods/Sections/ConversionSection.cs delete mode 100644 osu.Game/Overlays/Mods/Sections/DifficultyIncreaseSection.cs delete mode 100644 osu.Game/Overlays/Mods/Sections/DifficultyReductionSection.cs delete mode 100644 osu.Game/Overlays/Mods/Sections/FunSection.cs diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index 573d1e5355..89a3e2f5cd 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -11,26 +11,23 @@ using System; using System.Linq; using System.Collections.Generic; using System.Threading; +using Humanizer; using osu.Framework.Input.Events; using osu.Game.Graphics; namespace osu.Game.Overlays.Mods { - public abstract class ModSection : Container + public class ModSection : CompositeDrawable { - private readonly OsuSpriteText headerLabel; + private readonly Drawable header; public FillFlowContainer ButtonsContainer { get; } public Action Action; - protected abstract Key[] ToggleKeys { get; } - public abstract ModType ModType { get; } - public string Header - { - get => headerLabel.Text; - set => headerLabel.Text = value; - } + public Key[] ToggleKeys; + + public readonly ModType ModType; public IEnumerable SelectedMods => buttons.Select(b => b.SelectedMod).Where(m => m != null); @@ -61,7 +58,7 @@ namespace osu.Game.Overlays.Mods if (modContainers.Length == 0) { ModIconsLoaded = true; - headerLabel.Hide(); + header.Hide(); Hide(); return; } @@ -76,7 +73,7 @@ namespace osu.Game.Overlays.Mods buttons = modContainers.OfType().ToArray(); - headerLabel.FadeIn(200); + header.FadeIn(200); this.FadeIn(200); } } @@ -153,23 +150,19 @@ namespace osu.Game.Overlays.Mods button.Deselect(); } - protected ModSection() + public ModSection(ModType type) { + ModType = type; + AutoSizeAxes = Axes.Y; RelativeSizeAxes = Axes.X; Origin = Anchor.TopCentre; Anchor = Anchor.TopCentre; - Children = new Drawable[] + InternalChildren = new[] { - headerLabel = new OsuSpriteText - { - Origin = Anchor.TopLeft, - Anchor = Anchor.TopLeft, - Position = new Vector2(0f, 0f), - Font = OsuFont.GetFont(weight: FontWeight.Bold) - }, + header = CreateHeader(type.Humanize(LetterCasing.Title)), ButtonsContainer = new FillFlowContainer { AutoSizeAxes = Axes.Y, @@ -185,5 +178,11 @@ namespace osu.Game.Overlays.Mods }, }; } + + protected virtual Drawable CreateHeader(string text) => new OsuSpriteText + { + Font = OsuFont.GetFont(weight: FontWeight.Bold), + Text = text + }; } } diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 1258ba719d..fd6f771f16 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -19,7 +19,6 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; -using osu.Game.Overlays.Mods.Sections; using osu.Game.Rulesets.Mods; using osu.Game.Screens; using osuTK; @@ -190,13 +189,31 @@ namespace osu.Game.Overlays.Mods Width = content_width, LayoutDuration = 200, LayoutEasing = Easing.OutQuint, - Children = new ModSection[] + Children = new[] { - new DifficultyReductionSection { Action = modButtonPressed }, - new DifficultyIncreaseSection { Action = modButtonPressed }, - new AutomationSection { Action = modButtonPressed }, - new ConversionSection { Action = modButtonPressed }, - new FunSection { Action = modButtonPressed }, + CreateModSection(ModType.DifficultyReduction).With(s => + { + s.ToggleKeys = new[] { Key.Q, Key.W, Key.E, Key.R, Key.T, Key.Y, Key.U, Key.I, Key.O, Key.P }; + s.Action = modButtonPressed; + }), + CreateModSection(ModType.DifficultyIncrease).With(s => + { + s.ToggleKeys = new[] { Key.A, Key.S, Key.D, Key.F, Key.G, Key.H, Key.J, Key.K, Key.L }; + s.Action = modButtonPressed; + }), + CreateModSection(ModType.Automation).With(s => + { + s.ToggleKeys = new[] { Key.Z, Key.X, Key.C, Key.V, Key.B, Key.N, Key.M }; + s.Action = modButtonPressed; + }), + CreateModSection(ModType.Conversion).With(s => + { + s.Action = modButtonPressed; + }), + CreateModSection(ModType.Fun).With(s => + { + s.Action = modButtonPressed; + }), } }, } @@ -454,6 +471,13 @@ namespace osu.Game.Overlays.Mods private void refreshSelectedMods() => SelectedMods.Value = ModSectionsContainer.Children.SelectMany(s => s.SelectedMods).ToArray(); + /// + /// Creates a that groups s with the same . + /// + /// The of s in the section. + /// The . + protected virtual ModSection CreateModSection(ModType type) => new ModSection(type); + #region Disposal protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Overlays/Mods/Sections/AutomationSection.cs b/osu.Game/Overlays/Mods/Sections/AutomationSection.cs deleted file mode 100644 index a2d7fec15f..0000000000 --- a/osu.Game/Overlays/Mods/Sections/AutomationSection.cs +++ /dev/null @@ -1,19 +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.Game.Rulesets.Mods; -using osuTK.Input; - -namespace osu.Game.Overlays.Mods.Sections -{ - public class AutomationSection : ModSection - { - protected override Key[] ToggleKeys => new[] { Key.Z, Key.X, Key.C, Key.V, Key.B, Key.N, Key.M }; - public override ModType ModType => ModType.Automation; - - public AutomationSection() - { - Header = @"Automation"; - } - } -} diff --git a/osu.Game/Overlays/Mods/Sections/ConversionSection.cs b/osu.Game/Overlays/Mods/Sections/ConversionSection.cs deleted file mode 100644 index 24fd8c30dd..0000000000 --- a/osu.Game/Overlays/Mods/Sections/ConversionSection.cs +++ /dev/null @@ -1,19 +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.Game.Rulesets.Mods; -using osuTK.Input; - -namespace osu.Game.Overlays.Mods.Sections -{ - public class ConversionSection : ModSection - { - protected override Key[] ToggleKeys => null; - public override ModType ModType => ModType.Conversion; - - public ConversionSection() - { - Header = @"Conversion"; - } - } -} diff --git a/osu.Game/Overlays/Mods/Sections/DifficultyIncreaseSection.cs b/osu.Game/Overlays/Mods/Sections/DifficultyIncreaseSection.cs deleted file mode 100644 index 0b7ccd1f25..0000000000 --- a/osu.Game/Overlays/Mods/Sections/DifficultyIncreaseSection.cs +++ /dev/null @@ -1,19 +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.Game.Rulesets.Mods; -using osuTK.Input; - -namespace osu.Game.Overlays.Mods.Sections -{ - public class DifficultyIncreaseSection : ModSection - { - protected override Key[] ToggleKeys => new[] { Key.A, Key.S, Key.D, Key.F, Key.G, Key.H, Key.J, Key.K, Key.L }; - public override ModType ModType => ModType.DifficultyIncrease; - - public DifficultyIncreaseSection() - { - Header = @"Difficulty Increase"; - } - } -} diff --git a/osu.Game/Overlays/Mods/Sections/DifficultyReductionSection.cs b/osu.Game/Overlays/Mods/Sections/DifficultyReductionSection.cs deleted file mode 100644 index 508e92508b..0000000000 --- a/osu.Game/Overlays/Mods/Sections/DifficultyReductionSection.cs +++ /dev/null @@ -1,19 +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.Game.Rulesets.Mods; -using osuTK.Input; - -namespace osu.Game.Overlays.Mods.Sections -{ - public class DifficultyReductionSection : ModSection - { - protected override Key[] ToggleKeys => new[] { Key.Q, Key.W, Key.E, Key.R, Key.T, Key.Y, Key.U, Key.I, Key.O, Key.P }; - public override ModType ModType => ModType.DifficultyReduction; - - public DifficultyReductionSection() - { - Header = @"Difficulty Reduction"; - } - } -} diff --git a/osu.Game/Overlays/Mods/Sections/FunSection.cs b/osu.Game/Overlays/Mods/Sections/FunSection.cs deleted file mode 100644 index af1f5836b1..0000000000 --- a/osu.Game/Overlays/Mods/Sections/FunSection.cs +++ /dev/null @@ -1,19 +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.Game.Rulesets.Mods; -using osuTK.Input; - -namespace osu.Game.Overlays.Mods.Sections -{ - public class FunSection : ModSection - { - protected override Key[] ToggleKeys => null; - public override ModType ModType => ModType.Fun; - - public FunSection() - { - Header = @"Fun"; - } - } -} From 6d620264f48369f807a79983da19bf2ff37773e9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 20:27:41 +0900 Subject: [PATCH 0307/1791] Allow mod buttons to not be stacked --- .../TestSceneModSelectOverlay.cs | 62 ++++++++++++------- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 12 +++- 2 files changed, 51 insertions(+), 23 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index bd4010a7f3..71c549b433 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -38,28 +38,7 @@ namespace osu.Game.Tests.Visual.UserInterface } [SetUp] - public void SetUp() => Schedule(() => - { - SelectedMods.Value = Array.Empty(); - Children = new Drawable[] - { - modSelect = new TestModSelectOverlay - { - Origin = Anchor.BottomCentre, - Anchor = Anchor.BottomCentre, - SelectedMods = { BindTarget = SelectedMods } - }, - - modDisplay = new ModDisplay - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - AutoSizeAxes = Axes.Both, - Position = new Vector2(-5, 25), - Current = { BindTarget = modSelect.SelectedMods } - } - }; - }); + public void SetUp() => Schedule(() => createDisplay(() => new TestModSelectOverlay())); [SetUpSteps] public void SetUpSteps() @@ -146,6 +125,18 @@ namespace osu.Game.Tests.Visual.UserInterface }); } + [Test] + public void TestNonStacked() + { + changeRuleset(0); + + AddStep("create overlay", () => createDisplay(() => new TestNonStackedModSelectOverlay())); + + AddStep("show", () => modSelect.Show()); + + AddAssert("ensure all buttons are spread out", () => modSelect.ChildrenOfType().All(m => m.Mods.Length <= 1)); + } + private void testSingleMod(Mod mod) { selectNext(mod); @@ -265,6 +256,28 @@ namespace osu.Game.Tests.Visual.UserInterface private void checkLabelColor(Func getColour) => AddAssert("check label has expected colour", () => modSelect.MultiplierLabel.Colour.AverageColour == getColour()); + private void createDisplay(Func createOverlayFunc) + { + SelectedMods.Value = Array.Empty(); + Children = new Drawable[] + { + modSelect = createOverlayFunc().With(d => + { + d.Origin = Anchor.BottomCentre; + d.Anchor = Anchor.BottomCentre; + d.SelectedMods.BindTarget = SelectedMods; + }), + modDisplay = new ModDisplay + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.Both, + Position = new Vector2(-5, 25), + Current = { BindTarget = modSelect.SelectedMods } + } + }; + } + private class TestModSelectOverlay : ModSelectOverlay { public new Bindable> SelectedMods => base.SelectedMods; @@ -283,5 +296,10 @@ namespace osu.Game.Tests.Visual.UserInterface public new Color4 LowMultiplierColour => base.LowMultiplierColour; public new Color4 HighMultiplierColour => base.HighMultiplierColour; } + + private class TestNonStackedModSelectOverlay : TestModSelectOverlay + { + protected override bool Stacked => false; + } } } diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 1258ba719d..c7e856028a 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -22,6 +22,7 @@ using osu.Game.Input.Bindings; using osu.Game.Overlays.Mods.Sections; using osu.Game.Rulesets.Mods; using osu.Game.Screens; +using osu.Game.Utils; using osuTK; using osuTK.Graphics; using osuTK.Input; @@ -43,6 +44,11 @@ namespace osu.Game.Overlays.Mods protected override bool DimMainContent => false; + /// + /// Whether s underneath the same instance should appear as stacked buttons. + /// + protected virtual bool Stacked => true; + protected readonly FillFlowContainer ModSectionsContainer; protected readonly ModSettingsContainer ModSettingsContainer; @@ -405,7 +411,11 @@ namespace osu.Game.Overlays.Mods if (mods.NewValue == null) return; foreach (var section in ModSectionsContainer.Children) - section.Mods = mods.NewValue[section.ModType].Where(isValidMod); + { + section.Mods = Stacked + ? availableMods.Value[section.ModType] + : ModUtils.FlattenMods(availableMods.Value[section.ModType]); + } } private void selectedModsChanged(ValueChangedEvent> mods) From 75f81bfa062c9199759cd8257a10e64e543eb8ed Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 20:35:31 +0900 Subject: [PATCH 0308/1791] Add back mod validation --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index c7e856028a..775b0de1c0 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -412,9 +412,12 @@ namespace osu.Game.Overlays.Mods foreach (var section in ModSectionsContainer.Children) { - section.Mods = Stacked - ? availableMods.Value[section.ModType] - : ModUtils.FlattenMods(availableMods.Value[section.ModType]); + IEnumerable modEnumeration = availableMods.Value[section.ModType]; + + if (!Stacked) + modEnumeration = ModUtils.FlattenMods(modEnumeration); + + section.Mods = modEnumeration.Where(isValidMod); } } From 10ceddf3ffcf861f71aee5f4a681441b15913226 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 20:47:50 +0900 Subject: [PATCH 0309/1791] Make IsValidMod adjustable --- .../TestSceneModSelectOverlay.cs | 16 ++++++ osu.Game/Overlays/Mods/ModSelectOverlay.cs | 54 ++++++++++++++++--- .../Multiplayer/MultiplayerMatchSongSelect.cs | 2 +- osu.Game/Screens/Select/MatchSongSelect.cs | 2 +- 4 files changed, 64 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 71c549b433..9cf8b95ddf 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -137,6 +137,22 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("ensure all buttons are spread out", () => modSelect.ChildrenOfType().All(m => m.Mods.Length <= 1)); } + [Test] + public void TestChangeIsValidChangesButtonVisibility() + { + changeRuleset(0); + + AddAssert("double time visible", () => modSelect.ChildrenOfType().Any(b => b.Mods.Any(m => m is OsuModDoubleTime))); + + AddStep("make double time invalid", () => modSelect.IsValidMod = m => !(m is OsuModDoubleTime)); + AddAssert("double time not visible", () => modSelect.ChildrenOfType().All(b => !b.Mods.Any(m => m is OsuModDoubleTime))); + AddAssert("nightcore still visible", () => modSelect.ChildrenOfType().Any(b => b.Mods.Any(m => m is OsuModNightcore))); + + AddStep("make double time valid again", () => modSelect.IsValidMod = m => true); + AddAssert("double time visible", () => modSelect.ChildrenOfType().Any(b => b.Mods.Any(m => m is OsuModDoubleTime))); + AddAssert("nightcore still visible", () => modSelect.ChildrenOfType().Any(b => b.Mods.Any(m => m is OsuModNightcore))); + } + private void testSingleMod(Mod mod) { selectNext(mod); diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 775b0de1c0..61e4b45495 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -31,7 +32,6 @@ namespace osu.Game.Overlays.Mods { public class ModSelectOverlay : WaveOverlayContainer { - private readonly Func isValidMod; public const float HEIGHT = 510; protected readonly TriangleButton DeselectAllButton; @@ -49,6 +49,23 @@ namespace osu.Game.Overlays.Mods /// protected virtual bool Stacked => true; + [NotNull] + private Func isValidMod = m => true; + + /// + /// A function that checks whether a given mod is selectable. + /// + [NotNull] + public Func IsValidMod + { + get => isValidMod; + set + { + isValidMod = value ?? throw new ArgumentNullException(nameof(value)); + updateAvailableMods(); + } + } + protected readonly FillFlowContainer ModSectionsContainer; protected readonly ModSettingsContainer ModSettingsContainer; @@ -67,10 +84,8 @@ namespace osu.Game.Overlays.Mods private SampleChannel sampleOn, sampleOff; - public ModSelectOverlay(Func isValidMod = null) + public ModSelectOverlay() { - this.isValidMod = isValidMod ?? (m => true); - Waves.FirstWaveColour = Color4Extensions.FromHex(@"19b0e2"); Waves.SecondWaveColour = Color4Extensions.FromHex(@"2280a2"); Waves.ThirdWaveColour = Color4Extensions.FromHex(@"005774"); @@ -351,7 +366,7 @@ namespace osu.Game.Overlays.Mods { base.LoadComplete(); - availableMods.BindValueChanged(availableModsChanged, true); + availableMods.BindValueChanged(_ => updateAvailableMods(), true); SelectedMods.BindValueChanged(selectedModsChanged, true); } @@ -406,9 +421,10 @@ namespace osu.Game.Overlays.Mods public override bool OnPressed(GlobalAction action) => false; // handled by back button - private void availableModsChanged(ValueChangedEvent>> mods) + private void updateAvailableMods() { - if (mods.NewValue == null) return; + if (availableMods?.Value == null) + return; foreach (var section in ModSectionsContainer.Children) { @@ -417,10 +433,32 @@ namespace osu.Game.Overlays.Mods if (!Stacked) modEnumeration = ModUtils.FlattenMods(modEnumeration); - section.Mods = modEnumeration.Where(isValidMod); + section.Mods = modEnumeration.Select(getValidModOrNull).Where(m => m != null); } } + /// + /// Returns a valid form of a given if possible, or null otherwise. + /// + /// + /// This is a recursive process during which any invalid mods are culled while preserving structures where possible. + /// + /// The to check. + /// A valid form of if exists, or null otherwise. + [CanBeNull] + private Mod getValidModOrNull([NotNull] Mod mod) + { + if (!(mod is MultiMod multi)) + return IsValidMod(mod) ? mod : null; + + var validSubset = multi.Mods.Select(getValidModOrNull).Where(m => m != null).ToArray(); + + if (validSubset.Length == 0) + return null; + + return validSubset.Length == 1 ? validSubset[0] : new MultiMod(validSubset); + } + private void selectedModsChanged(ValueChangedEvent> mods) { foreach (var section in ModSectionsContainer.Children) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index ebc06d2445..5bf9b1ee7e 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -111,7 +111,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea(); - protected override ModSelectOverlay CreateModSelectOverlay() => new ModSelectOverlay(isValidMod); + protected override ModSelectOverlay CreateModSelectOverlay() => new ModSelectOverlay { IsValidMod = isValidMod }; private bool isValidMod(Mod mod) => !(mod is ModAutoplay) && (mod as MultiMod)?.Mods.Any(mm => mm is ModAutoplay) != true; } diff --git a/osu.Game/Screens/Select/MatchSongSelect.cs b/osu.Game/Screens/Select/MatchSongSelect.cs index ed47b5d5ac..1bb7374ce3 100644 --- a/osu.Game/Screens/Select/MatchSongSelect.cs +++ b/osu.Game/Screens/Select/MatchSongSelect.cs @@ -81,7 +81,7 @@ namespace osu.Game.Screens.Select item.RequiredMods.AddRange(Mods.Value.Select(m => m.CreateCopy())); } - protected override ModSelectOverlay CreateModSelectOverlay() => new ModSelectOverlay(isValidMod); + protected override ModSelectOverlay CreateModSelectOverlay() => new ModSelectOverlay { IsValidMod = isValidMod }; private bool isValidMod(Mod mod) => !(mod is ModAutoplay) && (mod as MultiMod)?.Mods.Any(mm => mm is ModAutoplay) != true; } From 50e92bd0ed729a2d4aa551bea13a66f37de6035c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 20:50:54 +0900 Subject: [PATCH 0310/1791] Fix selection not being preserved when IsValidMod changes --- .../UserInterface/TestSceneModSelectOverlay.cs | 12 ++++++++++++ osu.Game/Overlays/Mods/ModSection.cs | 6 +++--- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 11 ++++++++--- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 9cf8b95ddf..81edcd8db8 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -153,6 +153,18 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("nightcore still visible", () => modSelect.ChildrenOfType().Any(b => b.Mods.Any(m => m is OsuModNightcore))); } + [Test] + public void TestChangeIsValidPreservesSelection() + { + changeRuleset(0); + + AddStep("select DT + HD", () => SelectedMods.Value = new Mod[] { new OsuModDoubleTime(), new OsuModHidden() }); + AddAssert("DT + HD selected", () => modSelect.ChildrenOfType().Count(b => b.Selected) == 2); + + AddStep("make NF invalid", () => modSelect.IsValidMod = m => !(m is ModNoFail)); + AddAssert("DT + HD still selected", () => modSelect.ChildrenOfType().Count(b => b.Selected) == 2); + } + private void testSingleMod(Mod mod) { selectNext(mod); diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index 573d1e5355..4c629aef54 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -130,13 +130,13 @@ namespace osu.Game.Overlays.Mods /// Updates all buttons with the given list of selected mods. /// /// The new list of selected mods to select. - public void UpdateSelectedMods(IReadOnlyList newSelectedMods) + public void UpdateSelectedButtons(IReadOnlyList newSelectedMods) { foreach (var button in buttons) - updateButtonMods(button, newSelectedMods); + updateButtonSelection(button, newSelectedMods); } - private void updateButtonMods(ModButton button, IReadOnlyList newSelectedMods) + private void updateButtonSelection(ModButton button, IReadOnlyList newSelectedMods) { foreach (var mod in newSelectedMods) { diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 61e4b45495..fcec6f3926 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -367,7 +367,7 @@ namespace osu.Game.Overlays.Mods base.LoadComplete(); availableMods.BindValueChanged(_ => updateAvailableMods(), true); - SelectedMods.BindValueChanged(selectedModsChanged, true); + SelectedMods.BindValueChanged(_ => updateSelectedButtons(), true); } protected override void PopOut() @@ -435,6 +435,8 @@ namespace osu.Game.Overlays.Mods section.Mods = modEnumeration.Select(getValidModOrNull).Where(m => m != null); } + + updateSelectedButtons(); } /// @@ -459,10 +461,13 @@ namespace osu.Game.Overlays.Mods return validSubset.Length == 1 ? validSubset[0] : new MultiMod(validSubset); } - private void selectedModsChanged(ValueChangedEvent> mods) + private void updateSelectedButtons() { + // Enumeration below may update the bindable list. + var selectedMods = SelectedMods.Value.ToList(); + foreach (var section in ModSectionsContainer.Children) - section.UpdateSelectedMods(mods.NewValue); + section.UpdateSelectedButtons(selectedMods); updateMods(); } From e58ece9e108b757a72aaff635090ad00b245aa5a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 20:58:31 +0900 Subject: [PATCH 0311/1791] Make ModSelectOverlay abstract --- .../Visual/UserInterface/TestSceneModSelectOverlay.cs | 2 +- .../Visual/UserInterface/TestSceneModSettings.cs | 2 +- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 4 ++-- osu.Game/Overlays/Mods/SoloModSelectOverlay.cs | 9 +++++++++ .../OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs | 2 +- osu.Game/Screens/Select/MatchSongSelect.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 2 +- 7 files changed, 16 insertions(+), 7 deletions(-) create mode 100644 osu.Game/Overlays/Mods/SoloModSelectOverlay.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 81edcd8db8..92104cfc72 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -306,7 +306,7 @@ namespace osu.Game.Tests.Visual.UserInterface }; } - private class TestModSelectOverlay : ModSelectOverlay + private class TestModSelectOverlay : SoloModSelectOverlay { public new Bindable> SelectedMods => base.SelectedMods; diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs index 8614700b15..3c889bdec4 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs @@ -151,7 +151,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddUntilStep("wait for ready", () => modSelect.State.Value == Visibility.Visible && modSelect.ButtonsLoaded); } - private class TestModSelectOverlay : ModSelectOverlay + private class TestModSelectOverlay : SoloModSelectOverlay { public new VisibilityContainer ModSettingsContainer => base.ModSettingsContainer; public new TriangleButton CustomiseButton => base.CustomiseButton; diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index fcec6f3926..75ad90f065 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -30,7 +30,7 @@ using osuTK.Input; namespace osu.Game.Overlays.Mods { - public class ModSelectOverlay : WaveOverlayContainer + public abstract class ModSelectOverlay : WaveOverlayContainer { public const float HEIGHT = 510; @@ -84,7 +84,7 @@ namespace osu.Game.Overlays.Mods private SampleChannel sampleOn, sampleOff; - public ModSelectOverlay() + protected ModSelectOverlay() { Waves.FirstWaveColour = Color4Extensions.FromHex(@"19b0e2"); Waves.SecondWaveColour = Color4Extensions.FromHex(@"2280a2"); diff --git a/osu.Game/Overlays/Mods/SoloModSelectOverlay.cs b/osu.Game/Overlays/Mods/SoloModSelectOverlay.cs new file mode 100644 index 0000000000..8f6819d7ff --- /dev/null +++ b/osu.Game/Overlays/Mods/SoloModSelectOverlay.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.Overlays.Mods +{ + public class SoloModSelectOverlay : ModSelectOverlay + { + } +} diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index 5bf9b1ee7e..930f70d087 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -111,7 +111,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea(); - protected override ModSelectOverlay CreateModSelectOverlay() => new ModSelectOverlay { IsValidMod = isValidMod }; + protected override ModSelectOverlay CreateModSelectOverlay() => new SoloModSelectOverlay { IsValidMod = isValidMod }; private bool isValidMod(Mod mod) => !(mod is ModAutoplay) && (mod as MultiMod)?.Mods.Any(mm => mm is ModAutoplay) != true; } diff --git a/osu.Game/Screens/Select/MatchSongSelect.cs b/osu.Game/Screens/Select/MatchSongSelect.cs index 1bb7374ce3..e181370cf7 100644 --- a/osu.Game/Screens/Select/MatchSongSelect.cs +++ b/osu.Game/Screens/Select/MatchSongSelect.cs @@ -81,7 +81,7 @@ namespace osu.Game.Screens.Select item.RequiredMods.AddRange(Mods.Value.Select(m => m.CreateCopy())); } - protected override ModSelectOverlay CreateModSelectOverlay() => new ModSelectOverlay { IsValidMod = isValidMod }; + protected override ModSelectOverlay CreateModSelectOverlay() => new SoloModSelectOverlay { IsValidMod = isValidMod }; private bool isValidMod(Mod mod) => !(mod is ModAutoplay) && (mod as MultiMod)?.Mods.Any(mm => mm is ModAutoplay) != true; } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 4fca77a176..ff49dd9f7e 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -301,7 +301,7 @@ namespace osu.Game.Screens.Select } } - protected virtual ModSelectOverlay CreateModSelectOverlay() => new ModSelectOverlay(); + protected virtual ModSelectOverlay CreateModSelectOverlay() => new SoloModSelectOverlay(); protected virtual void ApplyFilterToCarousel(FilterCriteria criteria) { From 728f8599b2bedbc721481c2bafb017a130177d25 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 21:06:32 +0900 Subject: [PATCH 0312/1791] Move incompatible mod deselection to SoloModOverlay --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 23 ++++++++----------- .../Overlays/Mods/SoloModSelectOverlay.cs | 9 ++++++++ 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 75ad90f065..c400e4cc43 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -349,19 +349,6 @@ namespace osu.Game.Overlays.Mods refreshSelectedMods(); } - /// - /// Deselect one or more mods. - /// - /// The types of s which should be deselected. - /// Set to true to bypass animations and update selections immediately. - private void deselectTypes(Type[] modTypes, bool immediate = false) - { - if (modTypes.Length == 0) return; - - foreach (var section in ModSectionsContainer.Children) - section.DeselectTypes(modTypes, immediate); - } - protected override void LoadComplete() { base.LoadComplete(); @@ -496,7 +483,7 @@ namespace osu.Game.Overlays.Mods { if (State.Value == Visibility.Visible) sampleOn?.Play(); - deselectTypes(selectedMod.IncompatibleMods, true); + OnModSelected(selectedMod); if (selectedMod.RequiresConfiguration) ModSettingsContainer.Show(); } @@ -508,6 +495,14 @@ namespace osu.Game.Overlays.Mods refreshSelectedMods(); } + /// + /// Invoked when a new has been selected. + /// + /// The that has been selected. + protected virtual void OnModSelected(Mod mod) + { + } + private void refreshSelectedMods() => SelectedMods.Value = ModSectionsContainer.Children.SelectMany(s => s.SelectedMods).ToArray(); #region Disposal diff --git a/osu.Game/Overlays/Mods/SoloModSelectOverlay.cs b/osu.Game/Overlays/Mods/SoloModSelectOverlay.cs index 8f6819d7ff..d039ad1f98 100644 --- a/osu.Game/Overlays/Mods/SoloModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/SoloModSelectOverlay.cs @@ -1,9 +1,18 @@ // 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.Mods; + namespace osu.Game.Overlays.Mods { public class SoloModSelectOverlay : ModSelectOverlay { + protected override void OnModSelected(Mod mod) + { + base.OnModSelected(mod); + + foreach (var section in ModSectionsContainer.Children) + section.DeselectTypes(mod.IncompatibleMods, true); + } } } From 643c0605d85cf7808394c4b2fe3ea4f39b62906b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 21:14:38 +0900 Subject: [PATCH 0313/1791] Implement the freemod selection overlay --- .../TestSceneFreeModSelectOverlay.cs | 21 +++ osu.Game/Overlays/Mods/ModSection.cs | 15 ++- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 19 +-- .../OnlinePlay/FreeModSelectOverlay.cs | 120 ++++++++++++++++++ 4 files changed, 165 insertions(+), 10 deletions(-) create mode 100644 osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs create mode 100644 osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs new file mode 100644 index 0000000000..26a0301d8a --- /dev/null +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs @@ -0,0 +1,21 @@ +// 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.Containers; +using osu.Game.Screens.OnlinePlay; + +namespace osu.Game.Tests.Visual.Multiplayer +{ + public class TestSceneFreeModSelectOverlay : MultiplayerTestScene + { + [SetUp] + public new void Setup() => Schedule(() => + { + Child = new FreeModSelectOverlay + { + State = { Value = Visibility.Visible } + }; + }); + } +} diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index b3ddd30772..87a45ebf63 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -94,7 +94,20 @@ namespace osu.Game.Overlays.Mods return base.OnKeyDown(e); } - public void DeselectAll() => DeselectTypes(buttons.Select(b => b.SelectedMod?.GetType()).Where(t => t != null)); + /// + /// Selects all mods. + /// + public void SelectAll() + { + foreach (var button in buttons.Where(b => !b.Selected)) + button.SelectAt(0); + } + + /// + /// Deselects all mods. + /// + /// Set to true to bypass animations and update selections immediately. + public void DeselectAll(bool immediate = false) => DeselectTypes(buttons.Select(b => b.SelectedMod?.GetType()).Where(t => t != null), immediate); /// /// Deselect one or more mods in this section. diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index e064a6fb84..8225c1b6bb 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -37,8 +37,11 @@ namespace osu.Game.Overlays.Mods protected readonly TriangleButton CustomiseButton; protected readonly TriangleButton CloseButton; + protected readonly Drawable MultiplierSection; protected readonly OsuSpriteText MultiplierLabel; + protected readonly FillFlowContainer FooterContainer; + protected override bool BlockNonPositionalInput => false; protected override bool DimMainContent => false; @@ -79,8 +82,6 @@ namespace osu.Game.Overlays.Mods private const float content_width = 0.8f; private const float footer_button_spacing = 20; - private readonly FillFlowContainer footerContainer; - private SampleChannel sampleOn, sampleOff; protected ModSelectOverlay() @@ -269,7 +270,7 @@ namespace osu.Game.Overlays.Mods Colour = new Color4(172, 20, 116, 255), Alpha = 0.5f, }, - footerContainer = new FillFlowContainer + FooterContainer = new FillFlowContainer { Origin = Anchor.BottomCentre, Anchor = Anchor.BottomCentre, @@ -283,7 +284,7 @@ namespace osu.Game.Overlays.Mods Vertical = 15, Horizontal = OsuScreen.HORIZONTAL_OVERFLOW_PADDING }, - Children = new Drawable[] + Children = new[] { DeselectAllButton = new TriangleButton { @@ -310,7 +311,7 @@ namespace osu.Game.Overlays.Mods Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, }, - new FillFlowContainer + MultiplierSection = new FillFlowContainer { AutoSizeAxes = Axes.Both, Spacing = new Vector2(footer_button_spacing / 2, 0), @@ -378,8 +379,8 @@ namespace osu.Game.Overlays.Mods { base.PopOut(); - footerContainer.MoveToX(content_width, WaveContainer.DISAPPEAR_DURATION, Easing.InSine); - footerContainer.FadeOut(WaveContainer.DISAPPEAR_DURATION, Easing.InSine); + FooterContainer.MoveToX(content_width, WaveContainer.DISAPPEAR_DURATION, Easing.InSine); + FooterContainer.FadeOut(WaveContainer.DISAPPEAR_DURATION, Easing.InSine); foreach (var section in ModSectionsContainer.Children) { @@ -393,8 +394,8 @@ namespace osu.Game.Overlays.Mods { base.PopIn(); - footerContainer.MoveToX(0, WaveContainer.APPEAR_DURATION, Easing.OutQuint); - footerContainer.FadeIn(WaveContainer.APPEAR_DURATION, Easing.OutQuint); + FooterContainer.MoveToX(0, WaveContainer.APPEAR_DURATION, Easing.OutQuint); + FooterContainer.FadeIn(WaveContainer.APPEAR_DURATION, Easing.OutQuint); foreach (var section in ModSectionsContainer.Children) { diff --git a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs new file mode 100644 index 0000000000..628199309a --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs @@ -0,0 +1,120 @@ +// 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.Graphics.UserInterface; +using osu.Game.Overlays.Mods; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Screens.OnlinePlay +{ + /// + /// A used for free-mod selection in online play. + /// + public class FreeModSelectOverlay : ModSelectOverlay + { + protected override bool Stacked => false; + + public FreeModSelectOverlay() + { + CustomiseButton.Alpha = 0; + MultiplierSection.Alpha = 0; + DeselectAllButton.Alpha = 0; + + Drawable selectAllButton; + Drawable deselectAllButton; + + FooterContainer.AddRange(new[] + { + selectAllButton = new TriangleButton + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Width = 180, + Text = "Select All", + Action = selectAll, + }, + // Unlike the base mod select overlay, this button deselects mods instantaneously. + deselectAllButton = new TriangleButton + { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Width = 180, + Text = "Deselect All", + Action = deselectAll, + }, + }); + + FooterContainer.SetLayoutPosition(selectAllButton, -2); + FooterContainer.SetLayoutPosition(deselectAllButton, -1); + } + + private void selectAll() + { + foreach (var section in ModSectionsContainer.Children) + section.SelectAll(); + } + + private void deselectAll() + { + foreach (var section in ModSectionsContainer.Children) + section.DeselectAll(true); + } + + protected override ModSection CreateModSection(ModType type) => new FreeModSection(type); + + private class FreeModSection : ModSection + { + private HeaderCheckbox checkbox; + + public FreeModSection(ModType type) + : base(type) + { + } + + protected override Drawable CreateHeader(string text) => new Container + { + AutoSizeAxes = Axes.Y, + Width = 175, + Child = checkbox = new HeaderCheckbox + { + LabelText = text, + Changed = onCheckboxChanged + } + }; + + private void onCheckboxChanged(bool value) + { + foreach (var button in ButtonsContainer.OfType()) + { + if (value) + button.SelectAt(0); + else + button.Deselect(); + } + } + + protected override void Update() + { + base.Update(); + + var validButtons = ButtonsContainer.OfType().Where(b => b.Mod.HasImplementation); + checkbox.Current.Value = validButtons.All(b => b.Selected); + } + } + + private class HeaderCheckbox : OsuCheckbox + { + public Action Changed; + + protected override void OnUserChange(bool value) + { + base.OnUserChange(value); + Changed?.Invoke(value); + } + } + } +} From f25535548ae1910215fff01186cac34285cdac81 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 21:20:16 +0900 Subject: [PATCH 0314/1791] Fix buzzing on select all/deselect all --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 8225c1b6bb..c308dc2451 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -14,6 +14,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using osu.Framework.Threading; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Containers; @@ -495,11 +496,17 @@ namespace osu.Game.Overlays.Mods MultiplierLabel.FadeColour(Color4.White, 200); } + private ScheduledDelegate sampleOnDelegate; + private ScheduledDelegate sampleOffDelegate; + private void modButtonPressed(Mod selectedMod) { if (selectedMod != null) { - if (State.Value == Visibility.Visible) sampleOn?.Play(); + // Fixes buzzing when multiple mods are selected in the same frame. + sampleOnDelegate?.Cancel(); + if (State.Value == Visibility.Visible) + sampleOnDelegate = Scheduler.Add(() => sampleOn?.Play()); OnModSelected(selectedMod); @@ -507,7 +514,10 @@ namespace osu.Game.Overlays.Mods } else { - if (State.Value == Visibility.Visible) sampleOff?.Play(); + // Fixes buzzing when multiple mods are deselected in the same frame. + sampleOffDelegate?.Cancel(); + if (State.Value == Visibility.Visible) + sampleOffDelegate = Scheduler.Add(() => sampleOff?.Play()); } refreshSelectedMods(); From 5a56e2ba4b66ae8058157a3b32935f154d0f0c02 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 21:29:00 +0900 Subject: [PATCH 0315/1791] Fix sound duplication due to checkbox --- osu.Game/Graphics/UserInterface/OsuCheckbox.cs | 17 +++++++++++++---- .../Screens/OnlinePlay/FreeModSelectOverlay.cs | 2 ++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuCheckbox.cs b/osu.Game/Graphics/UserInterface/OsuCheckbox.cs index 6593531099..517f83daa9 100644 --- a/osu.Game/Graphics/UserInterface/OsuCheckbox.cs +++ b/osu.Game/Graphics/UserInterface/OsuCheckbox.cs @@ -18,6 +18,11 @@ namespace osu.Game.Graphics.UserInterface public Color4 UncheckedColor { get; set; } = Color4.White; public int FadeDuration { get; set; } + /// + /// Whether to play sounds when the state changes as a result of user interaction. + /// + protected virtual bool PlaySoundsOnUserChange => true; + public string LabelText { set @@ -96,10 +101,14 @@ namespace osu.Game.Graphics.UserInterface protected override void OnUserChange(bool value) { base.OnUserChange(value); - if (value) - sampleChecked?.Play(); - else - sampleUnchecked?.Play(); + + if (PlaySoundsOnUserChange) + { + if (value) + sampleChecked?.Play(); + else + sampleUnchecked?.Play(); + } } } } diff --git a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs index 628199309a..608e58b534 100644 --- a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs @@ -110,6 +110,8 @@ namespace osu.Game.Screens.OnlinePlay { public Action Changed; + protected override bool PlaySoundsOnUserChange => false; + protected override void OnUserChange(bool value) { base.OnUserChange(value); From 6ff8e8dd37c3a79ea7b6c122f596272708cd6e58 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 21:29:08 +0900 Subject: [PATCH 0316/1791] Disable a few mods by default --- osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs index 608e58b534..5b9a19897f 100644 --- a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs @@ -18,8 +18,16 @@ namespace osu.Game.Screens.OnlinePlay { protected override bool Stacked => false; + public new Func IsValidMod + { + get => base.IsValidMod; + set => base.IsValidMod = m => m.HasImplementation && !m.RequiresConfiguration && !(m is ModAutoplay) && value(m); + } + public FreeModSelectOverlay() { + IsValidMod = m => true; + CustomiseButton.Alpha = 0; MultiplierSection.Alpha = 0; DeselectAllButton.Alpha = 0; From 921f008217719bb6768b54c7746eef2d7d0f0ba7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 21:35:08 +0900 Subject: [PATCH 0317/1791] Fix ModIcon not updating background colour correctly --- .../Visual/UserInterface/TestSceneModIcon.cs | 21 +++++++++ osu.Game/Rulesets/UI/ModIcon.cs | 46 +++++++++++-------- 2 files changed, 47 insertions(+), 20 deletions(-) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneModIcon.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModIcon.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModIcon.cs new file mode 100644 index 0000000000..e7fa7d9235 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModIcon.cs @@ -0,0 +1,21 @@ +// 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.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneModIcon : OsuTestScene + { + [Test] + public void TestChangeModType() + { + ModIcon icon = null; + + AddStep("create mod icon", () => Child = icon = new ModIcon(new OsuModDoubleTime())); + AddStep("change mod", () => icon.Mod = new OsuModEasy()); + } + } +} diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index 04a2e052fa..cae5da3d16 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -29,8 +29,6 @@ namespace osu.Game.Rulesets.UI private const float size = 80; - private readonly ModType type; - public virtual string TooltipText => showTooltip ? mod.IconTooltip : null; private Mod mod; @@ -42,10 +40,18 @@ namespace osu.Game.Rulesets.UI set { mod = value; - updateMod(value); + + if (IsLoaded) + updateMod(value); } } + [Resolved] + private OsuColour colours { get; set; } + + private Color4 backgroundColour; + private Color4 highlightedColour; + /// /// Construct a new instance. /// @@ -56,8 +62,6 @@ namespace osu.Game.Rulesets.UI this.mod = mod ?? throw new ArgumentNullException(nameof(mod)); this.showTooltip = showTooltip; - type = mod.Type; - Size = new Vector2(size); Children = new Drawable[] @@ -89,6 +93,13 @@ namespace osu.Game.Rulesets.UI Icon = FontAwesome.Solid.Question }, }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Selected.BindValueChanged(_ => updateColour()); updateMod(mod); } @@ -102,20 +113,14 @@ namespace osu.Game.Rulesets.UI { modIcon.FadeOut(); modAcronym.FadeIn(); - return; + } + else + { + modIcon.FadeIn(); + modAcronym.FadeOut(); } - modIcon.FadeIn(); - modAcronym.FadeOut(); - } - - private Color4 backgroundColour; - private Color4 highlightedColour; - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - switch (type) + switch (value.Type) { default: case ModType.DifficultyIncrease: @@ -149,12 +154,13 @@ namespace osu.Game.Rulesets.UI modIcon.Colour = colours.Yellow; break; } + + updateColour(); } - protected override void LoadComplete() + private void updateColour() { - base.LoadComplete(); - Selected.BindValueChanged(selected => background.Colour = selected.NewValue ? highlightedColour : backgroundColour, true); + background.Colour = Selected.Value ? highlightedColour : backgroundColour; } } } From aeb3ed8bb3bb12253eb2b4cf2092634e3c4c62ac Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Feb 2021 21:46:22 +0900 Subject: [PATCH 0318/1791] Renamespace footer button --- osu.Game/Screens/OnlinePlay/{Match => }/FooterButtonFreeMods.cs | 2 +- osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) rename osu.Game/Screens/OnlinePlay/{Match => }/FooterButtonFreeMods.cs (97%) diff --git a/osu.Game/Screens/OnlinePlay/Match/FooterButtonFreeMods.cs b/osu.Game/Screens/OnlinePlay/FooterButtonFreeMods.cs similarity index 97% rename from osu.Game/Screens/OnlinePlay/Match/FooterButtonFreeMods.cs rename to osu.Game/Screens/OnlinePlay/FooterButtonFreeMods.cs index ca2db877c3..a3cc383b67 100644 --- a/osu.Game/Screens/OnlinePlay/Match/FooterButtonFreeMods.cs +++ b/osu.Game/Screens/OnlinePlay/FooterButtonFreeMods.cs @@ -13,7 +13,7 @@ using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Select; using osuTK; -namespace osu.Game.Screens.OnlinePlay.Match +namespace osu.Game.Screens.OnlinePlay { public class FooterButtonFreeMods : FooterButton, IHasCurrentValue> { diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs index 1c345b883f..0baa663578 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs @@ -15,7 +15,6 @@ using osu.Game.Online.Rooms; using osu.Game.Overlays.Mods; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; -using osu.Game.Screens.OnlinePlay.Match; using osu.Game.Screens.Select; using osu.Game.Utils; From 8e96ffd1e6ea4d848766c72e703503de6b23cffd Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 2 Feb 2021 16:54:40 +0300 Subject: [PATCH 0319/1791] Fix "wait for import" until step potentially finishing early If not obvious, the issue with previous code is that it was checking for `IsAvailableLocally`, while the import is happening on a different thread, so that method could return `true` before the importing has finished and `ItemUpdated` event is called. --- .../TestSceneMultiplayerBeatmapAvailabilityTracker.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapAvailabilityTracker.cs index 3c3793670a..646c4139af 100644 --- a/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapAvailabilityTracker.cs @@ -89,7 +89,7 @@ namespace osu.Game.Tests.Online addAvailabilityCheckStep("state importing", BeatmapAvailability.Importing); AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true)); - AddUntilStep("wait for import", () => beatmaps.IsAvailableLocally(testBeatmapSet)); + AddUntilStep("wait for import", () => beatmaps.CurrentImportTask?.IsCompleted == true); addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable); } @@ -127,8 +127,6 @@ namespace osu.Game.Tests.Online private void addAvailabilityCheckStep(string description, Func expected) { - // In DownloadTrackingComposite, state changes are scheduled one frame later, wait one step. - AddWaitStep("wait for potential change", 1); AddAssert(description, () => availablilityTracker.Availability.Value.Equals(expected.Invoke())); } @@ -157,6 +155,8 @@ namespace osu.Game.Tests.Online { public TaskCompletionSource AllowImport = new TaskCompletionSource(); + public Task CurrentImportTask { get; private set; } + protected override ArchiveDownloadRequest CreateDownloadRequest(BeatmapSetInfo set, bool minimiseDownloadSize) => new TestDownloadRequest(set); @@ -168,7 +168,7 @@ namespace osu.Game.Tests.Online public override async Task Import(BeatmapSetInfo item, ArchiveReader archive = null, CancellationToken cancellationToken = default) { await AllowImport.Task; - return await base.Import(item, archive, cancellationToken); + return await (CurrentImportTask = base.Import(item, archive, cancellationToken)); } } From 50d57a39317c32d59e30006679a030bedfbdb10b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 2 Feb 2021 17:07:16 +0300 Subject: [PATCH 0320/1791] Move tracker loading into BDL --- osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index f2cb1120cb..cdf889e4f1 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -41,11 +41,11 @@ namespace osu.Game.Screens.OnlinePlay.Match private IBindable> managerUpdated; [Cached] - protected readonly MultiplayerBeatmapAvailablilityTracker BeatmapAvailablilityTracker; + protected MultiplayerBeatmapAvailablilityTracker BeatmapAvailablilityTracker { get; } protected RoomSubScreen() { - InternalChild = BeatmapAvailablilityTracker = new MultiplayerBeatmapAvailablilityTracker + BeatmapAvailablilityTracker = new MultiplayerBeatmapAvailablilityTracker { SelectedItem = { BindTarget = SelectedItem }, }; @@ -54,6 +54,8 @@ namespace osu.Game.Screens.OnlinePlay.Match [BackgroundDependencyLoader] private void load(AudioManager audio) { + AddInternal(BeatmapAvailablilityTracker); + sampleStart = audio.Samples.Get(@"SongSelect/confirm-selection"); } From 62d0036c819c0516005f9b5b3938d359e0cac987 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 2 Feb 2021 17:45:07 +0300 Subject: [PATCH 0321/1791] Fix using private constructor on MessagePack object --- osu.Game/Online/Rooms/BeatmapAvailability.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Rooms/BeatmapAvailability.cs b/osu.Game/Online/Rooms/BeatmapAvailability.cs index 2adeb9b959..a83327aad5 100644 --- a/osu.Game/Online/Rooms/BeatmapAvailability.cs +++ b/osu.Game/Online/Rooms/BeatmapAvailability.cs @@ -26,7 +26,7 @@ namespace osu.Game.Online.Rooms public readonly float? DownloadProgress; [JsonConstructor] - private BeatmapAvailability(DownloadState state, float? downloadProgress = null) + public BeatmapAvailability(DownloadState state, float? downloadProgress = null) { State = state; DownloadProgress = downloadProgress; From 181d2c672b5c93c1dfb84929b78a413dfe326db3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 2 Feb 2021 22:05:25 +0100 Subject: [PATCH 0322/1791] Fix outdated comment --- osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index a7015ba1c4..2d438bd96e 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -343,7 +343,7 @@ namespace osu.Game.Screens.OnlinePlay Colour = Color4.Black, Width = 0.4f, }, - // Piecewise-linear gradient with 3 segments to make it appear smoother + // Piecewise-linear gradient with 2 segments to make it appear smoother new Box { RelativeSizeAxes = Axes.Both, From fc84ec131347ee220c9c9df63561b3e6a099156f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 2 Feb 2021 22:18:14 +0100 Subject: [PATCH 0323/1791] Move anchor specification to central place --- .../Screens/OnlinePlay/DrawableRoomPlaylistItem.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 2d438bd96e..23c713a2c1 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -220,7 +220,11 @@ namespace osu.Game.Screens.OnlinePlay AutoSizeAxes = Axes.Both, Spacing = new Vector2(5), X = -10, - ChildrenEnumerable = CreateButtons() + ChildrenEnumerable = CreateButtons().Select(button => button.With(b => + { + b.Anchor = Anchor.Centre; + b.Origin = Anchor.Centre; + })) } } }; @@ -231,14 +235,10 @@ namespace osu.Game.Screens.OnlinePlay { new PlaylistDownloadButton(Item) { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, Size = new Vector2(50, 30) }, new PlaylistRemoveButton { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, Size = new Vector2(30, 30), Alpha = allowEdit ? 1 : 0, Action = () => RequestDeletion?.Invoke(Model), From 21d5f842fccb799ad83ae47ca868b4b3a9827cbb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 3 Feb 2021 14:52:36 +0900 Subject: [PATCH 0324/1791] Re-layout to reduce movement --- .../Multiplayer/MultiplayerMatchSubScreen.cs | 95 +++++++++++-------- 1 file changed, 54 insertions(+), 41 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 86982e2794..4664ac6bfe 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -97,18 +97,19 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer }, new Drawable[] { - new GridContainer + new Container { RelativeSizeAxes = Axes.Both, - Content = new[] + Padding = new MarginPadding { Horizontal = 5, Vertical = 10 }, + Child = new GridContainer { - new Drawable[] + RelativeSizeAxes = Axes.Both, + Content = new[] { - new Container + new Drawable[] { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Horizontal = 5, Vertical = 10 }, - Child = new GridContainer + // Main left column + new GridContainer { RelativeSizeAxes = Axes.Both, RowDimensions = new[] @@ -126,45 +127,57 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer }, } } - } - }, - new FillFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Horizontal = 5 }, - Spacing = new Vector2(0, 10), - Children = new[] + }, + // Main right column + new FillFlowContainer { - new FillFlowContainer + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new[] { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] + new FillFlowContainer { - new OverlinedHeader("Beatmap"), - new BeatmapSelectionControl { RelativeSizeAxes = Axes.X } - } - }, - userModsSection = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new OverlinedHeader("Beatmap"), + new BeatmapSelectionControl { RelativeSizeAxes = Axes.X } + } + }, + userModsSection = new FillFlowContainer { - new OverlinedHeader("Extra mods"), - new ModDisplay + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Margin = new MarginPadding { Top = 10 }, + Children = new Drawable[] { - DisplayUnrankedText = false, - Current = UserMods - }, - new PurpleTriangleButton - { - RelativeSizeAxes = Axes.X, - Text = "Select", - Action = () => userModsSelectOverlay.Show() + new OverlinedHeader("Extra mods"), + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), + Children = new Drawable[] + { + new PurpleTriangleButton + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Width = 90, + Text = "Select", + Action = () => userModsSelectOverlay.Show() + }, + new ModDisplay + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + DisplayUnrankedText = false, + Current = UserMods, + Scale = new Vector2(0.8f), + }, + } + } } } } From 8bb13915152ef6c5ac4a96f684bdbece349409c8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 3 Feb 2021 14:53:13 +0900 Subject: [PATCH 0325/1791] Fix inspection --- .../Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs index 768dc6512c..e2c98c0aad 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs @@ -134,7 +134,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Client.AddUser(new User { Id = 0, - Username = $"User 0", + Username = "User 0", CurrentModeRank = RNG.Next(1, 100000), CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", }); From 8295fb908174b78cdf7803a28e5b5be4ae5346e9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 3 Feb 2021 16:28:22 +0900 Subject: [PATCH 0326/1791] Implement mania constant speed mod --- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 1 + .../Mods/ManiaModConstantSpeed.cs | 35 +++++++++++++++++++ .../UI/DrawableManiaRuleset.cs | 5 +++ .../UI/Scrolling/DrawableScrollingRuleset.cs | 8 ++--- 4 files changed, 45 insertions(+), 4 deletions(-) create mode 100644 osu.Game.Rulesets.Mania/Mods/ManiaModConstantSpeed.cs diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 59c766fd84..4c729fef83 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -238,6 +238,7 @@ namespace osu.Game.Rulesets.Mania new ManiaModMirror(), new ManiaModDifficultyAdjust(), new ManiaModInvert(), + new ManiaModConstantSpeed() }; case ModType.Automation: diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModConstantSpeed.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModConstantSpeed.cs new file mode 100644 index 0000000000..078394b1d8 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModConstantSpeed.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 osu.Framework.Graphics.Sprites; +using osu.Game.Configuration; +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 ManiaModConstantSpeed : Mod, IApplicableToDrawableRuleset + { + public override string Name => "Constant Speed"; + + public override string Acronym => "CS"; + + public override double ScoreMultiplier => 1; + + public override string Description => "No more tricky speed changes!"; + + public override IconUsage? Icon => FontAwesome.Solid.Equals; + + public override ModType Type => ModType.Conversion; + + public override bool Ranked => false; + + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + { + var maniaRuleset = (DrawableManiaRuleset)drawableRuleset; + maniaRuleset.ScrollMethod = ScrollVisualisationMethod.Constant; + } + } +} diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 941ac9816c..6b34dbfa09 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics; using osu.Framework.Input; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Configuration; using osu.Game.Input.Handlers; using osu.Game.Replays; using osu.Game.Rulesets.Mania.Beatmaps; @@ -49,6 +50,10 @@ namespace osu.Game.Rulesets.Mania.UI protected new ManiaRulesetConfigManager Config => (ManiaRulesetConfigManager)base.Config; + public ScrollVisualisationMethod ScrollMethod = ScrollVisualisationMethod.Sequential; + + protected override ScrollVisualisationMethod VisualisationMethod => ScrollMethod; + private readonly Bindable configDirection = new Bindable(); private readonly Bindable configTimeRange = new BindableDouble(); diff --git a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs index 0955f32790..6ffdad211b 100644 --- a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs +++ b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs @@ -91,7 +91,11 @@ namespace osu.Game.Rulesets.UI.Scrolling scrollingInfo = new LocalScrollingInfo(); scrollingInfo.Direction.BindTo(Direction); scrollingInfo.TimeRange.BindTo(TimeRange); + } + [BackgroundDependencyLoader] + private void load() + { switch (VisualisationMethod) { case ScrollVisualisationMethod.Sequential: @@ -106,11 +110,7 @@ namespace osu.Game.Rulesets.UI.Scrolling scrollingInfo.Algorithm = new ConstantScrollAlgorithm(); break; } - } - [BackgroundDependencyLoader] - private void load() - { double lastObjectTime = Objects.LastOrDefault()?.GetEndTime() ?? double.MaxValue; double baseBeatLength = TimingControlPoint.DEFAULT_BEAT_LENGTH; From c8f1126bd793cdf6169a1d27742fd0909cdd9c33 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Feb 2021 19:44:39 +0900 Subject: [PATCH 0327/1791] Add failing test --- ...tion.cs => TestAPIModJsonSerialization.cs} | 2 +- .../TestAPIModMessagePackSerialization.cs | 139 ++++++++++++++++++ 2 files changed, 140 insertions(+), 1 deletion(-) rename osu.Game.Tests/Online/{TestAPIModSerialization.cs => TestAPIModJsonSerialization.cs} (99%) create mode 100644 osu.Game.Tests/Online/TestAPIModMessagePackSerialization.cs diff --git a/osu.Game.Tests/Online/TestAPIModSerialization.cs b/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs similarity index 99% rename from osu.Game.Tests/Online/TestAPIModSerialization.cs rename to osu.Game.Tests/Online/TestAPIModJsonSerialization.cs index 5948582d77..aa6f66da81 100644 --- a/osu.Game.Tests/Online/TestAPIModSerialization.cs +++ b/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs @@ -16,7 +16,7 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Tests.Online { [TestFixture] - public class TestAPIModSerialization + public class TestAPIModJsonSerialization { [Test] public void TestAcronymIsPreserved() diff --git a/osu.Game.Tests/Online/TestAPIModMessagePackSerialization.cs b/osu.Game.Tests/Online/TestAPIModMessagePackSerialization.cs new file mode 100644 index 0000000000..4294f89397 --- /dev/null +++ b/osu.Game.Tests/Online/TestAPIModMessagePackSerialization.cs @@ -0,0 +1,139 @@ +// 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 MessagePack; +using NUnit.Framework; +using osu.Framework.Bindables; +using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.Online.API; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Tests.Online +{ + [TestFixture] + public class TestAPIModMessagePackSerialization + { + [Test] + public void TestAcronymIsPreserved() + { + var apiMod = new APIMod(new TestMod()); + + var deserialized = MessagePackSerializer.Deserialize(MessagePackSerializer.Serialize(apiMod)); + + Assert.That(deserialized.Acronym, Is.EqualTo(apiMod.Acronym)); + } + + [Test] + public void TestRawSettingIsPreserved() + { + var apiMod = new APIMod(new TestMod { TestSetting = { Value = 2 } }); + + var deserialized = MessagePackSerializer.Deserialize(MessagePackSerializer.Serialize(apiMod)); + + Assert.That(deserialized.Settings, Contains.Key("test_setting").With.ContainValue(2.0)); + } + + [Test] + public void TestConvertedModHasCorrectSetting() + { + var apiMod = new APIMod(new TestMod { TestSetting = { Value = 2 } }); + + var deserialized = MessagePackSerializer.Deserialize(MessagePackSerializer.Serialize(apiMod)); + var converted = (TestMod)deserialized.ToMod(new TestRuleset()); + + Assert.That(converted.TestSetting.Value, Is.EqualTo(2)); + } + + [Test] + public void TestDeserialiseTimeRampMod() + { + // Create the mod with values different from default. + var apiMod = new APIMod(new TestModTimeRamp + { + AdjustPitch = { Value = false }, + InitialRate = { Value = 1.25 }, + FinalRate = { Value = 0.25 } + }); + + var deserialised = MessagePackSerializer.Deserialize(MessagePackSerializer.Serialize(apiMod)); + var converted = (TestModTimeRamp)deserialised.ToMod(new TestRuleset()); + + Assert.That(converted.AdjustPitch.Value, Is.EqualTo(false)); + Assert.That(converted.InitialRate.Value, Is.EqualTo(1.25)); + Assert.That(converted.FinalRate.Value, Is.EqualTo(0.25)); + } + + private class TestRuleset : Ruleset + { + public override IEnumerable GetModsFor(ModType type) => new Mod[] + { + new TestMod(), + new TestModTimeRamp(), + }; + + public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => throw new System.NotImplementedException(); + + public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => throw new System.NotImplementedException(); + + public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => throw new System.NotImplementedException(); + + public override string Description { get; } = string.Empty; + public override string ShortName { get; } = string.Empty; + } + + private class TestMod : Mod + { + public override string Name => "Test Mod"; + public override string Acronym => "TM"; + public override double ScoreMultiplier => 1; + + [SettingSource("Test")] + public BindableNumber TestSetting { get; } = new BindableDouble + { + MinValue = 0, + MaxValue = 10, + Default = 5, + Precision = 0.01, + }; + } + + private class TestModTimeRamp : ModTimeRamp + { + public override string Name => "Test Mod"; + public override string Acronym => "TMTR"; + public override double ScoreMultiplier => 1; + + [SettingSource("Initial rate", "The starting speed of the track")] + public override BindableNumber InitialRate { get; } = new BindableDouble + { + MinValue = 1, + MaxValue = 2, + Default = 1.5, + Value = 1.5, + Precision = 0.01, + }; + + [SettingSource("Final rate", "The speed increase to ramp towards")] + public override BindableNumber FinalRate { get; } = new BindableDouble + { + MinValue = 0, + MaxValue = 1, + Default = 0.5, + Value = 0.5, + Precision = 0.01, + }; + + [SettingSource("Adjust pitch", "Should pitch be adjusted with speed")] + public override BindableBool AdjustPitch { get; } = new BindableBool + { + Default = true, + Value = true + }; + } + } +} From 75f1ebd5f92ef4200f21f457bea58a23d4cdfa70 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Feb 2021 19:46:47 +0900 Subject: [PATCH 0328/1791] Add custom resolver for mod settings dictionary --- osu.Game/Online/API/APIMod.cs | 1 + .../API/ModSettingsDictionaryFormatter.cs | 96 +++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 osu.Game/Online/API/ModSettingsDictionaryFormatter.cs diff --git a/osu.Game/Online/API/APIMod.cs b/osu.Game/Online/API/APIMod.cs index 69ce3825ee..bff08b0515 100644 --- a/osu.Game/Online/API/APIMod.cs +++ b/osu.Game/Online/API/APIMod.cs @@ -23,6 +23,7 @@ namespace osu.Game.Online.API [JsonProperty("settings")] [Key(1)] + [MessagePackFormatter(typeof(ModSettingsDictionaryFormatter))] public Dictionary Settings { get; set; } = new Dictionary(); [JsonConstructor] diff --git a/osu.Game/Online/API/ModSettingsDictionaryFormatter.cs b/osu.Game/Online/API/ModSettingsDictionaryFormatter.cs new file mode 100644 index 0000000000..a8ee9beca5 --- /dev/null +++ b/osu.Game/Online/API/ModSettingsDictionaryFormatter.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using MessagePack; +using MessagePack.Formatters; +using osu.Framework.Bindables; + +namespace osu.Game.Online.API +{ + public class ModSettingsDictionaryFormatter : IMessagePackFormatter> + { + public int Serialize(ref byte[] bytes, int offset, Dictionary value, IFormatterResolver formatterResolver) + { + int startOffset = offset; + + offset += MessagePackBinary.WriteArrayHeader(ref bytes, offset, value.Count); + + foreach (var kvp in value) + { + offset += MessagePackBinary.WriteString(ref bytes, offset, kvp.Key); + + switch (kvp.Value) + { + case Bindable d: + offset += MessagePackBinary.WriteDouble(ref bytes, offset, d.Value); + break; + + case Bindable f: + offset += MessagePackBinary.WriteSingle(ref bytes, offset, f.Value); + break; + + case Bindable b: + offset += MessagePackBinary.WriteBoolean(ref bytes, offset, b.Value); + break; + + default: + throw new ArgumentException("A setting was of a type not supported by the messagepack serialiser", nameof(bytes)); + } + } + + return offset - startOffset; + } + + public Dictionary Deserialize(byte[] bytes, int offset, IFormatterResolver formatterResolver, out int readSize) + { + int startOffset = offset; + + var output = new Dictionary(); + + int itemCount = MessagePackBinary.ReadArrayHeader(bytes, offset, out readSize); + offset += readSize; + + for (int i = 0; i < itemCount; i++) + { + var key = MessagePackBinary.ReadString(bytes, offset, out readSize); + offset += readSize; + + switch (MessagePackBinary.GetMessagePackType(bytes, offset)) + { + case MessagePackType.Float: + { + // could be either float or double... + // see https://github.com/msgpack/msgpack/blob/master/spec.md#serialization-type-to-format-conversion + switch (MessagePackCode.ToFormatName(bytes[offset])) + { + case "float 32": + output[key] = MessagePackBinary.ReadSingle(bytes, offset, out readSize); + offset += readSize; + break; + + case "float 64": + output[key] = MessagePackBinary.ReadDouble(bytes, offset, out readSize); + offset += readSize; + break; + + default: + throw new ArgumentException("A setting was of a type not supported by the messagepack deserialiser", nameof(bytes)); + } + + break; + } + + case MessagePackType.Boolean: + output[key] = MessagePackBinary.ReadBoolean(bytes, offset, out readSize); + offset += readSize; + break; + + default: + throw new ArgumentException("A setting was of a type not supported by the messagepack deserialiser", nameof(bytes)); + } + } + + readSize = offset - startOffset; + return output; + } + } +} From d3f056f188ee7410f7603d71b6abec964755231b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Feb 2021 20:06:25 +0900 Subject: [PATCH 0329/1791] Add missing licence header --- osu.Game/Online/API/ModSettingsDictionaryFormatter.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Online/API/ModSettingsDictionaryFormatter.cs b/osu.Game/Online/API/ModSettingsDictionaryFormatter.cs index a8ee9beca5..1b381e7c98 100644 --- a/osu.Game/Online/API/ModSettingsDictionaryFormatter.cs +++ b/osu.Game/Online/API/ModSettingsDictionaryFormatter.cs @@ -1,3 +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; using System.Collections.Generic; using MessagePack; From 1380717ebb5b51da488736df8cd30484ecc62532 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Feb 2021 20:19:27 +0900 Subject: [PATCH 0330/1791] Use PrimitiveObjectFormatter to simplify code --- .../API/ModSettingsDictionaryFormatter.cs | 43 +++---------------- 1 file changed, 7 insertions(+), 36 deletions(-) diff --git a/osu.Game/Online/API/ModSettingsDictionaryFormatter.cs b/osu.Game/Online/API/ModSettingsDictionaryFormatter.cs index 1b381e7c98..3dd8bff61b 100644 --- a/osu.Game/Online/API/ModSettingsDictionaryFormatter.cs +++ b/osu.Game/Online/API/ModSettingsDictionaryFormatter.cs @@ -15,6 +15,8 @@ namespace osu.Game.Online.API { int startOffset = offset; + var primitiveFormatter = PrimitiveObjectFormatter.Instance; + offset += MessagePackBinary.WriteArrayHeader(ref bytes, offset, value.Count); foreach (var kvp in value) @@ -24,15 +26,15 @@ namespace osu.Game.Online.API switch (kvp.Value) { case Bindable d: - offset += MessagePackBinary.WriteDouble(ref bytes, offset, d.Value); + offset += primitiveFormatter.Serialize(ref bytes, offset, d.Value, formatterResolver); break; case Bindable f: - offset += MessagePackBinary.WriteSingle(ref bytes, offset, f.Value); + offset += primitiveFormatter.Serialize(ref bytes, offset, f.Value, formatterResolver); break; case Bindable b: - offset += MessagePackBinary.WriteBoolean(ref bytes, offset, b.Value); + offset += primitiveFormatter.Serialize(ref bytes, offset, b.Value, formatterResolver); break; default: @@ -57,39 +59,8 @@ namespace osu.Game.Online.API var key = MessagePackBinary.ReadString(bytes, offset, out readSize); offset += readSize; - switch (MessagePackBinary.GetMessagePackType(bytes, offset)) - { - case MessagePackType.Float: - { - // could be either float or double... - // see https://github.com/msgpack/msgpack/blob/master/spec.md#serialization-type-to-format-conversion - switch (MessagePackCode.ToFormatName(bytes[offset])) - { - case "float 32": - output[key] = MessagePackBinary.ReadSingle(bytes, offset, out readSize); - offset += readSize; - break; - - case "float 64": - output[key] = MessagePackBinary.ReadDouble(bytes, offset, out readSize); - offset += readSize; - break; - - default: - throw new ArgumentException("A setting was of a type not supported by the messagepack deserialiser", nameof(bytes)); - } - - break; - } - - case MessagePackType.Boolean: - output[key] = MessagePackBinary.ReadBoolean(bytes, offset, out readSize); - offset += readSize; - break; - - default: - throw new ArgumentException("A setting was of a type not supported by the messagepack deserialiser", nameof(bytes)); - } + output[key] = PrimitiveObjectFormatter.Instance.Deserialize(bytes, offset, formatterResolver, out readSize); + offset += readSize; } readSize = offset - startOffset; From 65d45ec74cefcc4db80ef0d83d9bc598bc2e864b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 3 Feb 2021 20:50:22 +0900 Subject: [PATCH 0331/1791] Unschedule cancellation --- .../Multiplayer/StatefulMultiplayerClient.cs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index de51a4b117..7d729a3c11 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -117,13 +117,7 @@ namespace osu.Game.Online.Multiplayer /// The API . public async Task JoinRoom(Room room) { - var cancellationSource = new CancellationTokenSource(); - - await scheduleAsync(() => - { - joinCancellationSource?.Cancel(); - joinCancellationSource = cancellationSource; - }, CancellationToken.None); + var cancellationSource = joinCancellationSource = new CancellationTokenSource(); await joinOrLeaveTaskChain.Add(async () => { @@ -162,15 +156,15 @@ namespace osu.Game.Online.Multiplayer public Task LeaveRoom() { + // The join may have not completed yet, so certain tasks that either update the room or reference the room should be cancelled. + // This includes the setting of Room itself along with the initial update of the room settings on join. + joinCancellationSource?.Cancel(); + // Leaving rooms is expected to occur instantaneously whilst the operation is finalised in the background. // However a few members need to be reset immediately to prevent other components from entering invalid states whilst the operation hasn't yet completed. // For example, if a room was left and the user immediately pressed the "create room" button, then the user could be taken into the lobby if the value of Room is not reset in time. var scheduledReset = scheduleAsync(() => { - // The join may have not completed yet, so certain tasks that either update the room or reference the room should be cancelled. - // This includes the setting of Room itself along with the initial update of the room settings on join. - joinCancellationSource?.Cancel(); - apiRoom = null; Room = null; CurrentMatchPlayingUserIds.Clear(); From 623b47f9af503a400facfea44e2db230db42a279 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 3 Feb 2021 21:25:05 +0900 Subject: [PATCH 0332/1791] Add flag to toggle follow circle tracking for slider heads --- .../Objects/Drawables/DrawableSliderHead.cs | 14 ++++++++++---- osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs | 4 ++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs index acc95ab036..c051a9918d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs @@ -12,6 +12,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { public class DrawableSliderHead : DrawableHitCircle { + public new SliderHeadCircle HitObject => (SliderHeadCircle)base.HitObject; + [CanBeNull] public Slider Slider => DrawableSlider?.HitObject; @@ -59,12 +61,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables base.Update(); Debug.Assert(Slider != null); + Debug.Assert(HitObject != null); - double completionProgress = Math.Clamp((Time.Current - Slider.StartTime) / Slider.Duration, 0, 1); + if (HitObject.TrackFollowCircle) + { + double completionProgress = Math.Clamp((Time.Current - Slider.StartTime) / Slider.Duration, 0, 1); - //todo: we probably want to reconsider this before adding scoring, but it looks and feels nice. - if (!IsHit) - Position = Slider.CurvePositionAt(completionProgress); + //todo: we probably want to reconsider this before adding scoring, but it looks and feels nice. + if (!IsHit) + Position = Slider.CurvePositionAt(completionProgress); + } } public Action OnShake; diff --git a/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs index f6d46aeef5..5fc480883a 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs @@ -5,5 +5,9 @@ namespace osu.Game.Rulesets.Osu.Objects { public class SliderHeadCircle : HitCircle { + /// + /// Makes the head circle track the follow circle when the start time is reached. + /// + public bool TrackFollowCircle = true; } } From 3fe190cfbe7c5fb3c6d2863ea53a33b370ea03cd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Feb 2021 22:00:16 +0900 Subject: [PATCH 0333/1791] Show original error message on web exceptions (or is no message is returned) --- osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs index 7936ab8ecd..dc98eb8687 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics; using System.Linq; +using System.Net; using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; @@ -65,7 +66,10 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { failed = true; - Logger.Log($"You are not able to submit a score: {e.Message}", LoggingTarget.Information, LogLevel.Important); + if (e is WebException || string.IsNullOrEmpty(e.Message)) + Logger.Error(e, "Failed to retrieve a score submission token.\n\nThis may happen if you are running an old or non-official release of osu! (ie. you are self-compiling)."); + else + Logger.Log($"You are not able to submit a score: {e.Message}", level: LogLevel.Important); Schedule(() => { From 9d7164816cf073be96b07353d6d996c50086ad71 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Feb 2021 22:02:40 +0900 Subject: [PATCH 0334/1791] Add reverse binding for max attempts (currently unused but good for safety) --- .../OnlinePlay/Playlists/PlaylistsMatchSettingsOverlay.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsMatchSettingsOverlay.cs index bf85ecf13d..56d3143f59 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsMatchSettingsOverlay.cs @@ -280,6 +280,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists RoomName.BindValueChanged(name => NameField.Text = name.NewValue, true); Availability.BindValueChanged(availability => AvailabilityPicker.Current.Value = availability.NewValue, true); MaxParticipants.BindValueChanged(count => MaxParticipantsField.Text = count.NewValue?.ToString(), true); + MaxAttempts.BindValueChanged(count => MaxAttemptsField.Text = count.NewValue?.ToString(), true); Duration.BindValueChanged(duration => DurationField.Current.Value = duration.NewValue ?? TimeSpan.FromMinutes(30), true); playlist.Items.BindTo(Playlist); From e3d323989c6925304f1f8e126e3cab6aee31695a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Feb 2021 20:42:27 +0900 Subject: [PATCH 0335/1791] Switch to SignalR 5.0 and implement using better API --- .../API/ModSettingsDictionaryFormatter.cs | 39 ++++++++----------- osu.Game/osu.Game.csproj | 7 ++-- 2 files changed, 20 insertions(+), 26 deletions(-) diff --git a/osu.Game/Online/API/ModSettingsDictionaryFormatter.cs b/osu.Game/Online/API/ModSettingsDictionaryFormatter.cs index 3dd8bff61b..fc6b82a16b 100644 --- a/osu.Game/Online/API/ModSettingsDictionaryFormatter.cs +++ b/osu.Game/Online/API/ModSettingsDictionaryFormatter.cs @@ -1,8 +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; +using System.Buffers; using System.Collections.Generic; +using System.Text; using MessagePack; using MessagePack.Formatters; using osu.Framework.Bindables; @@ -11,59 +12,51 @@ namespace osu.Game.Online.API { public class ModSettingsDictionaryFormatter : IMessagePackFormatter> { - public int Serialize(ref byte[] bytes, int offset, Dictionary value, IFormatterResolver formatterResolver) + public void Serialize(ref MessagePackWriter writer, Dictionary value, MessagePackSerializerOptions options) { - int startOffset = offset; - var primitiveFormatter = PrimitiveObjectFormatter.Instance; - offset += MessagePackBinary.WriteArrayHeader(ref bytes, offset, value.Count); + writer.WriteArrayHeader(value.Count); foreach (var kvp in value) { - offset += MessagePackBinary.WriteString(ref bytes, offset, kvp.Key); + var stringBytes = new ReadOnlySequence(Encoding.UTF8.GetBytes(kvp.Key)); + writer.WriteString(in stringBytes); switch (kvp.Value) { case Bindable d: - offset += primitiveFormatter.Serialize(ref bytes, offset, d.Value, formatterResolver); + primitiveFormatter.Serialize(ref writer, d.Value, options); break; case Bindable f: - offset += primitiveFormatter.Serialize(ref bytes, offset, f.Value, formatterResolver); + primitiveFormatter.Serialize(ref writer, f.Value, options); break; case Bindable b: - offset += primitiveFormatter.Serialize(ref bytes, offset, b.Value, formatterResolver); + primitiveFormatter.Serialize(ref writer, b.Value, options); break; default: - throw new ArgumentException("A setting was of a type not supported by the messagepack serialiser", nameof(bytes)); + // fall back for non-bindable cases. + primitiveFormatter.Serialize(ref writer, kvp.Value, options); + break; } } - - return offset - startOffset; } - public Dictionary Deserialize(byte[] bytes, int offset, IFormatterResolver formatterResolver, out int readSize) + public Dictionary Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) { - int startOffset = offset; - var output = new Dictionary(); - int itemCount = MessagePackBinary.ReadArrayHeader(bytes, offset, out readSize); - offset += readSize; + int itemCount = reader.ReadArrayHeader(); for (int i = 0; i < itemCount; i++) { - var key = MessagePackBinary.ReadString(bytes, offset, out readSize); - offset += readSize; - - output[key] = PrimitiveObjectFormatter.Instance.Deserialize(bytes, offset, formatterResolver, out readSize); - offset += readSize; + output[reader.ReadString()] = + PrimitiveObjectFormatter.Instance.Deserialize(ref reader, options); } - readSize = offset - startOffset; return output; } } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index e2b506e187..eabd5c7d12 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -20,11 +20,12 @@ - - - + + + + From 03b7817887ea059719094f001a81c5b4f1c9ee36 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 3 Feb 2021 22:12:20 +0900 Subject: [PATCH 0336/1791] Add flags to return to classic slider scoring --- .../Objects/Drawables/DrawableHitCircle.cs | 9 ++++++- .../Objects/Drawables/DrawableSlider.cs | 24 ++++++++++++++++++- .../Objects/Drawables/DrawableSliderHead.cs | 15 ++++++++++++ osu.Game.Rulesets.Osu/Objects/Slider.cs | 8 ++++++- .../Objects/SliderHeadCircle.cs | 14 ++++++++++- 5 files changed, 66 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index 3c0260f5f5..77094f928b 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -123,7 +123,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables return; } - var result = HitObject.HitWindows.ResultFor(timeOffset); + var result = ResultFor(timeOffset); if (result == HitResult.None || CheckHittable?.Invoke(this, Time.Current) == false) { @@ -146,6 +146,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables }); } + /// + /// Retrieves the for a time offset. + /// + /// The time offset. + /// The hit result, or if doesn't result in a judgement. + protected virtual HitResult ResultFor(double timeOffset) => HitObject.HitWindows.ResultFor(timeOffset); + protected override void UpdateInitialTransforms() { base.UpdateInitialTransforms(); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 511cbc2347..7061ce59d0 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -15,6 +15,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Skinning; using osu.Game.Rulesets.Osu.Skinning.Default; using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.Scoring; using osuTK.Graphics; using osu.Game.Skinning; @@ -249,7 +250,28 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (userTriggered || Time.Current < HitObject.EndTime) return; - ApplyResult(r => r.Type = NestedHitObjects.Any(h => h.Result.IsHit) ? r.Judgement.MaxResult : r.Judgement.MinResult); + if (HitObject.IgnoreJudgement) + { + ApplyResult(r => r.Type = NestedHitObjects.Any(h => h.Result.IsHit) ? r.Judgement.MaxResult : r.Judgement.MinResult); + return; + } + + // If not ignoring judgement, score proportionally based on the number of ticks hit, counting the head circle as a tick. + ApplyResult(r => + { + int totalTicks = NestedHitObjects.Count; + int hitTicks = NestedHitObjects.Count(h => h.IsHit); + double hitFraction = (double)totalTicks / hitTicks; + + if (hitTicks == totalTicks) + r.Type = HitResult.Great; + else if (hitFraction >= 0.5) + r.Type = HitResult.Ok; + else if (hitFraction > 0) + r.Type = HitResult.Meh; + else + r.Type = HitResult.Miss; + }); } public override void PlaySamples() diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs index c051a9918d..08e9c5eb14 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs @@ -7,6 +7,7 @@ using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects.Drawables { @@ -19,6 +20,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected DrawableSlider DrawableSlider => (DrawableSlider)ParentHitObject; + public override bool DisplayResult => HitObject?.JudgeAsNormalHitCircle ?? base.DisplayResult; + private readonly IBindable pathVersion = new Bindable(); protected override OsuSkinComponents CirclePieceComponent => OsuSkinComponents.SliderHeadHitCircle; @@ -73,6 +76,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } + protected override HitResult ResultFor(double timeOffset) + { + Debug.Assert(HitObject != null); + + if (HitObject.JudgeAsNormalHitCircle) + return base.ResultFor(timeOffset); + + // If not judged as a normal hitcircle, only track whether a hit has occurred (via IgnoreHit) rather than a scorable hit result. + var result = base.ResultFor(timeOffset); + return result.IsHit() ? HitResult.IgnoreHit : result; + } + public Action OnShake; public override void Shake(double maximumLength) => OnShake?.Invoke(maximumLength); diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 1670df24a8..e3365a8ccf 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -114,6 +114,12 @@ namespace osu.Game.Rulesets.Osu.Objects /// public double TickDistanceMultiplier = 1; + /// + /// Whether this 's judgement should be ignored. + /// If false, this will be judged proportionally to the number of ticks hit. + /// + public bool IgnoreJudgement = true; + [JsonIgnore] public HitCircle HeadCircle { get; protected set; } @@ -233,7 +239,7 @@ namespace osu.Game.Rulesets.Osu.Objects HeadCircle.Samples = this.GetNodeSamples(0); } - public override Judgement CreateJudgement() => new OsuIgnoreJudgement(); + public override Judgement CreateJudgement() => IgnoreJudgement ? new OsuIgnoreJudgement() : new OsuJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; } diff --git a/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs index 5fc480883a..13eac60300 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs @@ -1,13 +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 osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Osu.Judgements; + namespace osu.Game.Rulesets.Osu.Objects { public class SliderHeadCircle : HitCircle { /// - /// Makes the head circle track the follow circle when the start time is reached. + /// Makes this track the follow circle when the start time is reached. + /// If false, this will be pinned to its initial position in the slider. /// public bool TrackFollowCircle = true; + + /// + /// Whether to treat this as a normal for judgement purposes. + /// If false, judgement will be ignored. + /// + public bool JudgeAsNormalHitCircle = true; + + public override Judgement CreateJudgement() => JudgeAsNormalHitCircle ? base.CreateJudgement() : new OsuIgnoreJudgement(); } } From 2f22dbe06be3645ed9c5e8f99cc015414f578256 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 3 Feb 2021 22:42:50 +0900 Subject: [PATCH 0337/1791] Make sliders display judgements when not ignored --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index 13f5960bd4..79655c33e4 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (JudgedObject?.HitObject is OsuHitObject osuObject) { - Position = osuObject.StackedPosition; + Position = osuObject.StackedEndPosition; Scale = new Vector2(osuObject.Scale); } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 7061ce59d0..e607163b3e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public SliderBall Ball { get; private set; } public SkinnableDrawable Body { get; private set; } - public override bool DisplayResult => false; + public override bool DisplayResult => !HitObject.IgnoreJudgement; private PlaySliderBody sliderBody => Body.Drawable as PlaySliderBody; From 3b5c67a0630681b6e1e2f0d52486e241772661d3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 3 Feb 2021 23:08:59 +0900 Subject: [PATCH 0338/1791] Add OsuModClassic --- osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs | 43 +++++++++++++++++++++ osu.Game.Rulesets.Osu/OsuRuleset.cs | 1 + 2 files changed, 44 insertions(+) create mode 100644 osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs new file mode 100644 index 0000000000..5542580979 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.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 osu.Framework.Graphics.Sprites; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Objects; + +namespace osu.Game.Rulesets.Osu.Mods +{ + public class OsuModClassic : Mod, IApplicableToHitObject + { + public override string Name => "Classic"; + + public override string Acronym => "CL"; + + public override double ScoreMultiplier => 1; + + public override IconUsage? Icon => FontAwesome.Solid.History; + + public override string Description => "Feeling nostalgic?"; + + public override bool Ranked => false; + + public void ApplyToHitObject(HitObject hitObject) + { + switch (hitObject) + { + case Slider slider: + slider.IgnoreJudgement = false; + + foreach (var head in slider.NestedHitObjects.OfType()) + { + head.TrackFollowCircle = false; + head.JudgeAsNormalHitCircle = false; + } + + break; + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index cba0c5be14..18324a18a8 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -163,6 +163,7 @@ namespace osu.Game.Rulesets.Osu { new OsuModTarget(), new OsuModDifficultyAdjust(), + new OsuModClassic() }; case ModType.Automation: From abdd417eb6ace553d81a11c32b6bc723247557b8 Mon Sep 17 00:00:00 2001 From: vmaggioli Date: Wed, 3 Feb 2021 10:03:38 -0500 Subject: [PATCH 0339/1791] Remove slider changes --- .../TestSceneSliderSelectionBlueprint.cs | 19 ------------------- .../Sliders/SliderSelectionBlueprint.cs | 6 +----- 2 files changed, 1 insertion(+), 24 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs index 4edf778bfd..f6e1be693b 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs @@ -161,18 +161,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor checkControlPointSelected(1, false); } - [Test] - public void TestZeroLengthSliderNotAllowed() - { - moveMouseToControlPoint(1); - dragMouseToControlPoint(0); - - moveMouseToControlPoint(2); - dragMouseToControlPoint(0); - - AddAssert("slider has non-zero duration", () => slider.Duration > 0); - } - private void moveHitObject() { AddStep("move hitobject", () => @@ -201,13 +189,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor }); } - private void dragMouseToControlPoint(int index) - { - AddStep("hold down mouse button", () => InputManager.PressButton(MouseButton.Left)); - moveMouseToControlPoint(index); - AddStep("release mouse button", () => InputManager.ReleaseButton(MouseButton.Left)); - } - private void checkControlPointSelected(int index, bool selected) => AddAssert($"control point {index} {(selected ? "selected" : "not selected")}", () => blueprint.ControlPointVisualiser.Pieces[index].IsSelected.Value == selected); diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 508783a499..3d3dff653a 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -226,11 +226,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private void updatePath() { - float expectedDistance = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance; - if (expectedDistance < 1) - return; - - HitObject.Path.ExpectedDistance.Value = expectedDistance; + HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance; editorBeatmap?.Update(HitObject); } From db3f9e7cbee3ff3c719d80fc2943420b82a828a2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 4 Feb 2021 02:20:18 +0300 Subject: [PATCH 0340/1791] Apply documentation suggestion --- osu.Game/Online/DownloadTrackingComposite.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/DownloadTrackingComposite.cs b/osu.Game/Online/DownloadTrackingComposite.cs index 188cb9be7a..69c6ebd07c 100644 --- a/osu.Game/Online/DownloadTrackingComposite.cs +++ b/osu.Game/Online/DownloadTrackingComposite.cs @@ -67,10 +67,10 @@ namespace osu.Game.Online } /// - /// Verifies that the given databased model is in a correct state to be considered available. + /// Checks that a database model matches the one expected to be downloaded. /// /// - /// In the case of multiplayer/playlists, this has to verify that the databased beatmap set with the selected beatmap matches what's online. + /// In the case of multiplayer/playlists, this has to check that the databased beatmap set with the selected beatmap matches what's online. /// /// The model in database. protected virtual bool VerifyDatabasedModel([NotNull] TModel databasedModel) => true; From 76cfeae7e9cf844996ac9c53a54a1756034d1562 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Feb 2021 15:10:56 +0900 Subject: [PATCH 0341/1791] Add support for Bindable int in config --- osu.Game/Online/API/ModSettingsDictionaryFormatter.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Online/API/ModSettingsDictionaryFormatter.cs b/osu.Game/Online/API/ModSettingsDictionaryFormatter.cs index fc6b82a16b..99e87677fa 100644 --- a/osu.Game/Online/API/ModSettingsDictionaryFormatter.cs +++ b/osu.Game/Online/API/ModSettingsDictionaryFormatter.cs @@ -29,6 +29,10 @@ namespace osu.Game.Online.API primitiveFormatter.Serialize(ref writer, d.Value, options); break; + case Bindable i: + primitiveFormatter.Serialize(ref writer, i.Value, options); + break; + case Bindable f: primitiveFormatter.Serialize(ref writer, f.Value, options); break; From d165344070434f8e47af0d92c206b7a7fe9ee9ae Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Feb 2021 15:19:57 +0900 Subject: [PATCH 0342/1791] Force newer version of MessagePack for fixed iOS compatibility --- osu.Game/osu.Game.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index eabd5c7d12..f866b232d8 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -20,6 +20,7 @@ + From 30dae5bf1c1188139fb683e235b15b16af5e83ec Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 4 Feb 2021 15:17:47 +0900 Subject: [PATCH 0343/1791] Add test to make sure the algorithm is passed down in time --- .../Mods/TestSceneManiaModConstantSpeed.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModConstantSpeed.cs diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModConstantSpeed.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModConstantSpeed.cs new file mode 100644 index 0000000000..60363aaeef --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModConstantSpeed.cs @@ -0,0 +1,31 @@ +// 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.Allocation; +using osu.Framework.Testing; +using osu.Game.Rulesets.Mania.Mods; +using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Rulesets.UI.Scrolling.Algorithms; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Mania.Tests.Mods +{ + public class TestSceneManiaModConstantSpeed : ModTestScene + { + protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset(); + + [Test] + public void TestConstantScroll() => CreateModTest(new ModTestData + { + Mod = new ManiaModConstantSpeed(), + PassCondition = () => + { + var hitObject = Player.ChildrenOfType().FirstOrDefault(); + return hitObject?.Dependencies.Get().Algorithm is ConstantScrollAlgorithm; + } + }); + } +} From a36b426b243e42062b93782ad5204dd35bd5ad88 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Feb 2021 15:46:50 +0900 Subject: [PATCH 0344/1791] Force iOS back to previous versions of messagepack --- osu.iOS.props | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.iOS.props b/osu.iOS.props index dc3527c687..22d104f2e1 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -80,6 +80,9 @@ + + + From b2f1e133f86d3cf26d9581cdb45c0beecbe2ab5f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Feb 2021 16:53:55 +0900 Subject: [PATCH 0345/1791] Allow checkbox nub to be moved to the left --- .../Graphics/UserInterface/OsuCheckbox.cs | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuCheckbox.cs b/osu.Game/Graphics/UserInterface/OsuCheckbox.cs index 517f83daa9..313962d9c6 100644 --- a/osu.Game/Graphics/UserInterface/OsuCheckbox.cs +++ b/osu.Game/Graphics/UserInterface/OsuCheckbox.cs @@ -48,7 +48,7 @@ namespace osu.Game.Graphics.UserInterface private SampleChannel sampleChecked; private SampleChannel sampleUnchecked; - public OsuCheckbox() + public OsuCheckbox(bool nubOnRight = true) { AutoSizeAxes = Axes.Y; RelativeSizeAxes = Axes.X; @@ -61,17 +61,24 @@ namespace osu.Game.Graphics.UserInterface { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, - Padding = new MarginPadding { Right = Nub.EXPANDED_SIZE + nub_padding } - }, - Nub = new Nub - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Margin = new MarginPadding { Right = nub_padding }, }, + Nub = new Nub(), new HoverClickSounds() }; + if (nubOnRight) + { + Nub.Anchor = Anchor.CentreRight; + Nub.Origin = Anchor.CentreRight; + Nub.Margin = new MarginPadding { Right = nub_padding }; + labelText.Padding = new MarginPadding { Right = Nub.EXPANDED_SIZE + nub_padding }; + } + else + { + Nub.Margin = new MarginPadding { Left = nub_padding }; + labelText.Padding = new MarginPadding { Left = Nub.EXPANDED_SIZE + nub_padding }; + } + Nub.Current.BindTo(Current); Current.DisabledChanged += disabled => labelText.Alpha = Nub.Alpha = disabled ? 0.3f : 1; From 3148bbda2a09c00f38aec261bcb4b78559c9412e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Feb 2021 16:54:17 +0900 Subject: [PATCH 0346/1791] Allow custom font to be used in OsuCheckbox --- osu.Game/Graphics/UserInterface/OsuCheckbox.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/OsuCheckbox.cs b/osu.Game/Graphics/UserInterface/OsuCheckbox.cs index 313962d9c6..61a42dac23 100644 --- a/osu.Game/Graphics/UserInterface/OsuCheckbox.cs +++ b/osu.Game/Graphics/UserInterface/OsuCheckbox.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; @@ -57,7 +58,7 @@ namespace osu.Game.Graphics.UserInterface Children = new Drawable[] { - labelText = new OsuTextFlowContainer + labelText = new OsuTextFlowContainer(ApplyLabelParameters) { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, @@ -84,6 +85,13 @@ namespace osu.Game.Graphics.UserInterface Current.DisabledChanged += disabled => labelText.Alpha = Nub.Alpha = disabled ? 0.3f : 1; } + /// + /// A function which can be overridden to change the parameters of the label's text. + /// + protected virtual void ApplyLabelParameters(SpriteText text) + { + } + [BackgroundDependencyLoader] private void load(AudioManager audio) { From 48a58e790e2d2ec640d3273ee78e222ff6aed07e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Feb 2021 16:57:39 +0900 Subject: [PATCH 0347/1791] Don't specify arbitrary width --- osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs index 5b9a19897f..c60afeb2aa 100644 --- a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs @@ -86,7 +86,7 @@ namespace osu.Game.Screens.OnlinePlay protected override Drawable CreateHeader(string text) => new Container { AutoSizeAxes = Axes.Y, - Width = 175, + RelativeSizeAxes = Axes.X, Child = checkbox = new HeaderCheckbox { LabelText = text, From b32e10514d1f8f48d7e580947176a56faa497d5b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Feb 2021 16:58:02 +0900 Subject: [PATCH 0348/1791] Fix padding on label text not being double-applied (meaning no padding between nub and text) --- osu.Game/Graphics/UserInterface/OsuCheckbox.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuCheckbox.cs b/osu.Game/Graphics/UserInterface/OsuCheckbox.cs index 61a42dac23..b80941c0bc 100644 --- a/osu.Game/Graphics/UserInterface/OsuCheckbox.cs +++ b/osu.Game/Graphics/UserInterface/OsuCheckbox.cs @@ -72,12 +72,12 @@ namespace osu.Game.Graphics.UserInterface Nub.Anchor = Anchor.CentreRight; Nub.Origin = Anchor.CentreRight; Nub.Margin = new MarginPadding { Right = nub_padding }; - labelText.Padding = new MarginPadding { Right = Nub.EXPANDED_SIZE + nub_padding }; + labelText.Padding = new MarginPadding { Right = Nub.EXPANDED_SIZE + nub_padding * 2 }; } else { Nub.Margin = new MarginPadding { Left = nub_padding }; - labelText.Padding = new MarginPadding { Left = Nub.EXPANDED_SIZE + nub_padding }; + labelText.Padding = new MarginPadding { Left = Nub.EXPANDED_SIZE + nub_padding * 2 }; } Nub.Current.BindTo(Current); From daf7ab942274a71298ff4caf3505cee52381aed0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Feb 2021 16:58:15 +0900 Subject: [PATCH 0349/1791] Apply the expected font to the checkbox's label --- .../Screens/OnlinePlay/FreeModSelectOverlay.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs index c60afeb2aa..180f6079ac 100644 --- a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs @@ -3,8 +3,11 @@ using System; using System.Linq; +using System.Reflection.Emit; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Mods; using osu.Game.Rulesets.Mods; @@ -120,6 +123,19 @@ namespace osu.Game.Screens.OnlinePlay protected override bool PlaySoundsOnUserChange => false; + public HeaderCheckbox() + : base(false) + + { + } + + protected override void ApplyLabelParameters(SpriteText text) + { + base.ApplyLabelParameters(text); + + text.Font = OsuFont.GetFont(weight: FontWeight.Bold); + } + protected override void OnUserChange(bool value) { base.OnUserChange(value); From 4bfe3aabdc6c165e2bd368b9a70ab45ec4cc39b3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Feb 2021 17:06:11 +0900 Subject: [PATCH 0350/1791] Simplify sound debounce logic --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index c308dc2451..97902d1c15 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -496,17 +496,12 @@ namespace osu.Game.Overlays.Mods MultiplierLabel.FadeColour(Color4.White, 200); } - private ScheduledDelegate sampleOnDelegate; - private ScheduledDelegate sampleOffDelegate; - private void modButtonPressed(Mod selectedMod) { if (selectedMod != null) { - // Fixes buzzing when multiple mods are selected in the same frame. - sampleOnDelegate?.Cancel(); if (State.Value == Visibility.Visible) - sampleOnDelegate = Scheduler.Add(() => sampleOn?.Play()); + Scheduler.AddOnce(playSelectedSound); OnModSelected(selectedMod); @@ -514,15 +509,16 @@ namespace osu.Game.Overlays.Mods } else { - // Fixes buzzing when multiple mods are deselected in the same frame. - sampleOffDelegate?.Cancel(); if (State.Value == Visibility.Visible) - sampleOffDelegate = Scheduler.Add(() => sampleOff?.Play()); + Scheduler.AddOnce(playDeselectedSound); } refreshSelectedMods(); } + private void playSelectedSound() => sampleOn?.Play(); + private void playDeselectedSound() => sampleOff?.Play(); + /// /// Invoked when a new has been selected. /// From f23ca7c7cfd7bbd752e50464189c2c84546beba2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Feb 2021 18:10:55 +0900 Subject: [PATCH 0351/1791] Centralise selection animation logic --- osu.Game/Overlays/Mods/ModSection.cs | 51 +++++++++++++------ osu.Game/Overlays/Mods/ModSelectOverlay.cs | 1 - .../Overlays/Mods/SoloModSelectOverlay.cs | 2 +- .../OnlinePlay/FreeModSelectOverlay.cs | 25 +++++---- 4 files changed, 48 insertions(+), 31 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index 87a45ebf63..495b1c05cd 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -33,6 +33,8 @@ namespace osu.Game.Overlays.Mods private CancellationTokenSource modsLoadCts; + protected bool SelectionAnimationRunning => pendingSelectionOperations.Count > 0; + /// /// True when all mod icons have completed loading. /// @@ -49,7 +51,11 @@ namespace osu.Game.Overlays.Mods return new ModButton(m) { - SelectionChanged = Action, + SelectionChanged = mod => + { + ModButtonStateChanged(mod); + Action?.Invoke(mod); + }, }; }).ToArray(); @@ -78,6 +84,10 @@ namespace osu.Game.Overlays.Mods } } + protected virtual void ModButtonStateChanged(Mod mod) + { + } + private ModButton[] buttons = Array.Empty(); protected override bool OnKeyDown(KeyDownEvent e) @@ -94,44 +104,53 @@ namespace osu.Game.Overlays.Mods return base.OnKeyDown(e); } + private const double initial_multiple_selection_delay = 100; + + private readonly Queue pendingSelectionOperations = new Queue(); + + protected override void LoadComplete() + { + base.LoadComplete(); + + Scheduler.AddDelayed(() => + { + if (pendingSelectionOperations.TryDequeue(out var dequeuedAction)) + dequeuedAction(); + }, initial_multiple_selection_delay, true); + } + /// /// Selects all mods. /// public void SelectAll() { + pendingSelectionOperations.Clear(); + foreach (var button in buttons.Where(b => !b.Selected)) - button.SelectAt(0); + pendingSelectionOperations.Enqueue(() => button.SelectAt(0)); } /// /// Deselects all mods. /// - /// Set to true to bypass animations and update selections immediately. - public void DeselectAll(bool immediate = false) => DeselectTypes(buttons.Select(b => b.SelectedMod?.GetType()).Where(t => t != null), immediate); + public void DeselectAll() => DeselectTypes(buttons.Select(b => b.SelectedMod?.GetType()).Where(t => t != null)); /// /// Deselect one or more mods in this section. /// /// The types of s which should be deselected. - /// Set to true to bypass animations and update selections immediately. - public void DeselectTypes(IEnumerable modTypes, bool immediate = false) + public void DeselectTypes(IEnumerable modTypes) { - int delay = 0; + pendingSelectionOperations.Clear(); foreach (var button in buttons) { - Mod selected = button.SelectedMod; - if (selected == null) continue; + if (button.SelectedMod == null) continue; foreach (var type in modTypes) { - if (type.IsInstanceOfType(selected)) - { - if (immediate) - button.Deselect(); - else - Scheduler.AddDelayed(button.Deselect, delay += 50); - } + if (type.IsInstanceOfType(button.SelectedMod)) + pendingSelectionOperations.Enqueue(button.Deselect); } } } diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 97902d1c15..4f65a39ed3 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -14,7 +14,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; -using osu.Framework.Threading; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Containers; diff --git a/osu.Game/Overlays/Mods/SoloModSelectOverlay.cs b/osu.Game/Overlays/Mods/SoloModSelectOverlay.cs index d039ad1f98..aa0e78c126 100644 --- a/osu.Game/Overlays/Mods/SoloModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/SoloModSelectOverlay.cs @@ -12,7 +12,7 @@ namespace osu.Game.Overlays.Mods base.OnModSelected(mod); foreach (var section in ModSectionsContainer.Children) - section.DeselectTypes(mod.IncompatibleMods, true); + section.DeselectTypes(mod.IncompatibleMods); } } } diff --git a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs index 180f6079ac..7bc226bb3f 100644 --- a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs @@ -3,7 +3,6 @@ using System; using System.Linq; -using System.Reflection.Emit; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; @@ -72,7 +71,7 @@ namespace osu.Game.Screens.OnlinePlay private void deselectAll() { foreach (var section in ModSectionsContainer.Children) - section.DeselectAll(true); + section.DeselectAll(); } protected override ModSection CreateModSection(ModType type) => new FreeModSection(type); @@ -99,21 +98,21 @@ namespace osu.Game.Screens.OnlinePlay private void onCheckboxChanged(bool value) { - foreach (var button in ButtonsContainer.OfType()) - { - if (value) - button.SelectAt(0); - else - button.Deselect(); - } + if (value) + SelectAll(); + else + DeselectAll(); } - protected override void Update() + protected override void ModButtonStateChanged(Mod mod) { - base.Update(); + base.ModButtonStateChanged(mod); - var validButtons = ButtonsContainer.OfType().Where(b => b.Mod.HasImplementation); - checkbox.Current.Value = validButtons.All(b => b.Selected); + if (!SelectionAnimationRunning) + { + var validButtons = ButtonsContainer.OfType().Where(b => b.Mod.HasImplementation); + checkbox.Current.Value = validButtons.All(b => b.Selected); + } } } From 223b858227ed2a0d2aa3af284acbe581dbab84be Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Feb 2021 18:56:40 +0900 Subject: [PATCH 0352/1791] Ramp the animation speed --- osu.Game/Overlays/Mods/ModSection.cs | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index 495b1c05cd..b9640a6026 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -104,19 +104,31 @@ namespace osu.Game.Overlays.Mods return base.OnKeyDown(e); } - private const double initial_multiple_selection_delay = 100; + private const double initial_multiple_selection_delay = 120; + + private double selectionDelay = initial_multiple_selection_delay; + private double lastSelection; private readonly Queue pendingSelectionOperations = new Queue(); - protected override void LoadComplete() + protected override void Update() { - base.LoadComplete(); + base.Update(); - Scheduler.AddDelayed(() => + if (selectionDelay == initial_multiple_selection_delay || Time.Current - lastSelection >= selectionDelay) { if (pendingSelectionOperations.TryDequeue(out var dequeuedAction)) + { dequeuedAction(); - }, initial_multiple_selection_delay, true); + + selectionDelay = Math.Max(30, selectionDelay * 0.8f); + lastSelection = Time.Current; + } + else + { + selectionDelay = initial_multiple_selection_delay; + } + } } /// From a2674f3c3ff5a99d81127aae980c0ed05614fc47 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Feb 2021 18:58:56 +0900 Subject: [PATCH 0353/1791] Add comments --- osu.Game/Overlays/Mods/ModSection.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index b9640a6026..353f779b4f 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -121,11 +121,14 @@ namespace osu.Game.Overlays.Mods { dequeuedAction(); + // each time we play an animation, we decrease the time until the next animation (to ramp the visual and audible elements). selectionDelay = Math.Max(30, selectionDelay * 0.8f); lastSelection = Time.Current; } else { + // reset the selection delay after all animations have been completed. + // this will cause the next action to be immediately performed. selectionDelay = initial_multiple_selection_delay; } } From bf239f8bef321dbb90e61ebe8e453a13a22f11c8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Feb 2021 19:12:37 +0900 Subject: [PATCH 0354/1791] Flush animation on closing mod overlay --- osu.Game/Overlays/Mods/ModSection.cs | 9 +++++++++ osu.Game/Overlays/Mods/ModSelectOverlay.cs | 5 +++++ 2 files changed, 14 insertions(+) diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index 353f779b4f..440abfb1c0 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -231,5 +231,14 @@ namespace osu.Game.Overlays.Mods Font = OsuFont.GetFont(weight: FontWeight.Bold), Text = text }; + + /// + /// Play out all remaining animations immediately to leave mods in a good (final) state. + /// + public void FlushAnimation() + { + while (pendingSelectionOperations.TryDequeue(out var dequeuedAction)) + dequeuedAction(); + } } } diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 4f65a39ed3..93fe693937 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -379,6 +379,11 @@ namespace osu.Game.Overlays.Mods { base.PopOut(); + foreach (var section in ModSectionsContainer) + { + section.FlushAnimation(); + } + FooterContainer.MoveToX(content_width, WaveContainer.DISAPPEAR_DURATION, Easing.InSine); FooterContainer.FadeOut(WaveContainer.DISAPPEAR_DURATION, Easing.InSine); From 15062cc63f71ae8ae64b9a5343da7585a5bb5fa0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Feb 2021 19:29:48 +0900 Subject: [PATCH 0355/1791] Fix intermittent test failures --- .../Visual/UserInterface/TestSceneModSelectOverlay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 92104cfc72..7f4dfaa9b0 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -145,11 +145,11 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("double time visible", () => modSelect.ChildrenOfType().Any(b => b.Mods.Any(m => m is OsuModDoubleTime))); AddStep("make double time invalid", () => modSelect.IsValidMod = m => !(m is OsuModDoubleTime)); - AddAssert("double time not visible", () => modSelect.ChildrenOfType().All(b => !b.Mods.Any(m => m is OsuModDoubleTime))); + AddUntilStep("double time not visible", () => modSelect.ChildrenOfType().All(b => !b.Mods.Any(m => m is OsuModDoubleTime))); AddAssert("nightcore still visible", () => modSelect.ChildrenOfType().Any(b => b.Mods.Any(m => m is OsuModNightcore))); AddStep("make double time valid again", () => modSelect.IsValidMod = m => true); - AddAssert("double time visible", () => modSelect.ChildrenOfType().Any(b => b.Mods.Any(m => m is OsuModDoubleTime))); + AddUntilStep("double time visible", () => modSelect.ChildrenOfType().Any(b => b.Mods.Any(m => m is OsuModDoubleTime))); AddAssert("nightcore still visible", () => modSelect.ChildrenOfType().Any(b => b.Mods.Any(m => m is OsuModNightcore))); } From 8f2f1a444f25058760583545c3390ce0562b3e5d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Feb 2021 19:55:09 +0900 Subject: [PATCH 0356/1791] Avoid resetting selection on deselecting incompatibile types --- osu.Game/Overlays/Mods/ModSection.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index 440abfb1c0..3f93eec7df 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -148,7 +148,11 @@ namespace osu.Game.Overlays.Mods /// /// Deselects all mods. /// - public void DeselectAll() => DeselectTypes(buttons.Select(b => b.SelectedMod?.GetType()).Where(t => t != null)); + public void DeselectAll() + { + pendingSelectionOperations.Clear(); + DeselectTypes(buttons.Select(b => b.SelectedMod?.GetType()).Where(t => t != null)); + } /// /// Deselect one or more mods in this section. @@ -156,8 +160,6 @@ namespace osu.Game.Overlays.Mods /// The types of s which should be deselected. public void DeselectTypes(IEnumerable modTypes) { - pendingSelectionOperations.Clear(); - foreach (var button in buttons) { if (button.SelectedMod == null) continue; From cef16a9f61e6a9550f80b81f5c3cf6a2603c45e9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Feb 2021 19:55:15 +0900 Subject: [PATCH 0357/1791] Add test coverage of animation / selection flushing --- .../TestSceneModSelectOverlay.cs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 7f4dfaa9b0..44605f4994 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -8,6 +8,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -46,6 +47,32 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("show", () => modSelect.Show()); } + [Test] + public void TestAnimationFlushOnClose() + { + changeRuleset(0); + + AddStep("Select all fun mods", () => + { + modSelect.ModSectionsContainer + .Single(c => c.ModType == ModType.DifficultyIncrease) + .SelectAll(); + }); + + AddUntilStep("many mods selected", () => modDisplay.Current.Value.Count >= 5); + + AddStep("trigger deselect and close overlay", () => + { + modSelect.ModSectionsContainer + .Single(c => c.ModType == ModType.DifficultyIncrease) + .DeselectAll(); + + modSelect.Hide(); + }); + + AddAssert("all mods deselected", () => modDisplay.Current.Value.Count == 0); + } + [Test] public void TestOsuMods() { @@ -312,6 +339,9 @@ namespace osu.Game.Tests.Visual.UserInterface public bool AllLoaded => ModSectionsContainer.Children.All(c => c.ModIconsLoaded); + public new FillFlowContainer ModSectionsContainer => + base.ModSectionsContainer; + public ModButton GetModButton(Mod mod) { var section = ModSectionsContainer.Children.Single(s => s.ModType == mod.Type); From f86f3236254019b99ae64e37b3628159c6758c94 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Feb 2021 22:28:17 +0900 Subject: [PATCH 0358/1791] Add a basic guard against setting ScrollMethod too late in initialisation --- .../UI/DrawableManiaRuleset.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 6b34dbfa09..4ee060e91e 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.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.Collections.Generic; using System.Linq; using osu.Framework.Allocation; @@ -50,9 +51,21 @@ namespace osu.Game.Rulesets.Mania.UI protected new ManiaRulesetConfigManager Config => (ManiaRulesetConfigManager)base.Config; - public ScrollVisualisationMethod ScrollMethod = ScrollVisualisationMethod.Sequential; + public ScrollVisualisationMethod ScrollMethod + { + get => scrollMethod; + set + { + if (IsLoaded) + throw new InvalidOperationException($"Can't alter {nameof(ScrollMethod)} after ruleset is already loaded"); - protected override ScrollVisualisationMethod VisualisationMethod => ScrollMethod; + scrollMethod = value; + } + } + + private ScrollVisualisationMethod scrollMethod = ScrollVisualisationMethod.Sequential; + + protected override ScrollVisualisationMethod VisualisationMethod => scrollMethod; private readonly Bindable configDirection = new Bindable(); private readonly Bindable configTimeRange = new BindableDouble(); From 794f9e5e932294d8c77e9cdc5b0b995b8f8d9062 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Feb 2021 22:53:41 +0900 Subject: [PATCH 0359/1791] Add missing centre anchor/origin --- osu.Game/Graphics/UserInterface/OsuCheckbox.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Graphics/UserInterface/OsuCheckbox.cs b/osu.Game/Graphics/UserInterface/OsuCheckbox.cs index b80941c0bc..f6effa0834 100644 --- a/osu.Game/Graphics/UserInterface/OsuCheckbox.cs +++ b/osu.Game/Graphics/UserInterface/OsuCheckbox.cs @@ -76,6 +76,8 @@ namespace osu.Game.Graphics.UserInterface } else { + Nub.Anchor = Anchor.CentreLeft; + Nub.Origin = Anchor.CentreLeft; Nub.Margin = new MarginPadding { Left = nub_padding }; labelText.Padding = new MarginPadding { Left = Nub.EXPANDED_SIZE + nub_padding * 2 }; } From 0750c3cb6a3ae9f8bc614fe61f082e936ba4f705 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Feb 2021 23:44:46 +0900 Subject: [PATCH 0360/1791] Add back immediate deselection flow to ensure user selections can occur without contention --- osu.Game/Overlays/Mods/ModSection.cs | 10 ++++++++-- osu.Game/Overlays/Mods/SoloModSelectOverlay.cs | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index 3f93eec7df..ecbcba7ad3 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -158,7 +158,8 @@ namespace osu.Game.Overlays.Mods /// Deselect one or more mods in this section. /// /// The types of s which should be deselected. - public void DeselectTypes(IEnumerable modTypes) + /// 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) { @@ -167,7 +168,12 @@ namespace osu.Game.Overlays.Mods foreach (var type in modTypes) { if (type.IsInstanceOfType(button.SelectedMod)) - pendingSelectionOperations.Enqueue(button.Deselect); + { + if (immediate) + button.Deselect(); + else + pendingSelectionOperations.Enqueue(button.Deselect); + } } } } diff --git a/osu.Game/Overlays/Mods/SoloModSelectOverlay.cs b/osu.Game/Overlays/Mods/SoloModSelectOverlay.cs index aa0e78c126..d039ad1f98 100644 --- a/osu.Game/Overlays/Mods/SoloModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/SoloModSelectOverlay.cs @@ -12,7 +12,7 @@ namespace osu.Game.Overlays.Mods base.OnModSelected(mod); foreach (var section in ModSectionsContainer.Children) - section.DeselectTypes(mod.IncompatibleMods); + section.DeselectTypes(mod.IncompatibleMods, true); } } } From 18e50815232534bc456429b064ae3e71bc320a01 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 5 Feb 2021 00:42:38 +0900 Subject: [PATCH 0361/1791] Fix test failures --- .../Tests/Visual/Multiplayer/MultiplayerTestScene.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs index a87b22affe..d76f354774 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; using osu.Game.Online.Multiplayer; using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Lounge.Components; @@ -50,5 +51,13 @@ namespace osu.Game.Tests.Visual.Multiplayer if (joinRoom) RoomManager.Schedule(() => RoomManager.CreateRoom(Room)); }); + + public override void SetUpSteps() + { + base.SetUpSteps(); + + if (joinRoom) + AddUntilStep("wait for room join", () => Client.Room != null); + } } } From dbea6d4ceed7504ff104683efcdf316d3c712d47 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 5 Feb 2021 00:57:23 +0900 Subject: [PATCH 0362/1791] Remove unused using --- osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs index d76f354774..2e8c834c65 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs @@ -6,7 +6,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Testing; using osu.Game.Online.Multiplayer; using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Lounge.Components; From 4e530d2eaf45fc3c1b890b1016d2a6cbcec8658b Mon Sep 17 00:00:00 2001 From: Joehu Date: Wed, 3 Feb 2021 21:14:27 -0800 Subject: [PATCH 0363/1791] Remove old alpha hack from nub fill --- osu.Game/Graphics/UserInterface/Nub.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/Nub.cs b/osu.Game/Graphics/UserInterface/Nub.cs index 82b09e0821..18d8b880ea 100644 --- a/osu.Game/Graphics/UserInterface/Nub.cs +++ b/osu.Game/Graphics/UserInterface/Nub.cs @@ -42,13 +42,7 @@ namespace osu.Game.Graphics.UserInterface }, }; - Current.ValueChanged += filled => - { - if (filled.NewValue) - fill.FadeIn(200, Easing.OutQuint); - else - fill.FadeTo(0.01f, 200, Easing.OutQuint); //todo: remove once we figure why containers aren't drawing at all times - }; + Current.ValueChanged += filled => fill.FadeTo(filled.NewValue ? 1 : 0, 200, Easing.OutQuint); } [BackgroundDependencyLoader] From 9ef130cdccd46535c40db44b01b16f9624b4e36f Mon Sep 17 00:00:00 2001 From: Joehu Date: Thu, 4 Feb 2021 13:28:35 -0800 Subject: [PATCH 0364/1791] Fix codefactor style issues --- osu.Game.Rulesets.Catch/Skinning/Default/BorderPiece.cs | 1 - osu.Game.Rulesets.Catch/Skinning/Default/HyperBorderPiece.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/Default/BorderPiece.cs b/osu.Game.Rulesets.Catch/Skinning/Default/BorderPiece.cs index 7308d6b499..8d8ee49af7 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Default/BorderPiece.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Default/BorderPiece.cs @@ -29,4 +29,3 @@ namespace osu.Game.Rulesets.Catch.Skinning.Default } } } - diff --git a/osu.Game.Rulesets.Catch/Skinning/Default/HyperBorderPiece.cs b/osu.Game.Rulesets.Catch/Skinning/Default/HyperBorderPiece.cs index d160956a6e..c8895f32f4 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Default/HyperBorderPiece.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Default/HyperBorderPiece.cs @@ -19,4 +19,3 @@ namespace osu.Game.Rulesets.Catch.Skinning.Default } } } - From d62bbbb7627673b4dc03729639a54ad9b864079e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 5 Feb 2021 00:38:56 +0300 Subject: [PATCH 0365/1791] Enhance documentation Co-authored-by: Dan Balasescu --- osu.Game/Online/DownloadTrackingComposite.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/DownloadTrackingComposite.cs b/osu.Game/Online/DownloadTrackingComposite.cs index 69c6ebd07c..52042c266b 100644 --- a/osu.Game/Online/DownloadTrackingComposite.cs +++ b/osu.Game/Online/DownloadTrackingComposite.cs @@ -70,7 +70,7 @@ namespace osu.Game.Online /// Checks that a database model matches the one expected to be downloaded. /// /// - /// In the case of multiplayer/playlists, this has to check that the databased beatmap set with the selected beatmap matches what's online. + /// For online play, this could be used to check that the databased model matches the online beatmap. /// /// The model in database. protected virtual bool VerifyDatabasedModel([NotNull] TModel databasedModel) => true; From c0bd27fe832c0d05083107b0f488d7c979cce6d6 Mon Sep 17 00:00:00 2001 From: Joehu Date: Thu, 4 Feb 2021 13:55:15 -0800 Subject: [PATCH 0366/1791] Fix readme version badge not linking to correct page --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index efca075042..e09b4d86a5 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ # osu! [![Build status](https://ci.appveyor.com/api/projects/status/u2p01nx7l6og8buh?svg=true)](https://ci.appveyor.com/project/peppy/osu) -[![GitHub release](https://img.shields.io/github/release/ppy/osu.svg)]() +[![GitHub release](https://img.shields.io/github/release/ppy/osu.svg)](https://github.com/ppy/osu/releases/latest) [![CodeFactor](https://www.codefactor.io/repository/github/ppy/osu/badge)](https://www.codefactor.io/repository/github/ppy/osu) [![dev chat](https://discordapp.com/api/guilds/188630481301012481/widget.png?style=shield)](https://discord.gg/ppy) From a2fdba3e5173441147761ace92894f54cc6f309f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 5 Feb 2021 12:24:38 +0900 Subject: [PATCH 0367/1791] Rename to OnlinePlayBeatmapAvailabilityTracker --- ...s => TestSceneOnlinePlayBeatmapAvailabilityTracker.cs} | 8 ++++---- ...racker.cs => OnlinePlayBeatmapAvailablilityTracker.cs} | 4 ++-- osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) rename osu.Game.Tests/Online/{TestSceneMultiplayerBeatmapAvailabilityTracker.cs => TestSceneOnlinePlayBeatmapAvailabilityTracker.cs} (96%) rename osu.Game/Online/Rooms/{MultiplayerBeatmapAvailablilityTracker.cs => OnlinePlayBeatmapAvailablilityTracker.cs} (95%) diff --git a/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs similarity index 96% rename from osu.Game.Tests/Online/TestSceneMultiplayerBeatmapAvailabilityTracker.cs rename to osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index 646c4139af..d3475de157 100644 --- a/osu.Game.Tests/Online/TestSceneMultiplayerBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -28,7 +28,7 @@ using osu.Game.Tests.Visual; namespace osu.Game.Tests.Online { [HeadlessTest] - public class TestSceneMultiplayerBeatmapAvailabilityTracker : OsuTestScene + public class TestSceneOnlinePlayBeatmapAvailabilityTracker : OsuTestScene { private RulesetStore rulesets; private TestBeatmapManager beatmaps; @@ -38,7 +38,7 @@ namespace osu.Game.Tests.Online private BeatmapSetInfo testBeatmapSet; private readonly Bindable selectedItem = new Bindable(); - private MultiplayerBeatmapAvailablilityTracker availablilityTracker; + private OnlinePlayBeatmapAvailablilityTracker availablilityTracker; [BackgroundDependencyLoader] private void load(AudioManager audio, GameHost host) @@ -67,7 +67,7 @@ namespace osu.Game.Tests.Online Ruleset = { Value = testBeatmapInfo.Ruleset }, }; - Child = availablilityTracker = new MultiplayerBeatmapAvailablilityTracker + Child = availablilityTracker = new OnlinePlayBeatmapAvailablilityTracker { SelectedItem = { BindTarget = selectedItem, } }; @@ -118,7 +118,7 @@ namespace osu.Game.Tests.Online }); addAvailabilityCheckStep("state still not downloaded", BeatmapAvailability.NotDownloaded); - AddStep("recreate tracker", () => Child = availablilityTracker = new MultiplayerBeatmapAvailablilityTracker + AddStep("recreate tracker", () => Child = availablilityTracker = new OnlinePlayBeatmapAvailablilityTracker { SelectedItem = { BindTarget = selectedItem } }); diff --git a/osu.Game/Online/Rooms/MultiplayerBeatmapAvailablilityTracker.cs b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailablilityTracker.cs similarity index 95% rename from osu.Game/Online/Rooms/MultiplayerBeatmapAvailablilityTracker.cs rename to osu.Game/Online/Rooms/OnlinePlayBeatmapAvailablilityTracker.cs index 578c4db2f8..ad4b3c5151 100644 --- a/osu.Game/Online/Rooms/MultiplayerBeatmapAvailablilityTracker.cs +++ b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailablilityTracker.cs @@ -15,7 +15,7 @@ namespace osu.Game.Online.Rooms /// This differs from a regular download tracking composite as this accounts for the /// databased beatmap set's checksum, to disallow from playing with an altered version of the beatmap. /// - public class MultiplayerBeatmapAvailablilityTracker : DownloadTrackingComposite + public class OnlinePlayBeatmapAvailablilityTracker : DownloadTrackingComposite { public readonly IBindable SelectedItem = new Bindable(); @@ -26,7 +26,7 @@ namespace osu.Game.Online.Rooms private readonly Bindable availability = new Bindable(); - public MultiplayerBeatmapAvailablilityTracker() + public OnlinePlayBeatmapAvailablilityTracker() { State.BindValueChanged(_ => updateAvailability()); Progress.BindValueChanged(_ => updateAvailability(), true); diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index cdf889e4f1..b1b3dde26e 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -41,11 +41,11 @@ namespace osu.Game.Screens.OnlinePlay.Match private IBindable> managerUpdated; [Cached] - protected MultiplayerBeatmapAvailablilityTracker BeatmapAvailablilityTracker { get; } + protected OnlinePlayBeatmapAvailablilityTracker BeatmapAvailablilityTracker { get; } protected RoomSubScreen() { - BeatmapAvailablilityTracker = new MultiplayerBeatmapAvailablilityTracker + BeatmapAvailablilityTracker = new OnlinePlayBeatmapAvailablilityTracker { SelectedItem = { BindTarget = SelectedItem }, }; From 85e63afcb48288f7838409c5b14380507ea3443d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 5 Feb 2021 12:36:25 +0900 Subject: [PATCH 0368/1791] Rename Mods -> RequiredMods --- osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs | 6 +++--- osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs index d0e19d9f37..4fb9d724b5 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs @@ -30,7 +30,7 @@ namespace osu.Game.Online.Multiplayer [NotNull] [Key(4)] - public IEnumerable Mods { get; set; } = Enumerable.Empty(); + public IEnumerable RequiredMods { get; set; } = Enumerable.Empty(); [NotNull] [Key(5)] @@ -39,14 +39,14 @@ namespace osu.Game.Online.Multiplayer public bool Equals(MultiplayerRoomSettings other) => BeatmapID == other.BeatmapID && BeatmapChecksum == other.BeatmapChecksum - && Mods.SequenceEqual(other.Mods) + && RequiredMods.SequenceEqual(other.RequiredMods) && AllowedMods.SequenceEqual(other.AllowedMods) && RulesetID == other.RulesetID && Name.Equals(other.Name, StringComparison.Ordinal); public override string ToString() => $"Name:{Name}" + $" Beatmap:{BeatmapID} ({BeatmapChecksum})" - + $" Mods:{string.Join(',', Mods)}" + + $" RequiredMods:{string.Join(',', RequiredMods)}" + $" AllowedMods:{string.Join(',', AllowedMods)}" + $" Ruleset:{RulesetID}"; } diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index 69df2a69cc..aedbe37d56 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -192,7 +192,7 @@ namespace osu.Game.Online.Multiplayer BeatmapID = item.GetOr(existingPlaylistItem).BeatmapID, BeatmapChecksum = item.GetOr(existingPlaylistItem).Beatmap.Value.MD5Hash, RulesetID = item.GetOr(existingPlaylistItem).RulesetID, - Mods = item.HasValue ? item.Value.AsNonNull().RequiredMods.Select(m => new APIMod(m)).ToList() : Room.Settings.Mods, + RequiredMods = item.HasValue ? item.Value.AsNonNull().RequiredMods.Select(m => new APIMod(m)).ToList() : Room.Settings.RequiredMods, AllowedMods = item.HasValue ? item.Value.AsNonNull().AllowedMods.Select(m => new APIMod(m)).ToList() : Room.Settings.AllowedMods }); } @@ -531,7 +531,7 @@ namespace osu.Game.Online.Multiplayer beatmap.MD5Hash = settings.BeatmapChecksum; var ruleset = rulesets.GetRuleset(settings.RulesetID).CreateInstance(); - var mods = settings.Mods.Select(m => m.ToMod(ruleset)); + var mods = settings.RequiredMods.Select(m => m.ToMod(ruleset)); var allowedMods = settings.AllowedMods.Select(m => m.ToMod(ruleset)); PlaylistItem playlistItem = new PlaylistItem From 2e85ce5b824eeafe85f3b9b058b89601690314ea Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 5 Feb 2021 12:40:16 +0900 Subject: [PATCH 0369/1791] Rename UserMods -> Mods for MultiplayerRoomUser --- osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs | 2 +- osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs | 2 +- .../OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs b/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs index 3271133bcd..c654127b94 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs @@ -36,7 +36,7 @@ namespace osu.Game.Online.Multiplayer /// [Key(3)] [NotNull] - public IEnumerable UserMods { get; set; } = Enumerable.Empty(); + public IEnumerable Mods { get; set; } = Enumerable.Empty(); [IgnoreMember] public User? User { get; set; } diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index aedbe37d56..f7c9193dfe 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -400,7 +400,7 @@ namespace osu.Game.Online.Multiplayer if (user == null) return; - user.UserMods = mods; + user.Mods = mods; RoomUpdated?.Invoke(); }, false); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index 8036e5f702..2983d1268d 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs @@ -165,7 +165,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants var ruleset = rulesets.GetRuleset(Room.Settings.RulesetID).CreateInstance(); userStateDisplay.Status = User.State; - userModsDisplay.Current.Value = User.UserMods.Select(m => m.ToMod(ruleset)).ToList(); + userModsDisplay.Current.Value = User.Mods.Select(m => m.ToMod(ruleset)).ToList(); if (Room.Host?.Equals(User) == true) crown.FadeIn(fade_time); From 8004c19a8037bb4faece8646d604b1cb3c13ba97 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 5 Feb 2021 12:40:42 +0900 Subject: [PATCH 0370/1791] Remove ModValidationTest --- osu.Game.Tests/Mods/ModValidationTest.cs | 64 ------------------------ 1 file changed, 64 deletions(-) delete mode 100644 osu.Game.Tests/Mods/ModValidationTest.cs diff --git a/osu.Game.Tests/Mods/ModValidationTest.cs b/osu.Game.Tests/Mods/ModValidationTest.cs deleted file mode 100644 index 991adc221e..0000000000 --- a/osu.Game.Tests/Mods/ModValidationTest.cs +++ /dev/null @@ -1,64 +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 Moq; -using NUnit.Framework; -using osu.Game.Rulesets.Mods; -using osu.Game.Utils; - -namespace osu.Game.Tests.Mods -{ - [TestFixture] - public class ModValidationTest - { - [Test] - public void TestModIsCompatibleByItself() - { - var mod = new Mock(); - Assert.That(ModUtils.CheckCompatibleSet(new[] { mod.Object })); - } - - [Test] - public void TestIncompatibleThroughTopLevel() - { - var mod1 = new Mock(); - var mod2 = new Mock(); - - mod1.Setup(m => m.IncompatibleMods).Returns(new[] { mod2.Object.GetType() }); - - // Test both orderings. - Assert.That(ModUtils.CheckCompatibleSet(new[] { mod1.Object, mod2.Object }), Is.False); - Assert.That(ModUtils.CheckCompatibleSet(new[] { mod2.Object, mod1.Object }), Is.False); - } - - [Test] - public void TestIncompatibleThroughMultiMod() - { - var mod1 = new Mock(); - - // The nested mod. - var mod2 = new Mock(); - mod2.Setup(m => m.IncompatibleMods).Returns(new[] { mod1.Object.GetType() }); - - var multiMod = new MultiMod(new MultiMod(mod2.Object)); - - // Test both orderings. - Assert.That(ModUtils.CheckCompatibleSet(new[] { multiMod, mod1.Object }), Is.False); - Assert.That(ModUtils.CheckCompatibleSet(new[] { mod1.Object, multiMod }), Is.False); - } - - [Test] - public void TestAllowedThroughMostDerivedType() - { - var mod = new Mock(); - Assert.That(ModUtils.CheckAllowed(new[] { mod.Object }, new[] { mod.Object.GetType() })); - } - - [Test] - public void TestNotAllowedThroughBaseType() - { - var mod = new Mock(); - Assert.That(ModUtils.CheckAllowed(new[] { mod.Object }, new[] { typeof(Mod) }), Is.False); - } - } -} From df2da5950f2f7b1fd233f75900e3979a362204e3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 5 Feb 2021 13:05:11 +0900 Subject: [PATCH 0371/1791] Add back vertical spacer --- .../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 4664ac6bfe..0466c8209f 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -104,6 +104,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer Child = new GridContainer { RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.Relative, size: 0.5f, maxSize: 400), + new Dimension(), + new Dimension(GridSizeMode.Relative, size: 0.5f, maxSize: 600), + }, Content = new[] { new Drawable[] @@ -128,6 +134,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer } } }, + // Spacer + null, // Main right column new FillFlowContainer { From c5fa818630c25ee7b3912de0d1bfeca9194bc7a3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Feb 2021 14:08:11 +0900 Subject: [PATCH 0372/1791] Actually handle case of failing to achieve lock on SemaphoreSlim --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 9067c9a738..36cdf3bc8f 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -67,7 +67,8 @@ namespace osu.Game.Online.Multiplayer { cancelExistingConnect(); - await connectionLock.WaitAsync(10000); + if (!await connectionLock.WaitAsync(10000)) + throw new TimeoutException("Could not obtain a lock to connect. A previous attempt is likely stuck."); var builder = new HubConnectionBuilder() .WithUrl(endpoint, options => { options.Headers.Add("Authorization", $"Bearer {api.AccessToken}"); }); @@ -199,7 +200,10 @@ namespace osu.Game.Online.Multiplayer cancelExistingConnect(); if (takeLock) - await connectionLock.WaitAsync(10000); + { + if (!await connectionLock.WaitAsync(10000)) + throw new TimeoutException("Could not obtain a lock to disconnect. A previous attempt is likely stuck."); + } try { From fc37d8b7df27a398f0343fcefbb6f15ce3b9450f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Feb 2021 14:25:19 +0900 Subject: [PATCH 0373/1791] Refactor content redirection logic to be easier to parse --- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 19b68ee6d6..722b167387 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -45,18 +45,21 @@ namespace osu.Game.Screens.OnlinePlay.Match [Cached] protected OnlinePlayBeatmapAvailablilityTracker BeatmapAvailablilityTracker { get; } - private readonly Container content = new Container { RelativeSizeAxes = Axes.Both }; + private readonly Container content; protected RoomSubScreen() { - BeatmapAvailablilityTracker = new OnlinePlayBeatmapAvailablilityTracker + InternalChildren = new Drawable[] { - SelectedItem = { BindTarget = SelectedItem } + BeatmapAvailablilityTracker = new OnlinePlayBeatmapAvailablilityTracker + { + SelectedItem = { BindTarget = SelectedItem } + }, + content = new Container { RelativeSizeAxes = Axes.Both }, }; - - base.AddInternal(content); } + // Forward all internal management to content to ensure locally added components are not removed unintentionally. // This is a bit ugly but we don't have the concept of InternalContent so it'll have to do for now. (https://github.com/ppy/osu-framework/issues/1690) protected override void AddInternal(Drawable drawable) => content.Add(drawable); protected override bool RemoveInternal(Drawable drawable) => content.Remove(drawable); @@ -65,8 +68,6 @@ namespace osu.Game.Screens.OnlinePlay.Match [BackgroundDependencyLoader] private void load(AudioManager audio) { - base.AddInternal(BeatmapAvailablilityTracker); - sampleStart = audio.Samples.Get(@"SongSelect/confirm-selection"); } From de8724b1f6b7d714260589dab5f5619afea0ca6f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Feb 2021 14:39:25 +0900 Subject: [PATCH 0374/1791] Use AddRangeInternal for simplicity, but disallow ClearInternal for safety --- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 21 +++++-------------- .../Multiplayer/MultiplayerMatchSubScreen.cs | 4 ++-- .../Playlists/PlaylistsRoomSubScreen.cs | 4 ++-- 3 files changed, 9 insertions(+), 20 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 722b167387..8be0e89665 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -7,8 +7,6 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Game.Audio; using osu.Game.Beatmaps; @@ -45,25 +43,16 @@ namespace osu.Game.Screens.OnlinePlay.Match [Cached] protected OnlinePlayBeatmapAvailablilityTracker BeatmapAvailablilityTracker { get; } - private readonly Container content; - protected RoomSubScreen() { - InternalChildren = new Drawable[] + AddInternal(BeatmapAvailablilityTracker = new OnlinePlayBeatmapAvailablilityTracker { - BeatmapAvailablilityTracker = new OnlinePlayBeatmapAvailablilityTracker - { - SelectedItem = { BindTarget = SelectedItem } - }, - content = new Container { RelativeSizeAxes = Axes.Both }, - }; + SelectedItem = { BindTarget = SelectedItem } + }); } - // Forward all internal management to content to ensure locally added components are not removed unintentionally. - // This is a bit ugly but we don't have the concept of InternalContent so it'll have to do for now. (https://github.com/ppy/osu-framework/issues/1690) - protected override void AddInternal(Drawable drawable) => content.Add(drawable); - protected override bool RemoveInternal(Drawable drawable) => content.Remove(drawable); - protected override void ClearInternal(bool disposeChildren = true) => content.Clear(disposeChildren); + protected override void ClearInternal(bool disposeChildren = true) => + throw new InvalidOperationException($"{nameof(RoomSubScreen)}'s children should not be cleared as it will remove required components"); [BackgroundDependencyLoader] private void load(AudioManager audio) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 8cc2e9061b..e1524067da 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -54,7 +54,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer [BackgroundDependencyLoader] private void load() { - InternalChildren = new Drawable[] + AddRangeInternal(new Drawable[] { mainContent = new GridContainer { @@ -177,7 +177,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer RelativeSizeAxes = Axes.Both, State = { Value = client.Room == null ? Visibility.Visible : Visibility.Hidden } } - }; + }); if (client.Room == null) { diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index 745e5a58bf..0b8026044b 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -44,7 +44,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists [BackgroundDependencyLoader] private void load() { - InternalChildren = new Drawable[] + AddRangeInternal(new Drawable[] { mainContent = new GridContainer { @@ -190,7 +190,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists EditPlaylist = () => this.Push(new MatchSongSelect()), State = { Value = roomId.Value == null ? Visibility.Visible : Visibility.Hidden } } - }; + }); if (roomId.Value == null) { From 730e66f0ee00aec2b6c9f2a2f03ca56ec152a136 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 5 Feb 2021 09:07:59 +0300 Subject: [PATCH 0375/1791] Make pausing on window focus lose instant --- .../Screens/Play/HUD/HoldForMenuButton.cs | 40 ------------------- osu.Game/Screens/Play/Player.cs | 21 +++++++--- 2 files changed, 16 insertions(+), 45 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs index 387c0e587b..284ac899ed 100644 --- a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs +++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs @@ -88,11 +88,6 @@ namespace osu.Game.Screens.Play.HUD return base.OnMouseMove(e); } - public bool PauseOnFocusLost - { - set => button.PauseOnFocusLost = value; - } - protected override void Update() { base.Update(); @@ -120,8 +115,6 @@ namespace osu.Game.Screens.Play.HUD public Action HoverGained; public Action HoverLost; - private readonly IBindable gameActive = new Bindable(true); - [BackgroundDependencyLoader] private void load(OsuColour colours, Framework.Game game) { @@ -164,14 +157,6 @@ namespace osu.Game.Screens.Play.HUD }; bind(); - - gameActive.BindTo(game.IsActive); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - gameActive.BindValueChanged(_ => updateActive(), true); } private void bind() @@ -221,31 +206,6 @@ namespace osu.Game.Screens.Play.HUD base.OnHoverLost(e); } - private bool pauseOnFocusLost = true; - - public bool PauseOnFocusLost - { - set - { - if (pauseOnFocusLost == value) - return; - - pauseOnFocusLost = value; - if (IsLoaded) - updateActive(); - } - } - - private void updateActive() - { - if (!pauseOnFocusLost || IsPaused.Value) return; - - if (gameActive.Value) - AbortConfirm(); - else - BeginConfirm(); - } - public bool OnPressed(GlobalAction action) { switch (action) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index b622f11775..556964bca4 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -59,6 +59,8 @@ namespace osu.Game.Screens.Play // We are managing our own adjustments (see OnEntering/OnExiting). public override bool AllowRateAdjustments => false; + private readonly IBindable gameActive = new Bindable(true); + private readonly Bindable samplePlaybackDisabled = new Bindable(); /// @@ -154,6 +156,9 @@ namespace osu.Game.Screens.Play // replays should never be recorded or played back when autoplay is enabled if (!Mods.Value.Any(m => m is ModAutoplay)) PrepareReplay(); + + // needs to be bound here as the last binding, otherwise starting a replay while not focused causes player to exit. + gameActive.BindValueChanged(_ => updatePauseOnFocusLostState(), true); } [CanBeNull] @@ -170,7 +175,7 @@ namespace osu.Game.Screens.Play } [BackgroundDependencyLoader(true)] - private void load(AudioManager audio, OsuConfigManager config, OsuGame game) + private void load(AudioManager audio, OsuConfigManager config, OsuGame game, OsuGameBase gameBase) { Mods.Value = base.Mods.Value.Select(m => m.CreateCopy()).ToArray(); @@ -186,6 +191,8 @@ namespace osu.Game.Screens.Play mouseWheelDisabled = config.GetBindable(OsuSetting.MouseDisableWheel); + gameActive.BindTo(gameBase.IsActive); + if (game != null) LocalUserPlaying.BindTo(game.LocalUserPlaying); @@ -420,10 +427,14 @@ namespace osu.Game.Screens.Play samplePlaybackDisabled.Value = DrawableRuleset.FrameStableClock.IsCatchingUp.Value || GameplayClockContainer.GameplayClock.IsPaused.Value; } - private void updatePauseOnFocusLostState() => - HUDOverlay.HoldToQuit.PauseOnFocusLost = PauseOnFocusLost - && !DrawableRuleset.HasReplayLoaded.Value - && !breakTracker.IsBreakTime.Value; + private void updatePauseOnFocusLostState() + { + if (!IsLoaded || !PauseOnFocusLost || DrawableRuleset.HasReplayLoaded.Value || breakTracker.IsBreakTime.Value) + return; + + if (gameActive.Value == false) + performUserRequestedExit(); + } private IBeatmap loadPlayableBeatmap() { From 0528469b440d33308cdc61c7d2a136027e4731e4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 5 Feb 2021 14:04:04 +0900 Subject: [PATCH 0376/1791] Rename OrderedHitPolicy -> StartTimeOrderedHitPolicy --- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 4 ++-- .../UI/{OrderedHitPolicy.cs => StartTimeOrderedHitPolicy.cs} | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename osu.Game.Rulesets.Osu/UI/{OrderedHitPolicy.cs => StartTimeOrderedHitPolicy.cs} (97%) diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 975b444699..5c77112df7 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.UI private readonly ProxyContainer spinnerProxies; private readonly JudgementContainer judgementLayer; private readonly FollowPointRenderer followPoints; - private readonly OrderedHitPolicy hitPolicy; + private readonly StartTimeOrderedHitPolicy hitPolicy; public static readonly Vector2 BASE_SIZE = new Vector2(512, 384); @@ -54,7 +54,7 @@ namespace osu.Game.Rulesets.Osu.UI approachCircles = new ProxyContainer { RelativeSizeAxes = Axes.Both }, }; - hitPolicy = new OrderedHitPolicy(HitObjectContainer); + hitPolicy = new StartTimeOrderedHitPolicy(HitObjectContainer); var hitWindows = new OsuHitWindows(); diff --git a/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs b/osu.Game.Rulesets.Osu/UI/StartTimeOrderedHitPolicy.cs similarity index 97% rename from osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs rename to osu.Game.Rulesets.Osu/UI/StartTimeOrderedHitPolicy.cs index 8e4f81347d..1d9a5010ed 100644 --- a/osu.Game.Rulesets.Osu/UI/OrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu/UI/StartTimeOrderedHitPolicy.cs @@ -18,11 +18,11 @@ namespace osu.Game.Rulesets.Osu.UI /// The hit causes all previous s to missed otherwise. /// /// - public class OrderedHitPolicy + public class StartTimeOrderedHitPolicy { private readonly HitObjectContainer hitObjectContainer; - public OrderedHitPolicy(HitObjectContainer hitObjectContainer) + public StartTimeOrderedHitPolicy(HitObjectContainer hitObjectContainer) { this.hitObjectContainer = hitObjectContainer; } From df1df8184771f096a58acc095efc576485c7301c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 5 Feb 2021 14:04:21 +0900 Subject: [PATCH 0377/1791] Better indicate ordering --- osu.Game.Rulesets.Osu/UI/StartTimeOrderedHitPolicy.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/UI/StartTimeOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu/UI/StartTimeOrderedHitPolicy.cs index 1d9a5010ed..2b13a36e47 100644 --- a/osu.Game.Rulesets.Osu/UI/StartTimeOrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu/UI/StartTimeOrderedHitPolicy.cs @@ -11,7 +11,7 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Osu.UI { /// - /// Ensures that s are hit in-order. Affectionately known as "note lock". + /// Ensures that s are hit in-order of their start times. Affectionately known as "note lock". /// If a is hit out of order: /// /// The hit is blocked if it occurred earlier than the previous 's start time. From 08aae011c10880a24204717cfea5eb5c2629571b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 5 Feb 2021 14:04:32 +0900 Subject: [PATCH 0378/1791] Add IHitPolicy interface --- osu.Game.Rulesets.Osu/UI/IHitPolicy.cs | 25 +++++++++++++++++++ .../UI/StartTimeOrderedHitPolicy.cs | 13 ++-------- 2 files changed, 27 insertions(+), 11 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/UI/IHitPolicy.cs diff --git a/osu.Game.Rulesets.Osu/UI/IHitPolicy.cs b/osu.Game.Rulesets.Osu/UI/IHitPolicy.cs new file mode 100644 index 0000000000..fcc18b5f6f --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/IHitPolicy.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 osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; + +namespace osu.Game.Rulesets.Osu.UI +{ + public interface IHitPolicy + { + /// + /// Determines whether a can be hit at a point in time. + /// + /// The to check. + /// The time to check. + /// Whether can be hit at the given . + bool IsHittable(DrawableHitObject hitObject, double time); + + /// + /// Handles a being hit. + /// + /// The that was hit. + void HandleHit(DrawableHitObject hitObject); + } +} diff --git a/osu.Game.Rulesets.Osu/UI/StartTimeOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu/UI/StartTimeOrderedHitPolicy.cs index 2b13a36e47..2b5a440f67 100644 --- a/osu.Game.Rulesets.Osu/UI/StartTimeOrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu/UI/StartTimeOrderedHitPolicy.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.UI /// The hit causes all previous s to missed otherwise. /// /// - public class StartTimeOrderedHitPolicy + public class StartTimeOrderedHitPolicy : IHitPolicy { private readonly HitObjectContainer hitObjectContainer; @@ -27,12 +27,6 @@ namespace osu.Game.Rulesets.Osu.UI this.hitObjectContainer = hitObjectContainer; } - /// - /// Determines whether a can be hit at a point in time. - /// - /// The to check. - /// The time to check. - /// Whether can be hit at the given . public bool IsHittable(DrawableHitObject hitObject, double time) { DrawableHitObject blockingObject = null; @@ -54,10 +48,6 @@ namespace osu.Game.Rulesets.Osu.UI return blockingObject.Judged || time >= blockingObject.HitObject.StartTime; } - /// - /// Handles a being hit to potentially miss all earlier s. - /// - /// The that was hit. public void HandleHit(DrawableHitObject hitObject) { // Hitobjects which themselves don't block future hitobjects don't cause misses (e.g. slider ticks, spinners). @@ -67,6 +57,7 @@ namespace osu.Game.Rulesets.Osu.UI if (!IsHittable(hitObject, hitObject.HitObject.StartTime + hitObject.Result.TimeOffset)) throw new InvalidOperationException($"A {hitObject} was hit before it became hittable!"); + // Miss all hitobjects prior to the hit one. foreach (var obj in enumerateHitObjectsUpTo(hitObject.HitObject.StartTime)) { if (obj.Judged) From 8adf37d958abc4bb3b42e600149a4b0fdbbbaa16 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 5 Feb 2021 14:09:25 +0900 Subject: [PATCH 0379/1791] Add SetHitObjects() to IHitPolicy instead of using ctor --- osu.Game.Rulesets.Osu/UI/IHitPolicy.cs | 7 +++++++ osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 3 ++- osu.Game.Rulesets.Osu/UI/StartTimeOrderedHitPolicy.cs | 10 +++------- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/IHitPolicy.cs b/osu.Game.Rulesets.Osu/UI/IHitPolicy.cs index fcc18b5f6f..72c3d781bb 100644 --- a/osu.Game.Rulesets.Osu/UI/IHitPolicy.cs +++ b/osu.Game.Rulesets.Osu/UI/IHitPolicy.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; @@ -8,6 +9,12 @@ namespace osu.Game.Rulesets.Osu.UI { public interface IHitPolicy { + /// + /// Sets the s which this controls. + /// + /// An enumeration of the s. + void SetHitObjects(IEnumerable hitObjects); + /// /// Determines whether a can be hit at a point in time. /// diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 5c77112df7..c7900558a0 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -54,7 +54,8 @@ namespace osu.Game.Rulesets.Osu.UI approachCircles = new ProxyContainer { RelativeSizeAxes = Axes.Both }, }; - hitPolicy = new StartTimeOrderedHitPolicy(HitObjectContainer); + hitPolicy = new StartTimeOrderedHitPolicy(); + hitPolicy.SetHitObjects(HitObjectContainer.AliveObjects); var hitWindows = new OsuHitWindows(); diff --git a/osu.Game.Rulesets.Osu/UI/StartTimeOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu/UI/StartTimeOrderedHitPolicy.cs index 2b5a440f67..38ba5fc490 100644 --- a/osu.Game.Rulesets.Osu/UI/StartTimeOrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu/UI/StartTimeOrderedHitPolicy.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Osu.UI { @@ -20,12 +19,9 @@ namespace osu.Game.Rulesets.Osu.UI /// public class StartTimeOrderedHitPolicy : IHitPolicy { - private readonly HitObjectContainer hitObjectContainer; + private IEnumerable hitObjects; - public StartTimeOrderedHitPolicy(HitObjectContainer hitObjectContainer) - { - this.hitObjectContainer = hitObjectContainer; - } + public void SetHitObjects(IEnumerable hitObjects) => this.hitObjects = hitObjects; public bool IsHittable(DrawableHitObject hitObject, double time) { @@ -77,7 +73,7 @@ namespace osu.Game.Rulesets.Osu.UI private IEnumerable enumerateHitObjectsUpTo(double targetTime) { - foreach (var obj in hitObjectContainer.AliveObjects) + foreach (var obj in hitObjects) { if (obj.HitObject.StartTime >= targetTime) yield break; From c9481ebbafa4c28ba21730448dcdfbf2b71d59e6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 5 Feb 2021 14:49:33 +0900 Subject: [PATCH 0380/1791] Rename test scene --- ...eOutOfOrderHits.cs => TestSceneStartTimeOrderedHitPolicy.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename osu.Game.Rulesets.Osu.Tests/{TestSceneOutOfOrderHits.cs => TestSceneStartTimeOrderedHitPolicy.cs} (99%) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneStartTimeOrderedHitPolicy.cs similarity index 99% rename from osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs rename to osu.Game.Rulesets.Osu.Tests/TestSceneStartTimeOrderedHitPolicy.cs index 296b421a11..b8c1217a73 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneStartTimeOrderedHitPolicy.cs @@ -25,7 +25,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Tests { - public class TestSceneOutOfOrderHits : RateAdjustedBeatmapTestScene + public class TestSceneStartTimeOrderedHitPolicy : RateAdjustedBeatmapTestScene { private const double early_miss_window = 1000; // time after -1000 to -500 is considered a miss private const double late_miss_window = 500; // time after +500 is considered a miss From 64a2c7825e89811dec78f803507fdd4c8696cc52 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 5 Feb 2021 14:53:47 +0900 Subject: [PATCH 0381/1791] Fix incorrect assert --- .../TestSceneStartTimeOrderedHitPolicy.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneStartTimeOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneStartTimeOrderedHitPolicy.cs index b8c1217a73..177a4f50a1 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneStartTimeOrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneStartTimeOrderedHitPolicy.cs @@ -169,7 +169,7 @@ namespace osu.Game.Rulesets.Osu.Tests addJudgementAssert(hitObjects[0], HitResult.Great); addJudgementAssert(hitObjects[1], HitResult.Great); addJudgementOffsetAssert(hitObjects[0], -200); // time_first_circle - 200 - addJudgementOffsetAssert(hitObjects[0], -200); // time_second_circle - first_circle_time - 100 + addJudgementOffsetAssert(hitObjects[1], -200); // time_second_circle - first_circle_time - 100 } /// From 4bc324f040b3bfaca1e57af88f00558a6237d6bc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Feb 2021 15:29:32 +0900 Subject: [PATCH 0382/1791] Rename parameter to make more sense --- osu.Game/Graphics/UserInterface/ProgressBar.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/ProgressBar.cs b/osu.Game/Graphics/UserInterface/ProgressBar.cs index 4ee1c73bf5..50367e600e 100644 --- a/osu.Game/Graphics/UserInterface/ProgressBar.cs +++ b/osu.Game/Graphics/UserInterface/ProgressBar.cs @@ -40,13 +40,18 @@ namespace osu.Game.Graphics.UserInterface set => CurrentNumber.Value = value; } - private readonly bool userInteractive; - public override bool HandlePositionalInput => userInteractive; - public override bool HandleNonPositionalInput => userInteractive; + private readonly bool allowSeek; - public ProgressBar(bool userInteractive) + public override bool HandlePositionalInput => allowSeek; + public override bool HandleNonPositionalInput => allowSeek; + + /// + /// Construct a new progress bar. + /// + /// Whether the user should be allowed to click/drag to adjust the value. + public ProgressBar(bool allowSeek) { - this.userInteractive = userInteractive; + this.allowSeek = allowSeek; CurrentNumber.MinValue = 0; CurrentNumber.MaxValue = 1; From d1f9aa52a4c528053d7ee45344ad5ec15dff4fe5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Feb 2021 15:33:48 +0900 Subject: [PATCH 0383/1791] Inline variable --- .../OnlinePlay/Multiplayer/Participants/StateDisplay.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs index 4245628a59..c117c026dc 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs @@ -101,9 +101,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants case DownloadState.Downloading: Debug.Assert(availability.DownloadProgress != null); - var progress = availability.DownloadProgress.Value; progressBar.FadeIn(fade_time); - progressBar.CurrentTime = progress; + progressBar.CurrentTime = availability.DownloadProgress.Value; text.Text = "downloading map"; icon.Icon = FontAwesome.Solid.ArrowAltCircleDown; From a4551dc1eeb2eb6afb9dec61752559eaa6e5632c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 5 Feb 2021 14:31:22 +0900 Subject: [PATCH 0384/1791] Add object-ordered hit policy --- .../UI/ObjectOrderedHitPolicy.cs | 55 +++++++++++++++++++ osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 4 +- 2 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/UI/ObjectOrderedHitPolicy.cs diff --git a/osu.Game.Rulesets.Osu/UI/ObjectOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu/UI/ObjectOrderedHitPolicy.cs new file mode 100644 index 0000000000..fdab241a9b --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/ObjectOrderedHitPolicy.cs @@ -0,0 +1,55 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables; + +namespace osu.Game.Rulesets.Osu.UI +{ + /// + /// Ensures that s are hit in order of appearance. The classic note lock. + /// + /// Hits will be blocked until the previous s have been judged. + /// + /// + public class ObjectOrderedHitPolicy : IHitPolicy + { + private IEnumerable hitObjects; + + public void SetHitObjects(IEnumerable hitObjects) => this.hitObjects = hitObjects; + + public bool IsHittable(DrawableHitObject hitObject, double time) => enumerateHitObjectsUpTo(hitObject.HitObject.StartTime).All(obj => obj.AllJudged); + + public void HandleHit(DrawableHitObject hitObject) + { + } + + private IEnumerable enumerateHitObjectsUpTo(double targetTime) + { + foreach (var obj in hitObjects) + { + if (obj.HitObject.StartTime >= targetTime) + yield break; + + switch (obj) + { + case DrawableSpinner _: + continue; + + case DrawableSlider slider: + yield return slider.HeadCircle; + + break; + + default: + yield return obj; + + break; + } + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index c7900558a0..6cb890323b 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.UI private readonly ProxyContainer spinnerProxies; private readonly JudgementContainer judgementLayer; private readonly FollowPointRenderer followPoints; - private readonly StartTimeOrderedHitPolicy hitPolicy; + private readonly IHitPolicy hitPolicy; public static readonly Vector2 BASE_SIZE = new Vector2(512, 384); @@ -54,7 +54,7 @@ namespace osu.Game.Rulesets.Osu.UI approachCircles = new ProxyContainer { RelativeSizeAxes = Axes.Both }, }; - hitPolicy = new StartTimeOrderedHitPolicy(); + hitPolicy = new ObjectOrderedHitPolicy(); hitPolicy.SetHitObjects(HitObjectContainer.AliveObjects); var hitWindows = new OsuHitWindows(); From 6aece18f8dedc392a84996d1c7e4a906b72b827e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 5 Feb 2021 15:23:03 +0900 Subject: [PATCH 0385/1791] Add OOHP tests --- .../TestSceneObjectOrderedHitPolicy.cs | 491 ++++++++++++++++++ osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs | 29 +- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 17 +- 3 files changed, 529 insertions(+), 8 deletions(-) create mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs new file mode 100644 index 0000000000..039a4f142f --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs @@ -0,0 +1,491 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Extensions.TypeExtensions; +using osu.Framework.Screens; +using osu.Framework.Utils; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Replays; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Replays; +using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Screens.Play; +using osu.Game.Tests.Visual; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class TestSceneObjectOrderedHitPolicy : RateAdjustedBeatmapTestScene + { + private const double early_miss_window = 1000; // time after -1000 to -500 is considered a miss + private const double late_miss_window = 500; // time after +500 is considered a miss + + /// + /// Tests clicking a future circle before the first circle's start time, while the first circle HAS NOT been judged. + /// + [Test] + public void TestClickSecondCircleBeforeFirstCircleTime() + { + const double time_first_circle = 1500; + const double time_second_circle = 1600; + Vector2 positionFirstCircle = Vector2.Zero; + Vector2 positionSecondCircle = new Vector2(80); + + var hitObjects = new List + { + new TestHitCircle + { + StartTime = time_first_circle, + Position = positionFirstCircle + }, + new TestHitCircle + { + StartTime = time_second_circle, + Position = positionSecondCircle + } + }; + + performTest(hitObjects, new List + { + new OsuReplayFrame { Time = time_first_circle - 100, Position = positionSecondCircle, Actions = { OsuAction.LeftButton } } + }); + + addJudgementAssert(hitObjects[0], HitResult.Miss); + addJudgementAssert(hitObjects[1], HitResult.Miss); + addJudgementOffsetAssert(hitObjects[0], late_miss_window); + } + + /// + /// Tests clicking a future circle at the first circle's start time, while the first circle HAS NOT been judged. + /// + [Test] + public void TestClickSecondCircleAtFirstCircleTime() + { + const double time_first_circle = 1500; + const double time_second_circle = 1600; + Vector2 positionFirstCircle = Vector2.Zero; + Vector2 positionSecondCircle = new Vector2(80); + + var hitObjects = new List + { + new TestHitCircle + { + StartTime = time_first_circle, + Position = positionFirstCircle + }, + new TestHitCircle + { + StartTime = time_second_circle, + Position = positionSecondCircle + } + }; + + performTest(hitObjects, new List + { + new OsuReplayFrame { Time = time_first_circle, Position = positionSecondCircle, Actions = { OsuAction.LeftButton } } + }); + + addJudgementAssert(hitObjects[0], HitResult.Miss); + addJudgementAssert(hitObjects[1], HitResult.Miss); + addJudgementOffsetAssert(hitObjects[0], late_miss_window); + } + + /// + /// Tests clicking a future circle after the first circle's start time, while the first circle HAS NOT been judged. + /// + [Test] + public void TestClickSecondCircleAfterFirstCircleTime() + { + const double time_first_circle = 1500; + const double time_second_circle = 1600; + Vector2 positionFirstCircle = Vector2.Zero; + Vector2 positionSecondCircle = new Vector2(80); + + var hitObjects = new List + { + new TestHitCircle + { + StartTime = time_first_circle, + Position = positionFirstCircle + }, + new TestHitCircle + { + StartTime = time_second_circle, + Position = positionSecondCircle + } + }; + + performTest(hitObjects, new List + { + new OsuReplayFrame { Time = time_first_circle + 100, Position = positionSecondCircle, Actions = { OsuAction.LeftButton } } + }); + + addJudgementAssert(hitObjects[0], HitResult.Miss); + addJudgementAssert(hitObjects[1], HitResult.Miss); + addJudgementOffsetAssert(hitObjects[0], late_miss_window); + } + + /// + /// Tests clicking a future circle before the first circle's start time, while the first circle HAS been judged. + /// + [Test] + public void TestClickSecondCircleBeforeFirstCircleTimeWithFirstCircleJudged() + { + const double time_first_circle = 1500; + const double time_second_circle = 1600; + Vector2 positionFirstCircle = Vector2.Zero; + Vector2 positionSecondCircle = new Vector2(80); + + var hitObjects = new List + { + new TestHitCircle + { + StartTime = time_first_circle, + Position = positionFirstCircle + }, + new TestHitCircle + { + StartTime = time_second_circle, + Position = positionSecondCircle + } + }; + + performTest(hitObjects, new List + { + new OsuReplayFrame { Time = time_first_circle - 200, Position = positionFirstCircle, Actions = { OsuAction.LeftButton } }, + new OsuReplayFrame { Time = time_first_circle - 100, Position = positionSecondCircle, Actions = { OsuAction.RightButton } } + }); + + addJudgementAssert(hitObjects[0], HitResult.Great); + addJudgementAssert(hitObjects[1], HitResult.Great); + addJudgementOffsetAssert(hitObjects[0], -200); // time_first_circle - 200 + addJudgementOffsetAssert(hitObjects[0], -200); // time_second_circle - first_circle_time - 100 + } + + /// + /// Tests clicking a future circle after the first circle's start time, while the first circle HAS been judged. + /// + [Test] + public void TestClickSecondCircleAfterFirstCircleTimeWithFirstCircleJudged() + { + const double time_first_circle = 1500; + const double time_second_circle = 1600; + Vector2 positionFirstCircle = Vector2.Zero; + Vector2 positionSecondCircle = new Vector2(80); + + var hitObjects = new List + { + new TestHitCircle + { + StartTime = time_first_circle, + Position = positionFirstCircle + }, + new TestHitCircle + { + StartTime = time_second_circle, + Position = positionSecondCircle + } + }; + + performTest(hitObjects, new List + { + new OsuReplayFrame { Time = time_first_circle - 200, Position = positionFirstCircle, Actions = { OsuAction.LeftButton } }, + new OsuReplayFrame { Time = time_first_circle, Position = positionSecondCircle, Actions = { OsuAction.RightButton } } + }); + + addJudgementAssert(hitObjects[0], HitResult.Great); + addJudgementAssert(hitObjects[1], HitResult.Great); + addJudgementOffsetAssert(hitObjects[0], -200); // time_first_circle - 200 + addJudgementOffsetAssert(hitObjects[1], -100); // time_second_circle - first_circle_time + } + + /// + /// Tests clicking a future circle after a slider's start time, but hitting all slider ticks. + /// + [Test] + public void TestMissSliderHeadAndHitAllSliderTicks() + { + const double time_slider = 1500; + const double time_circle = 1510; + Vector2 positionCircle = Vector2.Zero; + Vector2 positionSlider = new Vector2(80); + + var hitObjects = new List + { + new TestHitCircle + { + StartTime = time_circle, + Position = positionCircle + }, + new TestSlider + { + StartTime = time_slider, + Position = positionSlider, + Path = new SliderPath(PathType.Linear, new[] + { + Vector2.Zero, + new Vector2(25, 0), + }) + } + }; + + performTest(hitObjects, new List + { + new OsuReplayFrame { Time = time_slider, Position = positionCircle, Actions = { OsuAction.LeftButton } }, + new OsuReplayFrame { Time = time_slider + 10, Position = positionSlider, Actions = { OsuAction.RightButton } } + }); + + addJudgementAssert(hitObjects[0], HitResult.Miss); + addJudgementAssert(hitObjects[1], HitResult.Great); + addJudgementAssert("slider head", () => ((Slider)hitObjects[1]).HeadCircle, HitResult.IgnoreHit); + addJudgementAssert("slider tick", () => ((Slider)hitObjects[1]).NestedHitObjects[1] as SliderTick, HitResult.LargeTickHit); + } + + /// + /// Tests clicking hitting future slider ticks before a circle. + /// + [Test] + public void TestHitSliderTicksBeforeCircle() + { + const double time_slider = 1500; + const double time_circle = 1510; + Vector2 positionCircle = Vector2.Zero; + Vector2 positionSlider = new Vector2(30); + + var hitObjects = new List + { + new TestHitCircle + { + StartTime = time_circle, + Position = positionCircle + }, + new TestSlider + { + StartTime = time_slider, + Position = positionSlider, + Path = new SliderPath(PathType.Linear, new[] + { + Vector2.Zero, + new Vector2(25, 0), + }) + } + }; + + performTest(hitObjects, new List + { + new OsuReplayFrame { Time = time_slider, Position = positionSlider, Actions = { OsuAction.LeftButton } }, + new OsuReplayFrame { Time = time_circle + late_miss_window - 100, Position = positionCircle, Actions = { OsuAction.RightButton } }, + new OsuReplayFrame { Time = time_circle + late_miss_window - 90, Position = positionSlider, Actions = { OsuAction.LeftButton } }, + }); + + addJudgementAssert(hitObjects[0], HitResult.Great); + addJudgementAssert(hitObjects[1], HitResult.Great); + addJudgementAssert("slider head", () => ((Slider)hitObjects[1]).HeadCircle, HitResult.IgnoreHit); + addJudgementAssert("slider tick", () => ((Slider)hitObjects[1]).NestedHitObjects[1] as SliderTick, HitResult.LargeTickHit); + } + + /// + /// Tests clicking a future circle before a spinner. + /// + [Test] + public void TestHitCircleBeforeSpinner() + { + const double time_spinner = 1500; + const double time_circle = 1800; + Vector2 positionCircle = Vector2.Zero; + + var hitObjects = new List + { + new TestSpinner + { + StartTime = time_spinner, + Position = new Vector2(256, 192), + EndTime = time_spinner + 1000, + }, + new TestHitCircle + { + StartTime = time_circle, + Position = positionCircle + }, + }; + + performTest(hitObjects, new List + { + new OsuReplayFrame { Time = time_spinner - 100, Position = positionCircle, Actions = { OsuAction.LeftButton } }, + new OsuReplayFrame { Time = time_spinner + 10, Position = new Vector2(236, 192), Actions = { OsuAction.RightButton } }, + new OsuReplayFrame { Time = time_spinner + 20, Position = new Vector2(256, 172), Actions = { OsuAction.RightButton } }, + new OsuReplayFrame { Time = time_spinner + 30, Position = new Vector2(276, 192), Actions = { OsuAction.RightButton } }, + new OsuReplayFrame { Time = time_spinner + 40, Position = new Vector2(256, 212), Actions = { OsuAction.RightButton } }, + new OsuReplayFrame { Time = time_spinner + 50, Position = new Vector2(236, 192), Actions = { OsuAction.RightButton } }, + }); + + addJudgementAssert(hitObjects[0], HitResult.Great); + addJudgementAssert(hitObjects[1], HitResult.Great); + } + + [Test] + public void TestHitSliderHeadBeforeHitCircle() + { + const double time_circle = 1000; + const double time_slider = 1200; + Vector2 positionCircle = Vector2.Zero; + Vector2 positionSlider = new Vector2(80); + + var hitObjects = new List + { + new TestHitCircle + { + StartTime = time_circle, + Position = positionCircle + }, + new TestSlider + { + StartTime = time_slider, + Position = positionSlider, + Path = new SliderPath(PathType.Linear, new[] + { + Vector2.Zero, + new Vector2(25, 0), + }) + } + }; + + performTest(hitObjects, new List + { + new OsuReplayFrame { Time = time_circle - 100, Position = positionSlider, Actions = { OsuAction.LeftButton } }, + new OsuReplayFrame { Time = time_circle, Position = positionCircle, Actions = { OsuAction.RightButton } }, + new OsuReplayFrame { Time = time_slider, Position = positionSlider, Actions = { OsuAction.LeftButton } }, + }); + + addJudgementAssert(hitObjects[0], HitResult.Great); + addJudgementAssert(hitObjects[1], HitResult.Great); + } + + private void addJudgementAssert(OsuHitObject hitObject, HitResult result) + { + AddAssert($"({hitObject.GetType().ReadableName()} @ {hitObject.StartTime}) judgement is {result}", + () => judgementResults.Single(r => r.HitObject == hitObject).Type == result); + } + + private void addJudgementAssert(string name, Func hitObject, HitResult result) + { + AddAssert($"{name} judgement is {result}", + () => judgementResults.Single(r => r.HitObject == hitObject()).Type == result); + } + + private void addJudgementOffsetAssert(OsuHitObject hitObject, double offset) + { + AddAssert($"({hitObject.GetType().ReadableName()} @ {hitObject.StartTime}) judged at {offset}", + () => Precision.AlmostEquals(judgementResults.Single(r => r.HitObject == hitObject).TimeOffset, offset, 100)); + } + + private ScoreAccessibleReplayPlayer currentPlayer; + private List judgementResults; + + private void performTest(List hitObjects, List frames) + { + AddStep("load player", () => + { + Beatmap.Value = CreateWorkingBeatmap(new Beatmap + { + HitObjects = hitObjects, + BeatmapInfo = + { + BaseDifficulty = new BeatmapDifficulty { SliderTickRate = 3 }, + Ruleset = new OsuRuleset().RulesetInfo + }, + }); + + Beatmap.Value.Beatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 0.1f }); + + SelectedMods.Value = new[] { new OsuModClassic() }; + + var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } }); + + p.OnLoadComplete += _ => + { + p.ScoreProcessor.NewJudgement += result => + { + if (currentPlayer == p) judgementResults.Add(result); + }; + }; + + LoadScreen(currentPlayer = p); + judgementResults = new List(); + }); + + AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0); + AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen()); + AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value); + } + + private class TestHitCircle : HitCircle + { + protected override HitWindows CreateHitWindows() => new TestHitWindows(); + } + + private class TestSlider : Slider + { + public TestSlider() + { + DefaultsApplied += _ => + { + HeadCircle.HitWindows = new TestHitWindows(); + TailCircle.HitWindows = new TestHitWindows(); + + HeadCircle.HitWindows.SetDifficulty(0); + TailCircle.HitWindows.SetDifficulty(0); + }; + } + } + + private class TestSpinner : Spinner + { + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + { + base.ApplyDefaultsToSelf(controlPointInfo, difficulty); + SpinsRequired = 1; + } + } + + private class TestHitWindows : HitWindows + { + private static readonly DifficultyRange[] ranges = + { + new DifficultyRange(HitResult.Great, 500, 500, 500), + new DifficultyRange(HitResult.Miss, early_miss_window, early_miss_window, early_miss_window), + }; + + public override bool IsHitResultAllowed(HitResult result) => result == HitResult.Great || result == HitResult.Miss; + + protected override DifficultyRange[] GetRanges() => ranges; + } + + private class ScoreAccessibleReplayPlayer : ReplayPlayer + { + public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; + + protected override bool PauseOnFocusLost => false; + + public ScoreAccessibleReplayPlayer(Score score) + : base(score, new PlayerConfiguration + { + AllowPause = false, + ShowResults = false, + }) + { + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs index 5542580979..6f41bcc0b0 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs @@ -2,14 +2,18 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; +using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; +using osu.Game.Configuration; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModClassic : Mod, IApplicableToHitObject + public class OsuModClassic : Mod, IApplicableToHitObject, IApplicableToDrawableRuleset { public override string Name => "Classic"; @@ -23,21 +27,38 @@ namespace osu.Game.Rulesets.Osu.Mods public override bool Ranked => false; + [SettingSource("Disable slider head judgement", "Scores sliders proportionally to the number of ticks hit.")] + public Bindable DisableSliderHeadJudgement { get; } = new BindableBool(true); + + [SettingSource("Disable slider head tracking", "Pins slider heads at their starting position, regardless of time.")] + public Bindable DisableSliderHeadTracking { get; } = new BindableBool(true); + + [SettingSource("Disable note lock lenience", "Applies note lock to the full hit window.")] + public Bindable DisableLenientNoteLock { get; } = new BindableBool(true); + public void ApplyToHitObject(HitObject hitObject) { switch (hitObject) { case Slider slider: - slider.IgnoreJudgement = false; + slider.IgnoreJudgement = !DisableSliderHeadJudgement.Value; foreach (var head in slider.NestedHitObjects.OfType()) { - head.TrackFollowCircle = false; - head.JudgeAsNormalHitCircle = false; + head.TrackFollowCircle = !DisableSliderHeadTracking.Value; + head.JudgeAsNormalHitCircle = !DisableSliderHeadJudgement.Value; } break; } } + + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + { + var osuRuleset = (DrawableOsuRuleset)drawableRuleset; + + if (!DisableLenientNoteLock.Value) + osuRuleset.Playfield.HitPolicy = new ObjectOrderedHitPolicy(); + } } } diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 6cb890323b..9bd1dc74b7 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -31,7 +31,6 @@ namespace osu.Game.Rulesets.Osu.UI private readonly ProxyContainer spinnerProxies; private readonly JudgementContainer judgementLayer; private readonly FollowPointRenderer followPoints; - private readonly IHitPolicy hitPolicy; public static readonly Vector2 BASE_SIZE = new Vector2(512, 384); @@ -54,11 +53,9 @@ namespace osu.Game.Rulesets.Osu.UI approachCircles = new ProxyContainer { RelativeSizeAxes = Axes.Both }, }; - hitPolicy = new ObjectOrderedHitPolicy(); - hitPolicy.SetHitObjects(HitObjectContainer.AliveObjects); + HitPolicy = new ObjectOrderedHitPolicy(); var hitWindows = new OsuHitWindows(); - foreach (var result in Enum.GetValues(typeof(HitResult)).OfType().Where(r => r > HitResult.None && hitWindows.IsHitResultAllowed(r))) poolDictionary.Add(result, new DrawableJudgementPool(result, onJudgmentLoaded)); @@ -67,6 +64,18 @@ namespace osu.Game.Rulesets.Osu.UI NewResult += onNewResult; } + private IHitPolicy hitPolicy; + + public IHitPolicy HitPolicy + { + get => hitPolicy; + set + { + hitPolicy = value ?? throw new ArgumentNullException(nameof(value)); + hitPolicy.SetHitObjects(HitObjectContainer.AliveObjects); + } + } + protected override void OnNewDrawableHitObject(DrawableHitObject drawable) { ((DrawableOsuHitObject)drawable).CheckHittable = hitPolicy.IsHittable; From 1b6a05279847569be9d5568bc9812fa55b7fe503 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Feb 2021 15:46:03 +0900 Subject: [PATCH 0386/1791] Refactor logic to suck a bit less --- .../Multiplayer/Participants/StateDisplay.cs | 161 +++++++++--------- 1 file changed, 83 insertions(+), 78 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs index c117c026dc..c571b51c83 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.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.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; @@ -87,85 +88,89 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants public void UpdateStatus(MultiplayerUserState state, BeatmapAvailability availability) { - if (availability.State != DownloadState.LocallyAvailable) - { - switch (availability.State) - { - case DownloadState.NotDownloaded: - progressBar.FadeOut(fade_time); - text.Text = "no map"; - icon.Icon = FontAwesome.Solid.MinusCircle; - icon.Colour = colours.RedLight; - break; - - case DownloadState.Downloading: - Debug.Assert(availability.DownloadProgress != null); - - progressBar.FadeIn(fade_time); - progressBar.CurrentTime = availability.DownloadProgress.Value; - - text.Text = "downloading map"; - icon.Icon = FontAwesome.Solid.ArrowAltCircleDown; - icon.Colour = colours.Blue; - break; - - case DownloadState.Importing: - progressBar.FadeOut(fade_time); - text.Text = "importing map"; - icon.Icon = FontAwesome.Solid.ArrowAltCircleDown; - icon.Colour = colours.Yellow; - break; - } - } - else - { - progressBar.FadeOut(fade_time); - - switch (state) - { - default: - this.FadeOut(fade_time); - return; - - case MultiplayerUserState.Ready: - text.Text = "ready"; - icon.Icon = FontAwesome.Solid.CheckCircle; - icon.Colour = Color4Extensions.FromHex("#AADD00"); - break; - - case MultiplayerUserState.WaitingForLoad: - text.Text = "loading"; - icon.Icon = FontAwesome.Solid.PauseCircle; - icon.Colour = colours.Yellow; - break; - - case MultiplayerUserState.Loaded: - text.Text = "loaded"; - icon.Icon = FontAwesome.Solid.DotCircle; - icon.Colour = colours.YellowLight; - break; - - case MultiplayerUserState.Playing: - text.Text = "playing"; - icon.Icon = FontAwesome.Solid.PlayCircle; - icon.Colour = colours.BlueLight; - break; - - case MultiplayerUserState.FinishedPlay: - text.Text = "results pending"; - icon.Icon = FontAwesome.Solid.ArrowAltCircleUp; - icon.Colour = colours.BlueLighter; - break; - - case MultiplayerUserState.Results: - text.Text = "results"; - icon.Icon = FontAwesome.Solid.ArrowAltCircleUp; - icon.Colour = colours.BlueLighter; - break; - } - } - + // the only case where the progress bar is used does its own local fade in. + // starting by fading out is a sane default. + progressBar.FadeOut(fade_time); this.FadeIn(fade_time); + + switch (state) + { + case MultiplayerUserState.Idle: + showBeatmapAvailability(availability); + break; + + case MultiplayerUserState.Ready: + text.Text = "ready"; + icon.Icon = FontAwesome.Solid.CheckCircle; + icon.Colour = Color4Extensions.FromHex("#AADD00"); + break; + + case MultiplayerUserState.WaitingForLoad: + text.Text = "loading"; + icon.Icon = FontAwesome.Solid.PauseCircle; + icon.Colour = colours.Yellow; + break; + + case MultiplayerUserState.Loaded: + text.Text = "loaded"; + icon.Icon = FontAwesome.Solid.DotCircle; + icon.Colour = colours.YellowLight; + break; + + case MultiplayerUserState.Playing: + text.Text = "playing"; + icon.Icon = FontAwesome.Solid.PlayCircle; + icon.Colour = colours.BlueLight; + break; + + case MultiplayerUserState.FinishedPlay: + text.Text = "results pending"; + icon.Icon = FontAwesome.Solid.ArrowAltCircleUp; + icon.Colour = colours.BlueLighter; + break; + + case MultiplayerUserState.Results: + text.Text = "results"; + icon.Icon = FontAwesome.Solid.ArrowAltCircleUp; + icon.Colour = colours.BlueLighter; + break; + + default: + throw new ArgumentOutOfRangeException(nameof(state), state, null); + } + } + + private void showBeatmapAvailability(BeatmapAvailability availability) + { + switch (availability.State) + { + default: + this.FadeOut(fade_time); + break; + + case DownloadState.NotDownloaded: + text.Text = "no map"; + icon.Icon = FontAwesome.Solid.MinusCircle; + icon.Colour = colours.RedLight; + break; + + case DownloadState.Downloading: + Debug.Assert(availability.DownloadProgress != null); + + progressBar.FadeIn(fade_time); + progressBar.CurrentTime = availability.DownloadProgress.Value; + + text.Text = "downloading map"; + icon.Icon = FontAwesome.Solid.ArrowAltCircleDown; + icon.Colour = colours.Blue; + break; + + case DownloadState.Importing: + text.Text = "importing map"; + icon.Icon = FontAwesome.Solid.ArrowAltCircleDown; + icon.Colour = colours.Yellow; + break; + } } } } From 98c4573240670b7992894d2ddaf84c8f9acdf476 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Feb 2021 15:52:49 +0900 Subject: [PATCH 0387/1791] Add assertions covering new test --- .../Multiplayer/TestSceneMultiplayerParticipantsList.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs index 4fec1c6dd8..b025440d04 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Testing; using osu.Framework.Utils; +using osu.Game.Graphics.UserInterface; using osu.Game.Online; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; @@ -82,12 +83,20 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("set to no map", () => Client.ChangeBeatmapAvailability(BeatmapAvailability.NotDownloaded())); AddStep("set to downloading map", () => Client.ChangeBeatmapAvailability(BeatmapAvailability.Downloading(0))); + + AddUntilStep("progress bar visible", () => this.ChildrenOfType().Single().IsPresent); + AddRepeatStep("increment progress", () => { var progress = this.ChildrenOfType().Single().User.BeatmapAvailability.DownloadProgress ?? 0; Client.ChangeBeatmapAvailability(BeatmapAvailability.Downloading(progress + RNG.NextSingle(0.1f))); }, 25); + + AddAssert("progress bar increased", () => this.ChildrenOfType().Single().Current.Value > 0); + AddStep("set to importing map", () => Client.ChangeBeatmapAvailability(BeatmapAvailability.Importing())); + AddUntilStep("progress bar not visible", () => !this.ChildrenOfType().Single().IsPresent); + AddStep("set to available", () => Client.ChangeBeatmapAvailability(BeatmapAvailability.LocallyAvailable())); } From 3aa3692ed4be0a5b94e4bbb9b489063b6532b0da Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 5 Feb 2021 15:56:13 +0900 Subject: [PATCH 0388/1791] Disable snaking out when tracking is disabled --- .../Sliders/SliderCircleSelectionBlueprint.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Slider.cs | 2 +- .../Skinning/Default/PlaySliderBody.cs | 22 ++++++++++++++++++- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs index a0392fe536..dec9cd8622 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { base.Update(); - CirclePiece.UpdateFrom(position == SliderPosition.Start ? HitObject.HeadCircle : HitObject.TailCircle); + CirclePiece.UpdateFrom(position == SliderPosition.Start ? (HitCircle)HitObject.HeadCircle : HitObject.TailCircle); } // Todo: This is temporary, since the slider circle masks don't do anything special yet. In the future they will handle input. diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index e3365a8ccf..01694a838b 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -121,7 +121,7 @@ namespace osu.Game.Rulesets.Osu.Objects public bool IgnoreJudgement = true; [JsonIgnore] - public HitCircle HeadCircle { get; protected set; } + public SliderHeadCircle HeadCircle { get; protected set; } [JsonIgnore] public SliderTailCircle TailCircle { get; protected set; } diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs index e77c93c721..e9b4bb416c 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs @@ -21,6 +21,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default [Resolved(CanBeNull = true)] private OsuRulesetConfigManager config { get; set; } + private readonly Bindable snakingOut = new Bindable(); + [BackgroundDependencyLoader] private void load(ISkinSource skin, DrawableHitObject drawableObject) { @@ -35,11 +37,29 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default accentColour = drawableObject.AccentColour.GetBoundCopy(); accentColour.BindValueChanged(accent => updateAccentColour(skin, accent.NewValue), true); + SnakingOut.BindTo(snakingOut); config?.BindWith(OsuRulesetSetting.SnakingInSliders, SnakingIn); - config?.BindWith(OsuRulesetSetting.SnakingOutSliders, SnakingOut); + config?.BindWith(OsuRulesetSetting.SnakingOutSliders, snakingOut); BorderSize = skin.GetConfig(OsuSkinConfiguration.SliderBorderSize)?.Value ?? 1; BorderColour = skin.GetConfig(OsuSkinColour.SliderBorder)?.Value ?? Color4.White; + + drawableObject.HitObjectApplied += onHitObjectApplied; + onHitObjectApplied(drawableObject); + } + + private void onHitObjectApplied(DrawableHitObject obj) + { + var drawableSlider = (DrawableSlider)obj; + if (drawableSlider.HitObject == null) + return; + + if (!drawableSlider.HitObject.HeadCircle.TrackFollowCircle) + { + // When not tracking the follow circle, force the path to not snake out as it looks better that way. + SnakingOut.UnbindFrom(snakingOut); + SnakingOut.Value = false; + } } private void updateAccentColour(ISkinSource skin, Color4 defaultAccentColour) From 1368d551529ac09ecb1d47c5d32c0ddbf5f313b1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Feb 2021 15:58:27 +0900 Subject: [PATCH 0389/1791] Add test coverage of precedence of display --- .../TestSceneMultiplayerParticipantsList.cs | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs index b025440d04..c3852fafd4 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs @@ -78,13 +78,27 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("single panel is for second user", () => this.ChildrenOfType().Single().User.User == secondUser); } + [Test] + public void TestGameStateHasPriorityOverDownloadState() + { + AddStep("set to downloading map", () => Client.ChangeBeatmapAvailability(BeatmapAvailability.Downloading(0))); + checkProgressBarVisibility(true); + + AddStep("make user ready", () => Client.ChangeState(MultiplayerUserState.Results)); + checkProgressBarVisibility(false); + AddUntilStep("ready mark visible", () => this.ChildrenOfType().Single().IsPresent); + + AddStep("make user ready", () => Client.ChangeState(MultiplayerUserState.Idle)); + checkProgressBarVisibility(true); + } + [Test] public void TestBeatmapDownloadingStates() { AddStep("set to no map", () => Client.ChangeBeatmapAvailability(BeatmapAvailability.NotDownloaded())); AddStep("set to downloading map", () => Client.ChangeBeatmapAvailability(BeatmapAvailability.Downloading(0))); - AddUntilStep("progress bar visible", () => this.ChildrenOfType().Single().IsPresent); + checkProgressBarVisibility(true); AddRepeatStep("increment progress", () => { @@ -95,7 +109,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("progress bar increased", () => this.ChildrenOfType().Single().Current.Value > 0); AddStep("set to importing map", () => Client.ChangeBeatmapAvailability(BeatmapAvailability.Importing())); - AddUntilStep("progress bar not visible", () => !this.ChildrenOfType().Single().IsPresent); + checkProgressBarVisibility(false); AddStep("set to available", () => Client.ChangeBeatmapAvailability(BeatmapAvailability.LocallyAvailable())); } @@ -197,5 +211,9 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep($"set state: {state}", () => Client.ChangeUserState(0, state)); } } + + private void checkProgressBarVisibility(bool visible) => + AddUntilStep($"progress bar {(visible ? "is" : "is not")}visible", () => + this.ChildrenOfType().Single().IsPresent == visible); } } From 9ba5ae3db7c7f235fab1d44fd75a2e2a2f0ded94 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Feb 2021 16:17:02 +0900 Subject: [PATCH 0390/1791] Remove lots of unnecessary client side logic --- .../Multiplayer/MultiplayerMatchSubScreen.cs | 20 ++----------------- 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 5bcb1d6dc8..bd1ec9c54a 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -270,7 +270,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer BeatmapAvailability.BindValueChanged(updateBeatmapAvailability, true); UserMods.BindValueChanged(onUserModsChanged); - client.RoomUpdated += onRoomUpdated; client.LoadRequested += onLoadRequested; isConnected = client.IsConnected.GetBoundCopy(); @@ -323,24 +322,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer client.ChangeUserMods(mods.NewValue); } - private void updateBeatmapAvailability(ValueChangedEvent _ = null) + private void updateBeatmapAvailability(ValueChangedEvent availability) { if (client.Room == null) return; - client.ChangeBeatmapAvailability(BeatmapAvailability.Value); - - if (client.LocalUser?.State == MultiplayerUserState.Ready) - client.ChangeState(MultiplayerUserState.Idle); - } - - private void onRoomUpdated() - { - if (client.Room == null) - return; - - if (client.LocalUser?.BeatmapAvailability.Equals(BeatmapAvailability.Value) == false) - updateBeatmapAvailability(); + client.ChangeBeatmapAvailability(availability.NewValue); } private void onReadyClick() @@ -392,10 +379,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer base.Dispose(isDisposing); if (client != null) - { client.LoadRequested -= onLoadRequested; - client.RoomUpdated -= onRoomUpdated; - } } private class UserModSelectOverlay : ModSelectOverlay From be91f54349202c6a633f0de9728ede538b120c9d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Feb 2021 16:19:45 +0900 Subject: [PATCH 0391/1791] Add back edge case with comment --- .../OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index bd1ec9c54a..39e179262e 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -328,6 +328,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer return; client.ChangeBeatmapAvailability(availability.NewValue); + + // while this flow is handled server-side, this covers the edge case of the local user being in a ready state and then deleting the current beatmap. + if (client.LocalUser?.State == MultiplayerUserState.Ready) + client.ChangeState(MultiplayerUserState.Idle); } private void onReadyClick() From e1789c29b1a1e01f235b8e6bb9bdcfd131e5d715 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 5 Feb 2021 10:28:13 +0300 Subject: [PATCH 0392/1791] Use `Pause()` instead of `performUserRequestedExit()` to avoid unexpected operations --- 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 556964bca4..542839f11d 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -433,7 +433,7 @@ namespace osu.Game.Screens.Play return; if (gameActive.Value == false) - performUserRequestedExit(); + Pause(); } private IBeatmap loadPlayableBeatmap() From 8d18c7e9299cc9c8e4a8b55563c3a9a22d1a99bd Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 5 Feb 2021 10:28:33 +0300 Subject: [PATCH 0393/1791] Fix `BreakTracker.IsBreakTime` not updated properly on breaks set Causes a pause from focus lose when playing a beatmap that has a break section at the beginning, due to `IsBreakTime` incorrectly set to `false` --- osu.Game/Screens/Play/BreakTracker.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/BreakTracker.cs b/osu.Game/Screens/Play/BreakTracker.cs index 51e21656e1..793b6b0ebe 100644 --- a/osu.Game/Screens/Play/BreakTracker.cs +++ b/osu.Game/Screens/Play/BreakTracker.cs @@ -23,16 +23,16 @@ namespace osu.Game.Screens.Play /// public IBindable IsBreakTime => isBreakTime; - private readonly BindableBool isBreakTime = new BindableBool(); + private readonly BindableBool isBreakTime = new BindableBool(true); public IReadOnlyList Breaks { set { - isBreakTime.Value = false; - breaks = new PeriodTracker(value.Where(b => b.HasEffect) .Select(b => new Period(b.StartTime, b.EndTime - BreakOverlay.BREAK_FADE_DURATION))); + + updateBreakTime(); } } @@ -45,8 +45,12 @@ namespace osu.Game.Screens.Play protected override void Update() { base.Update(); + updateBreakTime(); + } - var time = Clock.CurrentTime; + private void updateBreakTime() + { + var time = Clock?.CurrentTime ?? 0; isBreakTime.Value = breaks?.IsInAny(time) == true || time < gameplayStartTime From 3e750feaa49cd9b0f7dea85965f657181b1c2eeb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Feb 2021 16:42:35 +0900 Subject: [PATCH 0394/1791] Subclass LocalPlayerModSelectOverlay to correctly deselect incompatible mods on free mod selection --- .../Visual/UserInterface/TestSceneModSelectOverlay.cs | 2 +- osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs | 2 +- .../{SoloModSelectOverlay.cs => LocalPlayerModSelectOverlay.cs} | 2 +- .../Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 2 +- osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) rename osu.Game/Overlays/Mods/{SoloModSelectOverlay.cs => LocalPlayerModSelectOverlay.cs} (88%) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 44605f4994..37ebc72984 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -333,7 +333,7 @@ namespace osu.Game.Tests.Visual.UserInterface }; } - private class TestModSelectOverlay : SoloModSelectOverlay + private class TestModSelectOverlay : LocalPlayerModSelectOverlay { public new Bindable> SelectedMods => base.SelectedMods; diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs index 3c889bdec4..89f9b7381b 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs @@ -151,7 +151,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddUntilStep("wait for ready", () => modSelect.State.Value == Visibility.Visible && modSelect.ButtonsLoaded); } - private class TestModSelectOverlay : SoloModSelectOverlay + private class TestModSelectOverlay : LocalPlayerModSelectOverlay { public new VisibilityContainer ModSettingsContainer => base.ModSettingsContainer; public new TriangleButton CustomiseButton => base.CustomiseButton; diff --git a/osu.Game/Overlays/Mods/SoloModSelectOverlay.cs b/osu.Game/Overlays/Mods/LocalPlayerModSelectOverlay.cs similarity index 88% rename from osu.Game/Overlays/Mods/SoloModSelectOverlay.cs rename to osu.Game/Overlays/Mods/LocalPlayerModSelectOverlay.cs index d039ad1f98..78cd9bdae5 100644 --- a/osu.Game/Overlays/Mods/SoloModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/LocalPlayerModSelectOverlay.cs @@ -5,7 +5,7 @@ using osu.Game.Rulesets.Mods; namespace osu.Game.Overlays.Mods { - public class SoloModSelectOverlay : ModSelectOverlay + public class LocalPlayerModSelectOverlay : ModSelectOverlay { protected override void OnModSelected(Mod mod) { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 061e3b4d3f..f030879625 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -373,7 +373,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer client.LoadRequested -= onLoadRequested; } - private class UserModSelectOverlay : ModSelectOverlay + private class UserModSelectOverlay : LocalPlayerModSelectOverlay { public UserModSelectOverlay() { diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs index 0baa663578..b201c62b7f 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs @@ -125,7 +125,7 @@ namespace osu.Game.Screens.OnlinePlay return base.OnExiting(next); } - protected override ModSelectOverlay CreateModSelectOverlay() => new SoloModSelectOverlay + protected override ModSelectOverlay CreateModSelectOverlay() => new LocalPlayerModSelectOverlay { IsValidMod = IsValidMod }; diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index ed6e0a1028..b20effc67d 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -311,7 +311,7 @@ namespace osu.Game.Screens.Select (new FooterButtonOptions(), BeatmapOptions) }; - protected virtual ModSelectOverlay CreateModSelectOverlay() => new SoloModSelectOverlay(); + protected virtual ModSelectOverlay CreateModSelectOverlay() => new LocalPlayerModSelectOverlay(); protected virtual void ApplyFilterToCarousel(FilterCriteria criteria) { From 630c5bb74700ae3599013eddf70e2a1cda4a3642 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Feb 2021 16:46:21 +0900 Subject: [PATCH 0395/1791] Avoid potential crashes when lease is held on SelectedMods --- osu.Game/OsuGame.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index a00cd5e6a0..1a1f7bd233 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -469,6 +469,10 @@ namespace osu.Game { updateModDefaults(); + // a lease may be taken on the mods bindable, at which point we can't really ensure valid mods. + if (SelectedMods.Disabled) + return; + if (!ModUtils.CheckValidForGameplay(mods.NewValue, out var invalid)) { // ensure we always have a valid set of mods. From ee3367d7c549acecb778c652365c290e3e186e5b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 5 Feb 2021 16:59:13 +0900 Subject: [PATCH 0396/1791] Add classic slider ball tracking --- osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs | 17 ++++++++++++++++- .../Skinning/Default/SliderBall.cs | 15 ++++++++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs index 6f41bcc0b0..df3afb7063 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs @@ -1,19 +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 System.Collections.Generic; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; using osu.Game.Configuration; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModClassic : Mod, IApplicableToHitObject, IApplicableToDrawableRuleset + public class OsuModClassic : Mod, IApplicableToHitObject, IApplicableToDrawableHitObjects, IApplicableToDrawableRuleset { public override string Name => "Classic"; @@ -36,6 +39,9 @@ namespace osu.Game.Rulesets.Osu.Mods [SettingSource("Disable note lock lenience", "Applies note lock to the full hit window.")] public Bindable DisableLenientNoteLock { get; } = new BindableBool(true); + [SettingSource("Disable exact slider follow circle tracking", "Makes the slider follow circle track its final size at all times.")] + public Bindable DisableExactFollowCircleTracking { get; } = new BindableBool(true); + public void ApplyToHitObject(HitObject hitObject) { switch (hitObject) @@ -60,5 +66,14 @@ namespace osu.Game.Rulesets.Osu.Mods if (!DisableLenientNoteLock.Value) osuRuleset.Playfield.HitPolicy = new ObjectOrderedHitPolicy(); } + + public void ApplyToDrawableHitObjects(IEnumerable drawables) + { + foreach (var obj in drawables) + { + if (obj is DrawableSlider slider) + slider.Ball.TrackVisualSize = !DisableExactFollowCircleTracking.Value; + } + } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs index a96beb66d4..da3debbd42 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs @@ -31,6 +31,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default set => ball.Colour = value; } + /// + /// Whether to track accurately to the visual size of this . + /// If false, tracking will be performed at the final scale at all times. + /// + public bool TrackVisualSize = true; + private readonly Drawable followCircle; private readonly DrawableSlider drawableSlider; private readonly Drawable ball; @@ -94,7 +100,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default tracking = value; - followCircle.ScaleTo(tracking ? 2.4f : 1f, 300, Easing.OutQuint); + if (TrackVisualSize) + followCircle.ScaleTo(tracking ? 2.4f : 1f, 300, Easing.OutQuint); + else + { + // We need to always be tracking the final size, at both endpoints. For now, this is achieved by removing the scale duration. + followCircle.ScaleTo(tracking ? 2.4f : 1f); + } + followCircle.FadeTo(tracking ? 1f : 0, 300, Easing.OutQuint); } } From 791cbb7f03e9637a123b83b77f339c3d44abbf1f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Feb 2021 17:17:29 +0900 Subject: [PATCH 0397/1791] Don't reset ready state if the map is locally available --- .../OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 39e179262e..53c939115c 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -330,7 +330,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer client.ChangeBeatmapAvailability(availability.NewValue); // while this flow is handled server-side, this covers the edge case of the local user being in a ready state and then deleting the current beatmap. - if (client.LocalUser?.State == MultiplayerUserState.Ready) + if (availability.NewValue != Online.Rooms.BeatmapAvailability.LocallyAvailable() + && client.LocalUser?.State == MultiplayerUserState.Ready) client.ChangeState(MultiplayerUserState.Idle); } From 110458612d730e449174fe533606faecc041785b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Feb 2021 17:19:23 +0900 Subject: [PATCH 0398/1791] Avoid handling null playlist items when updating avaialability display --- .../Rooms/OnlinePlayBeatmapAvailablilityTracker.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailablilityTracker.cs b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailablilityTracker.cs index ad4b3c5151..dcb366ddab 100644 --- a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailablilityTracker.cs +++ b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailablilityTracker.cs @@ -36,7 +36,15 @@ namespace osu.Game.Online.Rooms { base.LoadComplete(); - SelectedItem.BindValueChanged(item => Model.Value = item.NewValue?.Beatmap.Value.BeatmapSet, true); + SelectedItem.BindValueChanged(item => + { + // the underlying playlist is regularly cleared for maintenance purposes (things which probably need to be fixed eventually). + // to avoid exposing a state change when there may actually be none, ignore all nulls for now. + if (item.NewValue == null) + return; + + Model.Value = item.NewValue.Beatmap.Value.BeatmapSet; + }, true); } protected override bool VerifyDatabasedModel(BeatmapSetInfo databasedSet) From a5855f5d28f8e7db6b9d4038806e5d70b38c3a7f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 5 Feb 2021 17:33:48 +0900 Subject: [PATCH 0399/1791] Move follow circle tracking to DrawableSliderHead --- osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs | 15 ++++++++++----- .../Objects/Drawables/DrawableSliderHead.cs | 8 +++++++- osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs | 6 ------ .../Skinning/Default/PlaySliderBody.cs | 2 +- 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs index df3afb7063..8cd6676f9d 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs @@ -50,10 +50,7 @@ namespace osu.Game.Rulesets.Osu.Mods slider.IgnoreJudgement = !DisableSliderHeadJudgement.Value; foreach (var head in slider.NestedHitObjects.OfType()) - { - head.TrackFollowCircle = !DisableSliderHeadTracking.Value; head.JudgeAsNormalHitCircle = !DisableSliderHeadJudgement.Value; - } break; } @@ -71,8 +68,16 @@ namespace osu.Game.Rulesets.Osu.Mods { foreach (var obj in drawables) { - if (obj is DrawableSlider slider) - slider.Ball.TrackVisualSize = !DisableExactFollowCircleTracking.Value; + switch (obj) + { + case DrawableSlider slider: + slider.Ball.TrackVisualSize = !DisableExactFollowCircleTracking.Value; + break; + + case DrawableSliderHead head: + head.TrackFollowCircle = !DisableSliderHeadTracking.Value; + break; + } } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs index 08e9c5eb14..ee1df00ef7 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs @@ -22,6 +22,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public override bool DisplayResult => HitObject?.JudgeAsNormalHitCircle ?? base.DisplayResult; + /// + /// Makes this track the follow circle when the start time is reached. + /// If false, this will be pinned to its initial position in the slider. + /// + public bool TrackFollowCircle = true; + private readonly IBindable pathVersion = new Bindable(); protected override OsuSkinComponents CirclePieceComponent => OsuSkinComponents.SliderHeadHitCircle; @@ -66,7 +72,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Debug.Assert(Slider != null); Debug.Assert(HitObject != null); - if (HitObject.TrackFollowCircle) + if (TrackFollowCircle) { double completionProgress = Math.Clamp((Time.Current - Slider.StartTime) / Slider.Duration, 0, 1); diff --git a/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs index 13eac60300..28e57567cb 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs @@ -8,12 +8,6 @@ namespace osu.Game.Rulesets.Osu.Objects { public class SliderHeadCircle : HitCircle { - /// - /// Makes this track the follow circle when the start time is reached. - /// If false, this will be pinned to its initial position in the slider. - /// - public bool TrackFollowCircle = true; - /// /// Whether to treat this as a normal for judgement purposes. /// If false, judgement will be ignored. diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs index e9b4bb416c..8eb2714c04 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs @@ -54,7 +54,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default if (drawableSlider.HitObject == null) return; - if (!drawableSlider.HitObject.HeadCircle.TrackFollowCircle) + if (!drawableSlider.HeadCircle.TrackFollowCircle) { // When not tracking the follow circle, force the path to not snake out as it looks better that way. SnakingOut.UnbindFrom(snakingOut); From dad32da4153e8cb18443c6c1f12c1a1847754936 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Feb 2021 17:34:05 +0900 Subject: [PATCH 0400/1791] Add rate limiting on sending download progress updates --- .../Rooms/OnlinePlayBeatmapAvailablilityTracker.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailablilityTracker.cs b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailablilityTracker.cs index dcb366ddab..cfaf43451f 100644 --- a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailablilityTracker.cs +++ b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailablilityTracker.cs @@ -5,6 +5,7 @@ using System; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Logging; +using osu.Framework.Threading; using osu.Game.Beatmaps; namespace osu.Game.Online.Rooms @@ -24,12 +25,20 @@ namespace osu.Game.Online.Rooms /// public IBindable Availability => availability; - private readonly Bindable availability = new Bindable(); + private readonly Bindable availability = new Bindable(BeatmapAvailability.LocallyAvailable()); + + private ScheduledDelegate progressUpdate; public OnlinePlayBeatmapAvailablilityTracker() { State.BindValueChanged(_ => updateAvailability()); - Progress.BindValueChanged(_ => updateAvailability(), true); + Progress.BindValueChanged(_ => + { + // incoming progress changes are going to be at a very high rate. + // we don't want to flood the network with this, so rate limit how often we send progress updates. + if (progressUpdate?.Completed != false) + progressUpdate = Scheduler.AddDelayed(updateAvailability, progressUpdate == null ? 0 : 500); + }); } protected override void LoadComplete() From 95ad7ea8f7667a5e3faacbcbe696ef09b5b38354 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 5 Feb 2021 18:44:26 +0900 Subject: [PATCH 0401/1791] Fix mods on participant panels flashing when changed --- .../Multiplayer/Participants/ParticipantPanel.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index 2983d1268d..0ee1b6d684 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs @@ -162,15 +162,20 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants const double fade_time = 50; - var ruleset = rulesets.GetRuleset(Room.Settings.RulesetID).CreateInstance(); - userStateDisplay.Status = User.State; - userModsDisplay.Current.Value = User.Mods.Select(m => m.ToMod(ruleset)).ToList(); if (Room.Host?.Equals(User) == true) crown.FadeIn(fade_time); else crown.FadeOut(fade_time); + + // If the mods are updated at the end of the frame, the flow container will skip a reflow cycle: https://github.com/ppy/osu-framework/issues/4187 + // This looks particularly jarring here, so re-schedule the update to that start of our frame as a fix. + Schedule(() => + { + var ruleset = rulesets.GetRuleset(Room.Settings.RulesetID).CreateInstance(); + userModsDisplay.Current.Value = User.Mods.Select(m => m.ToMod(ruleset)).ToList(); + }); } public MenuItem[] ContextMenuItems From 0679901e4d3048552b9e89ace1896958189c6d4e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Feb 2021 22:53:40 +0900 Subject: [PATCH 0402/1791] Update error handling --- osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs index dc98eb8687..33200ca076 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs @@ -66,8 +66,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { failed = true; - if (e is WebException || string.IsNullOrEmpty(e.Message)) - Logger.Error(e, "Failed to retrieve a score submission token.\n\nThis may happen if you are running an old or non-official release of osu! (ie. you are self-compiling)."); + 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); From 7f82a06a61284b89a99a5b95cbae09cf428fa159 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Feb 2021 23:08:31 +0900 Subject: [PATCH 0403/1791] Remove no longer used using directive --- osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs index 33200ca076..38eae2346a 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs @@ -4,7 +4,6 @@ using System; using System.Diagnostics; using System.Linq; -using System.Net; using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; From 5061231e599a2d04a40a2526872383987c9acddb Mon Sep 17 00:00:00 2001 From: vmaggioli Date: Fri, 5 Feb 2021 09:39:14 -0500 Subject: [PATCH 0404/1791] Switch to beat length --- .../Compose/Components/Timeline/TimelineHitObjectBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 301543b3c1..d24614299c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -388,7 +388,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline case IHasDuration endTimeHitObject: var snappedTime = Math.Max(hitObject.StartTime, beatSnapProvider.SnapTime(time)); - if (endTimeHitObject.EndTime == snappedTime || Precision.AlmostEquals(snappedTime, hitObject.StartTime, 1)) + if (endTimeHitObject.EndTime == snappedTime || Precision.AlmostEquals(snappedTime, hitObject.StartTime, beatmap.GetBeatLengthAtTime(snappedTime))) return; endTimeHitObject.Duration = snappedTime - hitObject.StartTime; From f29938e15d62bad02c0ff8d0f586bc2764f423a9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 5 Feb 2021 20:39:57 +0300 Subject: [PATCH 0405/1791] Make last binding game activity more sensible --- osu.Game/Screens/Play/Player.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 542839f11d..f38eba3f27 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -80,6 +80,9 @@ namespace osu.Game.Screens.Play public int RestartCount; + [Resolved] + private OsuGameBase gameBase { get; set; } + [Resolved] private ScoreManager scoreManager { get; set; } @@ -157,7 +160,8 @@ namespace osu.Game.Screens.Play if (!Mods.Value.Any(m => m is ModAutoplay)) PrepareReplay(); - // needs to be bound here as the last binding, otherwise starting a replay while not focused causes player to exit. + // needs to be bound here as the last binding, otherwise cases like starting a replay while not focused causes player to exit, if activity is bound before checks. + gameActive.BindTo(gameBase.IsActive); gameActive.BindValueChanged(_ => updatePauseOnFocusLostState(), true); } @@ -191,8 +195,6 @@ namespace osu.Game.Screens.Play mouseWheelDisabled = config.GetBindable(OsuSetting.MouseDisableWheel); - gameActive.BindTo(gameBase.IsActive); - if (game != null) LocalUserPlaying.BindTo(game.LocalUserPlaying); @@ -429,7 +431,7 @@ namespace osu.Game.Screens.Play private void updatePauseOnFocusLostState() { - if (!IsLoaded || !PauseOnFocusLost || DrawableRuleset.HasReplayLoaded.Value || breakTracker.IsBreakTime.Value) + if (!PauseOnFocusLost || DrawableRuleset.HasReplayLoaded.Value || breakTracker.IsBreakTime.Value) return; if (gameActive.Value == false) From f6d08f54e6e950f4784087012fe27cd3e566a22c Mon Sep 17 00:00:00 2001 From: Lucas A Date: Fri, 5 Feb 2021 21:19:13 +0100 Subject: [PATCH 0406/1791] Use the oldest user config file available when there happens to be multiple config files available. --- osu.Game/IO/StableStorage.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/IO/StableStorage.cs b/osu.Game/IO/StableStorage.cs index f86b18c724..614e548d93 100644 --- a/osu.Game/IO/StableStorage.cs +++ b/osu.Game/IO/StableStorage.cs @@ -35,7 +35,11 @@ namespace osu.Game.IO { var songsDirectoryPath = Path.Combine(BasePath, stable_default_songs_path); - var configFile = GetFiles(".", "osu!.*.cfg").SingleOrDefault(); + // enumerate the user config files available in case the user migrated their files from another pc / operating system. + var foundConfigFiles = GetFiles(".", "osu!.*.cfg"); + + // if more than one config file is found, let's use the oldest one (where the username in the filename doesn't match the local username). + var configFile = foundConfigFiles.Count() > 1 ? foundConfigFiles.FirstOrDefault(filename => !filename[5..^4].Contains(Environment.UserName, StringComparison.Ordinal)) : foundConfigFiles.FirstOrDefault(); if (configFile == null) return songsDirectoryPath; From c9db0bf88651affc9bdd3a165984ad7577770149 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 6 Feb 2021 20:54:13 +0300 Subject: [PATCH 0407/1791] Call break time update when loaded --- osu.Game/Screens/Play/BreakTracker.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/BreakTracker.cs b/osu.Game/Screens/Play/BreakTracker.cs index 793b6b0ebe..2f3673e91f 100644 --- a/osu.Game/Screens/Play/BreakTracker.cs +++ b/osu.Game/Screens/Play/BreakTracker.cs @@ -32,7 +32,8 @@ namespace osu.Game.Screens.Play breaks = new PeriodTracker(value.Where(b => b.HasEffect) .Select(b => new Period(b.StartTime, b.EndTime - BreakOverlay.BREAK_FADE_DURATION))); - updateBreakTime(); + if (IsLoaded) + updateBreakTime(); } } @@ -50,7 +51,7 @@ namespace osu.Game.Screens.Play private void updateBreakTime() { - var time = Clock?.CurrentTime ?? 0; + var time = Clock.CurrentTime; isBreakTime.Value = breaks?.IsInAny(time) == true || time < gameplayStartTime From 40ddccf0c73904af580d3023b3d79d45a14868f3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 6 Feb 2021 20:55:55 +0300 Subject: [PATCH 0408/1791] Do not consider replays for "pause on focus lost" Replays are not pausable as can be seen in the `canPause` check. --- osu.Game/Screens/Play/Player.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index f38eba3f27..81401b08e8 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -160,7 +160,6 @@ namespace osu.Game.Screens.Play if (!Mods.Value.Any(m => m is ModAutoplay)) PrepareReplay(); - // needs to be bound here as the last binding, otherwise cases like starting a replay while not focused causes player to exit, if activity is bound before checks. gameActive.BindTo(gameBase.IsActive); gameActive.BindValueChanged(_ => updatePauseOnFocusLostState(), true); } @@ -267,8 +266,6 @@ namespace osu.Game.Screens.Play DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updateGameplayState()); - DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updatePauseOnFocusLostState(), true); - // bind clock into components that require it DrawableRuleset.IsPaused.BindTo(GameplayClockContainer.IsPaused); @@ -431,7 +428,7 @@ namespace osu.Game.Screens.Play private void updatePauseOnFocusLostState() { - if (!PauseOnFocusLost || DrawableRuleset.HasReplayLoaded.Value || breakTracker.IsBreakTime.Value) + if (!PauseOnFocusLost || breakTracker.IsBreakTime.Value) return; if (gameActive.Value == false) From d0ca2b99a850f9903eec2f7ac1956e15a73089a2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 6 Feb 2021 20:57:01 +0300 Subject: [PATCH 0409/1791] Remove unnecessary injected dependency --- 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 81401b08e8..bd67d3f06a 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -178,7 +178,7 @@ namespace osu.Game.Screens.Play } [BackgroundDependencyLoader(true)] - private void load(AudioManager audio, OsuConfigManager config, OsuGame game, OsuGameBase gameBase) + private void load(AudioManager audio, OsuConfigManager config, OsuGame game) { Mods.Value = base.Mods.Value.Select(m => m.CreateCopy()).ToArray(); From 68c20a2a3705dcc0995303e83a4312164d9fd98d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 31 Jan 2021 17:19:07 +0100 Subject: [PATCH 0410/1791] Allow autoplay score generation to access mod list --- osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs | 3 ++- osu.Game.Rulesets.Catch/Mods/CatchModCinema.cs | 3 ++- osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs | 3 ++- osu.Game.Rulesets.Mania/Mods/ManiaModCinema.cs | 3 ++- .../TestSceneMissHitWindowJudgements.cs | 4 +++- osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs | 3 ++- osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs | 3 ++- osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs | 3 ++- osu.Game.Rulesets.Taiko/Mods/TaikoModCinema.cs | 3 ++- osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs | 2 +- osu.Game/Rulesets/Mods/ModAutoplay.cs | 12 +++++++++++- osu.Game/Rulesets/Mods/ModCinema.cs | 4 +++- 12 files changed, 34 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs b/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs index 692e63fa69..e1eceea606 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Replays; @@ -12,7 +13,7 @@ namespace osu.Game.Rulesets.Catch.Mods { public class CatchModAutoplay : ModAutoplay { - public override Score CreateReplayScore(IBeatmap beatmap) => new Score + public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score { ScoreInfo = new ScoreInfo { User = new User { Username = "osu!salad!" } }, Replay = new CatchAutoGenerator(beatmap).Generate(), diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModCinema.cs b/osu.Game.Rulesets.Catch/Mods/CatchModCinema.cs index 3bc1ee5bf5..d53d019e90 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModCinema.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModCinema.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Replays; @@ -12,7 +13,7 @@ namespace osu.Game.Rulesets.Catch.Mods { public class CatchModCinema : ModCinema { - public override Score CreateReplayScore(IBeatmap beatmap) => new Score + public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score { ScoreInfo = new ScoreInfo { User = new User { Username = "osu!salad!" } }, Replay = new CatchAutoGenerator(beatmap).Generate(), diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs index c05e979e9a..105d88129c 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; @@ -13,7 +14,7 @@ namespace osu.Game.Rulesets.Mania.Mods { public class ManiaModAutoplay : ModAutoplay { - public override Score CreateReplayScore(IBeatmap beatmap) => new Score + public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score { ScoreInfo = new ScoreInfo { User = new User { Username = "osu!topus!" } }, Replay = new ManiaAutoGenerator((ManiaBeatmap)beatmap).Generate(), diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModCinema.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModCinema.cs index 02c1fc1b79..064c55ed8d 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModCinema.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModCinema.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; @@ -13,7 +14,7 @@ namespace osu.Game.Rulesets.Mania.Mods { public class ManiaModCinema : ModCinema { - public override Score CreateReplayScore(IBeatmap beatmap) => new Score + public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score { ScoreInfo = new ScoreInfo { User = new User { Username = "osu!topus!" } }, Replay = new ManiaAutoGenerator((ManiaBeatmap)beatmap).Generate(), diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs index 39deba2f57..f73649fcd9 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs @@ -1,9 +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 System.Collections.Generic; using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Replays; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Objects; @@ -65,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Tests private class TestAutoMod : OsuModAutoplay { - public override Score CreateReplayScore(IBeatmap beatmap) => new Score + public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score { ScoreInfo = new ScoreInfo { User = new User { Username = "Autoplay" } }, Replay = new MissingAutoGenerator(beatmap).Generate() diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs index bea2bbcb32..454c94cd96 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; @@ -16,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).Append(typeof(OsuModSpunOut)).ToArray(); - public override Score CreateReplayScore(IBeatmap beatmap) => new Score + public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score { ScoreInfo = new ScoreInfo { User = new User { Username = "Autoplay" } }, Replay = new OsuAutoGenerator(beatmap).Generate() diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs b/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs index 5d9a524577..99e5568bb3 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; @@ -16,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).Append(typeof(OsuModSpunOut)).ToArray(); - public override Score CreateReplayScore(IBeatmap beatmap) => new Score + public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score { ScoreInfo = new ScoreInfo { User = new User { Username = "Autoplay" } }, Replay = new OsuAutoGenerator(beatmap).Generate() diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs index 5b890b3d03..64e59b64d0 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Taiko.Objects; @@ -12,7 +13,7 @@ namespace osu.Game.Rulesets.Taiko.Mods { public class TaikoModAutoplay : ModAutoplay { - public override Score CreateReplayScore(IBeatmap beatmap) => new Score + public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score { ScoreInfo = new ScoreInfo { User = new User { Username = "mekkadosu!" } }, Replay = new TaikoAutoGenerator(beatmap).Generate(), diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModCinema.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModCinema.cs index 71aa007d3b..00f0c8e321 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModCinema.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModCinema.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Taiko.Objects; @@ -12,7 +13,7 @@ namespace osu.Game.Rulesets.Taiko.Mods { public class TaikoModCinema : ModCinema { - public override Score CreateReplayScore(IBeatmap beatmap) => new Score + public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score { ScoreInfo = new ScoreInfo { User = new User { Username = "mekkadosu!" } }, Replay = new TaikoAutoGenerator(beatmap).Generate(), diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs index 3a71d4ca54..f94e122b30 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs @@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual.Gameplay { var beatmap = Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo, Array.Empty()); - return new ScoreAccessibleReplayPlayer(ruleset.GetAutoplayMod()?.CreateReplayScore(beatmap)); + return new ScoreAccessibleReplayPlayer(ruleset.GetAutoplayMod()?.CreateReplayScore(beatmap, Array.Empty())); } protected override void AddCheckSteps() diff --git a/osu.Game/Rulesets/Mods/ModAutoplay.cs b/osu.Game/Rulesets/Mods/ModAutoplay.cs index 945dd444be..748c7272f4 100644 --- a/osu.Game/Rulesets/Mods/ModAutoplay.cs +++ b/osu.Game/Rulesets/Mods/ModAutoplay.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; using osu.Game.Graphics; @@ -15,7 +16,11 @@ namespace osu.Game.Rulesets.Mods public abstract class ModAutoplay : ModAutoplay, IApplicableToDrawableRuleset where T : HitObject { - public virtual void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) => drawableRuleset.SetReplayScore(CreateReplayScore(drawableRuleset.Beatmap)); + public virtual void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + { + var mods = (IReadOnlyList)drawableRuleset.Dependencies.Get(typeof(IReadOnlyList)); + drawableRuleset.SetReplayScore(CreateReplayScore(drawableRuleset.Beatmap, mods)); + } } public abstract class ModAutoplay : Mod, IApplicableFailOverride @@ -35,6 +40,11 @@ namespace osu.Game.Rulesets.Mods public override bool HasImplementation => GetType().GenericTypeArguments.Length == 0; + [Obsolete("Use the mod-supporting override")] // can be removed 20210731 public virtual Score CreateReplayScore(IBeatmap beatmap) => new Score { Replay = new Replay() }; + +#pragma warning disable 618 + public virtual Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => CreateReplayScore(beatmap); +#pragma warning restore 618 } } diff --git a/osu.Game/Rulesets/Mods/ModCinema.cs b/osu.Game/Rulesets/Mods/ModCinema.cs index bee9e56edd..16e6400f23 100644 --- a/osu.Game/Rulesets/Mods/ModCinema.cs +++ b/osu.Game/Rulesets/Mods/ModCinema.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Rulesets.Objects; @@ -14,7 +15,8 @@ namespace osu.Game.Rulesets.Mods { public virtual void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - drawableRuleset.SetReplayScore(CreateReplayScore(drawableRuleset.Beatmap)); + var mods = (IReadOnlyList)drawableRuleset.Dependencies.Get(typeof(IReadOnlyList)); + drawableRuleset.SetReplayScore(CreateReplayScore(drawableRuleset.Beatmap, mods)); // AlwaysPresent required for hitsounds drawableRuleset.Playfield.AlwaysPresent = true; From 7daeacaff230b58a13847db3727918cf7da38d02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 31 Jan 2021 17:43:16 +0100 Subject: [PATCH 0411/1791] Add and implement IApplicableToRate interface --- osu.Game/Rulesets/Mods/IApplicableToRate.cs | 20 ++++++++++++++++++++ osu.Game/Rulesets/Mods/ModRateAdjust.cs | 4 +++- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 19 ++++++++++++------- 3 files changed, 35 insertions(+), 8 deletions(-) create mode 100644 osu.Game/Rulesets/Mods/IApplicableToRate.cs diff --git a/osu.Game/Rulesets/Mods/IApplicableToRate.cs b/osu.Game/Rulesets/Mods/IApplicableToRate.cs new file mode 100644 index 0000000000..f613867132 --- /dev/null +++ b/osu.Game/Rulesets/Mods/IApplicableToRate.cs @@ -0,0 +1,20 @@ +// 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.Rulesets.Mods +{ + /// + /// Interface that should be implemented by mods that affect the track playback speed, + /// and in turn, values of the track rate. + /// + public interface IApplicableToRate : IApplicableToAudio + { + /// + /// Returns the playback rate at after this mod is applied. + /// + /// The time instant at which the playback rate is queried. + /// The playback rate before applying this mod. + /// The playback rate after applying this mod. + double ApplyToRate(double time, double rate = 1); + } +} diff --git a/osu.Game/Rulesets/Mods/ModRateAdjust.cs b/osu.Game/Rulesets/Mods/ModRateAdjust.cs index 2150b0fb68..b016a6d43b 100644 --- a/osu.Game/Rulesets/Mods/ModRateAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModRateAdjust.cs @@ -8,7 +8,7 @@ using osu.Framework.Graphics.Audio; namespace osu.Game.Rulesets.Mods { - public abstract class ModRateAdjust : Mod, IApplicableToAudio + public abstract class ModRateAdjust : Mod, IApplicableToRate { public abstract BindableNumber SpeedChange { get; } @@ -22,6 +22,8 @@ namespace osu.Game.Rulesets.Mods sample.AddAdjustment(AdjustableProperty.Frequency, SpeedChange); } + public double ApplyToRate(double time, double rate) => rate * SpeedChange.Value; + 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 b6916c838e..7e801c3024 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -14,7 +14,7 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Mods { - public abstract class ModTimeRamp : Mod, IUpdatableByPlayfield, IApplicableToBeatmap, IApplicableToAudio + public abstract class ModTimeRamp : Mod, IUpdatableByPlayfield, IApplicableToBeatmap, IApplicableToRate { /// /// The point in the beatmap at which the final ramping rate should be reached. @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Mods protected ModTimeRamp() { // for preview purpose at song select. eventually we'll want to be able to update every frame. - FinalRate.BindValueChanged(val => applyRateAdjustment(1), true); + FinalRate.BindValueChanged(val => applyRateAdjustment(double.PositiveInfinity), true); AdjustPitch.BindValueChanged(applyPitchAdjustment); } @@ -75,17 +75,22 @@ namespace osu.Game.Rulesets.Mods finalRateTime = firstObjectStart + FINAL_RATE_PROGRESS * (lastObjectEnd - firstObjectStart); } + public double ApplyToRate(double time, double rate = 1) + { + double amount = (time - beginRampTime) / Math.Max(1, finalRateTime - beginRampTime); + double ramp = InitialRate.Value + (FinalRate.Value - InitialRate.Value) * Math.Clamp(amount, 0, 1); + return rate * ramp; + } + public virtual void Update(Playfield playfield) { - applyRateAdjustment((track.CurrentTime - beginRampTime) / Math.Max(1, finalRateTime - beginRampTime)); + applyRateAdjustment(track.CurrentTime); } /// - /// Adjust the rate along the specified ramp + /// Adjust the rate along the specified ramp. /// - /// The amount of adjustment to apply (from 0..1). - private void applyRateAdjustment(double amount) => - SpeedChange.Value = InitialRate.Value + (FinalRate.Value - InitialRate.Value) * Math.Clamp(amount, 0, 1); + private void applyRateAdjustment(double time) => SpeedChange.Value = ApplyToRate(time); private void applyPitchAdjustment(ValueChangedEvent adjustPitchSetting) { From 3fabe247b09a32ccf1fb6a652356875ce1c63b43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 31 Jan 2021 17:59:35 +0100 Subject: [PATCH 0412/1791] Allow OsuModGenerator to accept a mod list --- .../TestSceneMissHitWindowJudgements.cs | 6 +++--- osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs | 3 ++- osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs | 2 +- osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs | 6 ++++-- osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs | 3 ++- 6 files changed, 13 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs index f73649fcd9..af67ab5839 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs @@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Osu.Tests public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score { ScoreInfo = new ScoreInfo { User = new User { Username = "Autoplay" } }, - Replay = new MissingAutoGenerator(beatmap).Generate() + Replay = new MissingAutoGenerator(beatmap, mods).Generate() }; } @@ -78,8 +78,8 @@ namespace osu.Game.Rulesets.Osu.Tests { public new OsuBeatmap Beatmap => (OsuBeatmap)base.Beatmap; - public MissingAutoGenerator(IBeatmap beatmap) - : base(beatmap) + public MissingAutoGenerator(IBeatmap beatmap, IReadOnlyList mods) + : base(beatmap, mods) { } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index 8c819c4773..59a5295858 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -62,7 +62,8 @@ namespace osu.Game.Rulesets.Osu.Mods inputManager.AllowUserCursorMovement = false; // Generate the replay frames the cursor should follow - replayFrames = new OsuAutoGenerator(drawableRuleset.Beatmap).Generate().Frames.Cast().ToList(); + var mods = (IReadOnlyList)drawableRuleset.Dependencies.Get(typeof(IReadOnlyList)); + replayFrames = new OsuAutoGenerator(drawableRuleset.Beatmap, mods).Generate().Frames.Cast().ToList(); } } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs index 454c94cd96..3b1f271d41 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score { ScoreInfo = new ScoreInfo { User = new User { Username = "Autoplay" } }, - Replay = new OsuAutoGenerator(beatmap).Generate() + Replay = new OsuAutoGenerator(beatmap, mods).Generate() }; } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs b/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs index 99e5568bb3..df06988b70 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score { ScoreInfo = new ScoreInfo { User = new User { Username = "Autoplay" } }, - Replay = new OsuAutoGenerator(beatmap).Generate() + Replay = new OsuAutoGenerator(beatmap, mods).Generate() }; } } diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index 954a217473..e4b6f6425d 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -6,10 +6,12 @@ using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Rulesets.Osu.Objects; using System; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using osu.Framework.Graphics; using osu.Game.Replays; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Scoring; @@ -49,8 +51,8 @@ namespace osu.Game.Rulesets.Osu.Replays #region Construction / Initialisation - public OsuAutoGenerator(IBeatmap beatmap) - : base(beatmap) + public OsuAutoGenerator(IBeatmap beatmap, IReadOnlyList mods) + : base(beatmap, mods) { // Already superhuman, but still somewhat realistic reactionTime = ApplyModsToRate(100); diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs index 3356a0fbe0..f88594a3ee 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs @@ -6,6 +6,7 @@ using osu.Game.Beatmaps; using System; using System.Collections.Generic; using osu.Game.Replays; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Replays; @@ -34,7 +35,7 @@ namespace osu.Game.Rulesets.Osu.Replays protected Replay Replay; protected List Frames => Replay.Frames; - protected OsuAutoGeneratorBase(IBeatmap beatmap) + protected OsuAutoGeneratorBase(IBeatmap beatmap, IReadOnlyList mods) : base(beatmap) { Replay = new Replay(); From 0e1ec703d33907268dbb2acc49f3e8e19318ae2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 31 Jan 2021 17:56:03 +0100 Subject: [PATCH 0413/1791] Use IApplicableToRate in osu! auto generator --- .../Replays/OsuAutoGenerator.cs | 42 ++++++++++------- .../Replays/OsuAutoGeneratorBase.cs | 47 +++++++++++++++---- 2 files changed, 62 insertions(+), 27 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index e4b6f6425d..693943a08a 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -35,11 +35,6 @@ namespace osu.Game.Rulesets.Osu.Replays #region Constants - /// - /// The "reaction time" in ms between "seeing" a new hit object and moving to "react" to it. - /// - private readonly double reactionTime; - private readonly HitWindows defaultHitWindows; /// @@ -54,9 +49,6 @@ namespace osu.Game.Rulesets.Osu.Replays public OsuAutoGenerator(IBeatmap beatmap, IReadOnlyList mods) : base(beatmap, mods) { - // Already superhuman, but still somewhat realistic - reactionTime = ApplyModsToRate(100); - defaultHitWindows = new OsuHitWindows(); defaultHitWindows.SetDifficulty(Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty); } @@ -242,7 +234,7 @@ namespace osu.Game.Rulesets.Osu.Replays OsuReplayFrame lastFrame = (OsuReplayFrame)Frames[^1]; // Wait until Auto could "see and react" to the next note. - double waitTime = h.StartTime - Math.Max(0.0, h.TimePreempt - reactionTime); + double waitTime = h.StartTime - Math.Max(0.0, h.TimePreempt - getReactionTime(h.StartTime - h.TimePreempt)); if (waitTime > lastFrame.Time) { @@ -252,7 +244,7 @@ namespace osu.Game.Rulesets.Osu.Replays Vector2 lastPosition = lastFrame.Position; - double timeDifference = ApplyModsToTime(h.StartTime - lastFrame.Time); + double timeDifference = ApplyModsToTimeDelta(lastFrame.Time, h.StartTime); // Only "snap" to hitcircles if they are far enough apart. As the time between hitcircles gets shorter the snapping threshold goes up. if (timeDifference > 0 && // Sanity checks @@ -260,7 +252,7 @@ namespace osu.Game.Rulesets.Osu.Replays timeDifference >= 266)) // ... or the beats are slow enough to tap anyway. { // Perform eased movement - for (double time = lastFrame.Time + FrameDelay; time < h.StartTime; time += FrameDelay) + for (double time = lastFrame.Time + GetFrameDelay(lastFrame.Time); time < h.StartTime; time += GetFrameDelay(time)) { Vector2 currentPosition = Interpolation.ValueAt(time, lastPosition, targetPos, lastFrame.Time, h.StartTime, easing); AddFrameToReplay(new OsuReplayFrame((int)time, new Vector2(currentPosition.X, currentPosition.Y)) { Actions = lastFrame.Actions }); @@ -274,6 +266,14 @@ namespace osu.Game.Rulesets.Osu.Replays } } + /// + /// Calculates the "reaction time" in ms between "seeing" a new hit object and moving to "react" to it. + /// + /// + /// Already superhuman, but still somewhat realistic. + /// + private double getReactionTime(double timeInstant) => ApplyModsToRate(timeInstant, 100); + // Add frames to click the hitobject private void addHitObjectClickFrames(OsuHitObject h, Vector2 startPosition, float spinnerDirection) { @@ -343,17 +343,23 @@ namespace osu.Game.Rulesets.Osu.Replays float angle = radius == 0 ? 0 : MathF.Atan2(difference.Y, difference.X); double t; + double previousFrame = h.StartTime; - for (double j = h.StartTime + FrameDelay; j < spinner.EndTime; j += FrameDelay) + for (double nextFrame = h.StartTime + GetFrameDelay(h.StartTime); nextFrame < spinner.EndTime; nextFrame += GetFrameDelay(nextFrame)) { - t = ApplyModsToTime(j - h.StartTime) * spinnerDirection; + t = ApplyModsToTimeDelta(previousFrame, nextFrame) * spinnerDirection; + angle += (float)t / 20; - Vector2 pos = SPINNER_CENTRE + CirclePosition(t / 20 + angle, SPIN_RADIUS); - AddFrameToReplay(new OsuReplayFrame((int)j, new Vector2(pos.X, pos.Y), action)); + Vector2 pos = SPINNER_CENTRE + CirclePosition(angle, SPIN_RADIUS); + AddFrameToReplay(new OsuReplayFrame((int)nextFrame, new Vector2(pos.X, pos.Y), action)); + + previousFrame = nextFrame; } - t = ApplyModsToTime(spinner.EndTime - h.StartTime) * spinnerDirection; - Vector2 endPosition = SPINNER_CENTRE + CirclePosition(t / 20 + angle, SPIN_RADIUS); + t = ApplyModsToTimeDelta(previousFrame, spinner.EndTime) * spinnerDirection; + angle += (float)t / 20; + + Vector2 endPosition = SPINNER_CENTRE + CirclePosition(angle, SPIN_RADIUS); AddFrameToReplay(new OsuReplayFrame(spinner.EndTime, new Vector2(endPosition.X, endPosition.Y), action)); @@ -361,7 +367,7 @@ namespace osu.Game.Rulesets.Osu.Replays break; case Slider slider: - for (double j = FrameDelay; j < slider.Duration; j += FrameDelay) + for (double j = GetFrameDelay(slider.StartTime); j < slider.Duration; j += GetFrameDelay(slider.StartTime + j)) { Vector2 pos = slider.StackedPositionAt(j / slider.Duration); AddFrameToReplay(new OsuReplayFrame(h.StartTime + j, new Vector2(pos.X, pos.Y), action)); diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs index f88594a3ee..1cb3208c30 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs @@ -5,6 +5,7 @@ using osuTK; using osu.Game.Beatmaps; using System; using System.Collections.Generic; +using System.Linq; using osu.Game.Replays; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.UI; @@ -23,33 +24,61 @@ namespace osu.Game.Rulesets.Osu.Replays public const float SPIN_RADIUS = 50; - /// - /// The time in ms between each ReplayFrame. - /// - protected readonly double FrameDelay; - #endregion #region Construction / Initialisation protected Replay Replay; protected List Frames => Replay.Frames; + private readonly IReadOnlyList timeAffectingMods; protected OsuAutoGeneratorBase(IBeatmap beatmap, IReadOnlyList mods) : base(beatmap) { Replay = new Replay(); - // We are using ApplyModsToRate and not ApplyModsToTime to counteract the speed up / slow down from HalfTime / DoubleTime so that we remain at a constant framerate of 60 fps. - FrameDelay = ApplyModsToRate(1000.0 / 60.0); + timeAffectingMods = mods.OfType().ToList(); } #endregion #region Utilities - protected double ApplyModsToTime(double v) => v; - protected double ApplyModsToRate(double v) => v; + /// + /// Returns the real duration of time between and + /// after applying rate-affecting mods. + /// + /// + /// This method should only be used when and are very close. + /// That is because the track rate might be changing with time, + /// and the method used here is a rough instantaneous approximation. + /// + /// The start time of the time delta, in original track time. + /// The end time of the time delta, in original track time. + protected double ApplyModsToTimeDelta(double startTime, double endTime) + { + double delta = endTime - startTime; + + foreach (var mod in timeAffectingMods) + delta /= mod.ApplyToRate(startTime); + + return delta; + } + + protected double ApplyModsToRate(double time, double rate) + { + foreach (var mod in timeAffectingMods) + rate = mod.ApplyToRate(time, rate); + return rate; + } + + /// + /// Calculates the interval after which the next should be generated, + /// in milliseconds. + /// + /// The time of the previous frame. + protected double GetFrameDelay(double time) + => ApplyModsToRate(time, 1000.0 / 60); private class ReplayFrameComparer : IComparer { From 0229851c9ca93570e68dbe056cc15d20099b1cea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 7 Feb 2021 19:02:09 +0100 Subject: [PATCH 0414/1791] Apply rounding to ModTimeRamp to improve SPM consistency --- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index 7e801c3024..330945d3d3 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -79,7 +79,9 @@ namespace osu.Game.Rulesets.Mods { double amount = (time - beginRampTime) / Math.Max(1, finalRateTime - beginRampTime); double ramp = InitialRate.Value + (FinalRate.Value - InitialRate.Value) * Math.Clamp(amount, 0, 1); - return rate * ramp; + + // round the end result to match the bindable SpeedChange's precision, in case this is called externally. + return rate * Math.Round(ramp, 2); } public virtual void Update(Playfield playfield) From 0df15b4d7a4361368d1504ec18695902b9969d82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 7 Feb 2021 19:25:33 +0100 Subject: [PATCH 0415/1791] Add test coverage --- .../Mods/TestSceneOsuModAutoplay.cs | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutoplay.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutoplay.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutoplay.cs new file mode 100644 index 0000000000..856b6554b9 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutoplay.cs @@ -0,0 +1,65 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Testing; +using osu.Framework.Utils; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Skinning.Default; +using osu.Game.Rulesets.Osu.UI; + +namespace osu.Game.Rulesets.Osu.Tests.Mods +{ + public class TestSceneOsuModAutoplay : OsuModTestScene + { + [Test] + public void TestSpmUnaffectedByRateAdjust() + => runSpmTest(new OsuModDaycore + { + SpeedChange = { Value = 0.88 } + }); + + [Test] + public void TestSpmUnaffectedByTimeRamp() + => runSpmTest(new ModWindUp + { + InitialRate = { Value = 0.7 }, + FinalRate = { Value = 1.3 } + }); + + private void runSpmTest(Mod mod) + { + SpinnerSpmCounter spmCounter = null; + + CreateModTest(new ModTestData + { + Autoplay = true, + Mod = mod, + Beatmap = new Beatmap + { + HitObjects = + { + new Spinner + { + Duration = 2000, + Position = OsuPlayfield.BASE_SIZE / 2 + } + } + }, + PassCondition = () => Player.ScoreProcessor.JudgedHits >= 1 + }); + + AddUntilStep("fetch SPM counter", () => + { + spmCounter = this.ChildrenOfType().SingleOrDefault(); + return spmCounter != null; + }); + + AddUntilStep("SPM is correct", () => Precision.AlmostEquals(spmCounter.SpinsPerMinute, 477, 5)); + } + } +} From d74a1437beddf07f471254588a4609130a361cd2 Mon Sep 17 00:00:00 2001 From: Joehu Date: Sun, 7 Feb 2021 15:14:08 -0800 Subject: [PATCH 0416/1791] Fix player loader metadata not being centred --- .../Screens/Play/BeatmapMetadataDisplay.cs | 78 +++++++++++-------- 1 file changed, 46 insertions(+), 32 deletions(-) diff --git a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs index b53141e8fb..eff06e26ee 100644 --- a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs +++ b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs @@ -23,32 +23,6 @@ namespace osu.Game.Screens.Play /// public class BeatmapMetadataDisplay : Container { - private class MetadataLine : Container - { - public MetadataLine(string left, string right) - { - AutoSizeAxes = Axes.Both; - Children = new Drawable[] - { - new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopRight, - Margin = new MarginPadding { Right = 5 }, - Colour = OsuColour.Gray(0.8f), - Text = left, - }, - new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopLeft, - Margin = new MarginPadding { Left = 5 }, - Text = string.IsNullOrEmpty(right) ? @"-" : right, - } - }; - } - } - private readonly WorkingBeatmap beatmap; private readonly Bindable> mods; private readonly Drawable facade; @@ -144,15 +118,34 @@ namespace osu.Game.Screens.Play Bottom = 40 }, }, - new MetadataLine("Source", metadata.Source) + new GridContainer { - Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, - }, - new MetadataLine("Mapper", metadata.AuthorString) - { Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, + AutoSizeAxes = Axes.Both, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + }, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + }, + Content = new[] + { + new Drawable[] + { + new MetadataLineLabel("Source"), + new MetadataLineInfo(metadata.Source) + }, + new Drawable[] + { + new MetadataLineLabel("Mapper"), + new MetadataLineInfo(metadata.AuthorString) + } + } }, new ModDisplay { @@ -168,5 +161,26 @@ namespace osu.Game.Screens.Play Loading = true; } + + private class MetadataLineLabel : OsuSpriteText + { + public MetadataLineLabel(string text) + { + Anchor = Anchor.TopRight; + Origin = Anchor.TopRight; + Margin = new MarginPadding { Right = 5 }; + Colour = OsuColour.Gray(0.8f); + Text = text; + } + } + + private class MetadataLineInfo : OsuSpriteText + { + public MetadataLineInfo(string text) + { + Margin = new MarginPadding { Left = 5 }; + Text = string.IsNullOrEmpty(text) ? @"-" : text; + } + } } } From 2218247b21d09a8317ae84dd4dffa1bbf7a744c0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 8 Feb 2021 11:07:50 +0900 Subject: [PATCH 0417/1791] Override mod type --- osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs index 8cd6676f9d..863dc05216 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs @@ -30,6 +30,8 @@ namespace osu.Game.Rulesets.Osu.Mods public override bool Ranked => false; + public override ModType Type => ModType.Conversion; + [SettingSource("Disable slider head judgement", "Scores sliders proportionally to the number of ticks hit.")] public Bindable DisableSliderHeadJudgement { get; } = new BindableBool(true); From d955200e0718db14fa4b5ea13e6355e5b7134983 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 8 Feb 2021 11:10:07 +0900 Subject: [PATCH 0418/1791] Prevent invalid hit results for ignored slider heads --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs index ee1df00ef7..87cfa47091 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs @@ -91,7 +91,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables // If not judged as a normal hitcircle, only track whether a hit has occurred (via IgnoreHit) rather than a scorable hit result. var result = base.ResultFor(timeOffset); - return result.IsHit() ? HitResult.IgnoreHit : result; + return result.IsHit() ? HitResult.IgnoreHit : HitResult.IgnoreMiss; } public Action OnShake; From 9e0724b138fd4c251dc61e5daa3e702dd2a77cee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 8 Feb 2021 15:58:41 +0900 Subject: [PATCH 0419/1791] Remove unnecessary double resolution of OsuGame --- osu.Game/Screens/Play/Player.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index bd67d3f06a..669fa93298 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -80,9 +80,6 @@ namespace osu.Game.Screens.Play public int RestartCount; - [Resolved] - private OsuGameBase gameBase { get; set; } - [Resolved] private ScoreManager scoreManager { get; set; } @@ -160,7 +157,6 @@ namespace osu.Game.Screens.Play if (!Mods.Value.Any(m => m is ModAutoplay)) PrepareReplay(); - gameActive.BindTo(gameBase.IsActive); gameActive.BindValueChanged(_ => updatePauseOnFocusLostState(), true); } @@ -195,7 +191,10 @@ namespace osu.Game.Screens.Play mouseWheelDisabled = config.GetBindable(OsuSetting.MouseDisableWheel); if (game != null) + { LocalUserPlaying.BindTo(game.LocalUserPlaying); + gameActive.BindTo(game.IsActive); + } DrawableRuleset = ruleset.CreateDrawableRulesetWith(playableBeatmap, Mods.Value); From 10142a44716882a4671d4cae2391a96348bd90ba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 8 Feb 2021 16:59:21 +0900 Subject: [PATCH 0420/1791] Disable failing test temporarily pending resolution --- .../Visual/Multiplayer/TestScenePlaylistsSongSelect.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs index 2f7e59f800..1d13c6229c 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs @@ -143,6 +143,7 @@ namespace osu.Game.Tests.Visual.Multiplayer /// Tests that the same instances are not shared between two playlist items. /// [Test] + [Ignore("Temporarily disabled due to a non-trivial test failure")] public void TestNewItemHasNewModInstances() { AddStep("set dt mod", () => SelectedMods.Value = new[] { new OsuModDoubleTime() }); From 42c169054afa0b0aaf6e84002d4f05fd80e63e17 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 8 Feb 2021 17:46:34 +0900 Subject: [PATCH 0421/1791] Revert "Disable failing test temporarily pending resolution" This reverts commit 10142a44716882a4671d4cae2391a96348bd90ba. --- .../Visual/Multiplayer/TestScenePlaylistsSongSelect.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs index 1d13c6229c..2f7e59f800 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs @@ -143,7 +143,6 @@ namespace osu.Game.Tests.Visual.Multiplayer /// Tests that the same instances are not shared between two playlist items. /// [Test] - [Ignore("Temporarily disabled due to a non-trivial test failure")] public void TestNewItemHasNewModInstances() { AddStep("set dt mod", () => SelectedMods.Value = new[] { new OsuModDoubleTime() }); From fb8e31a30385636856e20ebdeb67d76ac6d815c8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 8 Feb 2021 17:51:57 +0900 Subject: [PATCH 0422/1791] Fix incorrect connection building due to bad merges --- .../Online/Multiplayer/MultiplayerClient.cs | 31 ++++++++----------- 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 1b966ae1dc..6908795510 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -71,20 +71,6 @@ namespace osu.Game.Online.Multiplayer if (!await connectionLock.WaitAsync(10000)) throw new TimeoutException("Could not obtain a lock to connect. A previous attempt is likely stuck."); - var builder = new HubConnectionBuilder() - .WithUrl(endpoint, options => { options.Headers.Add("Authorization", $"Bearer {api.AccessToken}"); }); - - if (RuntimeInfo.SupportsJIT) - builder.AddMessagePackProtocol(); - else - { - // eventually we will precompile resolvers for messagepack, but this isn't working currently - // see https://github.com/neuecc/MessagePack-CSharp/issues/780#issuecomment-768794308. - builder.AddNewtonsoftJsonProtocol(options => { options.PayloadSerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; }); - } - - connection = builder.Build(); - try { while (api.State.Value == APIState.Online) @@ -235,10 +221,19 @@ namespace osu.Game.Online.Multiplayer private HubConnection createConnection(CancellationToken cancellationToken) { - var newConnection = new HubConnectionBuilder() - .WithUrl(endpoint, options => { options.Headers.Add("Authorization", $"Bearer {api.AccessToken}"); }) - .AddNewtonsoftJsonProtocol(options => { options.PayloadSerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; }) - .Build(); + var builder = new HubConnectionBuilder() + .WithUrl(endpoint, options => { options.Headers.Add("Authorization", $"Bearer {api.AccessToken}"); }); + + if (RuntimeInfo.SupportsJIT) + builder.AddMessagePackProtocol(); + else + { + // eventually we will precompile resolvers for messagepack, but this isn't working currently + // see https://github.com/neuecc/MessagePack-CSharp/issues/780#issuecomment-768794308. + builder.AddNewtonsoftJsonProtocol(options => { options.PayloadSerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; }); + } + + var newConnection = builder.Build(); // this is kind of SILLY // https://github.com/dotnet/aspnetcore/issues/15198 From 6b26a18a23162e784fad3c941ace78fa557bf7d4 Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 8 Feb 2021 01:34:32 -0800 Subject: [PATCH 0423/1791] Fix attributes header not being aligned with content in editor timing mode --- osu.Game/Screens/Edit/Timing/ControlPointTable.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index e4b9150df1..81b006e6c8 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -74,7 +74,8 @@ namespace osu.Game.Screens.Edit.Timing { new TableColumn(string.Empty, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), new TableColumn("Time", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), - new TableColumn("Attributes", Anchor.Centre), + new TableColumn(), + new TableColumn("Attributes", Anchor.CentreLeft), }; return columns.ToArray(); @@ -93,6 +94,7 @@ namespace osu.Game.Screens.Edit.Timing Text = group.Time.ToEditorFormattedString(), Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) }, + null, new ControlGroupAttributes(group), }; @@ -108,7 +110,6 @@ namespace osu.Game.Screens.Edit.Timing { RelativeSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, - Padding = new MarginPadding(10), Spacing = new Vector2(2) }; From 5e7823b289a607731f253ec342c24fa9fcde7143 Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 8 Feb 2021 01:37:34 -0800 Subject: [PATCH 0424/1791] Fix attributes content being zero size and disappearing after being half off-screen --- osu.Game/Screens/Edit/Timing/ControlPointTable.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 81b006e6c8..8980c2019a 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -106,6 +106,7 @@ namespace osu.Game.Screens.Edit.Timing public ControlGroupAttributes(ControlPointGroup group) { + RelativeSizeAxes = Axes.Both; InternalChild = fill = new FillFlowContainer { RelativeSizeAxes = Axes.Both, From b40b159acb276d35bc553a597bc58980f3d3c1dd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 8 Feb 2021 18:52:50 +0900 Subject: [PATCH 0425/1791] Round beatlength --- osu.Game/Beatmaps/Beatmap.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 51fdbce96d..434bff14b5 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Objects; using System.Collections.Generic; @@ -65,7 +66,7 @@ namespace osu.Game.Beatmaps return (beatLength: t.BeatLength, duration: nextTime - t.Time); }) // Aggregate durations into a set of (beatLength, duration) tuples for each beat length - .GroupBy(t => t.beatLength) + .GroupBy(t => Math.Round(t.beatLength * 1000) / 1000) .Select(g => (beatLength: g.Key, duration: g.Sum(t => t.duration))) // And if there are no timing points, use a default. .DefaultIfEmpty((TimingControlPoint.DEFAULT_BEAT_LENGTH, 0)); From 18e3f8c233da2ed3eb8b859944994d39f9f54512 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 8 Feb 2021 19:03:19 +0900 Subject: [PATCH 0426/1791] Sort beat lengths rather than linear search --- osu.Game/Beatmaps/Beatmap.cs | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 434bff14b5..e5b6a4bc44 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -55,7 +55,7 @@ namespace osu.Game.Beatmaps // Note: This is more accurate and may present different results because osu-stable didn't have the ability to calculate slider durations in this context. double lastTime = HitObjects.LastOrDefault()?.GetEndTime() ?? ControlPointInfo.TimingPoints.LastOrDefault()?.Time ?? 0; - var beatLengthsAndDurations = + var mostCommon = // Construct a set of (beatLength, duration) tuples for each individual timing point. ControlPointInfo.TimingPoints.Select((t, i) => { @@ -68,23 +68,10 @@ namespace osu.Game.Beatmaps // Aggregate durations into a set of (beatLength, duration) tuples for each beat length .GroupBy(t => Math.Round(t.beatLength * 1000) / 1000) .Select(g => (beatLength: g.Key, duration: g.Sum(t => t.duration))) - // And if there are no timing points, use a default. - .DefaultIfEmpty((TimingControlPoint.DEFAULT_BEAT_LENGTH, 0)); + // Get the most common one, or 0 as a suitable default + .OrderByDescending(i => i.duration).FirstOrDefault(); - // Find the single beat length with the maximum aggregate duration. - double maxDurationBeatLength = double.NegativeInfinity; - double maxDuration = double.NegativeInfinity; - - foreach (var (beatLength, duration) in beatLengthsAndDurations) - { - if (duration > maxDuration) - { - maxDuration = duration; - maxDurationBeatLength = beatLength; - } - } - - return maxDurationBeatLength; + return mostCommon.beatLength; } IBeatmap IBeatmap.Clone() => Clone(); From f0dfa9f8f397269571b96d1165f03f36a555bc8e Mon Sep 17 00:00:00 2001 From: Lucas A Date: Mon, 8 Feb 2021 11:12:25 +0100 Subject: [PATCH 0427/1791] Use the newest config file available (where the local username matches the filename) --- osu.Game/IO/StableStorage.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game/IO/StableStorage.cs b/osu.Game/IO/StableStorage.cs index 614e548d93..ccc6f9c311 100644 --- a/osu.Game/IO/StableStorage.cs +++ b/osu.Game/IO/StableStorage.cs @@ -35,11 +35,7 @@ namespace osu.Game.IO { var songsDirectoryPath = Path.Combine(BasePath, stable_default_songs_path); - // enumerate the user config files available in case the user migrated their files from another pc / operating system. - var foundConfigFiles = GetFiles(".", "osu!.*.cfg"); - - // if more than one config file is found, let's use the oldest one (where the username in the filename doesn't match the local username). - var configFile = foundConfigFiles.Count() > 1 ? foundConfigFiles.FirstOrDefault(filename => !filename[5..^4].Contains(Environment.UserName, StringComparison.Ordinal)) : foundConfigFiles.FirstOrDefault(); + var configFile = GetFiles(".", $"osu!.{Environment.UserName}.cfg").SingleOrDefault(); if (configFile == null) return songsDirectoryPath; From a08c51f213594a02ea3d354c4e913f29723ab2fd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 8 Feb 2021 19:23:10 +0900 Subject: [PATCH 0428/1791] Remove duplicate code --- .../OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index e29fb658a3..ae32295676 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs @@ -164,9 +164,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants userStateDisplay.UpdateStatus(User.State, User.BeatmapAvailability); - var ruleset = rulesets.GetRuleset(Room.Settings.RulesetID).CreateInstance(); - userModsDisplay.Current.Value = User.Mods.Select(m => m.ToMod(ruleset)).ToList(); - if (Room.Host?.Equals(User) == true) crown.FadeIn(fade_time); else From d8c53e34ae5cba88b491d5379dec5d7ecd15e9f8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 8 Feb 2021 19:42:17 +0900 Subject: [PATCH 0429/1791] Fix missing using --- osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index 032493a5c6..f454fe619b 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -4,6 +4,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; From 19368f87fb8c37eec5bf06f71b2d15959722cf08 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 8 Feb 2021 19:59:07 +0900 Subject: [PATCH 0430/1791] Fix failing test --- osu.Game/Screens/Play/Player.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 669fa93298..7924e1390b 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -174,7 +174,7 @@ namespace osu.Game.Screens.Play } [BackgroundDependencyLoader(true)] - private void load(AudioManager audio, OsuConfigManager config, OsuGame game) + private void load(AudioManager audio, OsuConfigManager config, OsuGameBase game) { Mods.Value = base.Mods.Value.Select(m => m.CreateCopy()).ToArray(); @@ -191,10 +191,9 @@ namespace osu.Game.Screens.Play mouseWheelDisabled = config.GetBindable(OsuSetting.MouseDisableWheel); if (game != null) - { - LocalUserPlaying.BindTo(game.LocalUserPlaying); gameActive.BindTo(game.IsActive); - } + if (game is OsuGame osuGame) + LocalUserPlaying.BindTo(osuGame.LocalUserPlaying); DrawableRuleset = ruleset.CreateDrawableRulesetWith(playableBeatmap, Mods.Value); From 156f5bd5df715323e6dc227c7fb5be7e439ff72e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 8 Feb 2021 20:05:16 +0900 Subject: [PATCH 0431/1791] Add newline between statements --- osu.Game/Screens/Play/Player.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 7924e1390b..5d06ac5b3a 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -192,6 +192,7 @@ namespace osu.Game.Screens.Play if (game != null) gameActive.BindTo(game.IsActive); + if (game is OsuGame osuGame) LocalUserPlaying.BindTo(osuGame.LocalUserPlaying); From f4a31287bfca31bf088f077e014f7af4d68b6232 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 8 Feb 2021 20:11:06 +0900 Subject: [PATCH 0432/1791] Add/use IHitObjectContainer interface instead of IEnumerables --- osu.Game.Rulesets.Osu/UI/IHitPolicy.cs | 7 +++--- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 3 +-- .../UI/StartTimeOrderedHitPolicy.cs | 7 +++--- osu.Game/Rulesets/UI/HitObjectContainer.cs | 11 +-------- osu.Game/Rulesets/UI/IHitObjectContainer.cs | 24 +++++++++++++++++++ 5 files changed, 32 insertions(+), 20 deletions(-) create mode 100644 osu.Game/Rulesets/UI/IHitObjectContainer.cs diff --git a/osu.Game.Rulesets.Osu/UI/IHitPolicy.cs b/osu.Game.Rulesets.Osu/UI/IHitPolicy.cs index 72c3d781bb..5d8ea035a7 100644 --- a/osu.Game.Rulesets.Osu/UI/IHitPolicy.cs +++ b/osu.Game.Rulesets.Osu/UI/IHitPolicy.cs @@ -1,19 +1,18 @@ // 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.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Osu.UI { public interface IHitPolicy { /// - /// Sets the s which this controls. + /// The containing the s which this applies to. /// - /// An enumeration of the s. - void SetHitObjects(IEnumerable hitObjects); + IHitObjectContainer HitObjectContainer { set; } /// /// Determines whether a can be hit at a point in time. diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index c7900558a0..e085714265 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -54,8 +54,7 @@ namespace osu.Game.Rulesets.Osu.UI approachCircles = new ProxyContainer { RelativeSizeAxes = Axes.Both }, }; - hitPolicy = new StartTimeOrderedHitPolicy(); - hitPolicy.SetHitObjects(HitObjectContainer.AliveObjects); + hitPolicy = new StartTimeOrderedHitPolicy { HitObjectContainer = HitObjectContainer }; var hitWindows = new OsuHitWindows(); diff --git a/osu.Game.Rulesets.Osu/UI/StartTimeOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu/UI/StartTimeOrderedHitPolicy.cs index 38ba5fc490..0173156246 100644 --- a/osu.Game.Rulesets.Osu/UI/StartTimeOrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu/UI/StartTimeOrderedHitPolicy.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Osu.UI { @@ -19,9 +20,7 @@ namespace osu.Game.Rulesets.Osu.UI /// public class StartTimeOrderedHitPolicy : IHitPolicy { - private IEnumerable hitObjects; - - public void SetHitObjects(IEnumerable hitObjects) => this.hitObjects = hitObjects; + public IHitObjectContainer HitObjectContainer { get; set; } public bool IsHittable(DrawableHitObject hitObject, double time) { @@ -73,7 +72,7 @@ namespace osu.Game.Rulesets.Osu.UI private IEnumerable enumerateHitObjectsUpTo(double targetTime) { - foreach (var obj in hitObjects) + foreach (var obj in HitObjectContainer.AliveObjects) { if (obj.HitObject.StartTime >= targetTime) yield break; diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index 1972043ccb..11312a46df 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -17,19 +17,10 @@ using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.UI { - public class HitObjectContainer : LifetimeManagementContainer + public class HitObjectContainer : LifetimeManagementContainer, IHitObjectContainer { - /// - /// All currently in-use s. - /// public IEnumerable Objects => InternalChildren.Cast().OrderBy(h => h.HitObject.StartTime); - /// - /// All currently in-use s that are alive. - /// - /// - /// If this uses pooled objects, this is equivalent to . - /// public IEnumerable AliveObjects => AliveInternalChildren.Cast().OrderBy(h => h.HitObject.StartTime); /// diff --git a/osu.Game/Rulesets/UI/IHitObjectContainer.cs b/osu.Game/Rulesets/UI/IHitObjectContainer.cs new file mode 100644 index 0000000000..4c784132e8 --- /dev/null +++ b/osu.Game/Rulesets/UI/IHitObjectContainer.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 System.Collections.Generic; +using osu.Game.Rulesets.Objects.Drawables; + +namespace osu.Game.Rulesets.UI +{ + public interface IHitObjectContainer + { + /// + /// All currently in-use s. + /// + IEnumerable Objects { get; } + + /// + /// All currently in-use s that are alive. + /// + /// + /// If this uses pooled objects, this is equivalent to . + /// + IEnumerable AliveObjects { get; } + } +} From 414e05affdf867f3bda1eb02a9639c965368b3d7 Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 8 Feb 2021 02:41:07 -0800 Subject: [PATCH 0433/1791] Fix editor effect attribute tooltip having unnecessary whitespace when only one is enabled --- osu.Game/Screens/Edit/Timing/ControlPointTable.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 8980c2019a..cae7d5a021 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -151,7 +151,11 @@ namespace osu.Game.Screens.Edit.Timing return new RowAttribute("difficulty", () => $"{difficulty.SpeedMultiplier:n2}x", colour); case EffectControlPoint effect: - return new RowAttribute("effect", () => $"{(effect.KiaiMode ? "Kiai " : "")}{(effect.OmitFirstBarLine ? "NoBarLine " : "")}", colour); + return new RowAttribute("effect", () => string.Join(" ", new[] + { + effect.KiaiMode ? "Kiai" : string.Empty, + effect.OmitFirstBarLine ? "NoBarLine" : string.Empty + }.Where(s => !string.IsNullOrEmpty(s))), colour); case SampleControlPoint sample: return new RowAttribute("sample", () => $"{sample.SampleBank} {sample.SampleVolume}%", colour); From bebff61a9dde4ea61b27094ae9c4c4c214dababa Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 8 Feb 2021 21:13:00 +0300 Subject: [PATCH 0434/1791] Add method for retrieving condensed user statistics --- osu.Game/Users/User.cs | 62 +++++++++++++++++++++++++++++++++++------- 1 file changed, 52 insertions(+), 10 deletions(-) diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs index 518236755d..2f8c6823c7 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -2,10 +2,16 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.ComponentModel; using System.Linq; +using JetBrains.Annotations; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using osu.Framework.Bindables; +using osu.Game.IO.Serialization; +using osu.Game.Online.API.Requests; +using osu.Game.Rulesets; namespace osu.Game.Users { @@ -178,6 +184,11 @@ namespace osu.Game.Users private UserStatistics statistics; + /// + /// The user statistics of the ruleset specified within the API request. + /// If the user is fetched from a or similar + /// (i.e. is a user compact instance), use instead. + /// [JsonProperty(@"statistics")] public UserStatistics Statistics { @@ -228,13 +239,35 @@ namespace osu.Game.Users [JsonProperty("replays_watched_counts")] public UserHistoryCount[] ReplaysWatchedCounts; - public class UserHistoryCount - { - [JsonProperty("start_date")] - public DateTime Date; + [UsedImplicitly] + [JsonExtensionData] + private readonly IDictionary otherProperties = new Dictionary(); - [JsonProperty("count")] - public long Count; + private readonly Dictionary statisticsCache = new Dictionary(); + + /// + /// Retrieves the user statistics for a certain ruleset. + /// If user is fetched from a , + /// this will always return null, use instead. + /// + /// The ruleset to retrieve statistics for. + // todo: this should likely be moved to a separate UserCompact class at some point. + public UserStatistics GetStatisticsFor(RulesetInfo ruleset) + { + if (statisticsCache.TryGetValue(ruleset, out var existing)) + return existing; + + return statisticsCache[ruleset] = parseStatisticsFor(ruleset); + } + + private UserStatistics parseStatisticsFor(RulesetInfo ruleset) + { + if (!(otherProperties.TryGetValue($"statistics_{ruleset.ShortName}", out var token))) + return null; + + var settings = JsonSerializableExtensions.CreateGlobalSettings(); + settings.DefaultValueHandling = DefaultValueHandling.Include; + return token.ToObject(JsonSerializer.Create(settings)); } public override string ToString() => Username; @@ -249,6 +282,14 @@ namespace osu.Game.Users Id = 0 }; + public bool Equals(User other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + + return Id == other.Id; + } + public enum PlayStyle { [Description("Keyboard")] @@ -264,12 +305,13 @@ namespace osu.Game.Users Touch, } - public bool Equals(User other) + public class UserHistoryCount { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; + [JsonProperty("start_date")] + public DateTime Date; - return Id == other.Id; + [JsonProperty("count")] + public long Count; } } } From d101add1591599c53f860c21e31901cbed220060 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 8 Feb 2021 21:14:12 +0300 Subject: [PATCH 0435/1791] Display user global rank for selected ruleset in participants panel --- .../Participants/ParticipantPanel.cs | 21 ++++++++++++------- osu.Game/Users/UserStatistics.cs | 8 +++++++ 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index 0ee1b6d684..f69a21918a 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs @@ -35,9 +35,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants [Resolved] private RulesetStore rulesets { get; set; } + private SpriteIcon crown; + private OsuSpriteText userRankText; private ModDisplay userModsDisplay; private StateDisplay userStateDisplay; - private SpriteIcon crown; public ParticipantPanel(MultiplayerRoomUser user) { @@ -119,12 +120,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 18), Text = user?.Username }, - new OsuSpriteText + userRankText = new OsuSpriteText { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Font = OsuFont.GetFont(size: 14), - Text = user?.CurrentModeRank != null ? $"#{user.CurrentModeRank}" : string.Empty } } }, @@ -162,6 +162,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants const double fade_time = 50; + var ruleset = rulesets.GetRuleset(Room.Settings.RulesetID); + + var currentModeRank = User.User?.GetStatisticsFor(ruleset)?.GlobalRank; + + // fallback to current mode rank for testing purposes. + currentModeRank ??= User.User?.CurrentModeRank; + + userRankText.Text = currentModeRank != null ? $"#{currentModeRank.Value:N0}" : string.Empty; + userStateDisplay.Status = User.State; if (Room.Host?.Equals(User) == true) @@ -171,11 +180,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants // If the mods are updated at the end of the frame, the flow container will skip a reflow cycle: https://github.com/ppy/osu-framework/issues/4187 // This looks particularly jarring here, so re-schedule the update to that start of our frame as a fix. - Schedule(() => - { - var ruleset = rulesets.GetRuleset(Room.Settings.RulesetID).CreateInstance(); - userModsDisplay.Current.Value = User.Mods.Select(m => m.ToMod(ruleset)).ToList(); - }); + Schedule(() => userModsDisplay.Current.Value = User.Mods.Select(m => m.ToMod(ruleset.CreateInstance())).ToList()); } public MenuItem[] ContextMenuItems diff --git a/osu.Game/Users/UserStatistics.cs b/osu.Game/Users/UserStatistics.cs index 8b7699d0ad..6c069f674e 100644 --- a/osu.Game/Users/UserStatistics.cs +++ b/osu.Game/Users/UserStatistics.cs @@ -3,6 +3,7 @@ using System; using Newtonsoft.Json; +using osu.Game.Online.API.Requests; using osu.Game.Scoring; using osu.Game.Utils; using static osu.Game.Users.User; @@ -26,6 +27,13 @@ namespace osu.Game.Users public int Progress; } + /// + /// This must only be used when coming from condensed user responses (e.g. from ), otherwise use Ranks.Global. + /// + // todo: this should likely be moved to a separate UserStatisticsCompact class at some point. + [JsonProperty(@"global_rank")] + public int? GlobalRank; + [JsonProperty(@"pp")] public decimal? PP; From cca1bac67d56d9d261149eb6f497fee824a69811 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 8 Feb 2021 22:00:01 +0300 Subject: [PATCH 0436/1791] Pass empty user statistics for consistency Realized `Statistics` was never giving null user statistics, so decided to pass an empty one here as well. --- osu.Game/Users/User.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs index 2f8c6823c7..467f00e409 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -248,7 +248,7 @@ namespace osu.Game.Users /// /// Retrieves the user statistics for a certain ruleset. /// If user is fetched from a , - /// this will always return null, use instead. + /// this will always return empty instance, use instead. /// /// The ruleset to retrieve statistics for. // todo: this should likely be moved to a separate UserCompact class at some point. @@ -263,7 +263,7 @@ namespace osu.Game.Users private UserStatistics parseStatisticsFor(RulesetInfo ruleset) { if (!(otherProperties.TryGetValue($"statistics_{ruleset.ShortName}", out var token))) - return null; + return new UserStatistics(); var settings = JsonSerializableExtensions.CreateGlobalSettings(); settings.DefaultValueHandling = DefaultValueHandling.Include; From af345ea5db05c056234e665f665b53b0a4912d1a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 9 Feb 2021 01:52:35 +0300 Subject: [PATCH 0437/1791] Add a SignalR hub client connector component --- osu.Game/Online/HubClientConnector.cs | 209 ++++++++++++++++++++++++++ 1 file changed, 209 insertions(+) create mode 100644 osu.Game/Online/HubClientConnector.cs diff --git a/osu.Game/Online/HubClientConnector.cs b/osu.Game/Online/HubClientConnector.cs new file mode 100644 index 0000000000..49b1ab639a --- /dev/null +++ b/osu.Game/Online/HubClientConnector.cs @@ -0,0 +1,209 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.SignalR.Client; +using Microsoft.Extensions.DependencyInjection; +using Newtonsoft.Json; +using osu.Framework; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Logging; +using osu.Game.Online.API; + +namespace osu.Game.Online +{ + /// + /// A component that maintains over a hub connection between client and server. + /// + public class HubClientConnector : Component + { + /// + /// Invoked whenever a new hub connection is built. + /// + public Action? OnNewConnection; + + private readonly string clientName; + private readonly string endpoint; + + /// + /// The current connection opened by this connector. + /// + public HubConnection? CurrentConnection { get; private set; } + + /// + /// Whether this is connected to the hub, use to access the connection, if this is true. + /// + public IBindable IsConnected => isConnected; + + private readonly Bindable isConnected = new Bindable(); + private readonly SemaphoreSlim connectionLock = new SemaphoreSlim(1); + private CancellationTokenSource connectCancelSource = new CancellationTokenSource(); + + [Resolved] + private IAPIProvider api { get; set; } = null!; + + private readonly IBindable apiState = new Bindable(); + + /// + /// Constructs a new . + /// + /// The name of the client this connector connects for, used for logging. + /// The endpoint to the hub. + public HubClientConnector(string clientName, string endpoint) + { + this.clientName = clientName; + this.endpoint = endpoint; + } + + [BackgroundDependencyLoader] + private void load() + { + apiState.BindTo(api.State); + apiState.BindValueChanged(state => + { + switch (state.NewValue) + { + case APIState.Failing: + case APIState.Offline: + Task.Run(() => disconnect(true)); + break; + + case APIState.Online: + Task.Run(connect); + break; + } + }); + } + + private async Task connect() + { + cancelExistingConnect(); + + if (!await connectionLock.WaitAsync(10000)) + throw new TimeoutException("Could not obtain a lock to connect. A previous attempt is likely stuck."); + + try + { + while (apiState.Value == APIState.Online) + { + // ensure any previous connection was disposed. + // this will also create a new cancellation token source. + await disconnect(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. + var cancellationToken = connectCancelSource.Token; + + cancellationToken.ThrowIfCancellationRequested(); + + Logger.Log($"{clientName} connecting...", LoggingTarget.Network); + + try + { + // importantly, rebuild the connection each attempt to get an updated access token. + CurrentConnection = createConnection(cancellationToken); + + await CurrentConnection.StartAsync(cancellationToken); + + Logger.Log($"{clientName} connected!", LoggingTarget.Network); + isConnected.Value = true; + return; + } + catch (OperationCanceledException) + { + //connection process was cancelled. + throw; + } + catch (Exception e) + { + Logger.Log($"{clientName} connection error: {e}", LoggingTarget.Network); + + // retry on any failure. + await Task.Delay(5000, cancellationToken); + } + } + } + finally + { + connectionLock.Release(); + } + } + + private HubConnection createConnection(CancellationToken cancellationToken) + { + var builder = new HubConnectionBuilder() + .WithUrl(endpoint, options => { options.Headers.Add("Authorization", $"Bearer {api.AccessToken}"); }); + + if (RuntimeInfo.SupportsJIT) + builder.AddMessagePackProtocol(); + else + { + // eventually we will precompile resolvers for messagepack, but this isn't working currently + // see https://github.com/neuecc/MessagePack-CSharp/issues/780#issuecomment-768794308. + builder.AddNewtonsoftJsonProtocol(options => { options.PayloadSerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; }); + } + + var newConnection = builder.Build(); + + OnNewConnection?.Invoke(newConnection); + + newConnection.Closed += ex => + { + isConnected.Value = false; + + Logger.Log(ex != null ? $"{clientName} lost connection: {ex}" : $"{clientName} disconnected", LoggingTarget.Network); + + // make sure a disconnect wasn't triggered (and this is still the active connection). + if (!cancellationToken.IsCancellationRequested) + Task.Run(connect, default); + + return Task.CompletedTask; + }; + + return newConnection; + } + + private async Task disconnect(bool takeLock) + { + cancelExistingConnect(); + + if (takeLock) + { + if (!await connectionLock.WaitAsync(10000)) + throw new TimeoutException("Could not obtain a lock to disconnect. A previous attempt is likely stuck."); + } + + try + { + if (CurrentConnection != null) + await CurrentConnection.DisposeAsync(); + } + finally + { + CurrentConnection = null; + if (takeLock) + connectionLock.Release(); + } + } + + private void cancelExistingConnect() + { + connectCancelSource.Cancel(); + connectCancelSource = new CancellationTokenSource(); + } + + public override string ToString() => $"Connector for {clientName} ({(IsConnected.Value ? "connected" : "not connected")}"; + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + cancelExistingConnect(); + } + } +} From 28b815ffe13a5439241ccaeb763e26cb4e794ca6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 9 Feb 2021 02:01:52 +0300 Subject: [PATCH 0438/1791] Clean up multiplayer client with new hub connector --- .../Online/Multiplayer/MultiplayerClient.cs | 209 +++--------------- .../Multiplayer/StatefulMultiplayerClient.cs | 7 +- 2 files changed, 31 insertions(+), 185 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 493518ac80..07036e7ffc 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -3,17 +3,11 @@ #nullable enable -using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR.Client; -using Microsoft.Extensions.DependencyInjection; -using Newtonsoft.Json; -using osu.Framework; -using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Logging; using osu.Game.Online.API; using osu.Game.Online.Rooms; @@ -21,106 +15,37 @@ namespace osu.Game.Online.Multiplayer { public class MultiplayerClient : StatefulMultiplayerClient { - public override IBindable IsConnected => isConnected; + private readonly HubClientConnector connector; - private readonly Bindable isConnected = new Bindable(); - private readonly IBindable apiState = new Bindable(); + public override IBindable IsConnected => connector.IsConnected; - private readonly SemaphoreSlim connectionLock = new SemaphoreSlim(1); - - [Resolved] - private IAPIProvider api { get; set; } = null!; - - private HubConnection? connection; - - private CancellationTokenSource connectCancelSource = new CancellationTokenSource(); - - private readonly string endpoint; + private HubConnection? connection => connector.CurrentConnection; public MultiplayerClient(EndpointConfiguration endpoints) { - endpoint = endpoints.MultiplayerEndpointUrl; - } - - [BackgroundDependencyLoader] - private void load() - { - apiState.BindTo(api.State); - apiState.BindValueChanged(apiStateChanged, true); - } - - private void apiStateChanged(ValueChangedEvent state) - { - switch (state.NewValue) + InternalChild = connector = new HubClientConnector("Multiplayer client", endpoints.MultiplayerEndpointUrl) { - case APIState.Failing: - case APIState.Offline: - Task.Run(() => disconnect(true)); - break; - - case APIState.Online: - Task.Run(connect); - break; - } - } - - private async Task connect() - { - cancelExistingConnect(); - - if (!await connectionLock.WaitAsync(10000)) - throw new TimeoutException("Could not obtain a lock to connect. A previous attempt is likely stuck."); - - try - { - while (api.State.Value == APIState.Online) + OnNewConnection = newConnection => { - // ensure any previous connection was disposed. - // this will also create a new cancellation token source. - await disconnect(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. - var cancellationToken = connectCancelSource.Token; - - cancellationToken.ThrowIfCancellationRequested(); - - Logger.Log("Multiplayer client connecting...", LoggingTarget.Network); - - try - { - // importantly, rebuild the connection each attempt to get an updated access token. - connection = createConnection(cancellationToken); - - await connection.StartAsync(cancellationToken); - - Logger.Log("Multiplayer client connected!", LoggingTarget.Network); - isConnected.Value = true; - return; - } - catch (OperationCanceledException) - { - //connection process was cancelled. - throw; - } - catch (Exception e) - { - Logger.Log($"Multiplayer client connection error: {e}", LoggingTarget.Network); - - // retry on any failure. - await Task.Delay(5000, cancellationToken); - } - } - } - finally - { - connectionLock.Release(); - } + // this is kind of SILLY + // https://github.com/dotnet/aspnetcore/issues/15198 + newConnection.On(nameof(IMultiplayerClient.RoomStateChanged), ((IMultiplayerClient)this).RoomStateChanged); + newConnection.On(nameof(IMultiplayerClient.UserJoined), ((IMultiplayerClient)this).UserJoined); + newConnection.On(nameof(IMultiplayerClient.UserLeft), ((IMultiplayerClient)this).UserLeft); + newConnection.On(nameof(IMultiplayerClient.HostChanged), ((IMultiplayerClient)this).HostChanged); + newConnection.On(nameof(IMultiplayerClient.SettingsChanged), ((IMultiplayerClient)this).SettingsChanged); + newConnection.On(nameof(IMultiplayerClient.UserStateChanged), ((IMultiplayerClient)this).UserStateChanged); + newConnection.On(nameof(IMultiplayerClient.LoadRequested), ((IMultiplayerClient)this).LoadRequested); + newConnection.On(nameof(IMultiplayerClient.MatchStarted), ((IMultiplayerClient)this).MatchStarted); + newConnection.On(nameof(IMultiplayerClient.ResultsReady), ((IMultiplayerClient)this).ResultsReady); + newConnection.On>(nameof(IMultiplayerClient.UserModsChanged), ((IMultiplayerClient)this).UserModsChanged); + }, + }; } protected override Task JoinRoom(long roomId) { - if (!isConnected.Value) + if (!IsConnected.Value) return Task.FromCanceled(new CancellationToken(true)); return connection.InvokeAsync(nameof(IMultiplayerServer.JoinRoom), roomId); @@ -128,7 +53,7 @@ namespace osu.Game.Online.Multiplayer protected override Task LeaveRoomInternal() { - if (!isConnected.Value) + if (!IsConnected.Value) return Task.FromCanceled(new CancellationToken(true)); return connection.InvokeAsync(nameof(IMultiplayerServer.LeaveRoom)); @@ -136,7 +61,7 @@ namespace osu.Game.Online.Multiplayer public override Task TransferHost(int userId) { - if (!isConnected.Value) + if (!IsConnected.Value) return Task.CompletedTask; return connection.InvokeAsync(nameof(IMultiplayerServer.TransferHost), userId); @@ -144,7 +69,7 @@ namespace osu.Game.Online.Multiplayer public override Task ChangeSettings(MultiplayerRoomSettings settings) { - if (!isConnected.Value) + if (!IsConnected.Value) return Task.CompletedTask; return connection.InvokeAsync(nameof(IMultiplayerServer.ChangeSettings), settings); @@ -152,7 +77,7 @@ namespace osu.Game.Online.Multiplayer public override Task ChangeState(MultiplayerUserState newState) { - if (!isConnected.Value) + if (!IsConnected.Value) return Task.CompletedTask; return connection.InvokeAsync(nameof(IMultiplayerServer.ChangeState), newState); @@ -160,7 +85,7 @@ namespace osu.Game.Online.Multiplayer public override Task ChangeBeatmapAvailability(BeatmapAvailability newBeatmapAvailability) { - if (!isConnected.Value) + if (!IsConnected.Value) return Task.CompletedTask; return connection.InvokeAsync(nameof(IMultiplayerServer.ChangeBeatmapAvailability), newBeatmapAvailability); @@ -168,7 +93,7 @@ namespace osu.Game.Online.Multiplayer public override Task ChangeUserMods(IEnumerable newMods) { - if (!isConnected.Value) + if (!IsConnected.Value) return Task.CompletedTask; return connection.InvokeAsync(nameof(IMultiplayerServer.ChangeUserMods), newMods); @@ -176,90 +101,10 @@ namespace osu.Game.Online.Multiplayer public override Task StartMatch() { - if (!isConnected.Value) + if (!IsConnected.Value) return Task.CompletedTask; return connection.InvokeAsync(nameof(IMultiplayerServer.StartMatch)); } - - private async Task disconnect(bool takeLock) - { - cancelExistingConnect(); - - if (takeLock) - { - if (!await connectionLock.WaitAsync(10000)) - throw new TimeoutException("Could not obtain a lock to disconnect. A previous attempt is likely stuck."); - } - - try - { - if (connection != null) - await connection.DisposeAsync(); - } - finally - { - connection = null; - if (takeLock) - connectionLock.Release(); - } - } - - private void cancelExistingConnect() - { - connectCancelSource.Cancel(); - connectCancelSource = new CancellationTokenSource(); - } - - private HubConnection createConnection(CancellationToken cancellationToken) - { - var builder = new HubConnectionBuilder() - .WithUrl(endpoint, options => { options.Headers.Add("Authorization", $"Bearer {api.AccessToken}"); }); - - if (RuntimeInfo.SupportsJIT) - builder.AddMessagePackProtocol(); - else - { - // eventually we will precompile resolvers for messagepack, but this isn't working currently - // see https://github.com/neuecc/MessagePack-CSharp/issues/780#issuecomment-768794308. - builder.AddNewtonsoftJsonProtocol(options => { options.PayloadSerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; }); - } - - var newConnection = builder.Build(); - - // this is kind of SILLY - // https://github.com/dotnet/aspnetcore/issues/15198 - newConnection.On(nameof(IMultiplayerClient.RoomStateChanged), ((IMultiplayerClient)this).RoomStateChanged); - newConnection.On(nameof(IMultiplayerClient.UserJoined), ((IMultiplayerClient)this).UserJoined); - newConnection.On(nameof(IMultiplayerClient.UserLeft), ((IMultiplayerClient)this).UserLeft); - newConnection.On(nameof(IMultiplayerClient.HostChanged), ((IMultiplayerClient)this).HostChanged); - newConnection.On(nameof(IMultiplayerClient.SettingsChanged), ((IMultiplayerClient)this).SettingsChanged); - newConnection.On(nameof(IMultiplayerClient.UserStateChanged), ((IMultiplayerClient)this).UserStateChanged); - newConnection.On(nameof(IMultiplayerClient.LoadRequested), ((IMultiplayerClient)this).LoadRequested); - newConnection.On(nameof(IMultiplayerClient.MatchStarted), ((IMultiplayerClient)this).MatchStarted); - newConnection.On(nameof(IMultiplayerClient.ResultsReady), ((IMultiplayerClient)this).ResultsReady); - newConnection.On>(nameof(IMultiplayerClient.UserModsChanged), ((IMultiplayerClient)this).UserModsChanged); - - newConnection.Closed += ex => - { - isConnected.Value = false; - - Logger.Log(ex != null ? $"Multiplayer client lost connection: {ex}" : "Multiplayer client disconnected", LoggingTarget.Network); - - // make sure a disconnect wasn't triggered (and this is still the active connection). - if (!cancellationToken.IsCancellationRequested) - Task.Run(connect, default); - - return Task.CompletedTask; - }; - return newConnection; - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - cancelExistingConnect(); - } } } diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index f454fe619b..06f6754258 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -12,7 +12,7 @@ using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.ObjectExtensions; -using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Database; @@ -28,7 +28,7 @@ using osu.Game.Utils; namespace osu.Game.Online.Multiplayer { - public abstract class StatefulMultiplayerClient : Component, IMultiplayerClient, IMultiplayerRoomServer + public abstract class StatefulMultiplayerClient : CompositeDrawable, IMultiplayerClient, IMultiplayerRoomServer { /// /// Invoked when any change occurs to the multiplayer room. @@ -97,7 +97,8 @@ namespace osu.Game.Online.Multiplayer // Todo: This is temporary, until the multiplayer server returns the item id on match start or otherwise. private int playlistItemId; - protected StatefulMultiplayerClient() + [BackgroundDependencyLoader] + private void load() { IsConnected.BindValueChanged(connected => { From f76f92515e7eb3588af91e0b3aac4c47fbc26731 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 9 Feb 2021 02:15:51 +0300 Subject: [PATCH 0439/1791] Clean up spectator streaming client with new hub connector --- .../Visual/Gameplay/TestSceneSpectator.cs | 8 +- ...TestSceneMultiplayerGameplayLeaderboard.cs | 5 +- .../Spectator/SpectatorStreamingClient.cs | 159 +++++------------- 3 files changed, 48 insertions(+), 124 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 26524f07da..61b0961638 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; -using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -233,6 +232,8 @@ namespace osu.Game.Tests.Visual.Gameplay public class TestSpectatorStreamingClient : SpectatorStreamingClient { + protected override IBindable IsConnected { get; } = new BindableBool(false); + public readonly User StreamingUser = new User { Id = 55, Username = "Test user" }; public new BindableList PlayingUsers => (BindableList)base.PlayingUsers; @@ -244,11 +245,6 @@ namespace osu.Game.Tests.Visual.Gameplay { } - protected override Task Connect() - { - return Task.CompletedTask; - } - public void StartPlay(int beatmapId) { this.beatmapId = beatmapId; diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index d016accc25..6a777e2a78 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -96,6 +95,8 @@ namespace osu.Game.Tests.Visual.Multiplayer public class TestMultiplayerStreaming : SpectatorStreamingClient { + protected override IBindable IsConnected { get; } = new BindableBool(false); + public new BindableList PlayingUsers => (BindableList)base.PlayingUsers; private readonly int totalUsers; @@ -163,8 +164,6 @@ namespace osu.Game.Tests.Visual.Multiplayer ((ISpectatorClient)this).UserSentFrames(userId, new FrameDataBundle(header, Array.Empty())); } } - - protected override Task Connect() => Task.CompletedTask; } } } diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index b95e3f1297..7cea76c969 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -8,13 +8,9 @@ using System.Linq; using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.AspNetCore.SignalR.Client; -using Microsoft.Extensions.DependencyInjection; -using Newtonsoft.Json; -using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Logging; +using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Replays.Legacy; @@ -27,14 +23,18 @@ using osu.Game.Screens.Play; namespace osu.Game.Online.Spectator { - public class SpectatorStreamingClient : Component, ISpectatorClient + public class SpectatorStreamingClient : CompositeDrawable, ISpectatorClient { /// /// The maximum milliseconds between frame bundle sends. /// public const double TIME_BETWEEN_SENDS = 200; - private HubConnection connection; + private readonly HubClientConnector connector; + + protected virtual IBindable IsConnected => connector.IsConnected; + + private HubConnection connection => connector.CurrentConnection; private readonly List watchingUsers = new List(); @@ -44,13 +44,6 @@ namespace osu.Game.Online.Spectator private readonly BindableList playingUsers = new BindableList(); - private readonly IBindable apiState = new Bindable(); - - private bool isConnected; - - [Resolved] - private IAPIProvider api { get; set; } - [CanBeNull] private IBeatmap currentBeatmap; @@ -82,114 +75,50 @@ namespace osu.Game.Online.Spectator /// public event Action OnUserFinishedPlaying; - private readonly string endpoint; - public SpectatorStreamingClient(EndpointConfiguration endpoints) { - endpoint = endpoints.SpectatorEndpointUrl; + InternalChild = connector = new HubClientConnector("Spectator client", endpoints.SpectatorEndpointUrl) + { + OnNewConnection = newConnection => + { + // until strong typed client support is added, each method must be manually bound + // (see https://github.com/dotnet/aspnetcore/issues/15198) + newConnection.On(nameof(ISpectatorClient.UserBeganPlaying), ((ISpectatorClient)this).UserBeganPlaying); + newConnection.On(nameof(ISpectatorClient.UserSentFrames), ((ISpectatorClient)this).UserSentFrames); + newConnection.On(nameof(ISpectatorClient.UserFinishedPlaying), ((ISpectatorClient)this).UserFinishedPlaying); + } + }; } [BackgroundDependencyLoader] private void load() { - apiState.BindTo(api.State); - apiState.BindValueChanged(apiStateChanged, true); - } - - private void apiStateChanged(ValueChangedEvent state) - { - switch (state.NewValue) + IsConnected.BindValueChanged(connected => { - case APIState.Failing: - case APIState.Offline: - connection?.StopAsync(); - connection = null; - break; - - case APIState.Online: - Task.Run(Connect); - break; - } - } - - protected virtual async Task Connect() - { - if (connection != null) - return; - - var builder = new HubConnectionBuilder() - .WithUrl(endpoint, options => { options.Headers.Add("Authorization", $"Bearer {api.AccessToken}"); }); - - if (RuntimeInfo.SupportsJIT) - builder.AddMessagePackProtocol(); - else - { - // eventually we will precompile resolvers for messagepack, but this isn't working currently - // see https://github.com/neuecc/MessagePack-CSharp/issues/780#issuecomment-768794308. - builder.AddNewtonsoftJsonProtocol(options => { options.PayloadSerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; }); - } - - connection = builder.Build(); - // until strong typed client support is added, each method must be manually bound (see https://github.com/dotnet/aspnetcore/issues/15198) - connection.On(nameof(ISpectatorClient.UserBeganPlaying), ((ISpectatorClient)this).UserBeganPlaying); - connection.On(nameof(ISpectatorClient.UserSentFrames), ((ISpectatorClient)this).UserSentFrames); - connection.On(nameof(ISpectatorClient.UserFinishedPlaying), ((ISpectatorClient)this).UserFinishedPlaying); - - connection.Closed += async ex => - { - isConnected = false; - playingUsers.Clear(); - - if (ex != null) + if (connected.NewValue) { - Logger.Log($"Spectator client lost connection: {ex}", LoggingTarget.Network); - await tryUntilConnected(); + // get all the users that were previously being watched + int[] users; + + lock (userLock) + { + users = watchingUsers.ToArray(); + watchingUsers.Clear(); + } + + // resubscribe to watched users. + foreach (var userId in users) + WatchUser(userId); + + // re-send state in case it wasn't received + if (isPlaying) + beginPlaying(); } - }; - - await tryUntilConnected(); - - async Task tryUntilConnected() - { - Logger.Log("Spectator client connecting...", LoggingTarget.Network); - - while (api.State.Value == APIState.Online) + else { - try - { - // reconnect on any failure - await connection.StartAsync(); - Logger.Log("Spectator client connected!", LoggingTarget.Network); - - // get all the users that were previously being watched - int[] users; - - lock (userLock) - { - users = watchingUsers.ToArray(); - watchingUsers.Clear(); - } - - // success - isConnected = true; - - // resubscribe to watched users - foreach (var userId in users) - WatchUser(userId); - - // re-send state in case it wasn't received - if (isPlaying) - beginPlaying(); - - break; - } - catch (Exception e) - { - Logger.Log($"Spectator client connection error: {e}", LoggingTarget.Network); - await Task.Delay(5000); - } + playingUsers.Clear(); } - } + }, true); } Task ISpectatorClient.UserBeganPlaying(int userId, SpectatorState state) @@ -240,14 +169,14 @@ namespace osu.Game.Online.Spectator { Debug.Assert(isPlaying); - if (!isConnected) return; + if (!IsConnected.Value) return; connection.SendAsync(nameof(ISpectatorServer.BeginPlaySession), currentState); } public void SendFrames(FrameDataBundle data) { - if (!isConnected) return; + if (!IsConnected.Value) return; lastSend = connection.SendAsync(nameof(ISpectatorServer.SendFrameData), data); } @@ -257,7 +186,7 @@ namespace osu.Game.Online.Spectator isPlaying = false; currentBeatmap = null; - if (!isConnected) return; + if (!IsConnected.Value) return; connection.SendAsync(nameof(ISpectatorServer.EndPlaySession), currentState); } @@ -271,7 +200,7 @@ namespace osu.Game.Online.Spectator watchingUsers.Add(userId); - if (!isConnected) + if (!IsConnected.Value) return; } @@ -284,7 +213,7 @@ namespace osu.Game.Online.Spectator { watchingUsers.Remove(userId); - if (!isConnected) + if (!IsConnected.Value) return; } From 3ce605b5e5174632bc78ec5792c75ea0e92be009 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 9 Feb 2021 12:00:03 +0900 Subject: [PATCH 0440/1791] Small refactoring to use .Trim() instead --- osu.Game/Screens/Edit/Timing/ControlPointTable.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index cae7d5a021..a17b431fcc 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -151,11 +151,10 @@ namespace osu.Game.Screens.Edit.Timing return new RowAttribute("difficulty", () => $"{difficulty.SpeedMultiplier:n2}x", colour); case EffectControlPoint effect: - return new RowAttribute("effect", () => string.Join(" ", new[] - { + return new RowAttribute("effect", () => string.Join(" ", effect.KiaiMode ? "Kiai" : string.Empty, effect.OmitFirstBarLine ? "NoBarLine" : string.Empty - }.Where(s => !string.IsNullOrEmpty(s))), colour); + ).Trim(), colour); case SampleControlPoint sample: return new RowAttribute("sample", () => $"{sample.SampleBank} {sample.SampleVolume}%", colour); From 3133ccacfa79704dbf89faad3cb7c82fb242de32 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Feb 2021 13:09:38 +0900 Subject: [PATCH 0441/1791] Reset selected mods between each test method This doesn't actually fix or change behaviour, but does seem like something we probably want to do here. --- .../Visual/Multiplayer/TestScenePlaylistsSongSelect.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs index 2f7e59f800..7d83ba569d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs @@ -87,6 +87,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Ruleset.Value = new OsuRuleset().RulesetInfo; Beatmap.SetDefault(); + SelectedMods.Value = Array.Empty(); }); AddStep("create song select", () => LoadScreen(songSelect = new TestPlaylistsSongSelect())); From be379e0e3cc910b7b3cf1fb6b48460aeaa2673d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Feb 2021 13:44:11 +0900 Subject: [PATCH 0442/1791] Change CopyFrom to always overwrite all settings with incoming values --- osu.Game/Rulesets/Mods/Mod.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 3a8717e678..dec72d94e5 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -134,7 +134,7 @@ namespace osu.Game.Rulesets.Mods } /// - /// Copies mod setting values from into this instance. + /// Copies mod setting values from into this instance, overwriting all existing settings. /// /// The mod to copy properties from. public void CopyFrom(Mod source) @@ -147,9 +147,7 @@ namespace osu.Game.Rulesets.Mods var targetBindable = (IBindable)prop.GetValue(this); var sourceBindable = (IBindable)prop.GetValue(source); - // we only care about changes that have been made away from defaults. - if (!sourceBindable.IsDefault) - CopyAdjustedSetting(targetBindable, sourceBindable); + CopyAdjustedSetting(targetBindable, sourceBindable); } } From 8204d360a8d84f5ac3fe2eec40155999c23a5ba2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Feb 2021 13:44:42 +0900 Subject: [PATCH 0443/1791] Always reset local user settings when a mod is deselected in ModSelectOverlay --- osu.Game/Overlays/Mods/ModButton.cs | 2 ++ osu.Game/Overlays/Mods/ModSection.cs | 4 +++- osu.Game/Rulesets/Mods/Mod.cs | 5 +++++ osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs | 11 +++++++++++ 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs index 8e0d1f5bbd..06f2fea43f 100644 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ b/osu.Game/Overlays/Mods/ModButton.cs @@ -69,6 +69,8 @@ namespace osu.Game.Overlays.Mods Mod newSelection = SelectedMod ?? Mods[0]; + newSelection.ResetSettingsToDefaults(); + Schedule(() => { if (beforeSelected != Selected) diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index ecbcba7ad3..08bd3f8622 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -197,8 +197,10 @@ namespace osu.Game.Overlays.Mods continue; var buttonMod = button.Mods[index]; - buttonMod.CopyFrom(mod); button.SelectAt(index); + + // the selection above will reset settings to defaults, but as this is an external change we want to copy the new settings across. + buttonMod.CopyFrom(mod); return; } diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index dec72d94e5..2a11c92223 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -173,5 +173,10 @@ namespace osu.Game.Rulesets.Mods } public bool Equals(IMod other) => GetType() == other?.GetType(); + + /// + /// Reset all custom settings for this mod back to their defaults. + /// + public virtual void ResetSettingsToDefaults() => CopyFrom((Mod)Activator.CreateInstance(GetType())); } } diff --git a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs index a531e885db..dbc35569e7 100644 --- a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs @@ -141,5 +141,16 @@ namespace osu.Game.Rulesets.Mods ApplySetting(DrainRate, dr => difficulty.DrainRate = dr); ApplySetting(OverallDifficulty, od => difficulty.OverallDifficulty = od); } + + public override void ResetSettingsToDefaults() + { + base.ResetSettingsToDefaults(); + + if (difficulty != null) + { + // base implementation potentially overwrite modified defaults that came from a beatmap selection. + TransferSettings(difficulty); + } + } } } From 71e564d399e617d6083d8540bdcdc2d89a18d2e3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 9 Feb 2021 07:46:00 +0300 Subject: [PATCH 0444/1791] Revert clients to be `Component`s --- osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs | 4 ++-- osu.Game/Online/Spectator/SpectatorStreamingClient.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index 06f6754258..18464a5f61 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -12,7 +12,7 @@ using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.ObjectExtensions; -using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Database; @@ -28,7 +28,7 @@ using osu.Game.Utils; namespace osu.Game.Online.Multiplayer { - public abstract class StatefulMultiplayerClient : CompositeDrawable, IMultiplayerClient, IMultiplayerRoomServer + public abstract class StatefulMultiplayerClient : Component, IMultiplayerClient, IMultiplayerRoomServer { /// /// Invoked when any change occurs to the multiplayer room. diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 7cea76c969..33ebe27937 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -10,7 +10,7 @@ using JetBrains.Annotations; using Microsoft.AspNetCore.SignalR.Client; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Replays.Legacy; @@ -23,7 +23,7 @@ using osu.Game.Screens.Play; namespace osu.Game.Online.Spectator { - public class SpectatorStreamingClient : CompositeDrawable, ISpectatorClient + public class SpectatorStreamingClient : Component, ISpectatorClient { /// /// The maximum milliseconds between frame bundle sends. From 848b81e952934ee90f3cc4e86428bb385c0fdde6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 9 Feb 2021 07:53:22 +0300 Subject: [PATCH 0445/1791] Remove necessity of making hub client connector a component --- osu.Game/Online/HubClientConnector.cs | 51 ++++++++++++++------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/osu.Game/Online/HubClientConnector.cs b/osu.Game/Online/HubClientConnector.cs index 49b1ab639a..b740aabb92 100644 --- a/osu.Game/Online/HubClientConnector.cs +++ b/osu.Game/Online/HubClientConnector.cs @@ -4,15 +4,14 @@ #nullable enable using System; +using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR.Client; using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; using osu.Framework; -using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Game.Online.API; @@ -21,7 +20,7 @@ namespace osu.Game.Online /// /// A component that maintains over a hub connection between client and server. /// - public class HubClientConnector : Component + public class HubClientConnector : IDisposable { /// /// Invoked whenever a new hub connection is built. @@ -30,6 +29,7 @@ namespace osu.Game.Online private readonly string clientName; private readonly string endpoint; + private readonly IAPIProvider? api; /// /// The current connection opened by this connector. @@ -45,9 +45,6 @@ namespace osu.Game.Online private readonly SemaphoreSlim connectionLock = new SemaphoreSlim(1); private CancellationTokenSource connectCancelSource = new CancellationTokenSource(); - [Resolved] - private IAPIProvider api { get; set; } = null!; - private readonly IBindable apiState = new Bindable(); /// @@ -55,30 +52,32 @@ namespace osu.Game.Online /// /// The name of the client this connector connects for, used for logging. /// The endpoint to the hub. - public HubClientConnector(string clientName, string endpoint) + /// The API provider for listening to state changes, or null to not listen. + public HubClientConnector(string clientName, string endpoint, IAPIProvider? api) { this.clientName = clientName; this.endpoint = endpoint; - } - [BackgroundDependencyLoader] - private void load() - { - apiState.BindTo(api.State); - apiState.BindValueChanged(state => + this.api = api; + + if (api != null) { - switch (state.NewValue) + apiState.BindTo(api.State); + apiState.BindValueChanged(state => { - case APIState.Failing: - case APIState.Offline: - Task.Run(() => disconnect(true)); - break; + switch (state.NewValue) + { + case APIState.Failing: + case APIState.Offline: + Task.Run(() => disconnect(true)); + break; - case APIState.Online: - Task.Run(connect); - break; - } - }); + case APIState.Online: + Task.Run(connect); + break; + } + }, true); + } } private async Task connect() @@ -137,6 +136,8 @@ namespace osu.Game.Online private HubConnection createConnection(CancellationToken cancellationToken) { + Debug.Assert(api != null); + var builder = new HubConnectionBuilder() .WithUrl(endpoint, options => { options.Headers.Add("Authorization", $"Bearer {api.AccessToken}"); }); @@ -200,9 +201,9 @@ namespace osu.Game.Online public override string ToString() => $"Connector for {clientName} ({(IsConnected.Value ? "connected" : "not connected")}"; - protected override void Dispose(bool isDisposing) + public void Dispose() { - base.Dispose(isDisposing); + apiState.UnbindAll(); cancelExistingConnect(); } } From 0efad9ded10e03233ef2f14d644260178d9c746d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Feb 2021 13:54:13 +0900 Subject: [PATCH 0446/1791] Add test coverage of setting reset on deselection --- .../UserInterface/TestSceneModSelectOverlay.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 37ebc72984..85350c028c 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -47,6 +47,24 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("show", () => modSelect.Show()); } + [Test] + public void TestSettingsResetOnDeselection() + { + var osuModDoubleTime = new OsuModDoubleTime { SpeedChange = { Value = 1.2 } }; + + changeRuleset(0); + + AddStep("set dt mod with custom rate", () => { SelectedMods.Value = new[] { osuModDoubleTime }; }); + + AddAssert("selected mod matches", () => (SelectedMods.Value.Single() as OsuModDoubleTime)?.SpeedChange.Value == 1.2); + + AddStep("deselect", () => modSelect.DeselectAllButton.Click()); + AddAssert("selected mods empty", () => SelectedMods.Value.Count == 0); + + AddStep("reselect", () => modSelect.GetModButton(osuModDoubleTime).Click()); + AddAssert("selected mod has default value", () => (SelectedMods.Value.Single() as OsuModDoubleTime)?.SpeedChange.IsDefault == true); + } + [Test] public void TestAnimationFlushOnClose() { From f04d6d5e5e98ddbdc0d94e4825d4d31392024be7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 9 Feb 2021 08:02:32 +0300 Subject: [PATCH 0447/1791] Update hub clients with changes to connecotr --- .../Visual/Gameplay/TestSceneSpectator.cs | 2 - ...TestSceneMultiplayerGameplayLeaderboard.cs | 2 - .../Online/Multiplayer/MultiplayerClient.cs | 46 +++++++++++++------ .../Spectator/SpectatorStreamingClient.cs | 45 ++++++++++-------- 4 files changed, 56 insertions(+), 39 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 61b0961638..4a0e1282c4 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -232,8 +232,6 @@ namespace osu.Game.Tests.Visual.Gameplay public class TestSpectatorStreamingClient : SpectatorStreamingClient { - protected override IBindable IsConnected { get; } = new BindableBool(false); - public readonly User StreamingUser = new User { Id = 55, Username = "Test user" }; public new BindableList PlayingUsers => (BindableList)base.PlayingUsers; diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index 6a777e2a78..aab69d687a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -95,8 +95,6 @@ namespace osu.Game.Tests.Visual.Multiplayer public class TestMultiplayerStreaming : SpectatorStreamingClient { - protected override IBindable IsConnected { get; } = new BindableBool(false); - public new BindableList PlayingUsers => (BindableList)base.PlayingUsers; private readonly int totalUsers; diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 07036e7ffc..6b67954351 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR.Client; +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Online.API; using osu.Game.Online.Rooms; @@ -15,32 +16,41 @@ namespace osu.Game.Online.Multiplayer { public class MultiplayerClient : StatefulMultiplayerClient { - private readonly HubClientConnector connector; + private readonly string endpoint; + private HubClientConnector? connector; - public override IBindable IsConnected => connector.IsConnected; + public override IBindable IsConnected { get; } = new BindableBool(); - private HubConnection? connection => connector.CurrentConnection; + private HubConnection? connection => connector?.CurrentConnection; public MultiplayerClient(EndpointConfiguration endpoints) { - InternalChild = connector = new HubClientConnector("Multiplayer client", endpoints.MultiplayerEndpointUrl) + endpoint = endpoints.MultiplayerEndpointUrl; + } + + [BackgroundDependencyLoader] + private void load(IAPIProvider api) + { + connector = new HubClientConnector(nameof(MultiplayerClient), endpoint, api) { - OnNewConnection = newConnection => + OnNewConnection = connection => { // this is kind of SILLY // https://github.com/dotnet/aspnetcore/issues/15198 - newConnection.On(nameof(IMultiplayerClient.RoomStateChanged), ((IMultiplayerClient)this).RoomStateChanged); - newConnection.On(nameof(IMultiplayerClient.UserJoined), ((IMultiplayerClient)this).UserJoined); - newConnection.On(nameof(IMultiplayerClient.UserLeft), ((IMultiplayerClient)this).UserLeft); - newConnection.On(nameof(IMultiplayerClient.HostChanged), ((IMultiplayerClient)this).HostChanged); - newConnection.On(nameof(IMultiplayerClient.SettingsChanged), ((IMultiplayerClient)this).SettingsChanged); - newConnection.On(nameof(IMultiplayerClient.UserStateChanged), ((IMultiplayerClient)this).UserStateChanged); - newConnection.On(nameof(IMultiplayerClient.LoadRequested), ((IMultiplayerClient)this).LoadRequested); - newConnection.On(nameof(IMultiplayerClient.MatchStarted), ((IMultiplayerClient)this).MatchStarted); - newConnection.On(nameof(IMultiplayerClient.ResultsReady), ((IMultiplayerClient)this).ResultsReady); - newConnection.On>(nameof(IMultiplayerClient.UserModsChanged), ((IMultiplayerClient)this).UserModsChanged); + connection.On(nameof(IMultiplayerClient.RoomStateChanged), ((IMultiplayerClient)this).RoomStateChanged); + connection.On(nameof(IMultiplayerClient.UserJoined), ((IMultiplayerClient)this).UserJoined); + connection.On(nameof(IMultiplayerClient.UserLeft), ((IMultiplayerClient)this).UserLeft); + connection.On(nameof(IMultiplayerClient.HostChanged), ((IMultiplayerClient)this).HostChanged); + connection.On(nameof(IMultiplayerClient.SettingsChanged), ((IMultiplayerClient)this).SettingsChanged); + connection.On(nameof(IMultiplayerClient.UserStateChanged), ((IMultiplayerClient)this).UserStateChanged); + connection.On(nameof(IMultiplayerClient.LoadRequested), ((IMultiplayerClient)this).LoadRequested); + connection.On(nameof(IMultiplayerClient.MatchStarted), ((IMultiplayerClient)this).MatchStarted); + connection.On(nameof(IMultiplayerClient.ResultsReady), ((IMultiplayerClient)this).ResultsReady); + connection.On>(nameof(IMultiplayerClient.UserModsChanged), ((IMultiplayerClient)this).UserModsChanged); }, }; + + IsConnected.BindTo(connector.IsConnected); } protected override Task JoinRoom(long roomId) @@ -106,5 +116,11 @@ namespace osu.Game.Online.Multiplayer return connection.InvokeAsync(nameof(IMultiplayerServer.StartMatch)); } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + connector?.Dispose(); + } } } diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 33ebe27937..4ef59b5e47 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -30,9 +30,11 @@ namespace osu.Game.Online.Spectator /// public const double TIME_BETWEEN_SENDS = 200; - private readonly HubClientConnector connector; + private readonly string endpoint; - protected virtual IBindable IsConnected => connector.IsConnected; + private HubClientConnector connector; + + private readonly IBindable isConnected = new BindableBool(); private HubConnection connection => connector.CurrentConnection; @@ -77,23 +79,24 @@ namespace osu.Game.Online.Spectator public SpectatorStreamingClient(EndpointConfiguration endpoints) { - InternalChild = connector = new HubClientConnector("Spectator client", endpoints.SpectatorEndpointUrl) - { - OnNewConnection = newConnection => - { - // until strong typed client support is added, each method must be manually bound - // (see https://github.com/dotnet/aspnetcore/issues/15198) - newConnection.On(nameof(ISpectatorClient.UserBeganPlaying), ((ISpectatorClient)this).UserBeganPlaying); - newConnection.On(nameof(ISpectatorClient.UserSentFrames), ((ISpectatorClient)this).UserSentFrames); - newConnection.On(nameof(ISpectatorClient.UserFinishedPlaying), ((ISpectatorClient)this).UserFinishedPlaying); - } - }; + endpoint = endpoints.SpectatorEndpointUrl; } [BackgroundDependencyLoader] - private void load() + private void load(IAPIProvider api) { - IsConnected.BindValueChanged(connected => + connector = CreateConnector(nameof(SpectatorStreamingClient), endpoint, api); + connector.OnNewConnection = connection => + { + // until strong typed client support is added, each method must be manually bound + // (see https://github.com/dotnet/aspnetcore/issues/15198) + connection.On(nameof(ISpectatorClient.UserBeganPlaying), ((ISpectatorClient)this).UserBeganPlaying); + connection.On(nameof(ISpectatorClient.UserSentFrames), ((ISpectatorClient)this).UserSentFrames); + connection.On(nameof(ISpectatorClient.UserFinishedPlaying), ((ISpectatorClient)this).UserFinishedPlaying); + }; + + isConnected.BindTo(connector.IsConnected); + isConnected.BindValueChanged(connected => { if (connected.NewValue) { @@ -121,6 +124,8 @@ namespace osu.Game.Online.Spectator }, true); } + protected virtual HubClientConnector CreateConnector(string name, string endpoint, IAPIProvider api) => new HubClientConnector(name, endpoint, api); + Task ISpectatorClient.UserBeganPlaying(int userId, SpectatorState state) { if (!playingUsers.Contains(userId)) @@ -169,14 +174,14 @@ namespace osu.Game.Online.Spectator { Debug.Assert(isPlaying); - if (!IsConnected.Value) return; + if (!isConnected.Value) return; connection.SendAsync(nameof(ISpectatorServer.BeginPlaySession), currentState); } public void SendFrames(FrameDataBundle data) { - if (!IsConnected.Value) return; + if (!isConnected.Value) return; lastSend = connection.SendAsync(nameof(ISpectatorServer.SendFrameData), data); } @@ -186,7 +191,7 @@ namespace osu.Game.Online.Spectator isPlaying = false; currentBeatmap = null; - if (!IsConnected.Value) return; + if (!isConnected.Value) return; connection.SendAsync(nameof(ISpectatorServer.EndPlaySession), currentState); } @@ -200,7 +205,7 @@ namespace osu.Game.Online.Spectator watchingUsers.Add(userId); - if (!IsConnected.Value) + if (!isConnected.Value) return; } @@ -213,7 +218,7 @@ namespace osu.Game.Online.Spectator { watchingUsers.Remove(userId); - if (!IsConnected.Value) + if (!isConnected.Value) return; } From a0ead38496b9dcb9ead023729765c437e7902f95 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 9 Feb 2021 08:02:51 +0300 Subject: [PATCH 0448/1791] Prevent test spectator clients from attempting hub connections --- osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs | 7 +++++++ .../Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 4a0e1282c4..1e499f20cb 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -12,6 +12,7 @@ using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Online; +using osu.Game.Online.API; using osu.Game.Online.Spectator; using osu.Game.Replays.Legacy; using osu.Game.Rulesets.Osu; @@ -243,6 +244,12 @@ namespace osu.Game.Tests.Visual.Gameplay { } + protected override HubClientConnector CreateConnector(string name, string endpoint, IAPIProvider api) + { + // do not pass API to prevent attempting failing connections on an actual hub. + return base.CreateConnector(name, endpoint, null); + } + public void StartPlay(int beatmapId) { this.beatmapId = beatmapId; diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index aab69d687a..b459cebdd7 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -13,6 +13,7 @@ using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Database; using osu.Game.Online; +using osu.Game.Online.API; using osu.Game.Online.Spectator; using osu.Game.Replays.Legacy; using osu.Game.Rulesets.Osu.Scoring; @@ -105,6 +106,12 @@ namespace osu.Game.Tests.Visual.Multiplayer this.totalUsers = totalUsers; } + protected override HubClientConnector CreateConnector(string name, string endpoint, IAPIProvider api) + { + // do not pass API to prevent attempting failing connections on an actual hub. + return base.CreateConnector(name, endpoint, null); + } + public void Start(int beatmapId) { for (int i = 0; i < totalUsers; i++) From b96a594546b38a866c7e661b707de04518407baf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Feb 2021 15:11:58 +0900 Subject: [PATCH 0449/1791] Remove unnecessary initial call to HitObjectApplied bound method Was causing test failures. Looks to be unnecessary on a check of when HitObjectApplied is invoked. --- osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs index 8eb2714c04..f9b8ffca7b 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs @@ -45,7 +45,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default BorderColour = skin.GetConfig(OsuSkinColour.SliderBorder)?.Value ?? Color4.White; drawableObject.HitObjectApplied += onHitObjectApplied; - onHitObjectApplied(drawableObject); } private void onHitObjectApplied(DrawableHitObject obj) From 695e46a358ba1dd75da161ee70e09bd19d462334 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Feb 2021 15:31:55 +0900 Subject: [PATCH 0450/1791] Fix AutoPilot mod failing to block touch input --- osu.Game.Rulesets.Osu/OsuInputManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/OsuInputManager.cs b/osu.Game.Rulesets.Osu/OsuInputManager.cs index c8fe4f41ca..7314021a14 100644 --- a/osu.Game.Rulesets.Osu/OsuInputManager.cs +++ b/osu.Game.Rulesets.Osu/OsuInputManager.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Osu protected override bool Handle(UIEvent e) { - if (e is MouseMoveEvent && !AllowUserCursorMovement) return false; + if ((e is MouseMoveEvent || e is TouchMoveEvent) && !AllowUserCursorMovement) return false; return base.Handle(e); } From b87327841dec18cda4edcc9c6ee4a52d4b8ebf29 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Feb 2021 15:46:23 +0900 Subject: [PATCH 0451/1791] Add test covering initial state propagation --- .../TestSceneMultiplayerParticipantsList.cs | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs index c3852fafd4..0f7a9b442d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs @@ -22,16 +22,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public class TestSceneMultiplayerParticipantsList : MultiplayerTestScene { [SetUp] - public new void Setup() => Schedule(() => - { - Child = new ParticipantsList - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Y, - Size = new Vector2(380, 0.7f) - }; - }); + public new void Setup() => Schedule(createNewParticipantsList); [Test] public void TestAddUser() @@ -92,6 +83,14 @@ namespace osu.Game.Tests.Visual.Multiplayer checkProgressBarVisibility(true); } + [Test] + public void TestCorrectInitialState() + { + AddStep("set to downloading map", () => Client.ChangeBeatmapAvailability(BeatmapAvailability.Downloading(0))); + AddStep("recreate list", createNewParticipantsList); + checkProgressBarVisibility(true); + } + [Test] public void TestBeatmapDownloadingStates() { @@ -212,6 +211,11 @@ namespace osu.Game.Tests.Visual.Multiplayer } } + private void createNewParticipantsList() + { + Child = new ParticipantsList { Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Y, Size = new Vector2(380, 0.7f) }; + } + private void checkProgressBarVisibility(bool visible) => AddUntilStep($"progress bar {(visible ? "is" : "is not")}visible", () => this.ChildrenOfType().Single().IsPresent == visible); From 04c243386b136359a81606c612686fe1a8754527 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Feb 2021 16:02:56 +0900 Subject: [PATCH 0452/1791] Fix initial state transfer regressing --- .../OnlinePlayBeatmapAvailablilityTracker.cs | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailablilityTracker.cs b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailablilityTracker.cs index cfaf43451f..d6f4c45a75 100644 --- a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailablilityTracker.cs +++ b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailablilityTracker.cs @@ -29,18 +29,6 @@ namespace osu.Game.Online.Rooms private ScheduledDelegate progressUpdate; - public OnlinePlayBeatmapAvailablilityTracker() - { - State.BindValueChanged(_ => updateAvailability()); - Progress.BindValueChanged(_ => - { - // incoming progress changes are going to be at a very high rate. - // we don't want to flood the network with this, so rate limit how often we send progress updates. - if (progressUpdate?.Completed != false) - progressUpdate = Scheduler.AddDelayed(updateAvailability, progressUpdate == null ? 0 : 500); - }); - } - protected override void LoadComplete() { base.LoadComplete(); @@ -54,6 +42,16 @@ namespace osu.Game.Online.Rooms Model.Value = item.NewValue.Beatmap.Value.BeatmapSet; }, true); + + Progress.BindValueChanged(_ => + { + // incoming progress changes are going to be at a very high rate. + // we don't want to flood the network with this, so rate limit how often we send progress updates. + if (progressUpdate?.Completed != false) + progressUpdate = Scheduler.AddDelayed(updateAvailability, progressUpdate == null ? 0 : 500); + }); + + State.BindValueChanged(_ => updateAvailability(), true); } protected override bool VerifyDatabasedModel(BeatmapSetInfo databasedSet) From 5bd4f74ddf752f3ddc83d67d8ea48708a0248b13 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Feb 2021 16:24:29 +0900 Subject: [PATCH 0453/1791] Fix a potential crash when exiting play during the results screen transition --- osu.Game/Screens/Play/Player.cs | 48 +++++++++++++++++---------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 5d06ac5b3a..dbee49b5dd 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -339,7 +339,7 @@ namespace osu.Game.Screens.Play { HoldToQuit = { - Action = performUserRequestedExit, + Action = () => PerformExit(true), IsPaused = { BindTarget = GameplayClockContainer.IsPaused } }, PlayerSettingsOverlay = { PlaybackSettings = { UserPlaybackRate = { BindTarget = GameplayClockContainer.UserPlaybackRate } } }, @@ -363,14 +363,14 @@ namespace osu.Game.Screens.Play FailOverlay = new FailOverlay { OnRetry = Restart, - OnQuit = performUserRequestedExit, + OnQuit = () => PerformExit(true), }, PauseOverlay = new PauseOverlay { OnResume = Resume, Retries = RestartCount, OnRetry = Restart, - OnQuit = performUserRequestedExit, + OnQuit = () => PerformExit(true), }, new HotkeyExitOverlay { @@ -487,14 +487,30 @@ namespace osu.Game.Screens.Play // if a restart has been requested, cancel any pending completion (user has shown intent to restart). completionProgressDelegate?.Cancel(); - ValidForResume = false; - - if (!this.IsCurrentScreen()) return; + if (!this.IsCurrentScreen()) + { + // there is a chance that the exit was performed after the transition to results has started. + // we want to give the user what they want, so forcefully return to this screen (to proceed with the upwards exit process). + ValidForResume = false; + this.MakeCurrent(); + } if (userRequested) - performUserRequestedExit(); - else - this.Exit(); + { + if (ValidForResume && HasFailed && !FailOverlay.IsPresent) + { + failAnimation.FinishTransforms(true); + return; + } + + if (canPause) + { + Pause(); + return; + } + } + + this.Exit(); } private void performUserRequestedSkip() @@ -508,20 +524,6 @@ namespace osu.Game.Screens.Play updateSampleDisabledState(); } - private void performUserRequestedExit() - { - if (ValidForResume && HasFailed && !FailOverlay.IsPresent) - { - failAnimation.FinishTransforms(true); - return; - } - - if (canPause) - Pause(); - else - this.Exit(); - } - /// /// Restart gameplay via a parent . /// This can be called from a child screen in order to trigger the restart process. From 61b9539864289b9ded799cac93187fc641c3db35 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Feb 2021 17:14:16 +0900 Subject: [PATCH 0454/1791] Fix regression in quick exit logic --- osu.Game/Screens/Play/Player.cs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index dbee49b5dd..3f8651761e 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -478,11 +478,11 @@ namespace osu.Game.Screens.Play /// /// Exits the . /// - /// - /// Whether the exit is requested by the user, or a higher-level game component. - /// Pausing is allowed only in the former case. + /// + /// Whether the pause or fail dialog should be shown before performing an exit. + /// If true and a dialog is not yet displayed, the exit will be blocked the the relevant dialog will display instead. /// - protected void PerformExit(bool userRequested) + protected void PerformExit(bool showDialogFirst) { // if a restart has been requested, cancel any pending completion (user has shown intent to restart). completionProgressDelegate?.Cancel(); @@ -495,7 +495,7 @@ namespace osu.Game.Screens.Play this.MakeCurrent(); } - if (userRequested) + if (showDialogFirst) { if (ValidForResume && HasFailed && !FailOverlay.IsPresent) { @@ -503,7 +503,7 @@ namespace osu.Game.Screens.Play return; } - if (canPause) + if (canPause && !GameplayClockContainer.IsPaused.Value) { Pause(); return; @@ -540,10 +540,7 @@ namespace osu.Game.Screens.Play sampleRestart?.Play(); RestartRequested?.Invoke(); - if (this.IsCurrentScreen()) - PerformExit(true); - else - this.MakeCurrent(); + PerformExit(false); } private ScheduledDelegate completionProgressDelegate; From cba116ff090c650cbc4812f16e15ddfd13747e3f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Feb 2021 17:28:57 +0900 Subject: [PATCH 0455/1791] Fix incorrect call parameter for quick exit --- 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 3f8651761e..8a977b0498 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -379,7 +379,7 @@ namespace osu.Game.Screens.Play if (!this.IsCurrentScreen()) return; fadeOut(true); - PerformExit(true); + PerformExit(false); }, }, failAnimation = new FailAnimation(DrawableRuleset) { OnComplete = onFailComplete, }, From 2c052d70e8668bc9f64354fc14d3425b5f0e6552 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Feb 2021 17:29:18 +0900 Subject: [PATCH 0456/1791] Only trigger pause cooldown on pause (not exit) --- osu.Game/Screens/Play/Player.cs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 8a977b0498..dda52f4dae 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -505,6 +505,10 @@ namespace osu.Game.Screens.Play if (canPause && !GameplayClockContainer.IsPaused.Value) { + if (pauseCooldownActive && !GameplayClockContainer.IsPaused.Value) + // still want to block if we are within the cooldown period and not already paused. + return; + Pause(); return; } @@ -808,14 +812,6 @@ namespace osu.Game.Screens.Play return true; } - // ValidForResume is false when restarting - if (ValidForResume) - { - if (pauseCooldownActive && !GameplayClockContainer.IsPaused.Value) - // still want to block if we are within the cooldown period and not already paused. - return true; - } - // GameplayClockContainer performs seeks / start / stop operations on the beatmap's track. // as we are no longer the current screen, we cannot guarantee the track is still usable. GameplayClockContainer?.StopUsingBeatmapClock(); From 94f35825ddb28b8bf2d3e86562afc47dbd30e4a1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Feb 2021 17:29:27 +0900 Subject: [PATCH 0457/1791] Update test to cover changed exit/pause logic I think this makes more sense? --- osu.Game.Tests/Visual/Gameplay/TestScenePause.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index 46dd91710a..ae806883b0 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -108,19 +108,19 @@ namespace osu.Game.Tests.Visual.Gameplay } [Test] - public void TestExitTooSoon() + public void TestExitSoonAfterResumeSucceeds() { AddStep("seek before gameplay", () => Player.GameplayClockContainer.Seek(-5000)); pauseAndConfirm(); resume(); - AddStep("exit too soon", () => Player.Exit()); + AddStep("exit quick", () => Player.Exit()); confirmClockRunning(true); confirmPauseOverlayShown(false); - AddAssert("not exited", () => Player.IsCurrentScreen()); + AddAssert("exited", () => !Player.IsCurrentScreen()); } [Test] From b5fa9508006c76decac0752a6bdaf47598196d4d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Feb 2021 18:30:05 +0900 Subject: [PATCH 0458/1791] Remove unnecessary depth specification --- osu.Game/Overlays/OnlineOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/OnlineOverlay.cs b/osu.Game/Overlays/OnlineOverlay.cs index 4a7318d065..b07f91b9ed 100644 --- a/osu.Game/Overlays/OnlineOverlay.cs +++ b/osu.Game/Overlays/OnlineOverlay.cs @@ -32,7 +32,7 @@ namespace osu.Game.Overlays Direction = FillDirection.Vertical, Children = new Drawable[] { - Header.With(h => h.Depth = -float.MaxValue), + Header, content = new Container { RelativeSizeAxes = Axes.X, From 178d88bcf197393fab133d3bd760d33c35b8e3c4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Feb 2021 18:32:44 +0900 Subject: [PATCH 0459/1791] Change BackgroundColour into a property --- osu.Game/Overlays/BeatmapListingOverlay.cs | 2 +- osu.Game/Overlays/BeatmapSetOverlay.cs | 2 +- osu.Game/Overlays/ChangelogOverlay.cs | 2 +- osu.Game/Overlays/FullscreenOverlay.cs | 6 +++--- osu.Game/Overlays/UserProfileOverlay.cs | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index cfa0ff00bc..5df7a4650e 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -85,7 +85,7 @@ namespace osu.Game.Overlays protected override BeatmapListingHeader CreateHeader() => new BeatmapListingHeader(); - protected override Color4 GetBackgroundColour() => ColourProvider.Background6; + protected override Color4 BackgroundColour => ColourProvider.Background6; private void onTypingStarted() { diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index 723b61bbc5..bdb3715e73 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -68,7 +68,7 @@ namespace osu.Game.Overlays protected override BeatmapSetHeader CreateHeader() => new BeatmapSetHeader(); - protected override Color4 GetBackgroundColour() => ColourProvider.Background6; + protected override Color4 BackgroundColour => ColourProvider.Background6; protected override void PopOutComplete() { diff --git a/osu.Game/Overlays/ChangelogOverlay.cs b/osu.Game/Overlays/ChangelogOverlay.cs index 5200b567ff..05bad30107 100644 --- a/osu.Game/Overlays/ChangelogOverlay.cs +++ b/osu.Game/Overlays/ChangelogOverlay.cs @@ -55,7 +55,7 @@ namespace osu.Game.Overlays ListingSelected = ShowListing, }; - protected override Color4 GetBackgroundColour() => ColourProvider.Background4; + protected override Color4 BackgroundColour => ColourProvider.Background4; public void ShowListing() { diff --git a/osu.Game/Overlays/FullscreenOverlay.cs b/osu.Game/Overlays/FullscreenOverlay.cs index d0a0c994aa..735f0bcbd4 100644 --- a/osu.Game/Overlays/FullscreenOverlay.cs +++ b/osu.Game/Overlays/FullscreenOverlay.cs @@ -23,6 +23,8 @@ namespace osu.Game.Overlays public T Header { get; } + protected virtual Color4 BackgroundColour => ColourProvider.Background5; + [Resolved] protected IAPIProvider API { get; private set; } @@ -59,7 +61,7 @@ namespace osu.Game.Overlays new Box { RelativeSizeAxes = Axes.Both, - Colour = GetBackgroundColour() + Colour = BackgroundColour }, content = new Container { @@ -80,8 +82,6 @@ namespace osu.Game.Overlays [NotNull] protected abstract T CreateHeader(); - protected virtual Color4 GetBackgroundColour() => ColourProvider.Background5; - public override void Show() { if (State.Value == Visibility.Visible) diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index ccd9c291c4..299a14b250 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -36,7 +36,7 @@ namespace osu.Game.Overlays protected override ProfileHeader CreateHeader() => new ProfileHeader(); - protected override Color4 GetBackgroundColour() => ColourProvider.Background6; + protected override Color4 BackgroundColour => ColourProvider.Background6; public void ShowUser(int userId) => ShowUser(new User { Id = userId }); From 167076663304d009769df70fae7feccdf4128f0b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Feb 2021 18:42:15 +0900 Subject: [PATCH 0460/1791] Avoid unbinding external events --- osu.Game/Overlays/RankingsOverlay.cs | 44 +++++++++------------- osu.Game/Overlays/TabbableOnlineOverlay.cs | 2 +- 2 files changed, 18 insertions(+), 28 deletions(-) diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs index 6cd72d6e2c..a093969115 100644 --- a/osu.Game/Overlays/RankingsOverlay.cs +++ b/osu.Game/Overlays/RankingsOverlay.cs @@ -17,8 +17,6 @@ namespace osu.Game.Overlays { protected Bindable Country => Header.Country; - protected Bindable Scope => Header.Current; - private APIRequest lastRequest; [Resolved] @@ -42,31 +40,31 @@ namespace osu.Game.Overlays { // if a country is requested, force performance scope. if (Country.Value != null) - Scope.Value = RankingsScope.Performance; + Header.Current.Value = RankingsScope.Performance; - Scheduler.AddOnce(loadNewContent); - }); - - // Unbind events from scope so base class event will not be called - Scope.UnbindEvents(); - Scope.BindValueChanged(_ => - { - // country filtering is only valid for performance scope. - if (Scope.Value != RankingsScope.Performance) - Country.Value = null; - - Scheduler.AddOnce(loadNewContent); + Scheduler.AddOnce(triggerTabChanged); }); ruleset.BindValueChanged(_ => { - if (Scope.Value == RankingsScope.Spotlights) + if (Header.Current.Value == RankingsScope.Spotlights) return; - Scheduler.AddOnce(loadNewContent); + Scheduler.AddOnce(triggerTabChanged); }); } + protected override void OnTabChanged(RankingsScope tab) + { + // country filtering is only valid for performance scope. + if (Header.Current.Value != RankingsScope.Performance) + Country.Value = null; + + Scheduler.AddOnce(triggerTabChanged); + } + + private void triggerTabChanged() => base.OnTabChanged(Header.Current.Value); + protected override RankingsOverlayHeader CreateHeader() => new RankingsOverlayHeader(); public void ShowCountry(Country requested) @@ -79,17 +77,11 @@ namespace osu.Game.Overlays Country.Value = requested; } - public void ShowSpotlights() - { - Scope.Value = RankingsScope.Spotlights; - Show(); - } - protected override void CreateDisplayToLoad(RankingsScope tab) { lastRequest?.Cancel(); - if (Scope.Value == RankingsScope.Spotlights) + if (Header.Current.Value == RankingsScope.Spotlights) { LoadDisplay(new SpotlightsLayout { @@ -115,7 +107,7 @@ namespace osu.Game.Overlays private APIRequest createScopedRequest() { - switch (Scope.Value) + switch (Header.Current.Value) { case RankingsScope.Performance: return new GetUserRankingsRequest(ruleset.Value, country: Country.Value?.FlagName); @@ -153,8 +145,6 @@ namespace osu.Game.Overlays return null; } - private void loadNewContent() => OnTabChanged(Scope.Value); - protected override void Dispose(bool isDisposing) { lastRequest?.Cancel(); diff --git a/osu.Game/Overlays/TabbableOnlineOverlay.cs b/osu.Game/Overlays/TabbableOnlineOverlay.cs index cbcf3cd96e..8172e99c1b 100644 --- a/osu.Game/Overlays/TabbableOnlineOverlay.cs +++ b/osu.Game/Overlays/TabbableOnlineOverlay.cs @@ -68,7 +68,7 @@ namespace osu.Game.Overlays }, (cancellationToken = new CancellationTokenSource()).Token); } - protected void OnTabChanged(TEnum tab) + protected virtual void OnTabChanged(TEnum tab) { cancellationToken?.Cancel(); Loading.Show(); From 17475e60b0a733344a60a619a3c4fc16f2d9b95d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Feb 2021 18:48:50 +0900 Subject: [PATCH 0461/1791] Fix missed test scene update --- osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs index 626f545b91..aff510dd95 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs @@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.Online Add(rankingsOverlay = new TestRankingsOverlay { Country = { BindTarget = countryBindable }, - Scope = { BindTarget = scope }, + Header = { Current = { BindTarget = scope } }, }); } @@ -65,8 +65,6 @@ namespace osu.Game.Tests.Visual.Online private class TestRankingsOverlay : RankingsOverlay { public new Bindable Country => base.Country; - - public new Bindable Scope => base.Scope; } } } From 0a96f4d403cf3be814b10de8e2d7cc3e4e2c335e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Feb 2021 18:56:27 +0900 Subject: [PATCH 0462/1791] Avoid assigning null to a non-nullable property --- .../Visual/Online/TestSceneFullscreenOverlay.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneFullscreenOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneFullscreenOverlay.cs index f8b059e471..dc468bb62d 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneFullscreenOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneFullscreenOverlay.cs @@ -53,7 +53,16 @@ namespace osu.Game.Tests.Visual.Online }; } - protected override OverlayHeader CreateHeader() => null; + protected override OverlayHeader CreateHeader() => new TestHeader(); + + internal class TestHeader : OverlayHeader + { + protected override OverlayTitle CreateTitle() => new TestTitle(); + + internal class TestTitle : OverlayTitle + { + } + } } } } From d8d830db6e39a3d590dc3963f10e60a5919727fa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Feb 2021 19:46:57 +0900 Subject: [PATCH 0463/1791] Defer playlist load to improve load time of the now playing overlay --- osu.Game/Overlays/NowPlayingOverlay.cs | 37 +++++++++++++++++++------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index 9beb859f28..f94b41155a 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -84,11 +84,6 @@ namespace osu.Game.Overlays AutoSizeAxes = Axes.Y, Children = new Drawable[] { - playlist = new PlaylistOverlay - { - RelativeSizeAxes = Axes.X, - Y = player_height + 10, - }, playerContainer = new Container { RelativeSizeAxes = Axes.X, @@ -171,7 +166,7 @@ namespace osu.Game.Overlays Anchor = Anchor.CentreRight, Position = new Vector2(-bottom_black_area_height / 2, 0), Icon = FontAwesome.Solid.Bars, - Action = () => playlist.ToggleVisibility(), + Action = togglePlaylist }, } }, @@ -191,13 +186,35 @@ namespace osu.Game.Overlays }; } + private void togglePlaylist() + { + if (playlist == null) + { + LoadComponentAsync(playlist = new PlaylistOverlay + { + RelativeSizeAxes = Axes.X, + Y = player_height + 10, + }, _ => + { + dragContainer.Add(playlist); + + playlist.BeatmapSets.BindTo(musicController.BeatmapSets); + playlist.State.BindValueChanged(s => playlistButton.FadeColour(s.NewValue == Visibility.Visible ? colours.Yellow : Color4.White, 200, Easing.OutQuint), true); + + togglePlaylist(); + }); + + return; + } + + if (!beatmap.Disabled) + playlist.ToggleVisibility(); + } + protected override void LoadComplete() { base.LoadComplete(); - playlist.BeatmapSets.BindTo(musicController.BeatmapSets); - playlist.State.BindValueChanged(s => playlistButton.FadeColour(s.NewValue == Visibility.Visible ? colours.Yellow : Color4.White, 200, Easing.OutQuint), true); - beatmap.BindDisabledChanged(beatmapDisabledChanged, true); musicController.TrackChanged += trackChanged; @@ -306,7 +323,7 @@ namespace osu.Game.Overlays private void beatmapDisabledChanged(bool disabled) { if (disabled) - playlist.Hide(); + playlist?.Hide(); prevButton.Enabled.Value = !disabled; nextButton.Enabled.Value = !disabled; From d9dcf8a042bc99e0161a49088784e06553c8b27e Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 9 Feb 2021 20:30:31 +0300 Subject: [PATCH 0464/1791] Fix incorrect header depth in OnlineOverlay --- osu.Game/Overlays/OnlineOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/OnlineOverlay.cs b/osu.Game/Overlays/OnlineOverlay.cs index b07f91b9ed..4a7318d065 100644 --- a/osu.Game/Overlays/OnlineOverlay.cs +++ b/osu.Game/Overlays/OnlineOverlay.cs @@ -32,7 +32,7 @@ namespace osu.Game.Overlays Direction = FillDirection.Vertical, Children = new Drawable[] { - Header, + Header.With(h => h.Depth = -float.MaxValue), content = new Container { RelativeSizeAxes = Axes.X, From e44667e5e073016b471c13d7cce1427d0db89be5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 10 Feb 2021 11:31:34 +0900 Subject: [PATCH 0465/1791] Use MinValue instead Co-authored-by: Salman Ahmed --- osu.Game/Overlays/OnlineOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/OnlineOverlay.cs b/osu.Game/Overlays/OnlineOverlay.cs index 4a7318d065..7c9f751d3b 100644 --- a/osu.Game/Overlays/OnlineOverlay.cs +++ b/osu.Game/Overlays/OnlineOverlay.cs @@ -32,7 +32,7 @@ namespace osu.Game.Overlays Direction = FillDirection.Vertical, Children = new Drawable[] { - Header.With(h => h.Depth = -float.MaxValue), + Header.With(h => h.Depth = float.MinValue), content = new Container { RelativeSizeAxes = Axes.X, From e9ef4aaf88a8908596231461420bc83d5ccc569f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 10 Feb 2021 14:34:45 +0900 Subject: [PATCH 0466/1791] Add test covering expectations of external mod changes --- .../TestSceneModSelectOverlay.cs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 85350c028c..dec9e319ea 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -170,6 +170,31 @@ namespace osu.Game.Tests.Visual.UserInterface }); } + [Test] + public void TestExternallySetModIsReplacedByOverlayInstance() + { + Mod external = new OsuModDoubleTime(); + Mod overlayButtonMod = null; + + changeRuleset(0); + + AddStep("set mod externally", () => { SelectedMods.Value = new[] { external }; }); + + AddAssert("ensure button is selected", () => + { + var button = modSelect.GetModButton(SelectedMods.Value.Single()); + overlayButtonMod = button.SelectedMod; + return overlayButtonMod.GetType() == external.GetType(); + }); + + // Right now, when an external change occurs, the ModSelectOverlay will replace the global instance with its own + AddAssert("mod instance doesn't match", () => external != overlayButtonMod); + + AddAssert("one mod present in global selected", () => SelectedMods.Value.Count == 1); + AddAssert("globally selected matches button's mod instance", () => SelectedMods.Value.Contains(overlayButtonMod)); + AddAssert("globally selected doesn't contain original external change", () => !SelectedMods.Value.Contains(external)); + } + [Test] public void TestNonStacked() { From 52f0f3f3b212fb2d38057073138755b9e73c858b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 10 Feb 2021 14:38:15 +0900 Subject: [PATCH 0467/1791] Add a note about SelectedMods behavioural quirks --- osu.Game/OsuGameBase.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 20d88d33f2..d3936ed27e 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -98,7 +98,14 @@ namespace osu.Game [Cached(typeof(IBindable))] protected readonly Bindable Ruleset = new Bindable(); - // todo: move this to SongSelect once Screen has the ability to unsuspend. + /// + /// The current mod selection for the local user. + /// + /// + /// If a mod select overlay is present, mod instances set to this value are not guaranteed to remain as the provided instance and will be overwritten by a copy. + /// In such a case, changes to settings of a mod will *not* propagate after a mod is added to this collection. + /// As such, all settings should be finalised before adding a mod to this collection. + /// [Cached] [Cached(typeof(IBindable>))] protected readonly Bindable> SelectedMods = new Bindable>(Array.Empty()); From de8a60435fca3f4644efa5b268848829b3812ae8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 10 Feb 2021 14:44:37 +0900 Subject: [PATCH 0468/1791] Add failing test covering reported breaking case --- .../UserInterface/TestSceneModSelectOverlay.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index dec9e319ea..9ca1d4102a 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -170,6 +170,20 @@ namespace osu.Game.Tests.Visual.UserInterface }); } + [Test] + public void TestSettingsAreRetainedOnReload() + { + changeRuleset(0); + + AddStep("set customized mod externally", () => SelectedMods.Value = new[] { new OsuModDoubleTime { SpeedChange = { Value = 1.01 } } }); + + AddAssert("setting remains", () => (SelectedMods.Value.SingleOrDefault() as OsuModDoubleTime)?.SpeedChange.Value == 1.01); + + AddStep("create overlay", () => createDisplay(() => new TestNonStackedModSelectOverlay())); + + AddAssert("setting remains", () => (SelectedMods.Value.SingleOrDefault() as OsuModDoubleTime)?.SpeedChange.Value == 1.01); + } + [Test] public void TestExternallySetModIsReplacedByOverlayInstance() { From 75bc9f607e30495763ec305fe7f6fae608931754 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 10 Feb 2021 14:55:15 +0900 Subject: [PATCH 0469/1791] Rename wrongly named method --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 93fe693937..21ed9af421 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -479,10 +479,10 @@ namespace osu.Game.Overlays.Mods foreach (var section in ModSectionsContainer.Children) section.UpdateSelectedButtons(selectedMods); - updateMods(); + updateMultiplier(); } - private void updateMods() + private void updateMultiplier() { var multiplier = 1.0; From a39263423c95dd25344dff4e4a5e56afb0842d3a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 10 Feb 2021 15:12:29 +0900 Subject: [PATCH 0470/1791] Fix externally changed settings from being reset when ModSelectOverlay is initialised --- osu.Game/Overlays/Mods/ModButton.cs | 16 ++++++++++++---- osu.Game/Overlays/Mods/ModSection.cs | 5 +++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs index 06f2fea43f..5e3733cd5e 100644 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ b/osu.Game/Overlays/Mods/ModButton.cs @@ -46,8 +46,9 @@ namespace osu.Game.Overlays.Mods /// Change the selected mod index of this button. /// /// The new index. + /// Whether any settings applied to the mod should be reset on selection. /// Whether the selection changed. - private bool changeSelectedIndex(int newIndex) + private bool changeSelectedIndex(int newIndex, bool resetSettings = true) { if (newIndex == selectedIndex) return false; @@ -69,7 +70,8 @@ namespace osu.Game.Overlays.Mods Mod newSelection = SelectedMod ?? Mods[0]; - newSelection.ResetSettingsToDefaults(); + if (resetSettings) + newSelection.ResetSettingsToDefaults(); Schedule(() => { @@ -211,11 +213,17 @@ namespace osu.Game.Overlays.Mods Deselect(); } - public bool SelectAt(int index) + /// + /// Select the mod at the provided index. + /// + /// The index to select. + /// Whether any settings applied to the mod should be reset on selection. + /// Whether the selection changed. + public bool SelectAt(int index, bool resetSettings = true) { if (!Mods[index].HasImplementation) return false; - changeSelectedIndex(index); + changeSelectedIndex(index, resetSettings); return true; } diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index 08bd3f8622..71ecef2b82 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -197,9 +197,10 @@ namespace osu.Game.Overlays.Mods continue; var buttonMod = button.Mods[index]; - button.SelectAt(index); - // the selection above will reset settings to defaults, but as this is an external change we want to copy the new settings across. + button.SelectAt(index, false); + + // as this is likely coming from an external change, ensure the settings of the mod are in sync. buttonMod.CopyFrom(mod); return; } From 435c85a2e79b8682d093c261054c8d4ad67215b4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 10 Feb 2021 15:13:09 +0900 Subject: [PATCH 0471/1791] Avoid executing selection twice on ModSelectOverlay load --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 21ed9af421..f1bfa26a98 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -371,8 +371,8 @@ namespace osu.Game.Overlays.Mods { base.LoadComplete(); + SelectedMods.BindValueChanged(_ => updateSelectedButtons()); availableMods.BindValueChanged(_ => updateAvailableMods(), true); - SelectedMods.BindValueChanged(_ => updateSelectedButtons(), true); } protected override void PopOut() From 98a83722ff0ad8fffdd426a55a32792d8ce8febd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 10 Feb 2021 15:29:55 +0900 Subject: [PATCH 0472/1791] Move the point at which selected mods are reset in tests to allow mutliple creation test flow --- .../Visual/UserInterface/TestSceneModSelectOverlay.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 9ca1d4102a..2885dbee00 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -39,7 +39,11 @@ namespace osu.Game.Tests.Visual.UserInterface } [SetUp] - public void SetUp() => Schedule(() => createDisplay(() => new TestModSelectOverlay())); + public void SetUp() => Schedule(() => + { + SelectedMods.Value = Array.Empty(); + createDisplay(() => new TestModSelectOverlay()); + }); [SetUpSteps] public void SetUpSteps() @@ -370,7 +374,6 @@ namespace osu.Game.Tests.Visual.UserInterface private void createDisplay(Func createOverlayFunc) { - SelectedMods.Value = Array.Empty(); Children = new Drawable[] { modSelect = createOverlayFunc().With(d => From 67c1c4c1ebdd89e8c63c76e071f9598c1db32250 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 10 Feb 2021 15:30:17 +0900 Subject: [PATCH 0473/1791] Copy settings before applying selection --- osu.Game/Overlays/Mods/ModSection.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index 71ecef2b82..c3e56abd05 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -198,10 +198,10 @@ namespace osu.Game.Overlays.Mods var buttonMod = button.Mods[index]; - button.SelectAt(index, false); - // as this is likely coming from an external change, ensure the settings of the mod are in sync. buttonMod.CopyFrom(mod); + + button.SelectAt(index, false); return; } From b3b0d97354d7c57e554a69ea78f516e0e64833c3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 10 Feb 2021 15:32:57 +0900 Subject: [PATCH 0474/1791] Avoid potential feedback from bindable event binds --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index f1bfa26a98..a6de0ad6b1 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -371,8 +371,11 @@ namespace osu.Game.Overlays.Mods { base.LoadComplete(); - SelectedMods.BindValueChanged(_ => updateSelectedButtons()); availableMods.BindValueChanged(_ => updateAvailableMods(), true); + + // intentionally bound after the above line to avoid a potential update feedback cycle. + // i haven't actually observed this happening but as updateAvailableMods() changes the selection it is plausible. + SelectedMods.BindValueChanged(_ => updateSelectedButtons()); } protected override void PopOut() From 806324b196607d6d918f792289d4043100944904 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 15 Jan 2021 14:53:29 +0900 Subject: [PATCH 0475/1791] Allow overriding of Overlay pop-in and pop-out samples --- osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index 41fd37a0d7..ee99a39523 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs @@ -20,6 +20,8 @@ namespace osu.Game.Graphics.Containers { private SampleChannel samplePopIn; private SampleChannel samplePopOut; + protected virtual string PopInSampleName => "UI/overlay-pop-in"; + protected virtual string PopOutSampleName => "UI/overlay-pop-out"; protected override bool BlockNonPositionalInput => true; @@ -40,8 +42,8 @@ namespace osu.Game.Graphics.Containers [BackgroundDependencyLoader(true)] private void load(AudioManager audio) { - samplePopIn = audio.Samples.Get(@"UI/overlay-pop-in"); - samplePopOut = audio.Samples.Get(@"UI/overlay-pop-out"); + samplePopIn = audio.Samples.Get(PopInSampleName); + samplePopOut = audio.Samples.Get(PopOutSampleName); } protected override void LoadComplete() From 3eda78c363def6589adc0a84aee6c7795cd72d76 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 15 Jan 2021 14:57:46 +0900 Subject: [PATCH 0476/1791] Use unique samples for Dialog, NowPlaying, SettingsPanel and WaveOverlay pop-in/pop-out --- osu.Game/Overlays/DialogOverlay.cs | 3 +++ osu.Game/Overlays/NowPlayingOverlay.cs | 3 +++ osu.Game/Overlays/SettingsPanel.cs | 2 ++ osu.Game/Overlays/WaveOverlayContainer.cs | 2 ++ 4 files changed, 10 insertions(+) diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs index 9f9dbdbaf1..4cc17a4c14 100644 --- a/osu.Game/Overlays/DialogOverlay.cs +++ b/osu.Game/Overlays/DialogOverlay.cs @@ -14,6 +14,9 @@ namespace osu.Game.Overlays { private readonly Container dialogContainer; + protected override string PopInSampleName => "UI/dialog-pop-in"; + protected override string PopOutSampleName => "UI/dialog-pop-out"; + public PopupDialog CurrentDialog { get; private set; } public DialogOverlay() diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index 5c16a6e5c4..2866d2ad6d 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -51,6 +51,9 @@ namespace osu.Game.Overlays private Container dragContainer; private Container playerContainer; + protected override string PopInSampleName => "UI/now-playing-pop-in"; + protected override string PopOutSampleName => "UI/now-playing-pop-out"; + /// /// Provide a source for the toolbar height. /// diff --git a/osu.Game/Overlays/SettingsPanel.cs b/osu.Game/Overlays/SettingsPanel.cs index 7a5a586f67..f1270f750e 100644 --- a/osu.Game/Overlays/SettingsPanel.cs +++ b/osu.Game/Overlays/SettingsPanel.cs @@ -40,6 +40,8 @@ namespace osu.Game.Overlays private SeekLimitedSearchTextBox searchTextBox; + protected override string PopInSampleName => "UI/settings-pop-in"; + /// /// Provide a source for the toolbar height. /// diff --git a/osu.Game/Overlays/WaveOverlayContainer.cs b/osu.Game/Overlays/WaveOverlayContainer.cs index d0fa9987d5..52ae4dbdbb 100644 --- a/osu.Game/Overlays/WaveOverlayContainer.cs +++ b/osu.Game/Overlays/WaveOverlayContainer.cs @@ -18,6 +18,8 @@ namespace osu.Game.Overlays protected override bool StartHidden => true; + protected override string PopInSampleName => "UI/wave-pop-in"; + protected WaveOverlayContainer() { AddInternal(Waves = new WaveContainer From 22995c216deb26b16ad5e307b35cc5c69ff54260 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 15 Jan 2021 14:59:30 +0900 Subject: [PATCH 0477/1791] Use unique sample for edit button click (ButtonSystem) --- osu.Game/Screens/Menu/ButtonSystem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index f400b2114b..c6774127c1 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -129,7 +129,7 @@ namespace osu.Game.Screens.Menu buttonsPlay.ForEach(b => b.VisibleState = ButtonSystemState.Play); buttonsTopLevel.Add(new Button(@"play", @"button-play-select", OsuIcon.Logo, new Color4(102, 68, 204, 255), () => State = ButtonSystemState.Play, WEDGE_WIDTH, Key.P)); - buttonsTopLevel.Add(new Button(@"edit", @"button-generic-select", OsuIcon.EditCircle, new Color4(238, 170, 0, 255), () => OnEdit?.Invoke(), 0, Key.E)); + buttonsTopLevel.Add(new Button(@"edit", @"button-edit-select", OsuIcon.EditCircle, new Color4(238, 170, 0, 255), () => OnEdit?.Invoke(), 0, Key.E)); buttonsTopLevel.Add(new Button(@"browse", @"button-direct-select", OsuIcon.ChevronDownCircle, new Color4(165, 204, 0, 255), () => OnBeatmapListing?.Invoke(), 0, Key.D)); if (host.CanExit) From 73ab1b2b21f2eaaf40884d6d674d22c08031bfd7 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 15 Jan 2021 15:01:22 +0900 Subject: [PATCH 0478/1791] Add pitch randomisation to HoverSounds on-hover sample playback --- osu.Game/Graphics/UserInterface/HoverSounds.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/HoverSounds.cs b/osu.Game/Graphics/UserInterface/HoverSounds.cs index a1d06711db..21aae1b861 100644 --- a/osu.Game/Graphics/UserInterface/HoverSounds.cs +++ b/osu.Game/Graphics/UserInterface/HoverSounds.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Game.Configuration; +using osu.Framework.Utils; namespace osu.Game.Graphics.UserInterface { @@ -49,9 +50,11 @@ namespace osu.Game.Graphics.UserInterface { bool enoughTimePassedSinceLastPlayback = !lastPlaybackTime.Value.HasValue || Time.Current - lastPlaybackTime.Value >= HoverDebounceTime; - if (enoughTimePassedSinceLastPlayback) + if (enoughTimePassedSinceLastPlayback && sampleHover != null) { - sampleHover?.Play(); + sampleHover.Frequency.Value = 0.96 + RNG.NextDouble(0.08); + sampleHover.Play(); + lastPlaybackTime.Value = Time.Current; } From 4e2ab0bad2c1aed6c9abc75250d1160ae87218f4 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 15 Jan 2021 15:02:17 +0900 Subject: [PATCH 0479/1791] Use a separate sample set for Toolbar buttons --- osu.Game/Graphics/UserInterface/HoverSounds.cs | 5 ++++- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/HoverSounds.cs b/osu.Game/Graphics/UserInterface/HoverSounds.cs index a1d06711db..4a79d1fbec 100644 --- a/osu.Game/Graphics/UserInterface/HoverSounds.cs +++ b/osu.Game/Graphics/UserInterface/HoverSounds.cs @@ -68,6 +68,9 @@ namespace osu.Game.Graphics.UserInterface Normal, [Description("-softer")] - Soft + Soft, + + [Description("-toolbar")] + Toolbar } } diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index 49b9c62d85..83f2bdf6cb 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -77,7 +77,7 @@ namespace osu.Game.Overlays.Toolbar private KeyBindingStore keyBindings { get; set; } protected ToolbarButton() - : base(HoverSampleSet.Loud) + : base(HoverSampleSet.Toolbar) { Width = Toolbar.HEIGHT; RelativeSizeAxes = Axes.Y; From bc7f4a4f881b04feb4ac1c23b2d1fc5aba1fada5 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 15 Jan 2021 20:16:22 +0900 Subject: [PATCH 0480/1791] Use a single sample for CarouselHeader on-hover with randomised pitch instead of multiple samples --- osu.Game/Screens/Select/Carousel/CarouselHeader.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselHeader.cs b/osu.Game/Screens/Select/Carousel/CarouselHeader.cs index f1120f55a6..4f53a6e202 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselHeader.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselHeader.cs @@ -57,7 +57,7 @@ namespace osu.Game.Screens.Select.Carousel [BackgroundDependencyLoader] private void load(AudioManager audio, OsuColour colours) { - sampleHover = audio.Samples.Get($@"SongSelect/song-ping-variation-{RNG.Next(1, 5)}"); + sampleHover = audio.Samples.Get("SongSelect/song-ping"); hoverLayer.Colour = colours.Blue.Opacity(0.1f); } @@ -99,7 +99,11 @@ namespace osu.Game.Screens.Select.Carousel protected override bool OnHover(HoverEvent e) { - sampleHover?.Play(); + if (sampleHover != null) + { + sampleHover.Frequency.Value = 0.90 + RNG.NextDouble(0.2); + sampleHover.Play(); + } hoverLayer.FadeIn(100, Easing.OutQuint); return base.OnHover(e); From 625eb78a118566dc72161da36c9b5997d10309e4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 10 Feb 2021 17:59:52 +0900 Subject: [PATCH 0481/1791] Simplify with an early exit for null sample --- osu.Game/Graphics/UserInterface/HoverSounds.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/HoverSounds.cs b/osu.Game/Graphics/UserInterface/HoverSounds.cs index 21aae1b861..5d6d0896fd 100644 --- a/osu.Game/Graphics/UserInterface/HoverSounds.cs +++ b/osu.Game/Graphics/UserInterface/HoverSounds.cs @@ -48,9 +48,12 @@ namespace osu.Game.Graphics.UserInterface protected override bool OnHover(HoverEvent e) { + if (sampleHover == null) + return false; + bool enoughTimePassedSinceLastPlayback = !lastPlaybackTime.Value.HasValue || Time.Current - lastPlaybackTime.Value >= HoverDebounceTime; - if (enoughTimePassedSinceLastPlayback && sampleHover != null) + if (enoughTimePassedSinceLastPlayback) { sampleHover.Frequency.Value = 0.96 + RNG.NextDouble(0.08); sampleHover.Play(); @@ -58,7 +61,7 @@ namespace osu.Game.Graphics.UserInterface lastPlaybackTime.Value = Time.Current; } - return base.OnHover(e); + return false; } } From 996f1098f6e7054e97150421ead8188147f5aa09 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Wed, 10 Feb 2021 18:14:32 +0900 Subject: [PATCH 0482/1791] Use alternate sample on the downbeat while hovering OsuLogo --- osu.Game/Screens/Menu/OsuLogo.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index 68d23e1a32..1d0af30275 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -45,6 +45,7 @@ namespace osu.Game.Screens.Menu private SampleChannel sampleClick; private SampleChannel sampleBeat; + private SampleChannel sampleDownbeat; private readonly Container colourAndTriangles; private readonly Triangles triangles; @@ -259,6 +260,7 @@ namespace osu.Game.Screens.Menu { sampleClick = audio.Samples.Get(@"Menu/osu-logo-select"); sampleBeat = audio.Samples.Get(@"Menu/osu-logo-heartbeat"); + sampleDownbeat = audio.Samples.Get(@"Menu/osu-logo-downbeat"); logo.Texture = textures.Get(@"Menu/logo"); ripple.Texture = textures.Get(@"Menu/logo"); @@ -281,7 +283,15 @@ namespace osu.Game.Screens.Menu if (beatIndex < 0) return; if (IsHovered) - this.Delay(early_activation).Schedule(() => sampleBeat.Play()); + { + this.Delay(early_activation).Schedule(() => + { + if (beatIndex % (int)timingPoint.TimeSignature == 0) + sampleDownbeat.Play(); + else + sampleBeat.Play(); + }); + } logoBeatContainer .ScaleTo(1 - 0.02f * amplitudeAdjust, early_activation, Easing.Out).Then() From cf06684ad121d58548d6cf0b9e87a439167dbc57 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 10 Feb 2021 18:38:31 +0900 Subject: [PATCH 0483/1791] Judge heads as slider ticks instead --- .../Judgements/SliderTickJudgement.cs | 12 ++++++++++++ .../Objects/Drawables/DrawableSliderHead.cs | 2 +- osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs | 4 ++-- osu.Game.Rulesets.Osu/Objects/SliderTick.cs | 5 ----- 4 files changed, 15 insertions(+), 8 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Judgements/SliderTickJudgement.cs diff --git a/osu.Game.Rulesets.Osu/Judgements/SliderTickJudgement.cs b/osu.Game.Rulesets.Osu/Judgements/SliderTickJudgement.cs new file mode 100644 index 0000000000..a088696784 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Judgements/SliderTickJudgement.cs @@ -0,0 +1,12 @@ +// 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.Scoring; + +namespace osu.Game.Rulesets.Osu.Judgements +{ + public class SliderTickJudgement : OsuJudgement + { + public override HitResult MaxResult => HitResult.LargeTickHit; + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs index 87cfa47091..c3759b6a34 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs @@ -91,7 +91,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables // If not judged as a normal hitcircle, only track whether a hit has occurred (via IgnoreHit) rather than a scorable hit result. var result = base.ResultFor(timeOffset); - return result.IsHit() ? HitResult.IgnoreHit : HitResult.IgnoreMiss; + return result.IsHit() ? HitResult.LargeTickHit : HitResult.LargeTickMiss; } public Action OnShake; diff --git a/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs index 28e57567cb..5672283230 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderHeadCircle.cs @@ -10,10 +10,10 @@ namespace osu.Game.Rulesets.Osu.Objects { /// /// Whether to treat this as a normal for judgement purposes. - /// If false, judgement will be ignored. + /// If false, this will be judged as a instead. /// public bool JudgeAsNormalHitCircle = true; - public override Judgement CreateJudgement() => JudgeAsNormalHitCircle ? base.CreateJudgement() : new OsuIgnoreJudgement(); + public override Judgement CreateJudgement() => JudgeAsNormalHitCircle ? base.CreateJudgement() : new SliderTickJudgement(); } } diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs index a427ee1955..725dbe81fb 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs @@ -33,10 +33,5 @@ namespace osu.Game.Rulesets.Osu.Objects protected override HitWindows CreateHitWindows() => HitWindows.Empty; public override Judgement CreateJudgement() => new SliderTickJudgement(); - - public class SliderTickJudgement : OsuJudgement - { - public override HitResult MaxResult => HitResult.LargeTickHit; - } } } From 6730c4c58b22ce2e753ac0f4b7055b5b87f62cde Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 10 Feb 2021 18:41:28 +0900 Subject: [PATCH 0484/1791] Apply review comments (user explanations + property names) --- osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs | 26 ++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs index 863dc05216..642da87693 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs @@ -32,27 +32,27 @@ namespace osu.Game.Rulesets.Osu.Mods public override ModType Type => ModType.Conversion; - [SettingSource("Disable slider head judgement", "Scores sliders proportionally to the number of ticks hit.")] - public Bindable DisableSliderHeadJudgement { get; } = new BindableBool(true); + [SettingSource("No slider head accuracy requirement", "Scores sliders proportionally to the number of ticks hit.")] + public Bindable NoSliderHeadAccuracy { get; } = new BindableBool(true); - [SettingSource("Disable slider head tracking", "Pins slider heads at their starting position, regardless of time.")] - public Bindable DisableSliderHeadTracking { get; } = new BindableBool(true); + [SettingSource("No slider head movement", "Pins slider heads at their starting position, regardless of time.")] + public Bindable NoSliderHeadMovement { get; } = new BindableBool(true); - [SettingSource("Disable note lock lenience", "Applies note lock to the full hit window.")] - public Bindable DisableLenientNoteLock { get; } = new BindableBool(true); + [SettingSource("Apply classic note lock", "Applies note lock to the full hit window.")] + public Bindable ClassicNoteLock { get; } = new BindableBool(true); - [SettingSource("Disable exact slider follow circle tracking", "Makes the slider follow circle track its final size at all times.")] - public Bindable DisableExactFollowCircleTracking { get; } = new BindableBool(true); + [SettingSource("Use fixed slider follow circle hit area", "Makes the slider follow circle track its final size at all times.")] + public Bindable FixedFollowCircleHitArea { get; } = new BindableBool(true); public void ApplyToHitObject(HitObject hitObject) { switch (hitObject) { case Slider slider: - slider.IgnoreJudgement = !DisableSliderHeadJudgement.Value; + slider.IgnoreJudgement = !NoSliderHeadAccuracy.Value; foreach (var head in slider.NestedHitObjects.OfType()) - head.JudgeAsNormalHitCircle = !DisableSliderHeadJudgement.Value; + head.JudgeAsNormalHitCircle = !NoSliderHeadAccuracy.Value; break; } @@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Osu.Mods { var osuRuleset = (DrawableOsuRuleset)drawableRuleset; - if (!DisableLenientNoteLock.Value) + if (!ClassicNoteLock.Value) osuRuleset.Playfield.HitPolicy = new ObjectOrderedHitPolicy(); } @@ -73,11 +73,11 @@ namespace osu.Game.Rulesets.Osu.Mods switch (obj) { case DrawableSlider slider: - slider.Ball.TrackVisualSize = !DisableExactFollowCircleTracking.Value; + slider.Ball.TrackVisualSize = !FixedFollowCircleHitArea.Value; break; case DrawableSliderHead head: - head.TrackFollowCircle = !DisableSliderHeadTracking.Value; + head.TrackFollowCircle = !NoSliderHeadMovement.Value; break; } } From 18a29dcb9664075e4fd1dadfc57157cbcc2fc21a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 10 Feb 2021 18:42:13 +0900 Subject: [PATCH 0485/1791] Rename bindable member, reorder binds --- osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs index f9b8ffca7b..b9cd176c63 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default [Resolved(CanBeNull = true)] private OsuRulesetConfigManager config { get; set; } - private readonly Bindable snakingOut = new Bindable(); + private readonly Bindable configSnakingOut = new Bindable(); [BackgroundDependencyLoader] private void load(ISkinSource skin, DrawableHitObject drawableObject) @@ -37,9 +37,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default accentColour = drawableObject.AccentColour.GetBoundCopy(); accentColour.BindValueChanged(accent => updateAccentColour(skin, accent.NewValue), true); - SnakingOut.BindTo(snakingOut); config?.BindWith(OsuRulesetSetting.SnakingInSliders, SnakingIn); - config?.BindWith(OsuRulesetSetting.SnakingOutSliders, snakingOut); + config?.BindWith(OsuRulesetSetting.SnakingOutSliders, configSnakingOut); + + SnakingOut.BindTo(configSnakingOut); BorderSize = skin.GetConfig(OsuSkinConfiguration.SliderBorderSize)?.Value ?? 1; BorderColour = skin.GetConfig(OsuSkinColour.SliderBorder)?.Value ?? Color4.White; @@ -56,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default if (!drawableSlider.HeadCircle.TrackFollowCircle) { // When not tracking the follow circle, force the path to not snake out as it looks better that way. - SnakingOut.UnbindFrom(snakingOut); + SnakingOut.UnbindFrom(configSnakingOut); SnakingOut.Value = false; } } From 9519b7f7c16e234d119ecaf65e3ff019ad84c400 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 10 Feb 2021 18:43:14 +0900 Subject: [PATCH 0486/1791] Adjust comment --- osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs index b9cd176c63..4dd7b2d69c 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/PlaySliderBody.cs @@ -54,9 +54,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default if (drawableSlider.HitObject == null) return; + // When not tracking the follow circle, unbind from the config and forcefully disable snaking out - it looks better that way. if (!drawableSlider.HeadCircle.TrackFollowCircle) { - // When not tracking the follow circle, force the path to not snake out as it looks better that way. SnakingOut.UnbindFrom(configSnakingOut); SnakingOut.Value = false; } From 2fcc4213e16f8a9ebc33d91474fe3c0a632cf36c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 10 Feb 2021 18:46:26 +0900 Subject: [PATCH 0487/1791] Rename IgnoreJudgement -> OnlyJudgeNestedObjects --- osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 4 ++-- osu.Game.Rulesets.Osu/Objects/Slider.cs | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs index 642da87693..8e533854c0 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Osu.Mods switch (hitObject) { case Slider slider: - slider.IgnoreJudgement = !NoSliderHeadAccuracy.Value; + slider.OnlyJudgeNestedObjects = !NoSliderHeadAccuracy.Value; foreach (var head in slider.NestedHitObjects.OfType()) head.JudgeAsNormalHitCircle = !NoSliderHeadAccuracy.Value; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index e607163b3e..13057d7a9a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public SliderBall Ball { get; private set; } public SkinnableDrawable Body { get; private set; } - public override bool DisplayResult => !HitObject.IgnoreJudgement; + public override bool DisplayResult => !HitObject.OnlyJudgeNestedObjects; private PlaySliderBody sliderBody => Body.Drawable as PlaySliderBody; @@ -250,7 +250,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (userTriggered || Time.Current < HitObject.EndTime) return; - if (HitObject.IgnoreJudgement) + if (HitObject.OnlyJudgeNestedObjects) { ApplyResult(r => r.Type = NestedHitObjects.Any(h => h.Result.IsHit) ? r.Judgement.MaxResult : r.Judgement.MinResult); return; diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 01694a838b..332163454a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -115,10 +115,10 @@ namespace osu.Game.Rulesets.Osu.Objects public double TickDistanceMultiplier = 1; /// - /// Whether this 's judgement should be ignored. - /// If false, this will be judged proportionally to the number of ticks hit. + /// Whether this 's judgement is fully handled by its nested s. + /// If false, this will be judged proportionally to the number of nested s hit. /// - public bool IgnoreJudgement = true; + public bool OnlyJudgeNestedObjects = true; [JsonIgnore] public SliderHeadCircle HeadCircle { get; protected set; } @@ -239,7 +239,7 @@ namespace osu.Game.Rulesets.Osu.Objects HeadCircle.Samples = this.GetNodeSamples(0); } - public override Judgement CreateJudgement() => IgnoreJudgement ? new OsuIgnoreJudgement() : new OsuJudgement(); + public override Judgement CreateJudgement() => OnlyJudgeNestedObjects ? new OsuIgnoreJudgement() : new OsuJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; } From a16f4cee3a0d44bbcdf69adb4949f2d3a425efd1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 10 Feb 2021 18:52:39 +0900 Subject: [PATCH 0488/1791] Adjust DrawableSlider comment --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 13057d7a9a..921139c4e9 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -250,13 +250,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (userTriggered || Time.Current < HitObject.EndTime) return; + // If only the nested hitobjects are judged, then the slider's own judgement is ignored for scoring purposes. + // But the slider needs to still be judged with a reasonable hit/miss result for visual purposes (hit/miss transforms, etc). if (HitObject.OnlyJudgeNestedObjects) { ApplyResult(r => r.Type = NestedHitObjects.Any(h => h.Result.IsHit) ? r.Judgement.MaxResult : r.Judgement.MinResult); return; } - // If not ignoring judgement, score proportionally based on the number of ticks hit, counting the head circle as a tick. + // Otherwise, if this slider is also needs to be judged, apply judgement proportionally to the number of nested hitobjects hit. This is the classic osu!stable scoring. ApplyResult(r => { int totalTicks = NestedHitObjects.Count; From 6bf40170db22f31cea0c040824a218794d236718 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 10 Feb 2021 18:53:23 +0900 Subject: [PATCH 0489/1791] Rename SliderBall flag --- osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs | 2 +- osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs index 8e533854c0..17b0b18b52 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Osu.Mods switch (obj) { case DrawableSlider slider: - slider.Ball.TrackVisualSize = !FixedFollowCircleHitArea.Value; + slider.Ball.InputTracksVisualSize = !FixedFollowCircleHitArea.Value; break; case DrawableSliderHead head: diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs index da3debbd42..82b677e12c 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default /// Whether to track accurately to the visual size of this . /// If false, tracking will be performed at the final scale at all times. /// - public bool TrackVisualSize = true; + public bool InputTracksVisualSize = true; private readonly Drawable followCircle; private readonly DrawableSlider drawableSlider; @@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default tracking = value; - if (TrackVisualSize) + if (InputTracksVisualSize) followCircle.ScaleTo(tracking ? 2.4f : 1f, 300, Easing.OutQuint); else { From 0dcdad98397453b439cab67421e29ecb3ca13f00 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 10 Feb 2021 19:04:23 +0900 Subject: [PATCH 0490/1791] Adjust comment for DrawableSliderHead --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs index c3759b6a34..01c0d988ee 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs @@ -89,7 +89,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (HitObject.JudgeAsNormalHitCircle) return base.ResultFor(timeOffset); - // If not judged as a normal hitcircle, only track whether a hit has occurred (via IgnoreHit) rather than a scorable hit result. + // If not judged as a normal hitcircle, judge as a slider tick instead. This is the classic osu!stable scoring. var result = base.ResultFor(timeOffset); return result.IsHit() ? HitResult.LargeTickHit : HitResult.LargeTickMiss; } From 393cd6c74a354919dc0b410d0bea38b8701bd032 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 10 Feb 2021 19:39:47 +0900 Subject: [PATCH 0491/1791] Add helper class for tracking changes to mod settings --- .../Configuration/SettingSourceAttribute.cs | 27 ++++++++++++++++ .../Screens/Select/Details/AdvancedStats.cs | 31 ++++++------------- 2 files changed, 37 insertions(+), 21 deletions(-) diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index 50069be4b2..00c322065a 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -9,6 +9,7 @@ using JetBrains.Annotations; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Overlays.Settings; +using osu.Game.Rulesets.Mods; namespace osu.Game.Configuration { @@ -140,4 +141,30 @@ namespace osu.Game.Configuration return orderedRelative.Concat(unordered); } } + + public class ModSettingChangeTracker : IDisposable + { + public Action SettingChanged; + + private readonly List references = new List(); + + public ModSettingChangeTracker(IEnumerable mods) + { + foreach (var mod in mods) + { + foreach (var setting in mod.CreateSettingsControls().OfType()) + { + setting.SettingChanged += () => SettingChanged?.Invoke(mod); + references.Add(setting); + } + } + } + + public void Dispose() + { + foreach (var r in references) + r.Dispose(); + references.Clear(); + } + } } diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 44d908fc46..7966ec4240 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -18,7 +18,6 @@ using System.Threading; using osu.Framework.Threading; using osu.Framework.Utils; using osu.Game.Configuration; -using osu.Game.Overlays.Settings; using osu.Game.Rulesets; namespace osu.Game.Screens.Select.Details @@ -83,32 +82,22 @@ namespace osu.Game.Screens.Select.Details mods.BindValueChanged(modsChanged, true); } - private readonly List references = new List(); + private ModSettingChangeTracker settingChangeTracker; + private ScheduledDelegate debouncedStatisticsUpdate; private void modsChanged(ValueChangedEvent> mods) { - // TODO: find a more permanent solution for this if/when it is needed in other components. - // this is generating drawables for the only purpose of storing bindable references. - foreach (var r in references) - r.Dispose(); + settingChangeTracker?.Dispose(); - references.Clear(); - - ScheduledDelegate debounce = null; - - foreach (var mod in mods.NewValue.OfType()) + settingChangeTracker = new ModSettingChangeTracker(mods.NewValue); + settingChangeTracker.SettingChanged += m => { - foreach (var setting in mod.CreateSettingsControls().OfType()) - { - setting.SettingChanged += () => - { - debounce?.Cancel(); - debounce = Scheduler.AddDelayed(updateStatistics, 100); - }; + if (!(m is IApplicableToDifficulty)) + return; - references.Add(setting); - } - } + debouncedStatisticsUpdate?.Cancel(); + debouncedStatisticsUpdate = Scheduler.AddDelayed(updateStatistics, 100); + }; updateStatistics(); } From 7827e991b2dda30e265c3fc210e3828698d9d00b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 10 Feb 2021 19:43:39 +0900 Subject: [PATCH 0492/1791] Also clear event on dispose --- osu.Game/Configuration/SettingSourceAttribute.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index 00c322065a..04b8f8e962 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -162,6 +162,8 @@ namespace osu.Game.Configuration public void Dispose() { + SettingChanged = null; + foreach (var r in references) r.Dispose(); references.Clear(); From 822c66033f0d9cb2c2dbee0d138cdb67bc1fdd3c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 10 Feb 2021 19:56:59 +0900 Subject: [PATCH 0493/1791] Add local-user freemod configuration --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 9 +++++++- .../OnlinePlay/FreeModSelectOverlay.cs | 5 +++-- .../Multiplayer/MultiplayerMatchSongSelect.cs | 2 +- .../Multiplayer/MultiplayerMatchSubScreen.cs | 21 +++++++++++++++---- 4 files changed, 29 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 93fe693937..488c0659f4 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -51,6 +51,11 @@ namespace osu.Game.Overlays.Mods /// protected virtual bool Stacked => true; + /// + /// Whether configurable s can be configured by the local user. + /// + protected virtual bool AllowConfiguration => true; + [NotNull] private Func isValidMod = m => true; @@ -300,6 +305,7 @@ namespace osu.Game.Overlays.Mods Text = "Customisation", Action = () => ModSettingsContainer.ToggleVisibility(), Enabled = { Value = false }, + Alpha = AllowConfiguration ? 1 : 0, Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, }, @@ -509,7 +515,8 @@ namespace osu.Game.Overlays.Mods OnModSelected(selectedMod); - if (selectedMod.RequiresConfiguration) ModSettingsContainer.Show(); + if (selectedMod.RequiresConfiguration && AllowConfiguration) + ModSettingsContainer.Show(); } else { diff --git a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs index 7bc226bb3f..ab7be13479 100644 --- a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs @@ -20,17 +20,18 @@ namespace osu.Game.Screens.OnlinePlay { protected override bool Stacked => false; + protected override bool AllowConfiguration => false; + public new Func IsValidMod { get => base.IsValidMod; - set => base.IsValidMod = m => m.HasImplementation && !m.RequiresConfiguration && !(m is ModAutoplay) && value(m); + set => base.IsValidMod = m => m.HasImplementation && !(m is ModAutoplay) && value(m); } public FreeModSelectOverlay() { IsValidMod = m => true; - CustomiseButton.Alpha = 0; MultiplierSection.Alpha = 0; DeselectAllButton.Alpha = 0; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index 84e8849726..f17d97c3fd 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -59,6 +59,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea(); - protected override bool IsValidFreeMod(Mod mod) => base.IsValidFreeMod(mod) && !(mod is ModTimeRamp) && !(mod is ModRateAdjust) && !mod.RequiresConfiguration; + protected override bool IsValidFreeMod(Mod mod) => base.IsValidFreeMod(mod) && !(mod is ModTimeRamp) && !(mod is ModRateAdjust); } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 5f2f1366f7..7edba3231c 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -12,6 +12,8 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Screens; +using osu.Framework.Threading; +using osu.Game.Configuration; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; using osu.Game.Overlays.Mods; @@ -314,12 +316,27 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer } } + private ModSettingChangeTracker modSettingChangeTracker; + private ScheduledDelegate debouncedModSettingsUpdate; + private void onUserModsChanged(ValueChangedEvent> mods) { + modSettingChangeTracker?.Dispose(); + if (client.Room == null) return; client.ChangeUserMods(mods.NewValue); + + modSettingChangeTracker = new ModSettingChangeTracker(mods.NewValue); + modSettingChangeTracker.SettingChanged += onModSettingsChanged; + } + + private void onModSettingsChanged(Mod mod) + { + // Debounce changes to mod settings so as to not thrash the network. + debouncedModSettingsUpdate?.Cancel(); + debouncedModSettingsUpdate = Scheduler.AddDelayed(() => client.ChangeUserMods(UserMods.Value), 500); } private void updateBeatmapAvailability(ValueChangedEvent availability) @@ -389,10 +406,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer private class UserModSelectOverlay : LocalPlayerModSelectOverlay { - public UserModSelectOverlay() - { - CustomiseButton.Alpha = 0; - } } } } From 4a405bb8598a8dcdbc6772eaac801c9fa4907956 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 10 Feb 2021 20:04:16 +0900 Subject: [PATCH 0494/1791] Split ModSettingChangeTracker into separate file --- .../Configuration/ModSettingChangeTracker.cs | 39 +++++++++++++++++++ .../Configuration/SettingSourceAttribute.cs | 29 -------------- 2 files changed, 39 insertions(+), 29 deletions(-) create mode 100644 osu.Game/Configuration/ModSettingChangeTracker.cs diff --git a/osu.Game/Configuration/ModSettingChangeTracker.cs b/osu.Game/Configuration/ModSettingChangeTracker.cs new file mode 100644 index 0000000000..f702a2fc22 --- /dev/null +++ b/osu.Game/Configuration/ModSettingChangeTracker.cs @@ -0,0 +1,39 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Game.Overlays.Settings; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Configuration +{ + public class ModSettingChangeTracker : IDisposable + { + public Action SettingChanged; + + private readonly List references = new List(); + + public ModSettingChangeTracker(IEnumerable mods) + { + foreach (var mod in mods) + { + foreach (var setting in mod.CreateSettingsControls().OfType()) + { + setting.SettingChanged += () => SettingChanged?.Invoke(mod); + references.Add(setting); + } + } + } + + public void Dispose() + { + SettingChanged = null; + + foreach (var r in references) + r.Dispose(); + references.Clear(); + } + } +} diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index 04b8f8e962..50069be4b2 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -9,7 +9,6 @@ using JetBrains.Annotations; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Overlays.Settings; -using osu.Game.Rulesets.Mods; namespace osu.Game.Configuration { @@ -141,32 +140,4 @@ namespace osu.Game.Configuration return orderedRelative.Concat(unordered); } } - - public class ModSettingChangeTracker : IDisposable - { - public Action SettingChanged; - - private readonly List references = new List(); - - public ModSettingChangeTracker(IEnumerable mods) - { - foreach (var mod in mods) - { - foreach (var setting in mod.CreateSettingsControls().OfType()) - { - setting.SettingChanged += () => SettingChanged?.Invoke(mod); - references.Add(setting); - } - } - } - - public void Dispose() - { - SettingChanged = null; - - foreach (var r in references) - r.Dispose(); - references.Clear(); - } - } } From 6fff7c39daca9c27c39511b84223e7e98172325b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 10 Feb 2021 20:09:45 +0900 Subject: [PATCH 0495/1791] Ensure tracker is disposed --- .../Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 2 ++ osu.Game/Screens/Select/Details/AdvancedStats.cs | 1 + 2 files changed, 3 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 7edba3231c..59418cb348 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -402,6 +402,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer if (client != null) client.LoadRequested -= onLoadRequested; + + modSettingChangeTracker?.Dispose(); } private class UserModSelectOverlay : LocalPlayerModSelectOverlay diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 7966ec4240..4a03c5e614 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -162,6 +162,7 @@ namespace osu.Game.Screens.Select.Details protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); + settingChangeTracker?.Dispose(); starDifficultyCancellationSource?.Cancel(); } From 169acb42de35e88c8755a5140808cb3563cfb97d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 10 Feb 2021 20:11:36 +0900 Subject: [PATCH 0496/1791] Xmldoc + cleanup --- .../Configuration/ModSettingChangeTracker.cs | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/osu.Game/Configuration/ModSettingChangeTracker.cs b/osu.Game/Configuration/ModSettingChangeTracker.cs index f702a2fc22..e2ade7dc6a 100644 --- a/osu.Game/Configuration/ModSettingChangeTracker.cs +++ b/osu.Game/Configuration/ModSettingChangeTracker.cs @@ -9,12 +9,25 @@ using osu.Game.Rulesets.Mods; namespace osu.Game.Configuration { + /// + /// A helper class for tracking changes to the settings of a set of s. + /// + /// + /// Ensure to dispose when usage is finished. + /// public class ModSettingChangeTracker : IDisposable { + /// + /// Notifies that the setting of a has changed. + /// public Action SettingChanged; - private readonly List references = new List(); + private readonly List settings = new List(); + /// + /// Creates a new for a set of s. + /// + /// The set of s whose settings need to be tracked. public ModSettingChangeTracker(IEnumerable mods) { foreach (var mod in mods) @@ -22,7 +35,7 @@ namespace osu.Game.Configuration foreach (var setting in mod.CreateSettingsControls().OfType()) { setting.SettingChanged += () => SettingChanged?.Invoke(mod); - references.Add(setting); + settings.Add(setting); } } } @@ -31,9 +44,9 @@ namespace osu.Game.Configuration { SettingChanged = null; - foreach (var r in references) + foreach (var r in settings) r.Dispose(); - references.Clear(); + settings.Clear(); } } } From 86682cdb3480e711d2ecdeab04926c7488a17046 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 10 Feb 2021 20:16:26 +0900 Subject: [PATCH 0497/1791] Add client/room null check --- .../OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 59418cb348..b7adb71e2f 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -336,7 +336,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { // Debounce changes to mod settings so as to not thrash the network. debouncedModSettingsUpdate?.Cancel(); - debouncedModSettingsUpdate = Scheduler.AddDelayed(() => client.ChangeUserMods(UserMods.Value), 500); + debouncedModSettingsUpdate = Scheduler.AddDelayed(() => + { + if (client.Room == null) + return; + + client.ChangeUserMods(UserMods.Value); + }, 500); } private void updateBeatmapAvailability(ValueChangedEvent availability) From c458c4cfaee1d9a830e8ddafa512d30d35b5b4cf Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 10 Feb 2021 20:27:47 +0900 Subject: [PATCH 0498/1791] Fix unintended changes due to renaming or otherwise --- osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs | 2 +- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs index 17b0b18b52..5470d0fcb4 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs @@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Osu.Mods { var osuRuleset = (DrawableOsuRuleset)drawableRuleset; - if (!ClassicNoteLock.Value) + if (ClassicNoteLock.Value) osuRuleset.Playfield.HitPolicy = new ObjectOrderedHitPolicy(); } diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 189ef2d76c..b1069149f3 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Osu.UI approachCircles = new ProxyContainer { RelativeSizeAxes = Axes.Both }, }; - HitPolicy = new ObjectOrderedHitPolicy(); + HitPolicy = new StartTimeOrderedHitPolicy(); var hitWindows = new OsuHitWindows(); foreach (var result in Enum.GetValues(typeof(HitResult)).OfType().Where(r => r > HitResult.None && hitWindows.IsHitResultAllowed(r))) From 321ca43b61a2a47857e04a117d622da6af385492 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 10 Feb 2021 20:28:00 +0900 Subject: [PATCH 0499/1791] Update test --- .../TestSceneObjectOrderedHitPolicy.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs index 039a4f142f..77a68b714b 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs @@ -248,7 +248,7 @@ namespace osu.Game.Rulesets.Osu.Tests addJudgementAssert(hitObjects[0], HitResult.Miss); addJudgementAssert(hitObjects[1], HitResult.Great); - addJudgementAssert("slider head", () => ((Slider)hitObjects[1]).HeadCircle, HitResult.IgnoreHit); + addJudgementAssert("slider head", () => ((Slider)hitObjects[1]).HeadCircle, HitResult.LargeTickHit); addJudgementAssert("slider tick", () => ((Slider)hitObjects[1]).NestedHitObjects[1] as SliderTick, HitResult.LargeTickHit); } @@ -291,7 +291,7 @@ namespace osu.Game.Rulesets.Osu.Tests addJudgementAssert(hitObjects[0], HitResult.Great); addJudgementAssert(hitObjects[1], HitResult.Great); - addJudgementAssert("slider head", () => ((Slider)hitObjects[1]).HeadCircle, HitResult.IgnoreHit); + addJudgementAssert("slider head", () => ((Slider)hitObjects[1]).HeadCircle, HitResult.LargeTickHit); addJudgementAssert("slider tick", () => ((Slider)hitObjects[1]).NestedHitObjects[1] as SliderTick, HitResult.LargeTickHit); } From 4a391ce03d84adf76adb4214993c28e15cdebdbb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 10 Feb 2021 21:24:41 +0900 Subject: [PATCH 0500/1791] Fix div-by-0 when 0 ticks are hit --- .../Objects/Drawables/DrawableSlider.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 921139c4e9..d35da64ad5 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -263,16 +263,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { int totalTicks = NestedHitObjects.Count; int hitTicks = NestedHitObjects.Count(h => h.IsHit); - double hitFraction = (double)totalTicks / hitTicks; if (hitTicks == totalTicks) r.Type = HitResult.Great; - else if (hitFraction >= 0.5) - r.Type = HitResult.Ok; - else if (hitFraction > 0) - r.Type = HitResult.Meh; - else + else if (hitTicks == 0) r.Type = HitResult.Miss; + else + { + double hitFraction = (double)totalTicks / hitTicks; + + if (hitFraction >= 0.5) + r.Type = HitResult.Ok; + else if (hitFraction > 0) + r.Type = HitResult.Meh; + } }); } From 1d425b83224ff7ac765b9af6d16389e637f1d840 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 10 Feb 2021 21:25:31 +0900 Subject: [PATCH 0501/1791] Simplify case --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index d35da64ad5..847011850c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -271,11 +271,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables else { double hitFraction = (double)totalTicks / hitTicks; - - if (hitFraction >= 0.5) - r.Type = HitResult.Ok; - else if (hitFraction > 0) - r.Type = HitResult.Meh; + r.Type = hitFraction >= 0.5 ? HitResult.Ok : HitResult.Meh; } }); } From bd2486e5a04bda5fb2a0ff32df6d2dce8681f2f3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 10 Feb 2021 21:27:12 +0900 Subject: [PATCH 0502/1791] Fix grammatical error in comment --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 847011850c..253b9800a6 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -258,7 +258,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables return; } - // Otherwise, if this slider is also needs to be judged, apply judgement proportionally to the number of nested hitobjects hit. This is the classic osu!stable scoring. + // Otherwise, if this slider also needs to be judged, apply judgement proportionally to the number of nested hitobjects hit. This is the classic osu!stable scoring. ApplyResult(r => { int totalTicks = NestedHitObjects.Count; From 20a6405fd20a6cef1251eebfd7435928344679a6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 10 Feb 2021 22:06:19 +0900 Subject: [PATCH 0503/1791] Add explanatory comments + const --- .../Drawables/Connections/FollowPointConnection.cs | 6 ++++-- osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs | 14 ++++++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs index 40154ca84c..5541d0e790 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs @@ -110,8 +110,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections double startTime = start.GetEndTime(); double duration = end.StartTime - startTime; - // For now, adjust the pre-empt for approach rates > 10. - double preempt = PREEMPT * Math.Min(1, start.TimePreempt / 450); + // Preempt time can go below 800ms. Normally, this is achieved via the DT mod which uniformly speeds up all animations game wide regardless of AR. + // This uniform speedup is hard to match 1:1, however we can at least make AR>10 (via mods) feel good by extending the upper linear preempt function (see: OsuHitObject). + // Note that this doesn't exactly match the AR>10 visuals as they're classically known, but it feels good. + double preempt = PREEMPT * Math.Min(1, start.TimePreempt / OsuHitObject.PREEMPT_MIN); fadeOutTime = startTime + fraction * duration; fadeInTime = fadeOutTime - preempt; diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index 6d28a576a4..22b64af3df 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -26,6 +26,11 @@ namespace osu.Game.Rulesets.Osu.Objects /// internal const float BASE_SCORING_DISTANCE = 100; + /// + /// Minimum preempt time at AR=10. + /// + public const double PREEMPT_MIN = 450; + public double TimePreempt = 600; public double TimeFadeIn = 400; @@ -113,8 +118,13 @@ namespace osu.Game.Rulesets.Osu.Objects { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); - TimePreempt = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450); - TimeFadeIn = 400 * Math.Min(1, TimePreempt / 450); + TimePreempt = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1800, 1200, PREEMPT_MIN); + + // Preempt time can go below 450ms. Normally, this is achieved via the DT mod which uniformly speeds up all animations game wide regardless of AR. + // This uniform speedup is hard to match 1:1, however we can at least make AR>10 (via mods) feel good by extending the upper linear function above. + // Note that this doesn't exactly match the AR>10 visuals as they're classically known, but it feels good. + // This adjustment is necessary for AR>10, otherwise TimePreempt can become smaller leading to hitcircles not fully fading in. + TimeFadeIn = 400 * Math.Min(1, TimePreempt / PREEMPT_MIN); Scale = (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5) / 2; } From 5d1d6ec1cbeef3ff0cf17b9e04d556e01772de1a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 10 Feb 2021 22:09:24 +0900 Subject: [PATCH 0504/1791] Fix inverted calculation --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 253b9800a6..9122f347d0 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -270,7 +270,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables r.Type = HitResult.Miss; else { - double hitFraction = (double)totalTicks / hitTicks; + double hitFraction = (double)hitTicks / totalTicks; r.Type = hitFraction >= 0.5 ? HitResult.Ok : HitResult.Meh; } }); From 07b661e28c2d2267dbe1a431f1a75e1d60fc6620 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 10 Feb 2021 23:44:06 +0900 Subject: [PATCH 0505/1791] Add Messagepack support for serialising unknown bindable types --- .../TestAPIModMessagePackSerialization.cs | 27 +++++++++++++++++++ .../API/ModSettingsDictionaryFormatter.cs | 8 ++++++ 2 files changed, 35 insertions(+) diff --git a/osu.Game.Tests/Online/TestAPIModMessagePackSerialization.cs b/osu.Game.Tests/Online/TestAPIModMessagePackSerialization.cs index 4294f89397..74db477cfc 100644 --- a/osu.Game.Tests/Online/TestAPIModMessagePackSerialization.cs +++ b/osu.Game.Tests/Online/TestAPIModMessagePackSerialization.cs @@ -68,6 +68,16 @@ namespace osu.Game.Tests.Online Assert.That(converted.FinalRate.Value, Is.EqualTo(0.25)); } + [Test] + public void TestDeserialiseEnumMod() + { + var apiMod = new APIMod(new TestModEnum { TestSetting = { Value = TestEnum.Value2 } }); + + var deserialized = MessagePackSerializer.Deserialize(MessagePackSerializer.Serialize(apiMod)); + + Assert.That(deserialized.Settings, Contains.Key("test_setting").With.ContainValue(1)); + } + private class TestRuleset : Ruleset { public override IEnumerable GetModsFor(ModType type) => new Mod[] @@ -135,5 +145,22 @@ namespace osu.Game.Tests.Online Value = true }; } + + private class TestModEnum : Mod + { + public override string Name => "Test Mod"; + public override string Acronym => "TM"; + public override double ScoreMultiplier => 1; + + [SettingSource("Test")] + public Bindable TestSetting { get; } = new Bindable(); + } + + private enum TestEnum + { + Value1 = 0, + Value2 = 1, + Value3 = 2 + } } } diff --git a/osu.Game/Online/API/ModSettingsDictionaryFormatter.cs b/osu.Game/Online/API/ModSettingsDictionaryFormatter.cs index 99e87677fa..dd854acc32 100644 --- a/osu.Game/Online/API/ModSettingsDictionaryFormatter.cs +++ b/osu.Game/Online/API/ModSettingsDictionaryFormatter.cs @@ -3,6 +3,7 @@ using System.Buffers; using System.Collections.Generic; +using System.Diagnostics; using System.Text; using MessagePack; using MessagePack.Formatters; @@ -41,6 +42,13 @@ namespace osu.Game.Online.API primitiveFormatter.Serialize(ref writer, b.Value, options); break; + case IBindable u: + // A mod with unknown (e.g. enum) generic type. + var valueMethod = u.GetType().GetProperty(nameof(IBindable.Value)); + Debug.Assert(valueMethod != null); + primitiveFormatter.Serialize(ref writer, valueMethod.GetValue(u), options); + break; + default: // fall back for non-bindable cases. primitiveFormatter.Serialize(ref writer, kvp.Value, options); From 97e799a26bc22316442500aefc79fad3f9c6d646 Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Wed, 10 Feb 2021 18:10:31 +0100 Subject: [PATCH 0506/1791] add more MIME types to Android share intent filter --- 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 48b059b482..d3bb97973b 100644 --- a/osu.Android/OsuGameActivity.cs +++ b/osu.Android/OsuGameActivity.cs @@ -21,7 +21,7 @@ namespace osu.Android [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.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataMimeTypes = new[] { "application/x-osu-beatmap", "application/x-osu-skin" })] - [IntentFilter(new[] { Intent.ActionSend, Intent.ActionSendMultiple }, Categories = new[] { Intent.CategoryDefault }, DataMimeTypes = new[] { "application/x-osu-beatmap", "application/x-osu-skin", "application/zip", "application/octet-stream", "application/x-zip", "application/x-zip-compressed" })] + [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" })] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryBrowsable, Intent.CategoryDefault }, DataSchemes = new[] { "osu", "osump" })] public class OsuGameActivity : AndroidGameActivity { From fed2dea7353a380103772ba99edb4882a6639d73 Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Wed, 10 Feb 2021 18:13:59 +0100 Subject: [PATCH 0507/1791] remove unnecesary view intent filter --- osu.Android/OsuGameActivity.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Android/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs index d3bb97973b..ad929bbac3 100644 --- a/osu.Android/OsuGameActivity.cs +++ b/osu.Android/OsuGameActivity.cs @@ -20,7 +20,6 @@ namespace osu.Android [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false, LaunchMode = LaunchMode.SingleInstance)] [IntentFilter(new[] { Intent.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.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataMimeTypes = new[] { "application/x-osu-beatmap", "application/x-osu-skin" })] [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" })] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryBrowsable, Intent.CategoryDefault }, DataSchemes = new[] { "osu", "osump" })] public class OsuGameActivity : AndroidGameActivity From 63e6ec1c9fa3b0ecb12ecbd2f389edc0042de939 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 15 Jan 2021 20:06:55 +0900 Subject: [PATCH 0508/1791] Add audio feedback for OSD value changes --- osu.Game/Overlays/OSD/TrackedSettingToast.cs | 46 ++++++++++++++++++-- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/OSD/TrackedSettingToast.cs b/osu.Game/Overlays/OSD/TrackedSettingToast.cs index 8e8a99a0a7..c5a289a85c 100644 --- a/osu.Game/Overlays/OSD/TrackedSettingToast.cs +++ b/osu.Game/Overlays/OSD/TrackedSettingToast.cs @@ -3,6 +3,8 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Configuration.Tracking; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -19,6 +21,15 @@ namespace osu.Game.Overlays.OSD { private const int lights_bottom_margin = 40; + private readonly int optionCount; + private readonly int selectedOption = -1; + + private SampleChannel sampleOn; + private SampleChannel sampleOff; + private SampleChannel sampleChange; + + private bool playedSample; + public TrackedSettingToast(SettingDescription description) : base(description.Name, description.Value, description.Shortcut) { @@ -46,9 +57,6 @@ namespace osu.Game.Overlays.OSD } }; - int optionCount = 0; - int selectedOption = -1; - switch (description.RawValue) { case bool val: @@ -69,6 +77,38 @@ namespace osu.Game.Overlays.OSD optionLights.Add(new OptionLight { Glowing = i == selectedOption }); } + protected override void Update() + { + base.Update(); + + if (playedSample) return; + + if (optionCount == 1) + { + if (selectedOption == 0) + sampleOn?.Play(); + else + sampleOff?.Play(); + } + else + { + if (sampleChange == null) return; + + sampleChange.Frequency.Value = 1 + (double)selectedOption / (optionCount - 1) * 0.25f; + sampleChange.Play(); + } + + playedSample = true; + } + + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + sampleOn = audio.Samples.Get("UI/osd-on"); + sampleOff = audio.Samples.Get("UI/osd-off"); + sampleChange = audio.Samples.Get("UI/osd-change"); + } + private class OptionLight : Container { private Color4 glowingColour, idleColour; From c12c09ec4d497ad7e52a3c4b625e3793dc23d8a1 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 11 Feb 2021 09:57:14 +0900 Subject: [PATCH 0509/1791] Move logic to LoadComplete instead --- osu.Game/Overlays/OSD/TrackedSettingToast.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/OSD/TrackedSettingToast.cs b/osu.Game/Overlays/OSD/TrackedSettingToast.cs index c5a289a85c..d61180baa2 100644 --- a/osu.Game/Overlays/OSD/TrackedSettingToast.cs +++ b/osu.Game/Overlays/OSD/TrackedSettingToast.cs @@ -28,8 +28,6 @@ namespace osu.Game.Overlays.OSD private SampleChannel sampleOff; private SampleChannel sampleChange; - private bool playedSample; - public TrackedSettingToast(SettingDescription description) : base(description.Name, description.Value, description.Shortcut) { @@ -77,11 +75,9 @@ namespace osu.Game.Overlays.OSD optionLights.Add(new OptionLight { Glowing = i == selectedOption }); } - protected override void Update() + protected override void LoadComplete() { - base.Update(); - - if (playedSample) return; + base.LoadComplete(); if (optionCount == 1) { @@ -97,8 +93,6 @@ namespace osu.Game.Overlays.OSD sampleChange.Frequency.Value = 1 + (double)selectedOption / (optionCount - 1) * 0.25f; sampleChange.Play(); } - - playedSample = true; } [BackgroundDependencyLoader] From a1ae739a6235f72995ec460d93874eac2a504aa6 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 15 Jan 2021 15:21:50 +0900 Subject: [PATCH 0510/1791] Add support for Notification sounds --- .../Overlays/Notifications/Notification.cs | 24 ++++++++++++++++++- .../Notifications/NotificationSection.cs | 7 +++++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index 2dc6b39a92..86e409d0f6 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -3,6 +3,8 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; @@ -40,6 +42,11 @@ namespace osu.Game.Overlays.Notifications /// public virtual bool DisplayOnTop => true; + private SampleChannel samplePopIn; + private SampleChannel samplePopOut; + protected virtual string PopInSampleName => "UI/notification-pop-in"; + protected virtual string PopOutSampleName => "UI/overlay-pop-out"; // TODO: replace with a unique sample? + protected NotificationLight Light; private readonly CloseButton closeButton; protected Container IconContent; @@ -120,6 +127,13 @@ namespace osu.Game.Overlays.Notifications }); } + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + samplePopIn = audio.Samples.Get(PopInSampleName); + samplePopOut = audio.Samples.Get(PopOutSampleName); + } + protected override bool OnHover(HoverEvent e) { closeButton.FadeIn(75); @@ -143,6 +157,9 @@ namespace osu.Game.Overlays.Notifications protected override void LoadComplete() { base.LoadComplete(); + + samplePopIn?.Play(); + this.FadeInFromZero(200); NotificationContent.MoveToX(DrawSize.X); NotificationContent.MoveToX(0, 500, Easing.OutQuint); @@ -150,12 +167,17 @@ namespace osu.Game.Overlays.Notifications public bool WasClosed; - public virtual void Close() + public virtual void Close() => Close(true); + + public virtual void Close(bool playSound) { if (WasClosed) return; WasClosed = true; + if (playSound) + samplePopOut?.Play(); + Closed?.Invoke(); this.FadeOut(100); Expire(); diff --git a/osu.Game/Overlays/Notifications/NotificationSection.cs b/osu.Game/Overlays/Notifications/NotificationSection.cs index c2a958b65e..c8cee22370 100644 --- a/osu.Game/Overlays/Notifications/NotificationSection.cs +++ b/osu.Game/Overlays/Notifications/NotificationSection.cs @@ -109,7 +109,12 @@ namespace osu.Game.Overlays.Notifications private void clearAll() { - notifications.Children.ForEach(c => c.Close()); + bool playSound = true; + notifications.Children.ForEach(c => + { + c.Close(playSound); + playSound = false; + }); } protected override void Update() From 2ee634d173647a97fa9e5ef40bfe1e262920162b Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 21 Jan 2021 18:42:53 +0900 Subject: [PATCH 0511/1791] Create subclass for "Error" notifications to allow them to have a unique pop-in sound --- .../TestSceneNotificationOverlay.cs | 20 ++++++++++++++++++- osu.Game/OsuGame.cs | 2 +- .../Notifications/SimpleErrorNotification.cs | 17 ++++++++++++++++ 3 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 osu.Game/Overlays/Notifications/SimpleErrorNotification.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs index 43ba23e6c6..d0f6f3fe47 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs @@ -105,6 +105,15 @@ namespace osu.Game.Tests.Visual.UserInterface checkDisplayedCount(3); } + [Test] + public void TestError() + { + setState(Visibility.Visible); + AddStep(@"error #1", sendErrorNotification); + AddAssert("Is visible", () => notificationOverlay.State.Value == Visibility.Visible); + checkDisplayedCount(1); + } + [Test] public void TestSpam() { @@ -179,7 +188,7 @@ namespace osu.Game.Tests.Visual.UserInterface private void sendBarrage() { - switch (RNG.Next(0, 4)) + switch (RNG.Next(0, 5)) { case 0: sendHelloNotification(); @@ -196,6 +205,10 @@ namespace osu.Game.Tests.Visual.UserInterface case 3: sendDownloadProgress(); break; + + case 4: + sendErrorNotification(); + break; } } @@ -214,6 +227,11 @@ namespace osu.Game.Tests.Visual.UserInterface notificationOverlay.Post(new BackgroundNotification { Text = @"Welcome to osu!. Enjoy your stay!" }); } + private void sendErrorNotification() + { + notificationOverlay.Post(new SimpleErrorNotification { Text = @"Rut roh!. Something went wrong!" }); + } + private void sendManyNotifications() { for (int i = 0; i < 10; i++) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 1a1f7bd233..0dc63dcd4b 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -778,7 +778,7 @@ namespace osu.Game if (recentLogCount < short_term_display_limit) { - Schedule(() => notifications.Post(new SimpleNotification + Schedule(() => notifications.Post(new SimpleErrorNotification { Icon = entry.Level == LogLevel.Important ? FontAwesome.Solid.ExclamationCircle : FontAwesome.Solid.Bomb, Text = entry.Message.Truncate(256) + (entry.Exception != null && IsDeployedBuild ? "\n\nThis error has been automatically reported to the devs." : string.Empty), diff --git a/osu.Game/Overlays/Notifications/SimpleErrorNotification.cs b/osu.Game/Overlays/Notifications/SimpleErrorNotification.cs new file mode 100644 index 0000000000..13c9c5a02d --- /dev/null +++ b/osu.Game/Overlays/Notifications/SimpleErrorNotification.cs @@ -0,0 +1,17 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Sprites; + +namespace osu.Game.Overlays.Notifications +{ + public class SimpleErrorNotification : SimpleNotification + { + protected override string PopInSampleName => "UI/error-notification-pop-in"; + + public SimpleErrorNotification() + { + Icon = FontAwesome.Solid.Bomb; + } + } +} From 72562070bcea0718bd6ddce220e354d934b6a987 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Feb 2021 14:18:00 +0900 Subject: [PATCH 0512/1791] Remove second overload of Close (makes the call structure hard to follow / invoke correctly) --- osu.Game/Overlays/Notifications/Notification.cs | 6 ++---- osu.Game/Overlays/Notifications/ProgressNotification.cs | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index 86e409d0f6..daf931bc24 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -114,7 +114,7 @@ namespace osu.Game.Overlays.Notifications closeButton = new CloseButton { Alpha = 0, - Action = Close, + Action = () => Close(), Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, Margin = new MarginPadding @@ -167,9 +167,7 @@ namespace osu.Game.Overlays.Notifications public bool WasClosed; - public virtual void Close() => Close(true); - - public virtual void Close(bool playSound) + public virtual void Close(bool playSound = true) { if (WasClosed) return; diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index 3105ecd742..703c14af2b 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -150,12 +150,12 @@ namespace osu.Game.Overlays.Notifications colourCancelled = colours.Red; } - public override void Close() + public override void Close(bool playSound = true) { switch (State) { case ProgressNotificationState.Cancelled: - base.Close(); + base.Close(playSound); break; case ProgressNotificationState.Active: From 896f318b56866306e455d92c4443c252723d75f3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Feb 2021 14:18:40 +0900 Subject: [PATCH 0513/1791] Rename variable for clarity --- osu.Game/Overlays/Notifications/NotificationSection.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Notifications/NotificationSection.cs b/osu.Game/Overlays/Notifications/NotificationSection.cs index c8cee22370..38ba712254 100644 --- a/osu.Game/Overlays/Notifications/NotificationSection.cs +++ b/osu.Game/Overlays/Notifications/NotificationSection.cs @@ -109,11 +109,11 @@ namespace osu.Game.Overlays.Notifications private void clearAll() { - bool playSound = true; + bool first = true; notifications.Children.ForEach(c => { - c.Close(playSound); - playSound = false; + c.Close(first); + first = false; }); } From 800f12a358b234f7d3409e843c5b913cc563f4b6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Feb 2021 14:19:48 +0900 Subject: [PATCH 0514/1791] Update resources --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 7060e88026..a522a5f43d 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index f866b232d8..f69613cfd3 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -30,7 +30,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 22d104f2e1..1c602e1584 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + From c8899aff92f793ee03bf61ab026263d2e6bd4dd6 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 11 Feb 2021 14:36:41 +0900 Subject: [PATCH 0515/1791] Prevent the default on-click sample from playing for OsuCheckbox --- osu.Game/Graphics/UserInterface/OsuCheckbox.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/OsuCheckbox.cs b/osu.Game/Graphics/UserInterface/OsuCheckbox.cs index f6effa0834..0d00bc0dce 100644 --- a/osu.Game/Graphics/UserInterface/OsuCheckbox.cs +++ b/osu.Game/Graphics/UserInterface/OsuCheckbox.cs @@ -64,7 +64,7 @@ namespace osu.Game.Graphics.UserInterface RelativeSizeAxes = Axes.X, }, Nub = new Nub(), - new HoverClickSounds() + new HoverSounds() }; if (nubOnRight) From f21a3c0c48abe02b8b949bd0881dfe02f5a143cc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Feb 2021 14:50:55 +0900 Subject: [PATCH 0516/1791] Decrease the game-wide track playback volume to give samples some head-room --- osu.Game/OsuGame.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 1a1f7bd233..b77097a3f0 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -174,6 +174,8 @@ namespace osu.Game protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + private readonly BindableNumber globalTrackVolumeAdjust = new BindableNumber(0.5f); + [BackgroundDependencyLoader] private void load() { @@ -230,6 +232,11 @@ namespace osu.Game Audio.AddAdjustment(AdjustableProperty.Volume, inactiveVolumeFade); + // drop track volume game-wide to leave some head-room for UI effects / samples. + // this means that for the time being, gameplay sample playback is louder relative to the audio track, compared to stable. + // we may want to revisit this if users notice or complain about the difference (consider this a bit of a trial). + Audio.Tracks.AddAdjustment(AdjustableProperty.Volume, globalTrackVolumeAdjust); + SelectedMods.BindValueChanged(modsChanged); Beatmap.BindValueChanged(beatmapChanged, true); } From eaa7b4cb93cdad7a27bc8958cacd860a7f2e3b10 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Feb 2021 14:54:50 +0900 Subject: [PATCH 0517/1791] Rename second usage variable name to match --- osu.Game/Screens/Select/Details/AdvancedStats.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 4a03c5e614..ab4f3f4796 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -82,15 +82,15 @@ namespace osu.Game.Screens.Select.Details mods.BindValueChanged(modsChanged, true); } - private ModSettingChangeTracker settingChangeTracker; + private ModSettingChangeTracker modSettingChangeTracker; private ScheduledDelegate debouncedStatisticsUpdate; private void modsChanged(ValueChangedEvent> mods) { - settingChangeTracker?.Dispose(); + modSettingChangeTracker?.Dispose(); - settingChangeTracker = new ModSettingChangeTracker(mods.NewValue); - settingChangeTracker.SettingChanged += m => + modSettingChangeTracker = new ModSettingChangeTracker(mods.NewValue); + modSettingChangeTracker.SettingChanged += m => { if (!(m is IApplicableToDifficulty)) return; @@ -162,7 +162,7 @@ namespace osu.Game.Screens.Select.Details protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - settingChangeTracker?.Dispose(); + modSettingChangeTracker?.Dispose(); starDifficultyCancellationSource?.Cancel(); } From df7aaa5c816e6064920c32028bb3689e31174c81 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Feb 2021 15:02:34 +0900 Subject: [PATCH 0518/1791] Move implementation to OsuGameBase to ensure it applies to test scenes This also removed a previous attempt at the same thing, which happened to not be applying due to the reference to the applied bindable not being held. Whoops. --- osu.Game/OsuGame.cs | 7 ------- osu.Game/OsuGameBase.cs | 9 ++++++--- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index b77097a3f0..1a1f7bd233 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -174,8 +174,6 @@ namespace osu.Game protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - private readonly BindableNumber globalTrackVolumeAdjust = new BindableNumber(0.5f); - [BackgroundDependencyLoader] private void load() { @@ -232,11 +230,6 @@ namespace osu.Game Audio.AddAdjustment(AdjustableProperty.Volume, inactiveVolumeFade); - // drop track volume game-wide to leave some head-room for UI effects / samples. - // this means that for the time being, gameplay sample playback is louder relative to the audio track, compared to stable. - // we may want to revisit this if users notice or complain about the difference (consider this a bit of a trial). - Audio.Tracks.AddAdjustment(AdjustableProperty.Volume, globalTrackVolumeAdjust); - SelectedMods.BindValueChanged(modsChanged); Beatmap.BindValueChanged(beatmapChanged, true); } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index d3936ed27e..a1b66ba9c0 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -155,6 +155,8 @@ namespace osu.Game protected override UserInputManager CreateUserInputManager() => new OsuUserInputManager(); + private readonly BindableNumber globalTrackVolumeAdjust = new BindableNumber(0.5f); + [BackgroundDependencyLoader] private void load() { @@ -278,9 +280,10 @@ namespace osu.Game RegisterImportHandler(ScoreManager); RegisterImportHandler(SkinManager); - // tracks play so loud our samples can't keep up. - // this adds a global reduction of track volume for the time being. - Audio.Tracks.AddAdjustment(AdjustableProperty.Volume, new BindableDouble(0.8)); + // drop track volume game-wide to leave some head-room for UI effects / samples. + // this means that for the time being, gameplay sample playback is louder relative to the audio track, compared to stable. + // we may want to revisit this if users notice or complain about the difference (consider this a bit of a trial). + Audio.Tracks.AddAdjustment(AdjustableProperty.Volume, globalTrackVolumeAdjust); Beatmap = new NonNullableBindable(defaultBeatmap); From 21f66a19fd21c492f8340cc70ef2934ff1d1033e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Feb 2021 15:55:08 +0900 Subject: [PATCH 0519/1791] Make server authoritative in which mods the client should be using when gameplay starts --- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 8 +++--- .../Multiplayer/MultiplayerMatchSubScreen.cs | 25 +++++++++++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index f3972ab7f9..6d06e638c8 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -78,7 +78,7 @@ namespace osu.Game.Screens.OnlinePlay.Match managerUpdated = beatmapManager.ItemUpdated.GetBoundCopy(); managerUpdated.BindValueChanged(beatmapUpdated); - UserMods.BindValueChanged(_ => updateMods()); + UserMods.BindValueChanged(_ => UpdateMods()); } public override void OnEntering(IScreen last) @@ -97,7 +97,7 @@ namespace osu.Game.Screens.OnlinePlay.Match { base.OnResuming(last); beginHandlingTrack(); - updateMods(); + UpdateMods(); } public override bool OnExiting(IScreen next) @@ -128,7 +128,7 @@ namespace osu.Game.Screens.OnlinePlay.Match .Where(m => SelectedItem.Value.AllowedMods.Any(a => m.GetType() == a.GetType())) .ToList(); - updateMods(); + UpdateMods(); Ruleset.Value = SelectedItem.Value.Ruleset.Value; } @@ -145,7 +145,7 @@ namespace osu.Game.Screens.OnlinePlay.Match Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap); } - private void updateMods() + protected virtual void UpdateMods() { if (SelectedItem.Value == null) return; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 5f2f1366f7..3dff291858 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -271,6 +271,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer UserMods.BindValueChanged(onUserModsChanged); client.LoadRequested += onLoadRequested; + client.RoomUpdated += onRoomUpdated; isConnected = client.IsConnected.GetBoundCopy(); isConnected.BindValueChanged(connected => @@ -367,6 +368,27 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer } } + private void onRoomUpdated() + { + UpdateMods(); // user mods may have changed. + } + + protected override void UpdateMods() + { + if (SelectedItem.Value == null || client.LocalUser == null) + return; + + // update local mods based on room's reported status for the local user (omitting the base call implementation). + // this makes the server authoritative, and avoids the local user potentially settings mods that the server is not aware of (ie. if the match was started during the selection being changed). + var localUserMods = client.LocalUser.Mods.ToList(); + + Schedule(() => + { + var ruleset = Ruleset.Value.CreateInstance(); + Mods.Value = localUserMods.Select(m => m.ToMod(ruleset)).Concat(SelectedItem.Value.RequiredMods).ToList(); + }); + } + private void onLoadRequested() { Debug.Assert(client.Room != null); @@ -384,7 +406,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer base.Dispose(isDisposing); if (client != null) + { + client.RoomUpdated -= onRoomUpdated; client.LoadRequested -= onLoadRequested; + } } private class UserModSelectOverlay : LocalPlayerModSelectOverlay From 549e7520c520cb9b9e45773ad41a5d25b0d2fc7a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Feb 2021 16:00:26 +0900 Subject: [PATCH 0520/1791] Move scheduler logic to client callback rather than inside the update method --- .../Multiplayer/MultiplayerMatchSubScreen.cs | 30 ++++++++----------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 3dff291858..1599936a51 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -281,6 +281,17 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer }, true); } + protected override void UpdateMods() + { + if (SelectedItem.Value == null || client.LocalUser == null) + return; + + // update local mods based on room's reported status for the local user (omitting the base call implementation). + // this makes the server authoritative, and avoids the local user potentially settings mods that the server is not aware of (ie. if the match was started during the selection being changed). + var ruleset = Ruleset.Value.CreateInstance(); + Mods.Value = client.LocalUser.Mods.Select(m => m.ToMod(ruleset)).Concat(SelectedItem.Value.RequiredMods).ToList(); + } + public override bool OnBackButton() { if (client.Room != null && settingsOverlay.State.Value == Visibility.Visible) @@ -370,23 +381,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer private void onRoomUpdated() { - UpdateMods(); // user mods may have changed. - } - - protected override void UpdateMods() - { - if (SelectedItem.Value == null || client.LocalUser == null) - return; - - // update local mods based on room's reported status for the local user (omitting the base call implementation). - // this makes the server authoritative, and avoids the local user potentially settings mods that the server is not aware of (ie. if the match was started during the selection being changed). - var localUserMods = client.LocalUser.Mods.ToList(); - - Schedule(() => - { - var ruleset = Ruleset.Value.CreateInstance(); - Mods.Value = localUserMods.Select(m => m.ToMod(ruleset)).Concat(SelectedItem.Value.RequiredMods).ToList(); - }); + // user mods may have changed. + Scheduler.AddOnce(UpdateMods); } private void onLoadRequested() From 889a99c49cd153b67e8ad7bc04b77517fbd90ef4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Feb 2021 16:00:35 +0900 Subject: [PATCH 0521/1791] Use AddOnce everywhere to reduce potential call count --- osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 6d06e638c8..3668743720 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -78,7 +78,7 @@ namespace osu.Game.Screens.OnlinePlay.Match managerUpdated = beatmapManager.ItemUpdated.GetBoundCopy(); managerUpdated.BindValueChanged(beatmapUpdated); - UserMods.BindValueChanged(_ => UpdateMods()); + UserMods.BindValueChanged(_ => Scheduler.AddOnce(UpdateMods)); } public override void OnEntering(IScreen last) @@ -97,7 +97,7 @@ namespace osu.Game.Screens.OnlinePlay.Match { base.OnResuming(last); beginHandlingTrack(); - UpdateMods(); + Scheduler.AddOnce(UpdateMods); } public override bool OnExiting(IScreen next) From dddd776802ad1f77f085c9862c317f93a3d1b191 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Feb 2021 16:38:17 +0900 Subject: [PATCH 0522/1791] Add the ability for settings items to have tooltips --- osu.Game/Configuration/SettingSourceAttribute.cs | 6 ++++++ osu.Game/Overlays/Settings/SettingsItem.cs | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index 50069be4b2..70d67aaaa0 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -57,6 +57,7 @@ namespace osu.Game.Configuration yield return new SettingsSlider { LabelText = attr.Label, + TooltipText = attr.Description, Current = bNumber, KeyboardStep = 0.1f, }; @@ -67,6 +68,7 @@ namespace osu.Game.Configuration yield return new SettingsSlider { LabelText = attr.Label, + TooltipText = attr.Description, Current = bNumber, KeyboardStep = 0.1f, }; @@ -77,6 +79,7 @@ namespace osu.Game.Configuration yield return new SettingsSlider { LabelText = attr.Label, + TooltipText = attr.Description, Current = bNumber }; @@ -86,6 +89,7 @@ namespace osu.Game.Configuration yield return new SettingsCheckbox { LabelText = attr.Label, + TooltipText = attr.Description, Current = bBool }; @@ -95,6 +99,7 @@ namespace osu.Game.Configuration yield return new SettingsTextBox { LabelText = attr.Label, + TooltipText = attr.Description, Current = bString }; @@ -105,6 +110,7 @@ namespace osu.Game.Configuration var dropdown = (Drawable)Activator.CreateInstance(dropdownType); dropdownType.GetProperty(nameof(SettingsDropdown.LabelText))?.SetValue(dropdown, attr.Label); + dropdownType.GetProperty(nameof(SettingsDropdown.TooltipText))?.SetValue(dropdown, attr.Description); dropdownType.GetProperty(nameof(SettingsDropdown.Current))?.SetValue(dropdown, bindable); yield return dropdown; diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index 278479e04f..27232d0a49 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -21,7 +21,7 @@ using osuTK; namespace osu.Game.Overlays.Settings { - public abstract class SettingsItem : Container, IFilterable, ISettingsItem, IHasCurrentValue + public abstract class SettingsItem : Container, IFilterable, ISettingsItem, IHasCurrentValue, IHasTooltip { protected abstract Drawable CreateControl(); @@ -214,5 +214,7 @@ namespace osu.Game.Overlays.Settings this.FadeColour(bindable.Disabled ? Color4.Gray : buttonColour, 200, Easing.OutQuint); } } + + public string TooltipText { get; set; } } } From 9fb41dc0b6f416e2232cffbeb576f0d9cdcf5955 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Feb 2021 16:41:21 +0900 Subject: [PATCH 0523/1791] Move property to a better place in the class --- osu.Game/Overlays/Settings/SettingsItem.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index 27232d0a49..af225889da 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -37,6 +37,8 @@ namespace osu.Game.Overlays.Settings public bool ShowsDefaultIndicator = true; + public string TooltipText { get; set; } + public virtual string LabelText { get => labelText?.Text ?? string.Empty; @@ -214,7 +216,5 @@ namespace osu.Game.Overlays.Settings this.FadeColour(bindable.Disabled ? Color4.Gray : buttonColour, 200, Easing.OutQuint); } } - - public string TooltipText { get; set; } } } From 5fb99fdc52e12845dadc48a57f043e0da4696bbd Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 11 Feb 2021 10:49:16 +0300 Subject: [PATCH 0524/1791] Rename some members and extract connection closure to separate method --- osu.Game/Online/HubClientConnector.cs | 35 ++++++++++--------- .../Online/Multiplayer/MultiplayerClient.cs | 2 +- .../Spectator/SpectatorStreamingClient.cs | 2 +- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/osu.Game/Online/HubClientConnector.cs b/osu.Game/Online/HubClientConnector.cs index b740aabb92..65285882d9 100644 --- a/osu.Game/Online/HubClientConnector.cs +++ b/osu.Game/Online/HubClientConnector.cs @@ -25,7 +25,7 @@ namespace osu.Game.Online /// /// Invoked whenever a new hub connection is built. /// - public Action? OnNewConnection; + public Action? ConfigureConnection; private readonly string clientName; private readonly string endpoint; @@ -106,7 +106,7 @@ namespace osu.Game.Online try { // importantly, rebuild the connection each attempt to get an updated access token. - CurrentConnection = createConnection(cancellationToken); + CurrentConnection = buildConnection(cancellationToken); await CurrentConnection.StartAsync(cancellationToken); @@ -134,7 +134,7 @@ namespace osu.Game.Online } } - private HubConnection createConnection(CancellationToken cancellationToken) + private HubConnection buildConnection(CancellationToken cancellationToken) { Debug.Assert(api != null); @@ -152,24 +152,25 @@ namespace osu.Game.Online var newConnection = builder.Build(); - OnNewConnection?.Invoke(newConnection); - - newConnection.Closed += ex => - { - isConnected.Value = false; - - Logger.Log(ex != null ? $"{clientName} lost connection: {ex}" : $"{clientName} disconnected", LoggingTarget.Network); - - // make sure a disconnect wasn't triggered (and this is still the active connection). - if (!cancellationToken.IsCancellationRequested) - Task.Run(connect, default); - - return Task.CompletedTask; - }; + ConfigureConnection?.Invoke(newConnection); + newConnection.Closed += ex => onConnectionClosed(ex, cancellationToken); return newConnection; } + private Task onConnectionClosed(Exception? ex, CancellationToken cancellationToken) + { + isConnected.Value = false; + + Logger.Log(ex != null ? $"{clientName} lost connection: {ex}" : $"{clientName} disconnected", LoggingTarget.Network); + + // make sure a disconnect wasn't triggered (and this is still the active connection). + if (!cancellationToken.IsCancellationRequested) + Task.Run(connect, default); + + return Task.CompletedTask; + } + private async Task disconnect(bool takeLock) { cancelExistingConnect(); diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index f025a5b429..ba2a8d7246 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -33,7 +33,7 @@ namespace osu.Game.Online.Multiplayer { connector = new HubClientConnector(nameof(MultiplayerClient), endpoint, api) { - OnNewConnection = connection => + ConfigureConnection = connection => { // this is kind of SILLY // https://github.com/dotnet/aspnetcore/issues/15198 diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 4ef59b5e47..532f717f2c 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -86,7 +86,7 @@ namespace osu.Game.Online.Spectator private void load(IAPIProvider api) { connector = CreateConnector(nameof(SpectatorStreamingClient), endpoint, api); - connector.OnNewConnection = connection => + connector.ConfigureConnection = connection => { // until strong typed client support is added, each method must be manually bound // (see https://github.com/dotnet/aspnetcore/issues/15198) From 18acd7f08032369f3c87d2327a3e1197b3c09d76 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 11 Feb 2021 10:51:04 +0300 Subject: [PATCH 0525/1791] Apply documentation suggestions Co-authored-by: Dean Herbert --- osu.Game/Online/HubClientConnector.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/HubClientConnector.cs b/osu.Game/Online/HubClientConnector.cs index 65285882d9..71d9df84c4 100644 --- a/osu.Game/Online/HubClientConnector.cs +++ b/osu.Game/Online/HubClientConnector.cs @@ -18,7 +18,7 @@ using osu.Game.Online.API; namespace osu.Game.Online { /// - /// A component that maintains over a hub connection between client and server. + /// A component that manages the life cycle of a connection to a SignalR Hub. /// public class HubClientConnector : IDisposable { @@ -52,7 +52,7 @@ namespace osu.Game.Online /// /// The name of the client this connector connects for, used for logging. /// The endpoint to the hub. - /// The API provider for listening to state changes, or null to not listen. + /// An API provider used to react to connection state changes, or null to not establish connection at all (for testing purposes). public HubClientConnector(string clientName, string endpoint, IAPIProvider? api) { this.clientName = clientName; From db79080bc4deebef8277866157d3067b024ee533 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Feb 2021 17:14:49 +0900 Subject: [PATCH 0526/1791] Fix GetNodeSamples potentially returning a live reference and overwriting existing samples --- osu.Game.Rulesets.Osu/Objects/Slider.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 1670df24a8..6fb36e80bc 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -140,7 +140,8 @@ namespace osu.Game.Rulesets.Osu.Objects // The samples should be attached to the slider tail, however this can only be done after LegacyLastTick is removed otherwise they would play earlier than they're intended to. // For now, the samples are attached to and played by the slider itself at the correct end time. - Samples = this.GetNodeSamples(repeatCount + 1); + // ToArray call is required as GetNodeSamples may fallback to Samples itself (without it it will get cleared due to the list reference being live). + Samples = this.GetNodeSamples(repeatCount + 1).ToArray(); } protected override void CreateNestedHitObjects(CancellationToken cancellationToken) From e9730d4782e9e8716d61bcf3b7e91c5cb2d1573d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Feb 2021 17:16:17 +0900 Subject: [PATCH 0527/1791] Move default sample addition to inside PlacementBlueprint This isn't actually required to fix the behaviour but it does feel like a better place to put this logic. --- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 4 ++++ .../Edit/Compose/Components/ComposeBlueprintContainer.cs | 3 --- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index c0eb891f5e..bfff93e7c5 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -7,6 +7,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; +using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Objects; @@ -45,6 +46,9 @@ namespace osu.Game.Rulesets.Edit { HitObject = hitObject; + // adding the default hit sample should be the case regardless of the ruleset. + HitObject.Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_NORMAL)); + RelativeSizeAxes = Axes.Both; // This is required to allow the blueprint's position to be updated via OnMouseMove/Handle diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index c09b935f28..79f457c050 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -211,9 +211,6 @@ namespace osu.Game.Screens.Edit.Compose.Components if (blueprint != null) { - // doing this post-creations as adding the default hit sample should be the case regardless of the ruleset. - blueprint.HitObject.Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_NORMAL)); - placementBlueprintContainer.Child = currentPlacement = blueprint; // Fixes a 1-frame position discrepancy due to the first mouse move event happening in the next frame From f84ea3063768bac3d8850c62f187f681ece7bbba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Feb 2021 17:47:29 +0900 Subject: [PATCH 0528/1791] Expose Mods in DrawableRuleset to avoid using external DI --- osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs | 3 +-- osu.Game/Rulesets/Mods/ModAutoplay.cs | 3 +-- osu.Game/Rulesets/Mods/ModCinema.cs | 4 +--- osu.Game/Rulesets/UI/DrawableRuleset.cs | 4 ++-- 4 files changed, 5 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index 59a5295858..77de0cb45b 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -62,8 +62,7 @@ namespace osu.Game.Rulesets.Osu.Mods inputManager.AllowUserCursorMovement = false; // Generate the replay frames the cursor should follow - var mods = (IReadOnlyList)drawableRuleset.Dependencies.Get(typeof(IReadOnlyList)); - replayFrames = new OsuAutoGenerator(drawableRuleset.Beatmap, mods).Generate().Frames.Cast().ToList(); + replayFrames = new OsuAutoGenerator(drawableRuleset.Beatmap, drawableRuleset.Mods).Generate().Frames.Cast().ToList(); } } } diff --git a/osu.Game/Rulesets/Mods/ModAutoplay.cs b/osu.Game/Rulesets/Mods/ModAutoplay.cs index 748c7272f4..d1d23def67 100644 --- a/osu.Game/Rulesets/Mods/ModAutoplay.cs +++ b/osu.Game/Rulesets/Mods/ModAutoplay.cs @@ -18,8 +18,7 @@ namespace osu.Game.Rulesets.Mods { public virtual void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - var mods = (IReadOnlyList)drawableRuleset.Dependencies.Get(typeof(IReadOnlyList)); - drawableRuleset.SetReplayScore(CreateReplayScore(drawableRuleset.Beatmap, mods)); + drawableRuleset.SetReplayScore(CreateReplayScore(drawableRuleset.Beatmap, drawableRuleset.Mods)); } } diff --git a/osu.Game/Rulesets/Mods/ModCinema.cs b/osu.Game/Rulesets/Mods/ModCinema.cs index 16e6400f23..eb0473016a 100644 --- a/osu.Game/Rulesets/Mods/ModCinema.cs +++ b/osu.Game/Rulesets/Mods/ModCinema.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.Collections.Generic; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Rulesets.Objects; @@ -15,8 +14,7 @@ namespace osu.Game.Rulesets.Mods { public virtual void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - var mods = (IReadOnlyList)drawableRuleset.Dependencies.Get(typeof(IReadOnlyList)); - drawableRuleset.SetReplayScore(CreateReplayScore(drawableRuleset.Beatmap, mods)); + drawableRuleset.SetReplayScore(CreateReplayScore(drawableRuleset.Beatmap, drawableRuleset.Mods)); // AlwaysPresent required for hitsounds drawableRuleset.Playfield.AlwaysPresent = true; diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 6940e43e5b..ca27e6b21a 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -92,7 +92,7 @@ namespace osu.Game.Rulesets.UI protected IRulesetConfigManager Config { get; private set; } [Cached(typeof(IReadOnlyList))] - protected override IReadOnlyList Mods { get; } + public sealed override IReadOnlyList Mods { get; } private FrameStabilityContainer frameStabilityContainer; @@ -434,7 +434,7 @@ namespace osu.Game.Rulesets.UI /// /// The mods which are to be applied. /// - protected abstract IReadOnlyList Mods { get; } + public abstract IReadOnlyList Mods { get; } /// ~ /// The associated ruleset. From ffd3caacb5167bf10d49587ef15cde22ae5896f7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Feb 2021 17:57:50 +0900 Subject: [PATCH 0529/1791] Update resources --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index a522a5f43d..d88a11257d 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index f69613cfd3..d68a8a515c 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -30,7 +30,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 1c602e1584..87ebd41fee 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + From d3c1b475929227b761731ab2c0f7b5c0a1ae54cd Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 11 Feb 2021 12:32:54 +0300 Subject: [PATCH 0530/1791] Replace nullable API with null connector instead --- .../Visual/Gameplay/TestSceneSpectator.cs | 6 +- ...TestSceneMultiplayerGameplayLeaderboard.cs | 6 +- osu.Game/Online/HubClientConnector.cs | 36 +++++----- .../Spectator/SpectatorStreamingClient.cs | 67 ++++++++++--------- 4 files changed, 53 insertions(+), 62 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 1e499f20cb..36e7e1fb29 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -244,11 +244,7 @@ namespace osu.Game.Tests.Visual.Gameplay { } - protected override HubClientConnector CreateConnector(string name, string endpoint, IAPIProvider api) - { - // do not pass API to prevent attempting failing connections on an actual hub. - return base.CreateConnector(name, endpoint, null); - } + protected override HubClientConnector CreateConnector(string name, string endpoint, IAPIProvider api) => null; public void StartPlay(int beatmapId) { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index b459cebdd7..49abd62dba 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -106,11 +106,7 @@ namespace osu.Game.Tests.Visual.Multiplayer this.totalUsers = totalUsers; } - protected override HubClientConnector CreateConnector(string name, string endpoint, IAPIProvider api) - { - // do not pass API to prevent attempting failing connections on an actual hub. - return base.CreateConnector(name, endpoint, null); - } + protected override HubClientConnector CreateConnector(string name, string endpoint, IAPIProvider api) => null; public void Start(int beatmapId) { diff --git a/osu.Game/Online/HubClientConnector.cs b/osu.Game/Online/HubClientConnector.cs index 71d9df84c4..cfc4ff5d60 100644 --- a/osu.Game/Online/HubClientConnector.cs +++ b/osu.Game/Online/HubClientConnector.cs @@ -29,7 +29,7 @@ namespace osu.Game.Online private readonly string clientName; private readonly string endpoint; - private readonly IAPIProvider? api; + private readonly IAPIProvider api; /// /// The current connection opened by this connector. @@ -52,32 +52,28 @@ namespace osu.Game.Online /// /// The name of the client this connector connects for, used for logging. /// The endpoint to the hub. - /// An API provider used to react to connection state changes, or null to not establish connection at all (for testing purposes). - public HubClientConnector(string clientName, string endpoint, IAPIProvider? api) + /// An API provider used to react to connection state changes. + public HubClientConnector(string clientName, string endpoint, IAPIProvider api) { this.clientName = clientName; this.endpoint = endpoint; - this.api = api; - if (api != null) + apiState.BindTo(api.State); + apiState.BindValueChanged(state => { - apiState.BindTo(api.State); - apiState.BindValueChanged(state => + switch (state.NewValue) { - switch (state.NewValue) - { - case APIState.Failing: - case APIState.Offline: - Task.Run(() => disconnect(true)); - break; + case APIState.Failing: + case APIState.Offline: + Task.Run(() => disconnect(true)); + break; - case APIState.Online: - Task.Run(connect); - break; - } - }, true); - } + case APIState.Online: + Task.Run(connect); + break; + } + }, true); } private async Task connect() @@ -136,8 +132,6 @@ namespace osu.Game.Online private HubConnection buildConnection(CancellationToken cancellationToken) { - Debug.Assert(api != null); - var builder = new HubConnectionBuilder() .WithUrl(endpoint, options => { options.Headers.Add("Authorization", $"Bearer {api.AccessToken}"); }); diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 532f717f2c..7e61da9b87 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -32,11 +32,12 @@ namespace osu.Game.Online.Spectator private readonly string endpoint; + [CanBeNull] private HubClientConnector connector; private readonly IBindable isConnected = new BindableBool(); - private HubConnection connection => connector.CurrentConnection; + private HubConnection connection => connector?.CurrentConnection; private readonly List watchingUsers = new List(); @@ -86,42 +87,46 @@ namespace osu.Game.Online.Spectator private void load(IAPIProvider api) { connector = CreateConnector(nameof(SpectatorStreamingClient), endpoint, api); - connector.ConfigureConnection = connection => - { - // until strong typed client support is added, each method must be manually bound - // (see https://github.com/dotnet/aspnetcore/issues/15198) - connection.On(nameof(ISpectatorClient.UserBeganPlaying), ((ISpectatorClient)this).UserBeganPlaying); - connection.On(nameof(ISpectatorClient.UserSentFrames), ((ISpectatorClient)this).UserSentFrames); - connection.On(nameof(ISpectatorClient.UserFinishedPlaying), ((ISpectatorClient)this).UserFinishedPlaying); - }; - isConnected.BindTo(connector.IsConnected); - isConnected.BindValueChanged(connected => + if (connector != null) { - if (connected.NewValue) + connector.ConfigureConnection = connection => { - // get all the users that were previously being watched - int[] users; + // until strong typed client support is added, each method must be manually bound + // (see https://github.com/dotnet/aspnetcore/issues/15198) + connection.On(nameof(ISpectatorClient.UserBeganPlaying), ((ISpectatorClient)this).UserBeganPlaying); + connection.On(nameof(ISpectatorClient.UserSentFrames), ((ISpectatorClient)this).UserSentFrames); + connection.On(nameof(ISpectatorClient.UserFinishedPlaying), ((ISpectatorClient)this).UserFinishedPlaying); + }; - lock (userLock) + isConnected.BindTo(connector.IsConnected); + isConnected.BindValueChanged(connected => + { + if (connected.NewValue) { - users = watchingUsers.ToArray(); - watchingUsers.Clear(); + // get all the users that were previously being watched + int[] users; + + lock (userLock) + { + users = watchingUsers.ToArray(); + watchingUsers.Clear(); + } + + // resubscribe to watched users. + foreach (var userId in users) + WatchUser(userId); + + // re-send state in case it wasn't received + if (isPlaying) + beginPlaying(); } - - // resubscribe to watched users. - foreach (var userId in users) - WatchUser(userId); - - // re-send state in case it wasn't received - if (isPlaying) - beginPlaying(); - } - else - { - playingUsers.Clear(); - } - }, true); + else + { + playingUsers.Clear(); + } + }, true); + } } protected virtual HubClientConnector CreateConnector(string name, string endpoint, IAPIProvider api) => new HubClientConnector(name, endpoint, api); From 37e3d95c35a67f24f66302977bb31f0906e6babc Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 11 Feb 2021 12:39:06 +0300 Subject: [PATCH 0531/1791] Slight reword in `ConfigureConnection`'s xmldoc --- osu.Game/Online/HubClientConnector.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/HubClientConnector.cs b/osu.Game/Online/HubClientConnector.cs index cfc4ff5d60..fb76049446 100644 --- a/osu.Game/Online/HubClientConnector.cs +++ b/osu.Game/Online/HubClientConnector.cs @@ -23,7 +23,7 @@ namespace osu.Game.Online public class HubClientConnector : IDisposable { /// - /// Invoked whenever a new hub connection is built. + /// Invoked whenever a new hub connection is built, to configure it before it's started. /// public Action? ConfigureConnection; From f4a7ec57e982922928ed3f327c61b228457135cc Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 11 Feb 2021 13:00:18 +0300 Subject: [PATCH 0532/1791] Remove unused using --- osu.Game/Online/HubClientConnector.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Online/HubClientConnector.cs b/osu.Game/Online/HubClientConnector.cs index fb76049446..2298ac4243 100644 --- a/osu.Game/Online/HubClientConnector.cs +++ b/osu.Game/Online/HubClientConnector.cs @@ -4,7 +4,6 @@ #nullable enable using System; -using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR.Client; From 970039b7e3ff29804999cc0f28633bb308db0eee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Feb 2021 12:14:49 +0900 Subject: [PATCH 0533/1791] Split out hover sample debounce logic so it can be more easily used in other places --- .../HoverSampleDebounceComponent.cs | 46 +++++++++++++++++++ .../Graphics/UserInterface/HoverSounds.cs | 32 ++----------- 2 files changed, 50 insertions(+), 28 deletions(-) create mode 100644 osu.Game/Graphics/UserInterface/HoverSampleDebounceComponent.cs diff --git a/osu.Game/Graphics/UserInterface/HoverSampleDebounceComponent.cs b/osu.Game/Graphics/UserInterface/HoverSampleDebounceComponent.cs new file mode 100644 index 0000000000..f0c7c20fe8 --- /dev/null +++ b/osu.Game/Graphics/UserInterface/HoverSampleDebounceComponent.cs @@ -0,0 +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 osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Bindables; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Events; +using osu.Game.Configuration; + +namespace osu.Game.Graphics.UserInterface +{ + /// + /// Handles debouncing hover sounds at a global level to ensure the effects are not overwhelming. + /// + public abstract class HoverSampleDebounceComponent : CompositeDrawable + { + /// + /// Length of debounce for hover sound playback, in milliseconds. + /// + public double HoverDebounceTime { get; } = 20; + + private Bindable lastPlaybackTime; + + [BackgroundDependencyLoader] + private void load(AudioManager audio, SessionStatics statics) + { + lastPlaybackTime = statics.GetBindable(Static.LastHoverSoundPlaybackTime); + } + + protected override bool OnHover(HoverEvent e) + { + bool enoughTimePassedSinceLastPlayback = !lastPlaybackTime.Value.HasValue || Time.Current - lastPlaybackTime.Value >= HoverDebounceTime; + + if (enoughTimePassedSinceLastPlayback) + { + PlayHoverSample(); + lastPlaybackTime.Value = Time.Current; + } + + return false; + } + + public abstract void PlayHoverSample(); + } +} diff --git a/osu.Game/Graphics/UserInterface/HoverSounds.cs b/osu.Game/Graphics/UserInterface/HoverSounds.cs index fa43d4543f..29238377c7 100644 --- a/osu.Game/Graphics/UserInterface/HoverSounds.cs +++ b/osu.Game/Graphics/UserInterface/HoverSounds.cs @@ -5,11 +5,8 @@ using System.ComponentModel; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; -using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Input.Events; using osu.Game.Configuration; using osu.Framework.Utils; @@ -19,19 +16,12 @@ namespace osu.Game.Graphics.UserInterface /// Adds hover sounds to a drawable. /// Does not draw anything. /// - public class HoverSounds : CompositeDrawable + public class HoverSounds : HoverSampleDebounceComponent { private SampleChannel sampleHover; - /// - /// Length of debounce for hover sound playback, in milliseconds. - /// - public double HoverDebounceTime { get; } = 20; - protected readonly HoverSampleSet SampleSet; - private Bindable lastPlaybackTime; - public HoverSounds(HoverSampleSet sampleSet = HoverSampleSet.Normal) { SampleSet = sampleSet; @@ -41,27 +31,13 @@ namespace osu.Game.Graphics.UserInterface [BackgroundDependencyLoader] private void load(AudioManager audio, SessionStatics statics) { - lastPlaybackTime = statics.GetBindable(Static.LastHoverSoundPlaybackTime); - sampleHover = audio.Samples.Get($@"UI/generic-hover{SampleSet.GetDescription()}"); } - protected override bool OnHover(HoverEvent e) + public override void PlayHoverSample() { - if (sampleHover == null) - return false; - - bool enoughTimePassedSinceLastPlayback = !lastPlaybackTime.Value.HasValue || Time.Current - lastPlaybackTime.Value >= HoverDebounceTime; - - if (enoughTimePassedSinceLastPlayback) - { - sampleHover.Frequency.Value = 0.96 + RNG.NextDouble(0.08); - sampleHover.Play(); - - lastPlaybackTime.Value = Time.Current; - } - - return false; + sampleHover.Frequency.Value = 0.96 + RNG.NextDouble(0.08); + sampleHover.Play(); } } From cd01591dda9ae57e66eddb0d5a80564a98f7ab20 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Feb 2021 12:14:57 +0900 Subject: [PATCH 0534/1791] Consume new debounce logic in carousel header --- .../Screens/Select/Carousel/CarouselHeader.cs | 69 +++++++++++-------- 1 file changed, 41 insertions(+), 28 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselHeader.cs b/osu.Game/Screens/Select/Carousel/CarouselHeader.cs index 4f53a6e202..90eebfc05b 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselHeader.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselHeader.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Utils; using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; using osuTK; using osuTK.Graphics; @@ -20,10 +21,6 @@ namespace osu.Game.Screens.Select.Carousel { public class CarouselHeader : Container { - private SampleChannel sampleHover; - - private readonly Box hoverLayer; - public Container BorderContainer; public readonly Bindable State = new Bindable(CarouselItemState.NotSelected); @@ -44,23 +41,11 @@ namespace osu.Game.Screens.Select.Carousel Children = new Drawable[] { Content, - hoverLayer = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - Blending = BlendingParameters.Additive, - }, + new HoverLayer() } }; } - [BackgroundDependencyLoader] - private void load(AudioManager audio, OsuColour colours) - { - sampleHover = audio.Samples.Get("SongSelect/song-ping"); - hoverLayer.Colour = colours.Blue.Opacity(0.1f); - } - protected override void LoadComplete() { base.LoadComplete(); @@ -97,22 +82,50 @@ namespace osu.Game.Screens.Select.Carousel } } - protected override bool OnHover(HoverEvent e) + public class HoverLayer : HoverSampleDebounceComponent { - if (sampleHover != null) + private SampleChannel sampleHover; + + private Box box; + + public HoverLayer() { + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load(AudioManager audio, OsuColour colours) + { + InternalChild = box = new Box + { + Colour = colours.Blue.Opacity(0.1f), + Alpha = 0, + Blending = BlendingParameters.Additive, + RelativeSizeAxes = Axes.Both, + }; + + sampleHover = audio.Samples.Get("SongSelect/song-ping"); + } + + protected override bool OnHover(HoverEvent e) + { + box.FadeIn(100, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + box.FadeOut(1000, Easing.OutQuint); + base.OnHoverLost(e); + } + + public override void PlayHoverSample() + { + if (sampleHover == null) return; + sampleHover.Frequency.Value = 0.90 + RNG.NextDouble(0.2); sampleHover.Play(); } - - hoverLayer.FadeIn(100, Easing.OutQuint); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - hoverLayer.FadeOut(1000, Easing.OutQuint); - base.OnHoverLost(e); } } } From a2035a2e84d6a187331e0c56e2ffa906be673659 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Feb 2021 12:20:39 +0900 Subject: [PATCH 0535/1791] Stop hover sounds from playing when dragging (scrolling) --- .../Graphics/UserInterface/HoverSampleDebounceComponent.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Graphics/UserInterface/HoverSampleDebounceComponent.cs b/osu.Game/Graphics/UserInterface/HoverSampleDebounceComponent.cs index f0c7c20fe8..55f43cfe46 100644 --- a/osu.Game/Graphics/UserInterface/HoverSampleDebounceComponent.cs +++ b/osu.Game/Graphics/UserInterface/HoverSampleDebounceComponent.cs @@ -30,6 +30,10 @@ namespace osu.Game.Graphics.UserInterface protected override bool OnHover(HoverEvent e) { + // hover sounds shouldn't be played during scroll operations. + if (e.HasAnyButtonPressed) + return false; + bool enoughTimePassedSinceLastPlayback = !lastPlaybackTime.Value.HasValue || Time.Current - lastPlaybackTime.Value >= HoverDebounceTime; if (enoughTimePassedSinceLastPlayback) From 5f23bd725941a94304481d7aa37409162426e8ae Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Feb 2021 12:48:32 +0900 Subject: [PATCH 0536/1791] Revert most of the changes to ArchiveModeManager by using better code --- osu.Game/Beatmaps/BeatmapManager.cs | 8 ++------ osu.Game/Database/ArchiveModelManager.cs | 26 ++++++++++++++---------- osu.Game/Scoring/ScoreManager.cs | 7 +++---- 3 files changed, 20 insertions(+), 21 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 4825569ee4..f23e135c68 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -64,13 +64,9 @@ namespace osu.Game.Beatmaps protected override string[] HashableFileTypes => new[] { ".osu" }; - protected override bool StableDirectoryExists(StableStorage stableStorage) => stableStorage.GetSongStorage().ExistsDirectory("."); + protected override string ImportFromStablePath => "."; - protected override IEnumerable GetStableImportPaths(StableStorage stableStorage) - { - var songStorage = stableStorage.GetSongStorage(); - return songStorage.GetDirectories(".").Select(path => songStorage.GetFullPath(path)); - } + protected override Storage PrepareStableStorage(StableStorage stableStorage) => stableStorage.GetSongStorage(); private readonly RulesetStore rulesets; private readonly BeatmapStore beatmaps; diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index fd94660a4b..b55020c437 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -637,16 +637,11 @@ namespace osu.Game.Database /// protected virtual string ImportFromStablePath => null; - /// - /// Checks for the existence of an osu-stable directory. - /// - protected virtual bool StableDirectoryExists(StableStorage stableStorage) => stableStorage.ExistsDirectory(ImportFromStablePath); - /// /// Select paths to import from stable where all paths should be absolute. Default implementation iterates all directories in . /// - protected virtual IEnumerable GetStableImportPaths(StableStorage stableStorage) => stableStorage.GetDirectories(ImportFromStablePath) - .Select(path => stableStorage.GetFullPath(path)); + protected virtual IEnumerable GetStableImportPaths(Storage storage) => storage.GetDirectories(ImportFromStablePath) + .Select(path => storage.GetFullPath(path)); /// /// Whether this specified path should be removed after successful import. @@ -660,24 +655,33 @@ namespace osu.Game.Database /// public Task ImportFromStableAsync() { - var stable = GetStableStorage?.Invoke(); + var stableStorage = GetStableStorage?.Invoke(); - if (stable == null) + if (stableStorage == null) { Logger.Log("No osu!stable installation available!", LoggingTarget.Information, LogLevel.Error); return Task.CompletedTask; } - if (!StableDirectoryExists(stable)) + var storage = PrepareStableStorage(stableStorage); + + if (!storage.ExistsDirectory(ImportFromStablePath)) { // This handles situations like when the user does not have a Skins folder Logger.Log($"No {ImportFromStablePath} folder available in osu!stable installation", LoggingTarget.Information, LogLevel.Error); return Task.CompletedTask; } - return Task.Run(async () => await Import(GetStableImportPaths(stable).ToArray())); + return Task.Run(async () => await Import(GetStableImportPaths(storage).ToArray())); } + /// + /// Run any required traversal operations on the stable storage location before performing operations. + /// + /// The stable storage. + /// The usable storage. Return the unchanged if no traversal is required. + protected virtual Storage PrepareStableStorage(StableStorage stableStorage) => stableStorage; + #endregion /// diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 6aa0a30a75..a6beb19876 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -16,7 +16,6 @@ using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Database; -using osu.Game.IO; using osu.Game.IO.Archives; using osu.Game.Online.API; using osu.Game.Online.API.Requests; @@ -72,9 +71,9 @@ namespace osu.Game.Scoring } } - protected override IEnumerable GetStableImportPaths(StableStorage stableStorage) - => stableStorage.GetFiles(ImportFromStablePath).Where(p => HandledExtensions.Any(ext => Path.GetExtension(p)?.Equals(ext, StringComparison.OrdinalIgnoreCase) ?? false)) - .Select(path => stableStorage.GetFullPath(path)); + protected override IEnumerable GetStableImportPaths(Storage storage) + => storage.GetFiles(ImportFromStablePath).Where(p => HandledExtensions.Any(ext => Path.GetExtension(p)?.Equals(ext, StringComparison.OrdinalIgnoreCase) ?? false)) + .Select(path => storage.GetFullPath(path)); public Score GetScore(ScoreInfo score) => new LegacyDatabasedScore(score, rulesets, beatmaps(), Files.Store); From 8ab7d07eab83befc7332496d20250fe54bf5ddc3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Feb 2021 12:57:57 +0900 Subject: [PATCH 0537/1791] Tidy up config parsing logic --- osu.Game/IO/StableStorage.cs | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/osu.Game/IO/StableStorage.cs b/osu.Game/IO/StableStorage.cs index ccc6f9c311..d4b0d300ff 100644 --- a/osu.Game/IO/StableStorage.cs +++ b/osu.Game/IO/StableStorage.cs @@ -23,6 +23,7 @@ namespace osu.Game.IO : base(path, host) { this.host = host; + songsPath = new Lazy(locateSongsDirectory); } @@ -33,32 +34,29 @@ namespace osu.Game.IO private string locateSongsDirectory() { - var songsDirectoryPath = Path.Combine(BasePath, stable_default_songs_path); - var configFile = GetFiles(".", $"osu!.{Environment.UserName}.cfg").SingleOrDefault(); - if (configFile == null) - return songsDirectoryPath; - - using (var stream = GetStream(configFile)) - using (var textReader = new StreamReader(stream)) + if (configFile != null) { - string line; - - while ((line = textReader.ReadLine()) != null) + using (var stream = GetStream(configFile)) + using (var textReader = new StreamReader(stream)) { - if (line.StartsWith("BeatmapDirectory", StringComparison.OrdinalIgnoreCase)) + string line; + + while ((line = textReader.ReadLine()) != null) { - var directory = line.Split('=')[1].TrimStart(); - if (Path.IsPathFullyQualified(directory)) - songsDirectoryPath = directory; + if (!line.StartsWith("BeatmapDirectory", StringComparison.OrdinalIgnoreCase)) continue; + + var customDirectory = line.Split('=').LastOrDefault()?.Trim(); + if (customDirectory != null && Path.IsPathFullyQualified(customDirectory)) + return customDirectory; break; } } } - return songsDirectoryPath; + return GetFullPath(stable_default_songs_path); } } } From 33c9ecac8a291657d19e4d4ba3cf7c55e15e3a5c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Feb 2021 14:54:19 +0900 Subject: [PATCH 0538/1791] Fix MessageFormatter not working for custom endpoints --- osu.Game/Online/Chat/MessageFormatter.cs | 7 ++++++- osu.Game/OsuGameBase.cs | 3 +++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index d2a117876d..8673d73be7 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -49,6 +49,11 @@ namespace osu.Game.Online.Chat // Unicode emojis private static readonly Regex emoji_regex = new Regex(@"(\uD83D[\uDC00-\uDE4F])"); + /// + /// The root URL for the website, used for chat link matching. + /// + public static string WebsiteRootUrl { get; set; } = "https://osu.ppy.sh"; + private static void handleMatches(Regex regex, string display, string link, MessageFormatterResult result, int startIndex = 0, LinkAction? linkActionOverride = null, char[] escapeChars = null) { int captureOffset = 0; @@ -119,7 +124,7 @@ namespace osu.Game.Online.Chat case "http": case "https": // length > 3 since all these links need another argument to work - if (args.Length > 3 && args[1] == "osu.ppy.sh") + if (args.Length > 3 && url.StartsWith(WebsiteRootUrl, StringComparison.OrdinalIgnoreCase)) { switch (args[2]) { diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index a1b66ba9c0..174b5006a2 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -31,6 +31,7 @@ using osu.Game.Input; using osu.Game.Input.Bindings; using osu.Game.IO; using osu.Game.Online; +using osu.Game.Online.Chat; using osu.Game.Online.Multiplayer; using osu.Game.Online.Spectator; using osu.Game.Overlays; @@ -225,6 +226,8 @@ namespace osu.Game EndpointConfiguration endpoints = UseDevelopmentServer ? (EndpointConfiguration)new DevelopmentEndpointConfiguration() : new ProductionEndpointConfiguration(); + MessageFormatter.WebsiteRootUrl = endpoints.WebsiteRootUrl; + dependencies.CacheAs(API ??= new APIAccess(LocalConfig, endpoints)); dependencies.CacheAs(spectatorStreaming = new SpectatorStreamingClient(endpoints)); From 6a42d312f62fd1ba45b2ffda559fbcb31b075c74 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Feb 2021 14:56:46 +0900 Subject: [PATCH 0539/1791] Match using EndsWith to ignore protocol (and allow http) --- osu.Game/Online/Chat/MessageFormatter.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 8673d73be7..d8e02e5b6d 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -52,7 +52,14 @@ namespace osu.Game.Online.Chat /// /// The root URL for the website, used for chat link matching. /// - public static string WebsiteRootUrl { get; set; } = "https://osu.ppy.sh"; + public static string WebsiteRootUrl + { + set => websiteRootUrl = value + .Trim('/') // trim potential trailing slash/ + .Split('/').Last(); // only keep domain name, ignoring protocol. + } + + private static string websiteRootUrl; private static void handleMatches(Regex regex, string display, string link, MessageFormatterResult result, int startIndex = 0, LinkAction? linkActionOverride = null, char[] escapeChars = null) { @@ -124,7 +131,7 @@ namespace osu.Game.Online.Chat case "http": case "https": // length > 3 since all these links need another argument to work - if (args.Length > 3 && url.StartsWith(WebsiteRootUrl, StringComparison.OrdinalIgnoreCase)) + if (args.Length > 3 && args[1].EndsWith(websiteRootUrl, StringComparison.OrdinalIgnoreCase)) { switch (args[2]) { From 1c5aaf3832e97a258bb764aa5dfd2f07200e4b35 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Feb 2021 15:03:53 +0900 Subject: [PATCH 0540/1791] Add back default value --- osu.Game/Online/Chat/MessageFormatter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index d8e02e5b6d..3c6df31462 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -59,7 +59,7 @@ namespace osu.Game.Online.Chat .Split('/').Last(); // only keep domain name, ignoring protocol. } - private static string websiteRootUrl; + private static string websiteRootUrl = "osu.ppy.sh"; private static void handleMatches(Regex regex, string display, string link, MessageFormatterResult result, int startIndex = 0, LinkAction? linkActionOverride = null, char[] escapeChars = null) { From 955c9a2dd3e47dab3752d62f4173bbde3b5ec0d5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Feb 2021 15:17:39 +0900 Subject: [PATCH 0541/1791] Add test coverage of beatmap link resolution --- osu.Game.Tests/Chat/MessageFormatterTests.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/osu.Game.Tests/Chat/MessageFormatterTests.cs b/osu.Game.Tests/Chat/MessageFormatterTests.cs index 600c820ce1..151e05b18d 100644 --- a/osu.Game.Tests/Chat/MessageFormatterTests.cs +++ b/osu.Game.Tests/Chat/MessageFormatterTests.cs @@ -21,6 +21,21 @@ namespace osu.Game.Tests.Chat Assert.AreEqual(36, result.Links[0].Length); } + [TestCase(LinkAction.OpenBeatmap, "456", "https://osu.ppy.sh/beatmapsets/123#osu/456")] + [TestCase(LinkAction.OpenBeatmap, "456", "https://osu.ppy.sh/beatmapsets/123#osu/456?whatever")] + [TestCase(LinkAction.OpenBeatmap, "456", "https://osu.ppy.sh/beatmapsets/123/456")] + [TestCase(LinkAction.OpenBeatmapSet, "123", "https://osu.ppy.sh/beatmapsets/123")] + [TestCase(LinkAction.OpenBeatmapSet, "123", "https://osu.ppy.sh/beatmapsets/123/whatever")] + public void TestBeatmapLinks(LinkAction expectedAction, string expectedArg, string link) + { + Message result = MessageFormatter.FormatMessage(new Message { Content = link }); + + Assert.AreEqual(result.Content, result.DisplayContent); + Assert.AreEqual(1, result.Links.Count); + Assert.AreEqual(expectedAction, result.Links[0].Action); + Assert.AreEqual(expectedArg, result.Links[0].Argument); + } + [Test] public void TestMultipleComplexLinks() { From bb9123eecd231cde3b1ed240e7ffe3575b005fee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Feb 2021 15:17:54 +0900 Subject: [PATCH 0542/1791] Better handle fallback scenarios for beatmap links --- osu.Game/Online/Chat/MessageFormatter.cs | 33 ++++++++++++++++++------ 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index d2a117876d..5aab476b6d 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -121,20 +121,37 @@ namespace osu.Game.Online.Chat // length > 3 since all these links need another argument to work if (args.Length > 3 && args[1] == "osu.ppy.sh") { + var mainArg = args[3]; + switch (args[2]) { + // old site only case "b": case "beatmaps": - return new LinkDetails(LinkAction.OpenBeatmap, args[3]); + { + string trimmed = mainArg.Split('?').First(); + if (int.TryParse(trimmed, out var id)) + return new LinkDetails(LinkAction.OpenBeatmap, id.ToString()); + + break; + } case "s": case "beatmapsets": case "d": - return new LinkDetails(LinkAction.OpenBeatmapSet, args[3]); + { + if (args.Length > 4 && int.TryParse(args[4], out var id)) + // https://osu.ppy.sh/beatmapsets/1154158#osu/2768184 + return new LinkDetails(LinkAction.OpenBeatmap, id.ToString()); + + // https://osu.ppy.sh/beatmapsets/1154158#whatever + string trimmed = mainArg.Split('#').First(); + return new LinkDetails(LinkAction.OpenBeatmapSet, trimmed); + } case "u": case "users": - return new LinkDetails(LinkAction.OpenUserProfile, args[3]); + return new LinkDetails(LinkAction.OpenUserProfile, mainArg); } } @@ -183,10 +200,9 @@ namespace osu.Game.Online.Chat case "osump": return new LinkDetails(LinkAction.JoinMultiplayerMatch, args[1]); - - default: - return new LinkDetails(LinkAction.External, null); } + + return new LinkDetails(LinkAction.External, null); } private static MessageFormatterResult format(string toFormat, int startIndex = 0, int space = 3) @@ -259,8 +275,9 @@ namespace osu.Game.Online.Chat public class LinkDetails { - public LinkAction Action; - public string Argument; + public readonly LinkAction Action; + + public readonly string Argument; public LinkDetails(LinkAction action, string argument) { From 3799493536907954559ed707d2edf07a1bd02c26 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Feb 2021 15:25:00 +0900 Subject: [PATCH 0543/1791] Add test coverage of int match failures --- osu.Game.Tests/Chat/MessageFormatterTests.cs | 4 ++++ osu.Game/Online/Chat/MessageFormatter.cs | 5 ++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Chat/MessageFormatterTests.cs b/osu.Game.Tests/Chat/MessageFormatterTests.cs index 151e05b18d..11e94f0b89 100644 --- a/osu.Game.Tests/Chat/MessageFormatterTests.cs +++ b/osu.Game.Tests/Chat/MessageFormatterTests.cs @@ -24,8 +24,10 @@ namespace osu.Game.Tests.Chat [TestCase(LinkAction.OpenBeatmap, "456", "https://osu.ppy.sh/beatmapsets/123#osu/456")] [TestCase(LinkAction.OpenBeatmap, "456", "https://osu.ppy.sh/beatmapsets/123#osu/456?whatever")] [TestCase(LinkAction.OpenBeatmap, "456", "https://osu.ppy.sh/beatmapsets/123/456")] + [TestCase(LinkAction.External, null, "https://osu.ppy.sh/beatmapsets/abc/def")] [TestCase(LinkAction.OpenBeatmapSet, "123", "https://osu.ppy.sh/beatmapsets/123")] [TestCase(LinkAction.OpenBeatmapSet, "123", "https://osu.ppy.sh/beatmapsets/123/whatever")] + [TestCase(LinkAction.External, null, "https://osu.ppy.sh/beatmapsets/abc")] public void TestBeatmapLinks(LinkAction expectedAction, string expectedArg, string link) { Message result = MessageFormatter.FormatMessage(new Message { Content = link }); @@ -34,6 +36,8 @@ namespace osu.Game.Tests.Chat Assert.AreEqual(1, result.Links.Count); Assert.AreEqual(expectedAction, result.Links[0].Action); Assert.AreEqual(expectedArg, result.Links[0].Argument); + if (expectedAction == LinkAction.External) + Assert.AreEqual(link, result.Links[0].Url); } [Test] diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 5aab476b6d..8e92078c2c 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -146,7 +146,10 @@ namespace osu.Game.Online.Chat // https://osu.ppy.sh/beatmapsets/1154158#whatever string trimmed = mainArg.Split('#').First(); - return new LinkDetails(LinkAction.OpenBeatmapSet, trimmed); + if (int.TryParse(trimmed, out id)) + return new LinkDetails(LinkAction.OpenBeatmapSet, id.ToString()); + + break; } case "u": From a1be3c8bfdf71df5b0b0476b379d36a427ee0ee4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 12 Feb 2021 15:27:37 +0900 Subject: [PATCH 0544/1791] Fix header background being invisible in multiplayer/playlists --- .../Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs index eb05cbaf85..3206f7b3ab 100644 --- a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs +++ b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs @@ -34,8 +34,8 @@ namespace osu.Game.Beatmaps.Drawables /// protected virtual double UnloadDelay => 10000; - protected override DelayedLoadWrapper CreateDelayedLoadWrapper(Func createContentFunc, double timeBeforeLoad) - => new DelayedLoadUnloadWrapper(createContentFunc, timeBeforeLoad, UnloadDelay); + protected override DelayedLoadWrapper CreateDelayedLoadWrapper(Func createContentFunc, double timeBeforeLoad) => + new DelayedLoadUnloadWrapper(createContentFunc, timeBeforeLoad, UnloadDelay) { RelativeSizeAxes = Axes.Both }; protected override double TransformDuration => 400; From f7374703f00ee87f8865a51c5ef2fa5c3befad79 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Feb 2021 15:29:21 +0900 Subject: [PATCH 0545/1791] Update tests to match dev domain --- .../Visual/Online/TestSceneChatLink.cs | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs index 9e69530a77..74f53ebdca 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs @@ -103,26 +103,26 @@ namespace osu.Game.Tests.Visual.Online private void testLinksGeneral() { addMessageWithChecks("test!"); - addMessageWithChecks("osu.ppy.sh!"); - addMessageWithChecks("https://osu.ppy.sh!", 1, expectedActions: LinkAction.External); + addMessageWithChecks("dev.ppy.sh!"); + addMessageWithChecks("https://dev.ppy.sh!", 1, expectedActions: LinkAction.External); addMessageWithChecks("00:12:345 (1,2) - Test?", 1, expectedActions: LinkAction.OpenEditorTimestamp); addMessageWithChecks("Wiki link for tasty [[Performance Points]]", 1, expectedActions: LinkAction.External); - addMessageWithChecks("(osu forums)[https://osu.ppy.sh/forum] (old link format)", 1, expectedActions: LinkAction.External); - addMessageWithChecks("[https://osu.ppy.sh/home New site] (new link format)", 1, expectedActions: LinkAction.External); - addMessageWithChecks("[osu forums](https://osu.ppy.sh/forum) (new link format 2)", 1, expectedActions: LinkAction.External); - addMessageWithChecks("[https://osu.ppy.sh/home This is only a link to the new osu webpage but this is supposed to test word wrap.]", 1, expectedActions: LinkAction.External); - addMessageWithChecks("is now listening to [https://osu.ppy.sh/s/93523 IMAGE -MATERIAL- ]", 1, true, expectedActions: LinkAction.OpenBeatmapSet); - addMessageWithChecks("is now playing [https://osu.ppy.sh/b/252238 IMAGE -MATERIAL- ]", 1, true, expectedActions: LinkAction.OpenBeatmap); - addMessageWithChecks("Let's (try)[https://osu.ppy.sh/home] [https://osu.ppy.sh/b/252238 multiple links] https://osu.ppy.sh/home", 3, + addMessageWithChecks("(osu forums)[https://dev.ppy.sh/forum] (old link format)", 1, expectedActions: LinkAction.External); + addMessageWithChecks("[https://dev.ppy.sh/home New site] (new link format)", 1, expectedActions: LinkAction.External); + addMessageWithChecks("[osu forums](https://dev.ppy.sh/forum) (new link format 2)", 1, expectedActions: LinkAction.External); + addMessageWithChecks("[https://dev.ppy.sh/home This is only a link to the new osu webpage but this is supposed to test word wrap.]", 1, expectedActions: LinkAction.External); + addMessageWithChecks("is now listening to [https://dev.ppy.sh/s/93523 IMAGE -MATERIAL- ]", 1, true, expectedActions: LinkAction.OpenBeatmapSet); + addMessageWithChecks("is now playing [https://dev.ppy.sh/b/252238 IMAGE -MATERIAL- ]", 1, true, expectedActions: LinkAction.OpenBeatmap); + addMessageWithChecks("Let's (try)[https://dev.ppy.sh/home] [https://dev.ppy.sh/b/252238 multiple links] https://dev.ppy.sh/home", 3, expectedActions: new[] { LinkAction.External, LinkAction.OpenBeatmap, LinkAction.External }); - addMessageWithChecks("[https://osu.ppy.sh/home New link format with escaped [and \\[ paired] braces]", 1, expectedActions: LinkAction.External); - addMessageWithChecks("[Markdown link format with escaped [and \\[ paired] braces](https://osu.ppy.sh/home)", 1, expectedActions: LinkAction.External); - addMessageWithChecks("(Old link format with escaped (and \\( paired) parentheses)[https://osu.ppy.sh/home] and [[also a rogue wiki link]]", 2, expectedActions: new[] { LinkAction.External, LinkAction.External }); + addMessageWithChecks("[https://dev.ppy.sh/home New link format with escaped [and \\[ paired] braces]", 1, expectedActions: LinkAction.External); + addMessageWithChecks("[Markdown link format with escaped [and \\[ paired] braces](https://dev.ppy.sh/home)", 1, expectedActions: LinkAction.External); + addMessageWithChecks("(Old link format with escaped (and \\( paired) parentheses)[https://dev.ppy.sh/home] and [[also a rogue wiki link]]", 2, expectedActions: new[] { LinkAction.External, LinkAction.External }); // note that there's 0 links here (they get removed if a channel is not found) addMessageWithChecks("#lobby or #osu would be blue (and work) in the ChatDisplay test (when a proper ChatOverlay is present)."); addMessageWithChecks("I am important!", 0, false, true); addMessageWithChecks("feels important", 0, true, true); - addMessageWithChecks("likes to post this [https://osu.ppy.sh/home link].", 1, true, true, expectedActions: LinkAction.External); + addMessageWithChecks("likes to post this [https://dev.ppy.sh/home link].", 1, true, true, expectedActions: LinkAction.External); addMessageWithChecks("Join my multiplayer game osump://12346.", 1, expectedActions: LinkAction.JoinMultiplayerMatch); addMessageWithChecks("Join my [multiplayer game](osump://12346).", 1, expectedActions: LinkAction.JoinMultiplayerMatch); addMessageWithChecks("Join my [#english](osu://chan/#english).", 1, expectedActions: LinkAction.OpenChannel); @@ -136,9 +136,9 @@ namespace osu.Game.Tests.Visual.Online int echoCounter = 0; addEchoWithWait("sent!", "received!"); - addEchoWithWait("https://osu.ppy.sh/home", null, 500); - addEchoWithWait("[https://osu.ppy.sh/forum let's try multiple words too!]"); - addEchoWithWait("(long loading times! clickable while loading?)[https://osu.ppy.sh/home]", null, 5000); + addEchoWithWait("https://dev.ppy.sh/home", null, 500); + addEchoWithWait("[https://dev.ppy.sh/forum let's try multiple words too!]"); + addEchoWithWait("(long loading times! clickable while loading?)[https://dev.ppy.sh/home]", null, 5000); void addEchoWithWait(string text, string completeText = null, double delay = 250) { From a0733769206b58026ec0b044da3e2820b71a0c76 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Feb 2021 15:18:16 +0900 Subject: [PATCH 0546/1791] Show URLs in tooltips when custom text has replaced the link --- osu.Game/Graphics/Containers/LinkFlowContainer.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index e3a9a5fe9d..914c8ff78d 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -38,7 +38,12 @@ namespace osu.Game.Graphics.Containers foreach (var link in links) { AddText(text[previousLinkEnd..link.Index]); - AddLink(text.Substring(link.Index, link.Length), link.Action, link.Argument ?? link.Url); + + string displayText = text.Substring(link.Index, link.Length); + string linkArgument = link.Argument ?? link.Url; + string tooltip = displayText == link.Url ? null : link.Url; + + AddLink(displayText, link.Action, linkArgument, tooltip); previousLinkEnd = link.Index + link.Length; } @@ -52,7 +57,7 @@ namespace osu.Game.Graphics.Containers => createLink(AddText(text, creationParameters), new LinkDetails(LinkAction.Custom, null), tooltipText, action); public void AddLink(string text, LinkAction action, string argument, string tooltipText = null, Action creationParameters = null) - => createLink(AddText(text, creationParameters), new LinkDetails(action, argument), null); + => createLink(AddText(text, creationParameters), new LinkDetails(action, argument), tooltipText); public void AddLink(IEnumerable text, LinkAction action = LinkAction.External, string linkArgument = null, string tooltipText = null) { From 4ab16694d1ca145d2af3f6aa758504e9dba0a511 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Feb 2021 16:22:19 +0900 Subject: [PATCH 0547/1791] Fix classic "welcome" intro not looping as expected --- osu.Game/Screens/Menu/IntroWelcome.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Menu/IntroWelcome.cs b/osu.Game/Screens/Menu/IntroWelcome.cs index abb83f894a..d454d85d9e 100644 --- a/osu.Game/Screens/Menu/IntroWelcome.cs +++ b/osu.Game/Screens/Menu/IntroWelcome.cs @@ -67,6 +67,10 @@ namespace osu.Game.Screens.Menu { StartTrack(); + // this classic intro loops forever. + if (UsingThemedIntro) + Track.Looping = true; + const float fade_in_time = 200; logo.ScaleTo(1); From 725db5683731e9b193fb9d309910c88e8e6ba398 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Feb 2021 16:55:34 +0900 Subject: [PATCH 0548/1791] Add loading spinner while tournament bracket is loading / retrieving data --- .../TournamentTestBrowser.cs | 2 + .../TournamentTestScene.cs | 2 + osu.Game.Tournament/TournamentGame.cs | 43 +++++++++++++------ osu.Game.Tournament/TournamentGameBase.cs | 22 ++++++---- 4 files changed, 48 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tournament.Tests/TournamentTestBrowser.cs b/osu.Game.Tournament.Tests/TournamentTestBrowser.cs index f7ad757926..2f50ae4141 100644 --- a/osu.Game.Tournament.Tests/TournamentTestBrowser.cs +++ b/osu.Game.Tournament.Tests/TournamentTestBrowser.cs @@ -13,6 +13,8 @@ namespace osu.Game.Tournament.Tests { base.LoadComplete(); + BracketLoadTask.Wait(); + LoadComponentAsync(new Background("Menu/menu-background-0") { Colour = OsuColour.Gray(0.5f), diff --git a/osu.Game.Tournament.Tests/TournamentTestScene.cs b/osu.Game.Tournament.Tests/TournamentTestScene.cs index d22da25f9d..62882d7188 100644 --- a/osu.Game.Tournament.Tests/TournamentTestScene.cs +++ b/osu.Game.Tournament.Tests/TournamentTestScene.cs @@ -154,6 +154,8 @@ namespace osu.Game.Tournament.Tests protected override void LoadAsyncComplete() { + BracketLoadTask.Wait(); + // this has to be run here rather than LoadComplete because // TestScene.cs is checking the IsLoaded state (on another thread) and expects // the runner to be loaded at that point. diff --git a/osu.Game.Tournament/TournamentGame.cs b/osu.Game.Tournament/TournamentGame.cs index bbe4a53d8f..fadb821bef 100644 --- a/osu.Game.Tournament/TournamentGame.cs +++ b/osu.Game.Tournament/TournamentGame.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.Colour; using osu.Game.Graphics.Cursor; using osu.Game.Tournament.Models; using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; using osuTK; using osuTK.Graphics; @@ -32,25 +33,24 @@ namespace osu.Game.Tournament private Drawable heightWarning; private Bindable windowSize; private Bindable windowMode; + private LoadingSpinner loadingSpinner; [BackgroundDependencyLoader] private void load(FrameworkConfigManager frameworkConfig) { windowSize = frameworkConfig.GetBindable(FrameworkSetting.WindowedSize); - windowSize.BindValueChanged(size => ScheduleAfterChildren(() => - { - var minWidth = (int)(size.NewValue.Height / 768f * TournamentSceneManager.REQUIRED_WIDTH) - 1; - - heightWarning.Alpha = size.NewValue.Width < minWidth ? 1 : 0; - }), true); - windowMode = frameworkConfig.GetBindable(FrameworkSetting.WindowMode); - windowMode.BindValueChanged(mode => ScheduleAfterChildren(() => - { - windowMode.Value = WindowMode.Windowed; - }), true); - AddRange(new[] + Add(loadingSpinner = new LoadingSpinner(true, true) + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Margin = new MarginPadding(40), + }); + + loadingSpinner.Show(); + + BracketLoadTask.ContinueWith(_ => LoadComponentsAsync(new[] { new Container { @@ -93,7 +93,24 @@ namespace osu.Game.Tournament RelativeSizeAxes = Axes.Both, Child = new TournamentSceneManager() } - }); + }, drawables => + { + loadingSpinner.Hide(); + loadingSpinner.Expire(); + + AddRange(drawables); + + windowSize.BindValueChanged(size => ScheduleAfterChildren(() => + { + var minWidth = (int)(size.NewValue.Height / 768f * TournamentSceneManager.REQUIRED_WIDTH) - 1; + heightWarning.Alpha = size.NewValue.Width < minWidth ? 1 : 0; + }), true); + + windowMode.BindValueChanged(mode => ScheduleAfterChildren(() => + { + windowMode.Value = WindowMode.Windowed; + }), true); + })); } } } diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 97c950261b..4dd072cf17 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -4,6 +4,8 @@ using System; using System.IO; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using Newtonsoft.Json; using osu.Framework.Allocation; using osu.Framework.Graphics.Textures; @@ -29,6 +31,8 @@ namespace osu.Game.Tournament private DependencyContainer dependencies; private FileBasedIPC ipc; + protected Task BracketLoadTask { get; private set; } + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { return dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); @@ -46,14 +50,9 @@ namespace osu.Game.Tournament Textures.AddStore(new TextureLoaderStore(new StorageBackedResourceStore(storage))); - readBracket(); - - ladder.CurrentMatch.Value = ladder.Matches.FirstOrDefault(p => p.Current.Value); + BracketLoadTask = Task.Run(readBracket); dependencies.CacheAs(new StableInfo(storage)); - - dependencies.CacheAs(ipc = new FileBasedIPC()); - Add(ipc); } private void readBracket() @@ -70,8 +69,6 @@ namespace osu.Game.Tournament Ruleset.BindTo(ladder.Ruleset); - dependencies.Cache(ladder); - bool addedInfo = false; // assign teams @@ -127,6 +124,15 @@ namespace osu.Game.Tournament if (addedInfo) SaveChanges(); + + ladder.CurrentMatch.Value = ladder.Matches.FirstOrDefault(p => p.Current.Value); + + Schedule(() => + { + dependencies.Cache(ladder); + dependencies.CacheAs(ipc = new FileBasedIPC()); + Add(ipc); + }); } /// From 0c3aef8645f234acdc8a4ceb4646af176ea8963b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 12 Feb 2021 17:42:02 +0900 Subject: [PATCH 0549/1791] Fix potential race in looping sample As mentioned via GitHub comments. Very unlikely for this to happen unless: the sample takes a short amount of time to load, is very short itself, and the update thread stalls until the sample fully completes. --- osu.Game/Skinning/PoolableSkinnableSample.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Skinning/PoolableSkinnableSample.cs b/osu.Game/Skinning/PoolableSkinnableSample.cs index cff793e8d4..45880a8e1e 100644 --- a/osu.Game/Skinning/PoolableSkinnableSample.cs +++ b/osu.Game/Skinning/PoolableSkinnableSample.cs @@ -115,8 +115,7 @@ namespace osu.Game.Skinning if (Sample == null) return; - activeChannel = Sample.Play(); - activeChannel.Looping = Looping; + activeChannel = Sample.Play(Looping); Played = true; } From 9b5995f2f1f6710e3d26357e806f00168d559d01 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 12 Feb 2021 19:05:17 +0900 Subject: [PATCH 0550/1791] Update with removal of looping parameter --- osu.Game/Skinning/PoolableSkinnableSample.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/PoolableSkinnableSample.cs b/osu.Game/Skinning/PoolableSkinnableSample.cs index 45880a8e1e..9025fdbd0f 100644 --- a/osu.Game/Skinning/PoolableSkinnableSample.cs +++ b/osu.Game/Skinning/PoolableSkinnableSample.cs @@ -115,7 +115,9 @@ namespace osu.Game.Skinning if (Sample == null) return; - activeChannel = Sample.Play(Looping); + activeChannel = Sample.GetChannel(); + activeChannel.Looping = Looping; + activeChannel.Play(); Played = true; } From 37a21cb192760c5f68a7842140d59d9421d681ef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Feb 2021 21:30:02 +0900 Subject: [PATCH 0551/1791] Set static locally in test to ensure tests always run correctly --- osu.Game.Tests/Chat/MessageFormatterTests.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Chat/MessageFormatterTests.cs b/osu.Game.Tests/Chat/MessageFormatterTests.cs index 11e94f0b89..b80da928c8 100644 --- a/osu.Game.Tests/Chat/MessageFormatterTests.cs +++ b/osu.Game.Tests/Chat/MessageFormatterTests.cs @@ -21,15 +21,17 @@ namespace osu.Game.Tests.Chat Assert.AreEqual(36, result.Links[0].Length); } - [TestCase(LinkAction.OpenBeatmap, "456", "https://osu.ppy.sh/beatmapsets/123#osu/456")] - [TestCase(LinkAction.OpenBeatmap, "456", "https://osu.ppy.sh/beatmapsets/123#osu/456?whatever")] - [TestCase(LinkAction.OpenBeatmap, "456", "https://osu.ppy.sh/beatmapsets/123/456")] - [TestCase(LinkAction.External, null, "https://osu.ppy.sh/beatmapsets/abc/def")] - [TestCase(LinkAction.OpenBeatmapSet, "123", "https://osu.ppy.sh/beatmapsets/123")] - [TestCase(LinkAction.OpenBeatmapSet, "123", "https://osu.ppy.sh/beatmapsets/123/whatever")] - [TestCase(LinkAction.External, null, "https://osu.ppy.sh/beatmapsets/abc")] + [TestCase(LinkAction.OpenBeatmap, "456", "https://dev.ppy.sh/beatmapsets/123#osu/456")] + [TestCase(LinkAction.OpenBeatmap, "456", "https://dev.ppy.sh/beatmapsets/123#osu/456?whatever")] + [TestCase(LinkAction.OpenBeatmap, "456", "https://dev.ppy.sh/beatmapsets/123/456")] + [TestCase(LinkAction.External, null, "https://dev.ppy.sh/beatmapsets/abc/def")] + [TestCase(LinkAction.OpenBeatmapSet, "123", "https://dev.ppy.sh/beatmapsets/123")] + [TestCase(LinkAction.OpenBeatmapSet, "123", "https://dev.ppy.sh/beatmapsets/123/whatever")] + [TestCase(LinkAction.External, null, "https://dev.ppy.sh/beatmapsets/abc")] public void TestBeatmapLinks(LinkAction expectedAction, string expectedArg, string link) { + MessageFormatter.WebsiteRootUrl = "dev.ppy.sh"; + Message result = MessageFormatter.FormatMessage(new Message { Content = link }); Assert.AreEqual(result.Content, result.DisplayContent); From 7d057ab6ce9d81a6afe6fe79884d7705a40491dd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Feb 2021 22:38:55 +0900 Subject: [PATCH 0552/1791] Fix two threading issues --- osu.Game.Tournament/TournamentGameBase.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 4dd072cf17..4224da4bbe 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -4,7 +4,6 @@ using System; using System.IO; using System.Linq; -using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json; using osu.Framework.Allocation; @@ -31,7 +30,9 @@ namespace osu.Game.Tournament private DependencyContainer dependencies; private FileBasedIPC ipc; - protected Task BracketLoadTask { get; private set; } + protected Task BracketLoadTask => taskCompletionSource.Task; + + private readonly TaskCompletionSource taskCompletionSource = new TaskCompletionSource(); protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { @@ -50,9 +51,9 @@ namespace osu.Game.Tournament Textures.AddStore(new TextureLoaderStore(new StorageBackedResourceStore(storage))); - BracketLoadTask = Task.Run(readBracket); - dependencies.CacheAs(new StableInfo(storage)); + + Task.Run(readBracket); } private void readBracket() @@ -67,8 +68,6 @@ namespace osu.Game.Tournament ladder ??= new LadderInfo(); ladder.Ruleset.Value ??= RulesetStore.AvailableRulesets.First(); - Ruleset.BindTo(ladder.Ruleset); - bool addedInfo = false; // assign teams @@ -129,9 +128,13 @@ namespace osu.Game.Tournament Schedule(() => { + Ruleset.BindTo(ladder.Ruleset); + dependencies.Cache(ladder); dependencies.CacheAs(ipc = new FileBasedIPC()); Add(ipc); + + taskCompletionSource.SetResult(true); }); } From 13aaf766f93eadf358ea9d6b0d0bac2e71358a46 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 13 Feb 2021 01:10:39 +0900 Subject: [PATCH 0553/1791] Fix regression in tournament test startup behaviour --- .../TournamentTestBrowser.cs | 19 ++++++++++--------- .../TournamentTestScene.cs | 13 +++++++------ 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tournament.Tests/TournamentTestBrowser.cs b/osu.Game.Tournament.Tests/TournamentTestBrowser.cs index 2f50ae4141..50bdcd86c5 100644 --- a/osu.Game.Tournament.Tests/TournamentTestBrowser.cs +++ b/osu.Game.Tournament.Tests/TournamentTestBrowser.cs @@ -13,17 +13,18 @@ namespace osu.Game.Tournament.Tests { base.LoadComplete(); - BracketLoadTask.Wait(); - - LoadComponentAsync(new Background("Menu/menu-background-0") + BracketLoadTask.ContinueWith(_ => Schedule(() => { - Colour = OsuColour.Gray(0.5f), - Depth = 10 - }, AddInternal); + LoadComponentAsync(new Background("Menu/menu-background-0") + { + Colour = OsuColour.Gray(0.5f), + Depth = 10 + }, AddInternal); - // Have to construct this here, rather than in the constructor, because - // we depend on some dependencies to be loaded within OsuGameBase.load(). - Add(new TestBrowser()); + // Have to construct this here, rather than in the constructor, because + // we depend on some dependencies to be loaded within OsuGameBase.load(). + Add(new TestBrowser()); + })); } } } diff --git a/osu.Game.Tournament.Tests/TournamentTestScene.cs b/osu.Game.Tournament.Tests/TournamentTestScene.cs index 62882d7188..025abfcbc6 100644 --- a/osu.Game.Tournament.Tests/TournamentTestScene.cs +++ b/osu.Game.Tournament.Tests/TournamentTestScene.cs @@ -154,12 +154,13 @@ namespace osu.Game.Tournament.Tests protected override void LoadAsyncComplete() { - BracketLoadTask.Wait(); - - // this has to be run here rather than LoadComplete because - // TestScene.cs is checking the IsLoaded state (on another thread) and expects - // the runner to be loaded at that point. - Add(runner = new TestSceneTestRunner.TestRunner()); + BracketLoadTask.ContinueWith(_ => Schedule(() => + { + // this has to be run here rather than LoadComplete because + // TestScene.cs is checking the IsLoaded state (on another thread) and expects + // the runner to be loaded at that point. + Add(runner = new TestSceneTestRunner.TestRunner()); + })); } public void RunTestBlocking(TestScene test) => runner.RunTestBlocking(test); From 52975c51854c665495a8384d5356829e90062bfc Mon Sep 17 00:00:00 2001 From: Joehu Date: Fri, 12 Feb 2021 10:23:33 -0800 Subject: [PATCH 0554/1791] Remove hardcoded padding from main content --- .../Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index b7adb71e2f..56882e0d38 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -77,7 +77,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { - Horizontal = 105, + Horizontal = HORIZONTAL_OVERFLOW_PADDING + 55, Vertical = 20 }, Child = new GridContainer From b28a906197eee66455c1201db6e3288afca2f571 Mon Sep 17 00:00:00 2001 From: Joehu Date: Fri, 12 Feb 2021 10:29:29 -0800 Subject: [PATCH 0555/1791] Fix extra mod settings overflowing from screen --- .../Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 56882e0d38..c5130baa94 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -237,6 +237,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer Origin = Anchor.BottomLeft, RelativeSizeAxes = Axes.Both, Height = 0.5f, + Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING }, Child = userModsSelectOverlay = new UserModSelectOverlay { SelectedMods = { BindTarget = UserMods }, From 982d8e35edc3efb9a05111b37596c94c44e182e8 Mon Sep 17 00:00:00 2001 From: Joehu Date: Fri, 12 Feb 2021 10:42:48 -0800 Subject: [PATCH 0556/1791] Fix mod settings showing scrollbar when screen is offset --- osu.Game/Overlays/Mods/ModSettingsContainer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/Mods/ModSettingsContainer.cs b/osu.Game/Overlays/Mods/ModSettingsContainer.cs index 1c57ff54ad..64d65cab3b 100644 --- a/osu.Game/Overlays/Mods/ModSettingsContainer.cs +++ b/osu.Game/Overlays/Mods/ModSettingsContainer.cs @@ -52,6 +52,7 @@ namespace osu.Game.Overlays.Mods new OsuScrollContainer { RelativeSizeAxes = Axes.Both, + ScrollbarVisible = false, Child = modSettingsContent = new FillFlowContainer { Anchor = Anchor.TopCentre, From a4dc54423531c77a0fb34023f8a634a9faaf108d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Feb 2021 14:23:59 +0900 Subject: [PATCH 0557/1791] Refactor some shared code in TestScenePause --- .../Visual/Gameplay/TestScenePause.cs | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index ae806883b0..1a0b594bb7 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -56,9 +56,7 @@ namespace osu.Game.Tests.Visual.Gameplay pauseAndConfirm(); resume(); - confirmClockRunning(false); - confirmPauseOverlayShown(false); - + confirmPausedWithNoOverlay(); AddStep("click to resume", () => InputManager.Click(MouseButton.Left)); confirmClockRunning(true); @@ -73,9 +71,7 @@ namespace osu.Game.Tests.Visual.Gameplay pauseAndConfirm(); resume(); - confirmClockRunning(false); - confirmPauseOverlayShown(false); - + confirmPausedWithNoOverlay(); pauseAndConfirm(); AddUntilStep("resume overlay is not active", () => Player.DrawableRuleset.ResumeOverlay.State.Value == Visibility.Hidden); @@ -94,7 +90,7 @@ namespace osu.Game.Tests.Visual.Gameplay } [Test] - public void TestPauseTooSoon() + public void TestPauseDuringCooldownTooSoon() { AddStep("move cursor outside", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.TopLeft - new Vector2(10))); @@ -103,8 +99,8 @@ namespace osu.Game.Tests.Visual.Gameplay resume(); pause(); - confirmClockRunning(true); - confirmPauseOverlayShown(false); + confirmResumed(); + AddAssert("not exited", () => Player.IsCurrentScreen()); } [Test] @@ -117,9 +113,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("exit quick", () => Player.Exit()); - confirmClockRunning(true); - confirmPauseOverlayShown(false); - + confirmResumed(); AddAssert("exited", () => !Player.IsCurrentScreen()); } @@ -133,9 +127,7 @@ namespace osu.Game.Tests.Visual.Gameplay pause(); - confirmClockRunning(false); - confirmPauseOverlayShown(false); - + confirmPausedWithNoOverlay(); AddAssert("fail overlay still shown", () => Player.FailOverlayVisible); exitAndConfirm(); @@ -277,6 +269,12 @@ namespace osu.Game.Tests.Visual.Gameplay confirmPauseOverlayShown(false); } + private void confirmPausedWithNoOverlay() + { + confirmClockRunning(false); + confirmPauseOverlayShown(false); + } + private void confirmExited() { AddUntilStep("player exited", () => !Player.IsCurrentScreen()); From 2b69c7b32530c4ab9ab712610b94664a2a9b9d43 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Feb 2021 14:00:30 +0900 Subject: [PATCH 0558/1791] Fix incorrect order of operation in pause blocking logic --- osu.Game/Screens/Play/Player.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index dda52f4dae..c462786916 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -503,14 +503,17 @@ namespace osu.Game.Screens.Play return; } - if (canPause && !GameplayClockContainer.IsPaused.Value) + if (!GameplayClockContainer.IsPaused.Value) { - if (pauseCooldownActive && !GameplayClockContainer.IsPaused.Value) - // still want to block if we are within the cooldown period and not already paused. + // if we are within the cooldown period and not already paused, the operation should block completely. + if (pauseCooldownActive) return; - Pause(); - return; + if (canPause) + { + Pause(); + return; + } } } From 25f5120fdf51be4ca58c208f55198cdcbcf41d0d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Feb 2021 14:36:14 +0900 Subject: [PATCH 0559/1791] Add failing test coverage of user pausing or quick exiting during cooldown --- .../Visual/Gameplay/TestScenePause.cs | 42 ++++++++++++++++--- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index 1a0b594bb7..8246e2c028 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -90,19 +90,47 @@ namespace osu.Game.Tests.Visual.Gameplay } [Test] - public void TestPauseDuringCooldownTooSoon() + public void TestExternalPauseDuringCooldownTooSoon() { AddStep("move cursor outside", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.TopLeft - new Vector2(10))); pauseAndConfirm(); resume(); - pause(); + pauseExternally(); confirmResumed(); AddAssert("not exited", () => Player.IsCurrentScreen()); } + [Test] + public void TestUserPauseDuringCooldownTooSoon() + { + AddStep("move cursor outside", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.TopLeft - new Vector2(10))); + + pauseAndConfirm(); + + resume(); + AddStep("pause via exit key", () => Player.ExitViaPause()); + + confirmResumed(); + AddAssert("not exited", () => Player.IsCurrentScreen()); + } + + [Test] + public void TestQuickExitDuringCooldownTooSoon() + { + AddStep("move cursor outside", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.TopLeft - new Vector2(10))); + + pauseAndConfirm(); + + resume(); + AddStep("pause via exit key", () => Player.ExitViaQuickExit()); + + confirmResumed(); + AddAssert("exited", () => !Player.IsCurrentScreen()); + } + [Test] public void TestExitSoonAfterResumeSucceeds() { @@ -125,7 +153,7 @@ namespace osu.Game.Tests.Visual.Gameplay confirmClockRunning(false); - pause(); + pauseExternally(); confirmPausedWithNoOverlay(); AddAssert("fail overlay still shown", () => Player.FailOverlayVisible); @@ -237,7 +265,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void pauseAndConfirm() { - pause(); + pauseExternally(); confirmPaused(); } @@ -286,7 +314,7 @@ namespace osu.Game.Tests.Visual.Gameplay } private void restart() => AddStep("restart", () => Player.Restart()); - private void pause() => AddStep("pause", () => Player.Pause()); + private void pauseExternally() => AddStep("pause", () => Player.Pause()); private void resume() => AddStep("resume", () => Player.Resume()); private void confirmPauseOverlayShown(bool isShown) => @@ -305,6 +333,10 @@ namespace osu.Game.Tests.Visual.Gameplay public bool PauseOverlayVisible => PauseOverlay.State.Value == Visibility.Visible; + public void ExitViaPause() => PerformExit(true); + + public void ExitViaQuickExit() => PerformExit(false); + public override void OnEntering(IScreen last) { base.OnEntering(last); From ec37e1602d566920601a1c197757991099b2447f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Feb 2021 15:02:58 +0900 Subject: [PATCH 0560/1791] Add failing test coverage of retrying from the results screen --- .../Visual/Navigation/OsuGameTestScene.cs | 3 +++ .../Navigation/TestSceneScreenNavigation.cs | 26 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs index c5038068ec..96393cc4c3 100644 --- a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs +++ b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs @@ -17,6 +17,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Screens; using osu.Game.Screens.Menu; @@ -115,6 +116,8 @@ namespace osu.Game.Tests.Visual.Navigation public new Bindable Ruleset => base.Ruleset; + public new Bindable> SelectedMods => base.SelectedMods; + // if we don't do this, when running under nUnit the version that gets populated is that of nUnit. public override string Version => "test game"; diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 8480e6eaaa..d8380b2dd3 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -11,7 +11,9 @@ using osu.Game.Beatmaps; using osu.Game.Overlays; using osu.Game.Overlays.Mods; using osu.Game.Overlays.Toolbar; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.Play; +using osu.Game.Screens.Ranking; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Options; using osu.Game.Tests.Beatmaps.IO; @@ -41,6 +43,30 @@ namespace osu.Game.Tests.Visual.Navigation exitViaEscapeAndConfirm(); } + [Test] + public void TestRetryFromResults() + { + Player player = null; + ResultsScreen results = null; + + WorkingBeatmap beatmap() => Game.Beatmap.Value; + + PushAndConfirm(() => new TestSongSelect()); + + AddStep("import beatmap", () => ImportBeatmapTest.LoadOszIntoOsu(Game, virtualTrack: true).Wait()); + + AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); + + AddStep("set autoplay", () => Game.SelectedMods.Value = new[] { new OsuModAutoplay() }); + + AddStep("press enter", () => InputManager.Key(Key.Enter)); + AddUntilStep("wait for player", () => (player = Game.ScreenStack.CurrentScreen as Player) != null); + AddStep("seek to end", () => beatmap().Track.Seek(beatmap().Track.Length)); + AddUntilStep("wait for pass", () => (results = Game.ScreenStack.CurrentScreen as ResultsScreen) != null && results.IsLoaded); + AddStep("attempt to retry", () => results.ChildrenOfType().First().Action()); + AddUntilStep("wait for player", () => Game.ScreenStack.CurrentScreen != player && Game.ScreenStack.CurrentScreen is Player); + } + [TestCase(true)] [TestCase(false)] public void TestSongContinuesAfterExitPlayer(bool withUserPause) From 1aea840504aa337e24173ca701188e6fd88a6d1a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Feb 2021 14:03:41 +0900 Subject: [PATCH 0561/1791] Add missing return in early exit scenario (MakeCurrent isn't compatible with the following Exit) --- osu.Game/Screens/Play/Player.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index c462786916..88ca516440 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -493,6 +493,7 @@ namespace osu.Game.Screens.Play // we want to give the user what they want, so forcefully return to this screen (to proceed with the upwards exit process). ValidForResume = false; this.MakeCurrent(); + return; } if (showDialogFirst) From 83183a84da2a877e44a5472cc375e0e9b162793e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Feb 2021 15:31:51 +0900 Subject: [PATCH 0562/1791] Ensure the tournament test runner is ready before performing the test run --- osu.Game.Tournament.Tests/TournamentTestScene.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tournament.Tests/TournamentTestScene.cs b/osu.Game.Tournament.Tests/TournamentTestScene.cs index 025abfcbc6..47d2160561 100644 --- a/osu.Game.Tournament.Tests/TournamentTestScene.cs +++ b/osu.Game.Tournament.Tests/TournamentTestScene.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; +using System.Threading; using osu.Framework.Allocation; using osu.Framework.Platform; using osu.Framework.Testing; @@ -163,7 +164,13 @@ namespace osu.Game.Tournament.Tests })); } - public void RunTestBlocking(TestScene test) => runner.RunTestBlocking(test); + public void RunTestBlocking(TestScene test) + { + while (runner?.IsLoaded != true && Host.ExecutionState == ExecutionState.Running) + Thread.Sleep(10); + + runner?.RunTestBlocking(test); + } } } } From 4f264758a499fea09585cdd9403cba15d9a7777c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Feb 2021 15:57:34 +0900 Subject: [PATCH 0563/1791] Add test coverage of pause from resume overlay --- .../Visual/Gameplay/TestScenePause.cs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index 8246e2c028..1ad1479cd4 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -69,13 +69,14 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for hitobjects", () => Player.HealthProcessor.Health.Value < 1); pauseAndConfirm(); - resume(); + confirmPausedWithNoOverlay(); pauseAndConfirm(); AddUntilStep("resume overlay is not active", () => Player.DrawableRuleset.ResumeOverlay.State.Value == Visibility.Hidden); confirmPaused(); + confirmNotExited(); } [Test] @@ -100,7 +101,7 @@ namespace osu.Game.Tests.Visual.Gameplay pauseExternally(); confirmResumed(); - AddAssert("not exited", () => Player.IsCurrentScreen()); + confirmNotExited(); } [Test] @@ -114,7 +115,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("pause via exit key", () => Player.ExitViaPause()); confirmResumed(); - AddAssert("not exited", () => Player.IsCurrentScreen()); + confirmNotExited(); } [Test] @@ -277,7 +278,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void exitAndConfirm() { - AddUntilStep("player not exited", () => Player.IsCurrentScreen()); + confirmNotExited(); AddStep("exit", () => Player.Exit()); confirmExited(); confirmNoTrackAdjustments(); @@ -286,7 +287,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void confirmPaused() { confirmClockRunning(false); - AddAssert("player not exited", () => Player.IsCurrentScreen()); + confirmNotExited(); AddAssert("player not failed", () => !Player.HasFailed); AddAssert("pause overlay shown", () => Player.PauseOverlayVisible); } @@ -303,10 +304,8 @@ namespace osu.Game.Tests.Visual.Gameplay confirmPauseOverlayShown(false); } - private void confirmExited() - { - AddUntilStep("player exited", () => !Player.IsCurrentScreen()); - } + private void confirmExited() => AddUntilStep("player exited", () => !Player.IsCurrentScreen()); + private void confirmNotExited() => AddAssert("player not exited", () => Player.IsCurrentScreen()); private void confirmNoTrackAdjustments() { From 9cba350337484a3e1466063411daeb489c18abdc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Feb 2021 15:57:21 +0900 Subject: [PATCH 0564/1791] Refactor again to better cover cases where the pause dialog should definitely be shown --- osu.Game/Screens/Play/Player.cs | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 88ca516440..a844d3bcf7 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -487,35 +487,30 @@ namespace osu.Game.Screens.Play // if a restart has been requested, cancel any pending completion (user has shown intent to restart). completionProgressDelegate?.Cancel(); + // there is a chance that the exit was performed after the transition to results has started. + // we want to give the user what they want, so forcefully return to this screen (to proceed with the upwards exit process). if (!this.IsCurrentScreen()) { - // there is a chance that the exit was performed after the transition to results has started. - // we want to give the user what they want, so forcefully return to this screen (to proceed with the upwards exit process). ValidForResume = false; this.MakeCurrent(); return; } - if (showDialogFirst) + bool pauseDialogShown = PauseOverlay.State.Value == Visibility.Visible; + + if (showDialogFirst && !pauseDialogShown) { + // if the fail animation is currently in progress, accelerate it (it will show the pause dialog on completion). if (ValidForResume && HasFailed && !FailOverlay.IsPresent) { failAnimation.FinishTransforms(true); return; } - if (!GameplayClockContainer.IsPaused.Value) - { - // if we are within the cooldown period and not already paused, the operation should block completely. - if (pauseCooldownActive) - return; - - if (canPause) - { - Pause(); - return; - } - } + // in the case a dialog needs to be shown, attempt to pause and show it. + // this may fail (see internal checks in Pause()) at which point the exit attempt will be aborted. + Pause(); + return; } this.Exit(); From f664fca0ddf2ac466af1d27048f0d370c74ecb94 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Feb 2021 16:11:17 +0900 Subject: [PATCH 0565/1791] Tidy up tests (and remove duplicate with new call logic) --- .../Visual/Gameplay/TestScenePause.cs | 22 ++++--------------- 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index 1ad1479cd4..aa56c636ab 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -90,20 +90,6 @@ namespace osu.Game.Tests.Visual.Gameplay resumeAndConfirm(); } - [Test] - public void TestExternalPauseDuringCooldownTooSoon() - { - AddStep("move cursor outside", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.TopLeft - new Vector2(10))); - - pauseAndConfirm(); - - resume(); - pauseExternally(); - - confirmResumed(); - confirmNotExited(); - } - [Test] public void TestUserPauseDuringCooldownTooSoon() { @@ -112,7 +98,7 @@ namespace osu.Game.Tests.Visual.Gameplay pauseAndConfirm(); resume(); - AddStep("pause via exit key", () => Player.ExitViaPause()); + pauseFromUserExitKey(); confirmResumed(); confirmNotExited(); @@ -154,7 +140,7 @@ namespace osu.Game.Tests.Visual.Gameplay confirmClockRunning(false); - pauseExternally(); + pauseFromUserExitKey(); confirmPausedWithNoOverlay(); AddAssert("fail overlay still shown", () => Player.FailOverlayVisible); @@ -266,7 +252,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void pauseAndConfirm() { - pauseExternally(); + pauseFromUserExitKey(); confirmPaused(); } @@ -313,7 +299,7 @@ namespace osu.Game.Tests.Visual.Gameplay } private void restart() => AddStep("restart", () => Player.Restart()); - private void pauseExternally() => AddStep("pause", () => Player.Pause()); + private void pauseFromUserExitKey() => AddStep("user pause", () => Player.ExitViaPause()); private void resume() => AddStep("resume", () => Player.Resume()); private void confirmPauseOverlayShown(bool isShown) => From 9ad38ab20e9bcc6b43aa8cab09ee6f14c8b34638 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Feb 2021 16:31:00 +0900 Subject: [PATCH 0566/1791] Move HubClientConnector retrieval to IAPIProvider --- .../Visual/Gameplay/TestSceneSpectator.cs | 3 -- ...TestSceneMultiplayerGameplayLeaderboard.cs | 3 -- osu.Game/Online/API/APIAccess.cs | 2 ++ osu.Game/Online/API/DummyAPIAccess.cs | 2 ++ osu.Game/Online/API/IAPIProvider.cs | 9 +++++ osu.Game/Online/HubClientConnector.cs | 7 ++-- osu.Game/Online/IHubClientConnector.cs | 34 +++++++++++++++++++ .../Online/Multiplayer/MultiplayerClient.cs | 15 ++++---- .../Spectator/SpectatorStreamingClient.cs | 6 ++-- 9 files changed, 60 insertions(+), 21 deletions(-) create mode 100644 osu.Game/Online/IHubClientConnector.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 36e7e1fb29..4a0e1282c4 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -12,7 +12,6 @@ using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Online; -using osu.Game.Online.API; using osu.Game.Online.Spectator; using osu.Game.Replays.Legacy; using osu.Game.Rulesets.Osu; @@ -244,8 +243,6 @@ namespace osu.Game.Tests.Visual.Gameplay { } - protected override HubClientConnector CreateConnector(string name, string endpoint, IAPIProvider api) => null; - public void StartPlay(int beatmapId) { this.beatmapId = beatmapId; diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index 49abd62dba..aab69d687a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -13,7 +13,6 @@ using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Database; using osu.Game.Online; -using osu.Game.Online.API; using osu.Game.Online.Spectator; using osu.Game.Replays.Legacy; using osu.Game.Rulesets.Osu.Scoring; @@ -106,8 +105,6 @@ namespace osu.Game.Tests.Visual.Multiplayer this.totalUsers = totalUsers; } - protected override HubClientConnector CreateConnector(string name, string endpoint, IAPIProvider api) => null; - public void Start(int beatmapId) { for (int i = 0; i < totalUsers; i++) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 2aaea22155..657487971b 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -243,6 +243,8 @@ namespace osu.Game.Online.API this.password = password; } + public IHubClientConnector GetHubConnector(string clientName, string endpoint) => new HubClientConnector(clientName, endpoint, this); + public RegistrationRequest.RegistrationRequestErrors CreateAccount(string email, string username, string password) { Debug.Assert(State.Value == APIState.Offline); diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index 3e996ac97f..943b52db88 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -83,6 +83,8 @@ namespace osu.Game.Online.API state.Value = APIState.Offline; } + public IHubClientConnector GetHubConnector(string clientName, string endpoint) => null; + public RegistrationRequest.RegistrationRequestErrors CreateAccount(string email, string username, string password) { Thread.Sleep(200); diff --git a/osu.Game/Online/API/IAPIProvider.cs b/osu.Game/Online/API/IAPIProvider.cs index 1951dfaf40..34b7dc5f17 100644 --- a/osu.Game/Online/API/IAPIProvider.cs +++ b/osu.Game/Online/API/IAPIProvider.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. +#nullable enable + using System.Threading.Tasks; using osu.Framework.Bindables; using osu.Game.Users; @@ -95,6 +97,13 @@ namespace osu.Game.Online.API /// void Logout(); + /// + /// Constructs a new . May be null if not supported. + /// + /// The name of the client this connector connects for, used for logging. + /// The endpoint to the hub. + IHubClientConnector? GetHubConnector(string clientName, string endpoint); + /// /// Create a new user account. This is a blocking operation. /// diff --git a/osu.Game/Online/HubClientConnector.cs b/osu.Game/Online/HubClientConnector.cs index 2298ac4243..7884a294d3 100644 --- a/osu.Game/Online/HubClientConnector.cs +++ b/osu.Game/Online/HubClientConnector.cs @@ -16,15 +16,12 @@ using osu.Game.Online.API; namespace osu.Game.Online { - /// - /// A component that manages the life cycle of a connection to a SignalR Hub. - /// - public class HubClientConnector : IDisposable + public class HubClientConnector : IHubClientConnector { /// /// Invoked whenever a new hub connection is built, to configure it before it's started. /// - public Action? ConfigureConnection; + public Action? ConfigureConnection { get; set; } private readonly string clientName; private readonly string endpoint; diff --git a/osu.Game/Online/IHubClientConnector.cs b/osu.Game/Online/IHubClientConnector.cs new file mode 100644 index 0000000000..d2ceb1f030 --- /dev/null +++ b/osu.Game/Online/IHubClientConnector.cs @@ -0,0 +1,34 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using System; +using Microsoft.AspNetCore.SignalR.Client; +using osu.Framework.Bindables; +using osu.Game.Online.API; + +namespace osu.Game.Online +{ + /// + /// A component that manages the life cycle of a connection to a SignalR Hub. + /// Should generally be retrieved from an . + /// + public interface IHubClientConnector : IDisposable + { + /// + /// The current connection opened by this connector. + /// + HubConnection? CurrentConnection { get; } + + /// + /// Whether this is connected to the hub, use to access the connection, if this is true. + /// + IBindable IsConnected { get; } + + /// + /// Invoked whenever a new hub connection is built, to configure it before it's started. + /// + public Action? ConfigureConnection { get; set; } + } +} diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index ba2a8d7246..95d76f384f 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -17,7 +17,8 @@ namespace osu.Game.Online.Multiplayer public class MultiplayerClient : StatefulMultiplayerClient { private readonly string endpoint; - private HubClientConnector? connector; + + private IHubClientConnector? connector; public override IBindable IsConnected { get; } = new BindableBool(); @@ -31,9 +32,11 @@ namespace osu.Game.Online.Multiplayer [BackgroundDependencyLoader] private void load(IAPIProvider api) { - connector = new HubClientConnector(nameof(MultiplayerClient), endpoint, api) + connector = api.GetHubConnector(nameof(MultiplayerClient), endpoint); + + if (connector != null) { - ConfigureConnection = connection => + connector.ConfigureConnection = connection => { // this is kind of SILLY // https://github.com/dotnet/aspnetcore/issues/15198 @@ -48,10 +51,10 @@ namespace osu.Game.Online.Multiplayer connection.On(nameof(IMultiplayerClient.ResultsReady), ((IMultiplayerClient)this).ResultsReady); connection.On>(nameof(IMultiplayerClient.UserModsChanged), ((IMultiplayerClient)this).UserModsChanged); connection.On(nameof(IMultiplayerClient.UserBeatmapAvailabilityChanged), ((IMultiplayerClient)this).UserBeatmapAvailabilityChanged); - }, - }; + }; - IsConnected.BindTo(connector.IsConnected); + IsConnected.BindTo(connector.IsConnected); + } } protected override Task JoinRoom(long roomId) diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 7e61da9b87..3a586874fe 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -33,7 +33,7 @@ namespace osu.Game.Online.Spectator private readonly string endpoint; [CanBeNull] - private HubClientConnector connector; + private IHubClientConnector connector; private readonly IBindable isConnected = new BindableBool(); @@ -86,7 +86,7 @@ namespace osu.Game.Online.Spectator [BackgroundDependencyLoader] private void load(IAPIProvider api) { - connector = CreateConnector(nameof(SpectatorStreamingClient), endpoint, api); + connector = api.GetHubConnector(nameof(SpectatorStreamingClient), endpoint); if (connector != null) { @@ -129,8 +129,6 @@ namespace osu.Game.Online.Spectator } } - protected virtual HubClientConnector CreateConnector(string name, string endpoint, IAPIProvider api) => new HubClientConnector(name, endpoint, api); - Task ISpectatorClient.UserBeganPlaying(int userId, SpectatorState state) { if (!playingUsers.Contains(userId)) From 55d5d8d5be4c3ad504681d1fa0d605429ccad684 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 14 Feb 2021 23:31:57 +0900 Subject: [PATCH 0567/1791] Send version hash on hub connection --- osu.Game/Online/API/APIAccess.cs | 7 +++++-- osu.Game/Online/HubClientConnector.cs | 11 +++++++++-- osu.Game/OsuGameBase.cs | 2 +- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 657487971b..8ffa0221c8 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -25,6 +25,8 @@ namespace osu.Game.Online.API { private readonly OsuConfigManager config; + private readonly string versionHash; + private readonly OAuth authentication; private readonly Queue queue = new Queue(); @@ -56,9 +58,10 @@ namespace osu.Game.Online.API private readonly Logger log; - public APIAccess(OsuConfigManager config, EndpointConfiguration endpointConfiguration) + public APIAccess(OsuConfigManager config, EndpointConfiguration endpointConfiguration, string versionHash) { this.config = config; + this.versionHash = versionHash; APIEndpointUrl = endpointConfiguration.APIEndpointUrl; WebsiteRootUrl = endpointConfiguration.WebsiteRootUrl; @@ -243,7 +246,7 @@ namespace osu.Game.Online.API this.password = password; } - public IHubClientConnector GetHubConnector(string clientName, string endpoint) => new HubClientConnector(clientName, endpoint, this); + 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/Online/HubClientConnector.cs b/osu.Game/Online/HubClientConnector.cs index 7884a294d3..fdb21c5000 100644 --- a/osu.Game/Online/HubClientConnector.cs +++ b/osu.Game/Online/HubClientConnector.cs @@ -25,6 +25,7 @@ namespace osu.Game.Online private readonly string clientName; private readonly string endpoint; + private readonly string versionHash; private readonly IAPIProvider api; /// @@ -49,11 +50,13 @@ namespace osu.Game.Online /// The name of the client this connector connects for, used for logging. /// The endpoint to the hub. /// An API provider used to react to connection state changes. - public HubClientConnector(string clientName, string endpoint, IAPIProvider api) + /// The hash representing the current game version, used for verification purposes. + public HubClientConnector(string clientName, string endpoint, IAPIProvider api, string versionHash) { this.clientName = clientName; this.endpoint = endpoint; this.api = api; + this.versionHash = versionHash; apiState.BindTo(api.State); apiState.BindValueChanged(state => @@ -129,7 +132,11 @@ namespace osu.Game.Online private HubConnection buildConnection(CancellationToken cancellationToken) { var builder = new HubConnectionBuilder() - .WithUrl(endpoint, options => { options.Headers.Add("Authorization", $"Bearer {api.AccessToken}"); }); + .WithUrl(endpoint, options => + { + options.Headers.Add("Authorization", $"Bearer {api.AccessToken}"); + options.Headers.Add("OsuVersionHash", versionHash); + }); if (RuntimeInfo.SupportsJIT) builder.AddMessagePackProtocol(); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 174b5006a2..00b436931a 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -228,7 +228,7 @@ namespace osu.Game MessageFormatter.WebsiteRootUrl = endpoints.WebsiteRootUrl; - dependencies.CacheAs(API ??= new APIAccess(LocalConfig, endpoints)); + dependencies.CacheAs(API ??= new APIAccess(LocalConfig, endpoints, VersionHash)); dependencies.CacheAs(spectatorStreaming = new SpectatorStreamingClient(endpoints)); dependencies.CacheAs(multiplayerClient = new MultiplayerClient(endpoints)); From 3562fddc27a2f4c31c3b93c93f70e660f8207dd4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Feb 2021 17:02:07 +0900 Subject: [PATCH 0568/1791] Add missing nullability flag on CreateAccount return value --- osu.Game/Online/API/IAPIProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/IAPIProvider.cs b/osu.Game/Online/API/IAPIProvider.cs index 34b7dc5f17..3a77b9cfee 100644 --- a/osu.Game/Online/API/IAPIProvider.cs +++ b/osu.Game/Online/API/IAPIProvider.cs @@ -111,6 +111,6 @@ namespace osu.Game.Online.API /// The username to create the account with. /// The password to create the account with. /// Any errors encoutnered during account creation. - RegistrationRequest.RegistrationRequestErrors CreateAccount(string email, string username, string password); + RegistrationRequest.RegistrationRequestErrors? CreateAccount(string email, string username, string password); } } From de52b8a5ba0dc72f41d694c2ba7d1aa583276486 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Feb 2021 17:14:41 +0900 Subject: [PATCH 0569/1791] Fix test failures in PerformFromScreen tests --- osu.Game/Overlays/Volume/VolumeMeter.cs | 2 ++ osu.Game/Screens/BackgroundScreen.cs | 10 +++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index 07accf8820..5b997bbd05 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -176,6 +176,7 @@ namespace osu.Game.Overlays.Volume } } }; + Bindable.ValueChanged += volume => { this.TransformTo("DisplayVolume", @@ -183,6 +184,7 @@ namespace osu.Game.Overlays.Volume 400, Easing.OutQuint); }; + bgProgress.Current.Value = 0.75f; } diff --git a/osu.Game/Screens/BackgroundScreen.cs b/osu.Game/Screens/BackgroundScreen.cs index c81362eebe..48c5523883 100644 --- a/osu.Game/Screens/BackgroundScreen.cs +++ b/osu.Game/Screens/BackgroundScreen.cs @@ -68,15 +68,19 @@ namespace osu.Game.Screens public override bool OnExiting(IScreen next) { - this.FadeOut(transition_length, Easing.OutExpo); - this.MoveToX(x_movement_amount, transition_length, Easing.OutExpo); + if (IsLoaded) + { + this.FadeOut(transition_length, Easing.OutExpo); + this.MoveToX(x_movement_amount, transition_length, Easing.OutExpo); + } return base.OnExiting(next); } public override void OnResuming(IScreen last) { - this.MoveToX(0, transition_length, Easing.OutExpo); + if (IsLoaded) + this.MoveToX(0, transition_length, Easing.OutExpo); base.OnResuming(last); } } From 6bfc7da671c355c5586cb556f473ae7773cecefb Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 15 Feb 2021 18:10:45 +0900 Subject: [PATCH 0570/1791] Fix sample potentially playing at the wrong frequency Co-authored-by: Dean Herbert --- .../OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs index 1888bf06bd..c9fb234ccc 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs @@ -117,8 +117,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match if (sampleReadyCount == null) return; - var channel = sampleReadyCount.Play(); + var channel = sampleReadyCount.GetChannel(); channel.Frequency.Value = 0.77f + countReady * 0.06f; + channel.Play(); } private void updateButtonColour(bool green) From 1ac274e478b8fcb50d3cad3bf95f1c40d585231e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Feb 2021 21:22:18 +0900 Subject: [PATCH 0571/1791] 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 d88a11257d..e30416bc1c 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 d68a8a515c..cccebeb023 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 87ebd41fee..137c96a72d 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -91,7 +91,7 @@ - + From 72b2123500f28257f140f6fc5f443e85973f99cf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Feb 2021 21:42:35 +0900 Subject: [PATCH 0572/1791] Update nunit in line with framework --- osu.Game.Benchmarks/osu.Game.Benchmarks.csproj | 2 +- .../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 +- osu.Game/osu.Game.csproj | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj index 7805bfcefc..ea43d9a54c 100644 --- a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj +++ b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj @@ -8,7 +8,7 @@ - + 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 54fddc297e..bf3aba5859 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 @@ -3,7 +3,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 d55b4fe08a..fcc0cafefc 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 @@ -3,7 +3,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 345c3e6d35..b4c686ccea 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 @@ -3,7 +3,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 2a5a2e2fdb..2b084f3bee 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 @@ -3,7 +3,7 @@ - + diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index d29ed94b5f..7e3868bd3b 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj index 185b35e40d..77ae06d89c 100644 --- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj +++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj @@ -6,7 +6,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index cccebeb023..72f680f6f8 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -33,7 +33,7 @@ - + From a1496cd8f3afed32bd9c126a18dc8a3714ca6212 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 9 Feb 2021 08:28:09 +0300 Subject: [PATCH 0573/1791] Remove necessity of using `CurrentModeRank` as a fallback --- .../TestSceneMultiplayerParticipantsList.cs | 10 ++++++++-- .../Multiplayer/Participants/ParticipantPanel.cs | 4 ---- osu.Game/Users/User.cs | 9 ++++++--- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs index 0f7a9b442d..5a0234e379 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs @@ -155,7 +155,10 @@ namespace osu.Game.Tests.Visual.Multiplayer { Id = i, Username = $"User {i}", - CurrentModeRank = RNG.Next(1, 100000), + AllStatistics = + { + { Ruleset.Value, new UserStatistics { GlobalRank = RNG.Next(1, 100000) } } + }, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", }); @@ -193,7 +196,10 @@ namespace osu.Game.Tests.Visual.Multiplayer { Id = 0, Username = "User 0", - CurrentModeRank = RNG.Next(1, 100000), + AllStatistics = + { + { Ruleset.Value, new UserStatistics { GlobalRank = RNG.Next(1, 100000) } } + }, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", }); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index 74bc86f279..e78264223e 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs @@ -165,10 +165,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants var ruleset = rulesets.GetRuleset(Room.Settings.RulesetID); var currentModeRank = User.User?.GetStatisticsFor(ruleset)?.GlobalRank; - - // fallback to current mode rank for testing purposes. - currentModeRank ??= User.User?.CurrentModeRank; - userRankText.Text = currentModeRank != null ? $"#{currentModeRank.Value:N0}" : string.Empty; userStateDisplay.UpdateStatus(User.State, User.BeatmapAvailability); diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs index 467f00e409..621d70301d 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -243,7 +243,10 @@ namespace osu.Game.Users [JsonExtensionData] private readonly IDictionary otherProperties = new Dictionary(); - private readonly Dictionary statisticsCache = new Dictionary(); + /// + /// Map for ruleset with their associated user statistics, can be altered for testing purposes. + /// + internal readonly Dictionary AllStatistics = new Dictionary(); /// /// Retrieves the user statistics for a certain ruleset. @@ -254,10 +257,10 @@ namespace osu.Game.Users // todo: this should likely be moved to a separate UserCompact class at some point. public UserStatistics GetStatisticsFor(RulesetInfo ruleset) { - if (statisticsCache.TryGetValue(ruleset, out var existing)) + if (AllStatistics.TryGetValue(ruleset, out var existing)) return existing; - return statisticsCache[ruleset] = parseStatisticsFor(ruleset); + return AllStatistics[ruleset] = parseStatisticsFor(ruleset); } private UserStatistics parseStatisticsFor(RulesetInfo ruleset) From 1466f36649f12edf22942b01c5bf278b1ee55f17 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 9 Feb 2021 08:55:50 +0300 Subject: [PATCH 0574/1791] Improve documentation on `Statistics` --- osu.Game/Users/User.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs index 621d70301d..58f25703fc 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -185,9 +185,8 @@ namespace osu.Game.Users private UserStatistics statistics; /// - /// The user statistics of the ruleset specified within the API request. - /// If the user is fetched from a or similar - /// (i.e. is a user compact instance), use instead. + /// User statistics for the requested ruleset (in the case of a response). + /// Otherwise empty. /// [JsonProperty(@"statistics")] public UserStatistics Statistics From 62514f23b59334ef3c6736941379a34c5ff0daf0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 9 Feb 2021 08:56:01 +0300 Subject: [PATCH 0575/1791] Remove unnecessary json settings override --- osu.Game/Users/User.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs index 58f25703fc..b8ca345f5c 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -267,9 +267,7 @@ namespace osu.Game.Users if (!(otherProperties.TryGetValue($"statistics_{ruleset.ShortName}", out var token))) return new UserStatistics(); - var settings = JsonSerializableExtensions.CreateGlobalSettings(); - settings.DefaultValueHandling = DefaultValueHandling.Include; - return token.ToObject(JsonSerializer.Create(settings)); + return token.ToObject(JsonSerializer.Create(JsonSerializableExtensions.CreateGlobalSettings())); } public override string ToString() => Username; From d15ffff9a57e6a09a1ecb6e196165eccd16c72e4 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 9 Feb 2021 09:54:17 +0300 Subject: [PATCH 0576/1791] Simplifiy user statistics retrieval to one-time on deserialization --- .../TestSceneMultiplayerParticipantsList.cs | 4 +- .../Participants/ParticipantPanel.cs | 3 +- osu.Game/Users/User.cs | 42 +++++++------------ 3 files changed, 20 insertions(+), 29 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs index 5a0234e379..5da5ab74b2 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs @@ -157,7 +157,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Username = $"User {i}", AllStatistics = { - { Ruleset.Value, new UserStatistics { GlobalRank = RNG.Next(1, 100000) } } + { Ruleset.Value.ShortName, new UserStatistics { GlobalRank = RNG.Next(1, 100000) } } }, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", }); @@ -198,7 +198,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Username = "User 0", AllStatistics = { - { Ruleset.Value, new UserStatistics { GlobalRank = RNG.Next(1, 100000) } } + { Ruleset.Value.ShortName, new UserStatistics { GlobalRank = RNG.Next(1, 100000) } } }, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", }); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index e78264223e..49d3bfc2dc 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; @@ -164,7 +165,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants var ruleset = rulesets.GetRuleset(Room.Settings.RulesetID); - var currentModeRank = User.User?.GetStatisticsFor(ruleset)?.GlobalRank; + var currentModeRank = User.User?.AllStatistics.GetValueOrDefault(ruleset.ShortName)?.GlobalRank; userRankText.Text = currentModeRank != null ? $"#{currentModeRank.Value:N0}" : string.Empty; userStateDisplay.UpdateStatus(User.State, User.BeatmapAvailability); diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs index b8ca345f5c..2c2f293aac 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -5,13 +5,13 @@ using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; +using System.Runtime.Serialization; using JetBrains.Annotations; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using osu.Framework.Bindables; using osu.Game.IO.Serialization; using osu.Game.Online.API.Requests; -using osu.Game.Rulesets; namespace osu.Game.Users { @@ -238,36 +238,26 @@ namespace osu.Game.Users [JsonProperty("replays_watched_counts")] public UserHistoryCount[] ReplaysWatchedCounts; + /// + /// All user statistics per ruleset's short name (in the case of a response). + /// Otherwise empty. Can be altered for testing purposes. + /// + // todo: this should likely be moved to a separate UserCompact class at some point. + [UsedImplicitly] + public readonly Dictionary AllStatistics = new Dictionary(); + [UsedImplicitly] [JsonExtensionData] private readonly IDictionary otherProperties = new Dictionary(); - /// - /// Map for ruleset with their associated user statistics, can be altered for testing purposes. - /// - internal readonly Dictionary AllStatistics = new Dictionary(); - - /// - /// Retrieves the user statistics for a certain ruleset. - /// If user is fetched from a , - /// this will always return empty instance, use instead. - /// - /// The ruleset to retrieve statistics for. - // todo: this should likely be moved to a separate UserCompact class at some point. - public UserStatistics GetStatisticsFor(RulesetInfo ruleset) + [OnDeserialized] + private void onDeserialized(StreamingContext context) { - if (AllStatistics.TryGetValue(ruleset, out var existing)) - return existing; - - return AllStatistics[ruleset] = parseStatisticsFor(ruleset); - } - - private UserStatistics parseStatisticsFor(RulesetInfo ruleset) - { - if (!(otherProperties.TryGetValue($"statistics_{ruleset.ShortName}", out var token))) - return new UserStatistics(); - - return token.ToObject(JsonSerializer.Create(JsonSerializableExtensions.CreateGlobalSettings())); + foreach (var kvp in otherProperties.Where(kvp => kvp.Key.StartsWith("statistics_", StringComparison.Ordinal))) + { + var shortName = kvp.Key.Replace("statistics_", string.Empty); + AllStatistics[shortName] = kvp.Value.ToObject(JsonSerializer.Create(JsonSerializableExtensions.CreateGlobalSettings())); + } } public override string ToString() => Username; From e838a5d9f09ae14738981a9d49ec002f353952e6 Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 15 Feb 2021 18:38:54 -0800 Subject: [PATCH 0577/1791] Fix comment edited at date not showing tooltip --- osu.Game/Overlays/Comments/DrawableComment.cs | 30 +++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index 31aa41e967..7c47ac655f 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -9,7 +9,6 @@ using osuTK; using osu.Game.Online.API.Requests.Responses; using osu.Game.Users.Drawables; using osu.Game.Graphics.Containers; -using osu.Game.Utils; using osu.Framework.Graphics.Cursor; using osu.Framework.Bindables; using System.Linq; @@ -245,11 +244,32 @@ namespace osu.Game.Overlays.Comments if (Comment.EditedAt.HasValue) { - info.Add(new OsuSpriteText + var font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular); + var colour = colourProvider.Foreground1; + + info.Add(new FillFlowContainer { - Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular), - Text = $@"edited {HumanizerUtils.Humanize(Comment.EditedAt.Value)} by {Comment.EditedUser.Username}", - Colour = colourProvider.Foreground1 + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new OsuSpriteText + { + Font = font, + Text = "edited ", + Colour = colour + }, + new DrawableDate(Comment.EditedAt.Value) + { + Font = font, + Colour = colour + }, + new OsuSpriteText + { + Font = font, + Text = $@" by {Comment.EditedUser.Username}", + Colour = colour + }, + } }); } From 02417697e994eb10183d4ba115c3085351a04a6d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Feb 2021 13:32:14 +0900 Subject: [PATCH 0578/1791] Display remaining attempts for playlist rooms with room-level attempt limits --- osu.Game/Online/Rooms/ItemAttemptsCount.cs | 19 +++++++++++++ .../Online/Rooms/PlaylistAggregateScore.cs | 16 +++++++++++ osu.Game/Online/Rooms/Room.cs | 5 ++++ .../Components/RoomLocalUserInfo.cs | 27 +++++++++++++++---- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 3 +++ 5 files changed, 65 insertions(+), 5 deletions(-) create mode 100644 osu.Game/Online/Rooms/ItemAttemptsCount.cs create mode 100644 osu.Game/Online/Rooms/PlaylistAggregateScore.cs diff --git a/osu.Game/Online/Rooms/ItemAttemptsCount.cs b/osu.Game/Online/Rooms/ItemAttemptsCount.cs new file mode 100644 index 0000000000..298603d778 --- /dev/null +++ b/osu.Game/Online/Rooms/ItemAttemptsCount.cs @@ -0,0 +1,19 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Newtonsoft.Json; + +namespace osu.Game.Online.Rooms +{ + /// + /// Represents attempts on a specific playlist item. + /// + public class ItemAttemptsCount + { + [JsonProperty("id")] + public int PlaylistItemID { get; set; } + + [JsonProperty("attempts")] + public int Attempts { get; set; } + } +} diff --git a/osu.Game/Online/Rooms/PlaylistAggregateScore.cs b/osu.Game/Online/Rooms/PlaylistAggregateScore.cs new file mode 100644 index 0000000000..61e0951cd5 --- /dev/null +++ b/osu.Game/Online/Rooms/PlaylistAggregateScore.cs @@ -0,0 +1,16 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Newtonsoft.Json; + +namespace osu.Game.Online.Rooms +{ + /// + /// Represents aggregated score for the local user for a playlist. + /// + public class PlaylistAggregateScore + { + [JsonProperty("playlist_item_attempts")] + public ItemAttemptsCount[] PlaylistItemAttempts { get; set; } + } +} diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index 763ba25d52..10a60ab374 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -72,6 +72,10 @@ namespace osu.Game.Online.Rooms [JsonIgnore] public readonly Bindable MaxParticipants = new Bindable(); + [Cached] + [JsonProperty("current_user_score")] + public readonly Bindable UserScore = new Bindable(); + [Cached] [JsonProperty("recent_participants")] public readonly BindableList RecentParticipants = new BindableList(); @@ -144,6 +148,7 @@ namespace osu.Game.Online.Rooms MaxParticipants.Value = other.MaxParticipants.Value; ParticipantCount.Value = other.ParticipantCount.Value; EndDate.Value = other.EndDate.Value; + UserScore.Value = other.UserScore.Value; if (EndDate.Value != null && DateTimeOffset.Now >= EndDate.Value) Status.Value = new RoomStatusEnded(); diff --git a/osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs b/osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs index f52e59b0c8..2206726beb 100644 --- a/osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs +++ b/osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.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.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; @@ -39,12 +41,27 @@ namespace osu.Game.Screens.OnlinePlay.Components { base.LoadComplete(); - MaxAttempts.BindValueChanged(attempts => + MaxAttempts.BindValueChanged(_ => updateAttempts()); + UserScore.BindValueChanged(_ => updateAttempts(), true); + } + + private void updateAttempts() + { + if (MaxAttempts.Value != null) { - attemptDisplay.Text = attempts.NewValue == null - ? string.Empty - : $"Maximum attempts: {attempts.NewValue:N0}"; - }, true); + attemptDisplay.Text = $"Maximum attempts: {MaxAttempts.Value:N0}"; + + if (UserScore.Value != null) + { + int remaining = MaxAttempts.Value.Value - UserScore.Value.PlaylistItemAttempts.Sum(a => a.Attempts); + attemptDisplay.Text += $" ({remaining} remaining)"; + } + } + + else + { + attemptDisplay.Text = string.Empty; + } } } } diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index b2f3e4a1d9..f7a51230eb 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -42,6 +42,9 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(typeof(Room))] protected Bindable MaxAttempts { get; private set; } + [Resolved(typeof(Room))] + public Bindable UserScore { get; private set; } + [Resolved(typeof(Room))] protected Bindable EndDate { get; private set; } From 5b4999e8afd8779e0da7cd153f3a59beaa59ea3c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 16 Feb 2021 04:51:21 +0300 Subject: [PATCH 0579/1791] Update user statistics retrieval with API changes --- .../TestSceneMultiplayerParticipantsList.cs | 5 +++-- .../Participants/ParticipantPanel.cs | 2 +- osu.Game/Users/User.cs | 22 +++---------------- 3 files changed, 7 insertions(+), 22 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs index 5da5ab74b2..a7398ebf02 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; @@ -155,7 +156,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Id = i, Username = $"User {i}", - AllStatistics = + RulesetsStatistics = new Dictionary { { Ruleset.Value.ShortName, new UserStatistics { GlobalRank = RNG.Next(1, 100000) } } }, @@ -196,7 +197,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Id = 0, Username = "User 0", - AllStatistics = + RulesetsStatistics = new Dictionary { { Ruleset.Value.ShortName, new UserStatistics { GlobalRank = RNG.Next(1, 100000) } } }, diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index 49d3bfc2dc..25bc314f1b 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs @@ -165,7 +165,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants var ruleset = rulesets.GetRuleset(Room.Settings.RulesetID); - var currentModeRank = User.User?.AllStatistics.GetValueOrDefault(ruleset.ShortName)?.GlobalRank; + var currentModeRank = User.User?.RulesetsStatistics?.GetValueOrDefault(ruleset.ShortName)?.GlobalRank; userRankText.Text = currentModeRank != null ? $"#{currentModeRank.Value:N0}" : string.Empty; userStateDisplay.UpdateStatus(User.State, User.BeatmapAvailability); diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs index 2c2f293aac..4a6fd540c7 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -5,12 +5,9 @@ using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; -using System.Runtime.Serialization; using JetBrains.Annotations; using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using osu.Framework.Bindables; -using osu.Game.IO.Serialization; using osu.Game.Online.API.Requests; namespace osu.Game.Users @@ -243,22 +240,9 @@ namespace osu.Game.Users /// Otherwise empty. Can be altered for testing purposes. /// // todo: this should likely be moved to a separate UserCompact class at some point. - [UsedImplicitly] - public readonly Dictionary AllStatistics = new Dictionary(); - - [UsedImplicitly] - [JsonExtensionData] - private readonly IDictionary otherProperties = new Dictionary(); - - [OnDeserialized] - private void onDeserialized(StreamingContext context) - { - foreach (var kvp in otherProperties.Where(kvp => kvp.Key.StartsWith("statistics_", StringComparison.Ordinal))) - { - var shortName = kvp.Key.Replace("statistics_", string.Empty); - AllStatistics[shortName] = kvp.Value.ToObject(JsonSerializer.Create(JsonSerializableExtensions.CreateGlobalSettings())); - } - } + [JsonProperty("statistics_rulesets")] + [CanBeNull] + public Dictionary RulesetsStatistics { get; set; } public override string ToString() => Username; From 0e7f52b5ccb1ae1a14147d1df9877d22024fde6e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 16 Feb 2021 07:28:51 +0300 Subject: [PATCH 0580/1791] Always use JSON property `global_rank` for global ranks instead --- .../TestSceneMultiplayerParticipantsList.cs | 16 ++++++++++-- .../Participants/ParticipantPanel.cs | 2 +- osu.Game/Users/UserStatistics.cs | 25 ++++++++----------- 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs index a7398ebf02..1e14bbbbea 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs @@ -158,7 +158,13 @@ namespace osu.Game.Tests.Visual.Multiplayer Username = $"User {i}", RulesetsStatistics = new Dictionary { - { Ruleset.Value.ShortName, new UserStatistics { GlobalRank = RNG.Next(1, 100000) } } + { + Ruleset.Value.ShortName, + new UserStatistics + { + Ranks = new UserStatistics.UserRanks { Global = RNG.Next(1, 100000) } + } + } }, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", }); @@ -199,7 +205,13 @@ namespace osu.Game.Tests.Visual.Multiplayer Username = "User 0", RulesetsStatistics = new Dictionary { - { Ruleset.Value.ShortName, new UserStatistics { GlobalRank = RNG.Next(1, 100000) } } + { + Ruleset.Value.ShortName, + new UserStatistics + { + Ranks = new UserStatistics.UserRanks { Global = RNG.Next(1, 100000) } + } + } }, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", }); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index 25bc314f1b..c4d11676e7 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs @@ -165,7 +165,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants var ruleset = rulesets.GetRuleset(Room.Settings.RulesetID); - var currentModeRank = User.User?.RulesetsStatistics?.GetValueOrDefault(ruleset.ShortName)?.GlobalRank; + var currentModeRank = User.User?.RulesetsStatistics?.GetValueOrDefault(ruleset.ShortName)?.Ranks.Global; userRankText.Text = currentModeRank != null ? $"#{currentModeRank.Value:N0}" : string.Empty; userStateDisplay.UpdateStatus(User.State, User.BeatmapAvailability); diff --git a/osu.Game/Users/UserStatistics.cs b/osu.Game/Users/UserStatistics.cs index 6c069f674e..1fed908c39 100644 --- a/osu.Game/Users/UserStatistics.cs +++ b/osu.Game/Users/UserStatistics.cs @@ -3,7 +3,6 @@ using System; using Newtonsoft.Json; -using osu.Game.Online.API.Requests; using osu.Game.Scoring; using osu.Game.Utils; using static osu.Game.Users.User; @@ -27,24 +26,22 @@ namespace osu.Game.Users public int Progress; } - /// - /// This must only be used when coming from condensed user responses (e.g. from ), otherwise use Ranks.Global. - /// - // todo: this should likely be moved to a separate UserStatisticsCompact class at some point. + [JsonProperty(@"rank")] + public UserRanks Ranks; + + // eventually UserRanks object will be completely replaced with separate global and country rank properties, see https://github.com/ppy/osu-web/blob/cb79bb72186c8f1a25f6a6f5ef315123decb4231/app/Transformers/UserStatisticsTransformer.php#L53. + // but for now, always point UserRanks.Global to the global_rank property, as that is included solely for requests like GetUsersRequest. [JsonProperty(@"global_rank")] - public int? GlobalRank; - - [JsonProperty(@"pp")] - public decimal? PP; - - [JsonProperty(@"pp_rank")] // the API sometimes only returns this value in condensed user responses - private int? rank + private int? globalRank { set => Ranks.Global = value; } - [JsonProperty(@"rank")] - public UserRanks Ranks; + [JsonProperty(@"pp")] + public decimal? PP; + + [JsonProperty(@"pp_rank")] + public int PPRank; [JsonProperty(@"ranked_score")] public long RankedScore; From e82922f8c545a6e82247c3fafb336094308f6c30 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Feb 2021 13:44:36 +0900 Subject: [PATCH 0581/1791] Add the ability to deselect the currently selected room via clicking away Always felt wrong that you couldn't do this until now. --- .../Multiplayer/TestSceneLoungeRoomsContainer.cs | 14 ++++++++++++++ .../OnlinePlay/Lounge/Components/RoomsContainer.cs | 10 ++++++++++ 2 files changed, 24 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index 279dcfa584..5682fd5c3c 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -69,6 +69,20 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("last room joined", () => RoomManager.Rooms.Last().Status.Value is JoinedRoomStatus); } + [Test] + public void TestClickDeselection() + { + AddRooms(1); + + AddAssert("no selection", () => checkRoomSelected(null)); + + press(Key.Down); + AddAssert("first room selected", () => checkRoomSelected(RoomManager.Rooms.First())); + + AddStep("click away", () => InputManager.Click(MouseButton.Left)); + AddAssert("no selection", () => checkRoomSelected(null)); + } + private void press(Key down) { AddStep($"press {down}", () => InputManager.Key(down)); diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index f70c33babe..134758d023 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -11,6 +11,7 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Framework.Threading; using osu.Game.Extensions; using osu.Game.Graphics.Cursor; @@ -42,6 +43,9 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components [Resolved(CanBeNull = true)] private LoungeSubScreen loungeSubScreen { get; set; } + // handle deselection + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; + public RoomsContainer() { RelativeSizeAxes = Axes.X; @@ -159,6 +163,12 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components JoinRequested?.Invoke(selectedRoom.Value); } + protected override bool OnClick(ClickEvent e) + { + selectRoom(null); + return base.OnClick(e); + } + #region Key selection logic (shared with BeatmapCarousel) public bool OnPressed(GlobalAction action) From e969ca8974d621eb4a089231f65b684f1a3d4f60 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Feb 2021 13:52:42 +0900 Subject: [PATCH 0582/1791] Remove unused using statement that rider could not identify --- osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs b/osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs index 2206726beb..c7d4ccd12e 100644 --- a/osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs +++ b/osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs @@ -3,7 +3,6 @@ using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; From 31a5cdd8ac3339ed680a948355c1087af3fa3fd7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Feb 2021 14:02:21 +0900 Subject: [PATCH 0583/1791] Fix current selection not updating visually after creating a new playlist --- .../Lounge/Components/RoomsContainer.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index f70c33babe..40102b1693 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -69,8 +69,16 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components rooms.BindTo(roomManager.Rooms); filter?.BindValueChanged(criteria => Filter(criteria.NewValue)); + + selectedRoom.BindValueChanged(selection => + { + updateSelection(); + }, true); } + private void updateSelection() => + roomFlow.Children.ForEach(r => r.State = r.Room == selectedRoom.Value ? SelectionState.Selected : SelectionState.NotSelected); + public void Filter(FilterCriteria criteria) { roomFlow.Children.ForEach(r => @@ -125,6 +133,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components } Filter(filter?.Value); + + updateSelection(); } private void removeRooms(IEnumerable rooms) @@ -146,11 +156,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components roomFlow.SetLayoutPosition(room, room.Room.Position.Value); } - private void selectRoom(Room room) - { - roomFlow.Children.ForEach(r => r.State = r.Room == room ? SelectionState.Selected : SelectionState.NotSelected); - selectedRoom.Value = room; - } + private void selectRoom(Room room) => selectedRoom.Value = room; private void joinSelected() { From 9ed45ce1ca49c365a9a54cd7af71c7513ba5f233 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Feb 2021 14:31:00 +0900 Subject: [PATCH 0584/1791] Remove redundant double call to ValueChanged on UserMods change --- .../Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 2f50bee677..cc63f53ac0 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -271,7 +271,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer Playlist.BindCollectionChanged(onPlaylistChanged, true); BeatmapAvailability.BindValueChanged(updateBeatmapAvailability, true); - UserMods.BindValueChanged(onUserModsChanged); client.LoadRequested += onLoadRequested; client.RoomUpdated += onRoomUpdated; From 52e544aa678a5a2e2d42160299d2bf9cdfd88170 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Feb 2021 14:42:31 +0900 Subject: [PATCH 0585/1791] Revert "Remove redundant double call to ValueChanged on UserMods change" This reverts commit 9ed45ce1ca49c365a9a54cd7af71c7513ba5f233. --- .../Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index cc63f53ac0..2f50bee677 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -271,6 +271,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer Playlist.BindCollectionChanged(onPlaylistChanged, true); BeatmapAvailability.BindValueChanged(updateBeatmapAvailability, true); + UserMods.BindValueChanged(onUserModsChanged); client.LoadRequested += onLoadRequested; client.RoomUpdated += onRoomUpdated; From da42c6d2825d1a97d048c29f2bd4730b1dd989d5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Feb 2021 15:14:19 +0900 Subject: [PATCH 0586/1791] Expose FreeMods from OnlinePlaySongSelect --- osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs index b201c62b7f..3f2873cbc4 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs @@ -31,7 +31,8 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(typeof(Room), nameof(Room.Playlist))] protected BindableList Playlist { get; private set; } - private readonly Bindable> freeMods = new Bindable>(Array.Empty()); + protected readonly Bindable> FreeMods = new Bindable>(Array.Empty()); + private readonly FreeModSelectOverlay freeModSelectOverlay; private WorkingBeatmap initialBeatmap; @@ -45,7 +46,7 @@ namespace osu.Game.Screens.OnlinePlay freeModSelectOverlay = new FreeModSelectOverlay { - SelectedMods = { BindTarget = freeMods }, + SelectedMods = { BindTarget = FreeMods }, IsValidMod = IsValidFreeMod, }; } @@ -67,14 +68,14 @@ namespace osu.Game.Screens.OnlinePlay // At this point, Mods contains both the required and allowed mods. For selection purposes, it should only contain the required mods. // Similarly, freeMods is currently empty but should only contain the allowed mods. Mods.Value = Playlist.FirstOrDefault()?.RequiredMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty(); - freeMods.Value = Playlist.FirstOrDefault()?.AllowedMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty(); + FreeMods.Value = Playlist.FirstOrDefault()?.AllowedMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty(); Ruleset.BindValueChanged(onRulesetChanged); } private void onRulesetChanged(ValueChangedEvent ruleset) { - freeMods.Value = Array.Empty(); + FreeMods.Value = Array.Empty(); } protected sealed override bool OnStart() @@ -90,7 +91,7 @@ namespace osu.Game.Screens.OnlinePlay item.RequiredMods.AddRange(Mods.Value.Select(m => m.CreateCopy())); item.AllowedMods.Clear(); - item.AllowedMods.AddRange(freeMods.Value.Select(m => m.CreateCopy())); + item.AllowedMods.AddRange(FreeMods.Value.Select(m => m.CreateCopy())); SelectItem(item); return true; @@ -133,7 +134,7 @@ namespace osu.Game.Screens.OnlinePlay protected override IEnumerable<(FooterButton, OverlayContainer)> CreateFooterButtons() { var buttons = base.CreateFooterButtons().ToList(); - buttons.Insert(buttons.FindIndex(b => b.Item1 is FooterButtonMods) + 1, (new FooterButtonFreeMods { Current = freeMods }, freeModSelectOverlay)); + buttons.Insert(buttons.FindIndex(b => b.Item1 is FooterButtonMods) + 1, (new FooterButtonFreeMods { Current = FreeMods }, freeModSelectOverlay)); return buttons; } From fff1cb0b355e9b8984dbc950dd8d2d4412e01a94 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Feb 2021 15:13:57 +0900 Subject: [PATCH 0587/1791] Fix allowed mods not being copied when populating playlist items --- osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs index 0e8db6dfe5..21335fc90c 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs @@ -56,6 +56,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists item.RequiredMods.Clear(); item.RequiredMods.AddRange(Mods.Value.Select(m => m.CreateCopy())); + + item.AllowedMods.Clear(); + item.AllowedMods.AddRange(FreeMods.Value.Select(m => m.CreateCopy())); } } } From 97a7572cb8daebc642cabe44e4ef8bbd1a0c97b4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Feb 2021 15:14:48 +0900 Subject: [PATCH 0588/1791] Move UserModSelectOverlay to RoomSubScreen for Playlists consumption --- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 68 ++++++++++++++++++- .../Multiplayer/MultiplayerMatchSubScreen.cs | 49 +------------ 2 files changed, 67 insertions(+), 50 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index e755f8c405..24d42283f7 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -3,16 +3,20 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Online.Rooms; using osu.Game.Overlays; +using osu.Game.Overlays.Mods; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Play; @@ -25,6 +29,14 @@ namespace osu.Game.Screens.OnlinePlay.Match public override bool DisallowExternalBeatmapRulesetChanges => true; + private readonly ModSelectOverlay userModsSelectOverlay; + + /// + /// A container that provides controls for selection of user mods. + /// This will be shown/hidden automatically when applicable. + /// + protected Drawable UserModsSection; + private Sample sampleStart; [Resolved(typeof(Room), nameof(Room.Playlist))] @@ -53,9 +65,26 @@ namespace osu.Game.Screens.OnlinePlay.Match protected RoomSubScreen() { - AddInternal(BeatmapAvailablilityTracker = new OnlinePlayBeatmapAvailablilityTracker + AddRangeInternal(new Drawable[] { - SelectedItem = { BindTarget = SelectedItem } + BeatmapAvailablilityTracker = new OnlinePlayBeatmapAvailablilityTracker + { + SelectedItem = { BindTarget = SelectedItem } + }, + new Container + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Depth = float.MinValue, + RelativeSizeAxes = Axes.Both, + Height = 0.5f, + Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING }, + Child = userModsSelectOverlay = new UserModSelectOverlay + { + SelectedMods = { BindTarget = UserMods }, + IsValidMod = _ => false + } + }, }); } @@ -73,7 +102,8 @@ namespace osu.Game.Screens.OnlinePlay.Match base.LoadComplete(); SelectedItem.BindValueChanged(_ => Scheduler.AddOnce(selectedItemChanged)); - SelectedItem.Value = Playlist.FirstOrDefault(); + + Playlist.BindCollectionChanged(onPlaylistChanged, true); managerUpdated = beatmapManager.ItemUpdated.GetBoundCopy(); managerUpdated.BindValueChanged(beatmapUpdated); @@ -81,6 +111,22 @@ namespace osu.Game.Screens.OnlinePlay.Match UserMods.BindValueChanged(_ => Scheduler.AddOnce(UpdateMods)); } + public override bool OnBackButton() + { + if (userModsSelectOverlay.State.Value == Visibility.Visible) + { + userModsSelectOverlay.Hide(); + return true; + } + + return base.OnBackButton(); + } + + private void onPlaylistChanged(object sender, NotifyCollectionChangedEventArgs e) => + SelectedItem.Value = Playlist.FirstOrDefault(); + + protected void ShowUserModSelect() => userModsSelectOverlay.Show(); + public override void OnEntering(IScreen last) { base.OnEntering(last); @@ -131,6 +177,18 @@ namespace osu.Game.Screens.OnlinePlay.Match UpdateMods(); Ruleset.Value = SelectedItem.Value.Ruleset.Value; + + if (SelectedItem.Value?.AllowedMods.Any() != true) + { + UserModsSection?.Hide(); + userModsSelectOverlay.Hide(); + userModsSelectOverlay.IsValidMod = _ => false; + } + else + { + UserModsSection?.Show(); + userModsSelectOverlay.IsValidMod = m => SelectedItem.Value.AllowedMods.Any(a => a.GetType() == m.GetType()); + } } private void beatmapUpdated(ValueChangedEvent> weakSet) => Schedule(updateWorkingBeatmap); @@ -190,5 +248,9 @@ namespace osu.Game.Screens.OnlinePlay.Match track.RestartPoint = 0; } } + + private class UserModSelectOverlay : LocalPlayerModSelectOverlay + { + } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 2f50bee677..49ac9f64ff 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Collections.Specialized; using System.Diagnostics; using System.Linq; using JetBrains.Annotations; @@ -16,7 +15,6 @@ using osu.Framework.Threading; using osu.Game.Configuration; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; -using osu.Game.Overlays.Mods; using osu.Game.Rulesets.Mods; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Match; @@ -43,9 +41,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer [Resolved] private OngoingOperationTracker ongoingOperationTracker { get; set; } - private ModSelectOverlay userModsSelectOverlay; private MultiplayerMatchSettingsOverlay settingsOverlay; - private Drawable userModsSection; private IBindable isConnected; @@ -155,7 +151,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer new BeatmapSelectionControl { RelativeSizeAxes = Axes.X } } }, - userModsSection = new FillFlowContainer + UserModsSection = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -176,7 +172,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer Origin = Anchor.CentreLeft, Width = 90, Text = "Select", - Action = () => userModsSelectOverlay.Show() + Action = ShowUserModSelect, }, new ModDisplay { @@ -231,19 +227,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer new Dimension(GridSizeMode.AutoSize), } }, - new Container - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.Both, - Height = 0.5f, - Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING }, - Child = userModsSelectOverlay = new UserModSelectOverlay - { - SelectedMods = { BindTarget = UserMods }, - IsValidMod = _ => false - } - }, settingsOverlay = new MultiplayerMatchSettingsOverlay { RelativeSizeAxes = Axes.Both, @@ -269,7 +252,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { base.LoadComplete(); - Playlist.BindCollectionChanged(onPlaylistChanged, true); BeatmapAvailability.BindValueChanged(updateBeatmapAvailability, true); UserMods.BindValueChanged(onUserModsChanged); @@ -303,32 +285,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer return true; } - if (userModsSelectOverlay.State.Value == Visibility.Visible) - { - userModsSelectOverlay.Hide(); - return true; - } - return base.OnBackButton(); } - private void onPlaylistChanged(object sender, NotifyCollectionChangedEventArgs e) - { - SelectedItem.Value = Playlist.FirstOrDefault(); - - if (SelectedItem.Value?.AllowedMods.Any() != true) - { - userModsSection.Hide(); - userModsSelectOverlay.Hide(); - userModsSelectOverlay.IsValidMod = _ => false; - } - else - { - userModsSection.Show(); - userModsSelectOverlay.IsValidMod = m => SelectedItem.Value.AllowedMods.Any(a => a.GetType() == m.GetType()); - } - } - private ModSettingChangeTracker modSettingChangeTracker; private ScheduledDelegate debouncedModSettingsUpdate; @@ -433,9 +392,5 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer modSettingChangeTracker?.Dispose(); } - - private class UserModSelectOverlay : LocalPlayerModSelectOverlay - { - } } } From fdcb6384cb808e3f616a73a8343c98c5956cacf7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Feb 2021 15:14:56 +0900 Subject: [PATCH 0589/1791] Add user mod selection to playlists room screen --- .../Playlists/PlaylistsRoomSubScreen.cs | 46 ++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index 88731a10bc..31c441bcd2 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -13,7 +13,9 @@ using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Match; using osu.Game.Screens.OnlinePlay.Match.Components; +using osu.Game.Screens.Play.HUD; using osu.Game.Users; +using osuTK; using Footer = osu.Game.Screens.OnlinePlay.Match.Components.Footer; namespace osu.Game.Screens.OnlinePlay.Playlists @@ -140,13 +142,55 @@ namespace osu.Game.Screens.OnlinePlay.Playlists RelativeSizeAxes = Axes.Both, Content = new[] { - new Drawable[] { new OverlinedHeader("Leaderboard"), }, + new[] + { + UserModsSection = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Margin = new MarginPadding { Bottom = 10 }, + Children = new Drawable[] + { + new OverlinedHeader("Extra mods"), + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), + Children = new Drawable[] + { + new PurpleTriangleButton + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Width = 90, + Text = "Select", + Action = ShowUserModSelect, + }, + new ModDisplay + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + DisplayUnrankedText = false, + Current = UserMods, + Scale = new Vector2(0.8f), + }, + } + } + } + }, + }, + new Drawable[] + { + new OverlinedHeader("Leaderboard") + }, new Drawable[] { leaderboard = new MatchLeaderboard { RelativeSizeAxes = Axes.Both }, }, new Drawable[] { new OverlinedHeader("Chat"), }, new Drawable[] { new MatchChatDisplay { RelativeSizeAxes = Axes.Both } } }, RowDimensions = new[] { + new Dimension(GridSizeMode.AutoSize), new Dimension(GridSizeMode.AutoSize), new Dimension(), new Dimension(GridSizeMode.AutoSize), From f25b5147ef319c42329fd645490bf7ecc1907eb1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 16 Feb 2021 15:37:45 +0900 Subject: [PATCH 0590/1791] Select last playlist item in match subscreen --- .../Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index c5130baa94..76608fb5c1 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 private void onPlaylistChanged(object sender, NotifyCollectionChangedEventArgs e) { - SelectedItem.Value = Playlist.FirstOrDefault(); + SelectedItem.Value = Playlist.LastOrDefault(); if (SelectedItem.Value?.AllowedMods.Any() != true) { From 855d24dce769ae8ab65bd8f2daadfe638e6a76d8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 16 Feb 2021 17:35:44 +0900 Subject: [PATCH 0591/1791] Cache selected item bindable from RoomSubScreen --- osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs | 1 + osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 2946d07588..6a2844fa74 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -21,6 +21,7 @@ namespace osu.Game.Screens.OnlinePlay.Match [Cached(typeof(IPreviewTrackOwner))] public abstract class RoomSubScreen : OnlinePlaySubScreen, IPreviewTrackOwner { + [Cached(typeof(IBindable))] protected readonly Bindable SelectedItem = new Bindable(); public override bool DisallowExternalBeatmapRulesetChanges => true; diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index b2f3e4a1d9..c1aa93526f 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -2,10 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osu.Game.Online.Rooms; +using osu.Game.Screens.OnlinePlay.Match; using osu.Game.Users; namespace osu.Game.Screens.OnlinePlay @@ -50,5 +52,13 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(typeof(Room))] protected Bindable Duration { get; private set; } + + /// + /// The currently selected item in the . + /// May be null if this is not inside a . + /// + [CanBeNull] + [Resolved(typeof(Room), CanBeNull = true)] + protected IBindable SelectedItem { get; private set; } } } From 3ff9e14e35f851b32f40eb2339b6652beea37c2c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 16 Feb 2021 18:56:13 +0900 Subject: [PATCH 0592/1791] Make StatefulMultiplayerClient control current playlist item --- .../Multiplayer/MultiplayerRoomSettings.cs | 12 +++++-- .../Multiplayer/StatefulMultiplayerClient.cs | 35 ++++++++++--------- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 4 --- .../Multiplayer/MultiplayerMatchSubScreen.cs | 13 ++++--- .../Playlists/PlaylistsRoomSubScreen.cs | 7 ++-- 5 files changed, 39 insertions(+), 32 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs index 4fb9d724b5..04752f4e6f 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs @@ -36,18 +36,26 @@ namespace osu.Game.Online.Multiplayer [Key(5)] public IEnumerable AllowedMods { get; set; } = Enumerable.Empty(); + /// + /// Only used for client-side mutation. + /// + [Key(6)] + public int PlaylistItemId { get; set; } + public bool Equals(MultiplayerRoomSettings other) => BeatmapID == other.BeatmapID && BeatmapChecksum == other.BeatmapChecksum && RequiredMods.SequenceEqual(other.RequiredMods) && AllowedMods.SequenceEqual(other.AllowedMods) && RulesetID == other.RulesetID - && Name.Equals(other.Name, StringComparison.Ordinal); + && Name.Equals(other.Name, StringComparison.Ordinal) + && PlaylistItemId == other.PlaylistItemId; public override string ToString() => $"Name:{Name}" + $" Beatmap:{BeatmapID} ({BeatmapChecksum})" + $" RequiredMods:{string.Join(',', RequiredMods)}" + $" AllowedMods:{string.Join(',', AllowedMods)}" - + $" Ruleset:{RulesetID}"; + + $" Ruleset:{RulesetID}" + + $" Item:{PlaylistItemId}"; } } diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index 18464a5f61..f5f4c3a8ba 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -66,6 +66,8 @@ namespace osu.Game.Online.Multiplayer /// public readonly BindableList CurrentMatchPlayingUserIds = new BindableList(); + public readonly Bindable CurrentMatchPlayingItem = new Bindable(); + /// /// The corresponding to the local player, if available. /// @@ -94,9 +96,6 @@ namespace osu.Game.Online.Multiplayer private Room? apiRoom; - // Todo: This is temporary, until the multiplayer server returns the item id on match start or otherwise. - private int playlistItemId; - [BackgroundDependencyLoader] private void load() { @@ -142,7 +141,6 @@ namespace osu.Game.Online.Multiplayer { Room = joinedRoom; apiRoom = room; - playlistItemId = room.Playlist.SingleOrDefault()?.ID ?? 0; }, cancellationSource.Token); // Update room settings. @@ -218,7 +216,8 @@ namespace osu.Game.Online.Multiplayer BeatmapChecksum = item.GetOr(existingPlaylistItem).Beatmap.Value.MD5Hash, RulesetID = item.GetOr(existingPlaylistItem).RulesetID, RequiredMods = item.HasValue ? item.Value.AsNonNull().RequiredMods.Select(m => new APIMod(m)).ToList() : Room.Settings.RequiredMods, - AllowedMods = item.HasValue ? item.Value.AsNonNull().AllowedMods.Select(m => new APIMod(m)).ToList() : Room.Settings.AllowedMods + AllowedMods = item.HasValue ? item.Value.AsNonNull().AllowedMods.Select(m => new APIMod(m)).ToList() : Room.Settings.AllowedMods, + PlaylistItemId = Room.Settings.PlaylistItemId, }); } @@ -506,14 +505,13 @@ namespace osu.Game.Online.Multiplayer Room.Settings = settings; apiRoom.Name.Value = Room.Settings.Name; - // The playlist update is delayed until an online beatmap lookup (below) succeeds. - // In-order for the client to not display an outdated beatmap, the playlist is forcefully cleared here. - apiRoom.Playlist.Clear(); + // The current item update is delayed until an online beatmap lookup (below) succeeds. + // In-order for the client to not display an outdated beatmap, the current item is forcefully cleared here. + CurrentMatchPlayingItem.Value = null; RoomUpdated?.Invoke(); var req = new GetBeatmapSetRequest(settings.BeatmapID, BeatmapSetLookupType.BeatmapId); - req.Success += res => { if (cancellationToken.IsCancellationRequested) @@ -540,18 +538,21 @@ namespace osu.Game.Online.Multiplayer var mods = settings.RequiredMods.Select(m => m.ToMod(ruleset)); var allowedMods = settings.AllowedMods.Select(m => m.ToMod(ruleset)); - PlaylistItem playlistItem = new PlaylistItem - { - ID = playlistItemId, - Beatmap = { Value = beatmap }, - Ruleset = { Value = ruleset.RulesetInfo }, - }; + // Update an existing playlist item from the API room, or create a new item. + var playlistItem = apiRoom.Playlist.FirstOrDefault(i => i.ID == settings.PlaylistItemId); + if (playlistItem == null) + apiRoom.Playlist.Add(playlistItem = new PlaylistItem()); + + playlistItem.ID = settings.PlaylistItemId; + playlistItem.Beatmap.Value = beatmap; + playlistItem.Ruleset.Value = ruleset.RulesetInfo; + playlistItem.RequiredMods.Clear(); playlistItem.RequiredMods.AddRange(mods); + playlistItem.AllowedMods.Clear(); playlistItem.AllowedMods.AddRange(allowedMods); - apiRoom.Playlist.Clear(); // Clearing should be unnecessary, but here for sanity. - apiRoom.Playlist.Add(playlistItem); + CurrentMatchPlayingItem.Value = playlistItem; } /// diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 6a2844fa74..7740af9f63 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -28,9 +28,6 @@ namespace osu.Game.Screens.OnlinePlay.Match private Sample sampleStart; - [Resolved(typeof(Room), nameof(Room.Playlist))] - protected BindableList Playlist { get; private set; } - /// /// Any mods applied by/to the local user. /// @@ -74,7 +71,6 @@ namespace osu.Game.Screens.OnlinePlay.Match base.LoadComplete(); SelectedItem.BindValueChanged(_ => Scheduler.AddOnce(selectedItemChanged)); - SelectedItem.Value = Playlist.FirstOrDefault(); managerUpdated = beatmapManager.ItemUpdated.GetBoundCopy(); managerUpdated.BindValueChanged(beatmapUpdated); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 76608fb5c1..f55b5b9713 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Collections.Specialized; using System.Diagnostics; using System.Linq; using JetBrains.Annotations; @@ -269,7 +268,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { base.LoadComplete(); - Playlist.BindCollectionChanged(onPlaylistChanged, true); + SelectedItem.BindValueChanged(onSelectedItemChanged); + SelectedItem.BindTo(client.CurrentMatchPlayingItem); + BeatmapAvailability.BindValueChanged(updateBeatmapAvailability, true); UserMods.BindValueChanged(onUserModsChanged); @@ -300,11 +301,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer return base.OnBackButton(); } - private void onPlaylistChanged(object sender, NotifyCollectionChangedEventArgs e) + private void onSelectedItemChanged(ValueChangedEvent item) { - SelectedItem.Value = Playlist.LastOrDefault(); - - if (SelectedItem.Value?.AllowedMods.Any() != true) + if (item.NewValue?.AllowedMods.Any() != true) { userModsSection.Hide(); userModsSelectOverlay.Hide(); @@ -313,7 +312,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer else { userModsSection.Show(); - userModsSelectOverlay.IsValidMod = m => SelectedItem.Value.AllowedMods.Any(a => a.GetType() == m.GetType()); + userModsSelectOverlay.IsValidMod = m => item.NewValue.AllowedMods.Any(a => a.GetType() == m.GetType()); } } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index 88731a10bc..b4870ab580 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -27,6 +27,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists [Resolved(typeof(Room), nameof(Room.RoomID))] private Bindable roomId { get; set; } + [Resolved(typeof(Room), nameof(Room.Playlist))] + private BindableList playlist { get; set; } + private MatchSettingsOverlay settingsOverlay; private MatchLeaderboard leaderboard; @@ -117,7 +120,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists new DrawableRoomPlaylistWithResults { RelativeSizeAxes = Axes.Both, - Items = { BindTarget = Playlist }, + Items = { BindTarget = playlist }, SelectedItem = { BindTarget = SelectedItem }, RequestShowResults = item => { @@ -222,7 +225,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists // Set the first playlist item. // This is scheduled since updating the room and playlist may happen in an arbitrary order (via Room.CopyFrom()). - Schedule(() => SelectedItem.Value = Playlist.FirstOrDefault()); + Schedule(() => SelectedItem.Value = playlist.FirstOrDefault()); } }, true); } From 2a1096a3c8ca7a3c5f656963dcbcb2c6e7770eed Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 16 Feb 2021 19:02:16 +0900 Subject: [PATCH 0593/1791] Make BeatmapSelectionControl use the selected item --- .../Match/BeatmapSelectionControl.cs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs index f17e04d4d4..769596956b 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs @@ -1,14 +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 System.Collections.Specialized; -using System.Linq; +using System.Diagnostics; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using osu.Framework.Screens; using osu.Game.Online.API; +using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Match.Components; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match @@ -60,7 +61,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { base.LoadComplete(); - Playlist.BindCollectionChanged(onPlaylistChanged, true); + Debug.Assert(SelectedItem != null); + SelectedItem.BindValueChanged(onSelectedItemChanged, true); + Host.BindValueChanged(host => { if (RoomID.Value == null || host.NewValue?.Equals(api.LocalUser.Value) == true) @@ -70,12 +73,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match }, true); } - private void onPlaylistChanged(object sender, NotifyCollectionChangedEventArgs e) + private void onSelectedItemChanged(ValueChangedEvent selectedItem) { - if (Playlist.Any()) - beatmapPanelContainer.Child = new DrawableRoomPlaylistItem(Playlist.Single(), false, false); - else + if (selectedItem.NewValue == null) beatmapPanelContainer.Clear(); + else + beatmapPanelContainer.Child = new DrawableRoomPlaylistItem(selectedItem.NewValue, false, false); } } } From e24a5949c5a61c105e9f4670c07cdbccf0d7def6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 16 Feb 2021 19:26:51 +0900 Subject: [PATCH 0594/1791] Fix resolve --- osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index c1aa93526f..4c4b1ff6d0 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -58,7 +58,7 @@ namespace osu.Game.Screens.OnlinePlay /// May be null if this is not inside a . /// [CanBeNull] - [Resolved(typeof(Room), CanBeNull = true)] + [Resolved(CanBeNull = true)] protected IBindable SelectedItem { get; private set; } } } From 3e802531d384761c0be4eeabed1d9c923ada7fc0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 16 Feb 2021 19:29:40 +0900 Subject: [PATCH 0595/1791] Use long type where required in multiplayer --- osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs | 2 +- osu.Game/Online/Rooms/CreateRoomScoreRequest.cs | 6 +++--- osu.Game/Online/Rooms/GetRoomLeaderboardRequest.cs | 4 ++-- osu.Game/Online/Rooms/GetRoomRequest.cs | 4 ++-- osu.Game/Online/Rooms/IndexPlaylistScoresRequest.cs | 8 ++++---- osu.Game/Online/Rooms/MultiplayerScore.cs | 2 +- osu.Game/Online/Rooms/PlaylistItem.cs | 2 +- osu.Game/Online/Rooms/Room.cs | 2 +- osu.Game/Online/Rooms/ShowPlaylistUserScoreRequest.cs | 6 +++--- osu.Game/Online/Rooms/SubmitRoomScoreRequest.cs | 8 ++++---- osu.Game/Screens/OnlinePlay/Components/RoomManager.cs | 2 +- .../OnlinePlay/Match/Components/MatchChatDisplay.cs | 2 +- .../OnlinePlay/Match/Components/MatchLeaderboard.cs | 2 +- .../Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs | 2 +- .../OnlinePlay/Multiplayer/MultiplayerResultsScreen.cs | 2 +- osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs | 2 +- osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs | 2 +- .../OnlinePlay/Playlists/PlaylistsResultsScreen.cs | 4 ++-- .../OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs | 2 +- 19 files changed, 32 insertions(+), 32 deletions(-) diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index 18464a5f61..639dce9230 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -95,7 +95,7 @@ namespace osu.Game.Online.Multiplayer private Room? apiRoom; // Todo: This is temporary, until the multiplayer server returns the item id on match start or otherwise. - private int playlistItemId; + private long playlistItemId; [BackgroundDependencyLoader] private void load() diff --git a/osu.Game/Online/Rooms/CreateRoomScoreRequest.cs b/osu.Game/Online/Rooms/CreateRoomScoreRequest.cs index afd0dadc7e..d4303e77df 100644 --- a/osu.Game/Online/Rooms/CreateRoomScoreRequest.cs +++ b/osu.Game/Online/Rooms/CreateRoomScoreRequest.cs @@ -9,11 +9,11 @@ namespace osu.Game.Online.Rooms { public class CreateRoomScoreRequest : APIRequest { - private readonly int roomId; - private readonly int playlistItemId; + private readonly long roomId; + private readonly long playlistItemId; private readonly string versionHash; - public CreateRoomScoreRequest(int roomId, int playlistItemId, string versionHash) + public CreateRoomScoreRequest(long roomId, long playlistItemId, string versionHash) { this.roomId = roomId; this.playlistItemId = playlistItemId; diff --git a/osu.Game/Online/Rooms/GetRoomLeaderboardRequest.cs b/osu.Game/Online/Rooms/GetRoomLeaderboardRequest.cs index 15f1221a00..67e2a2b27f 100644 --- a/osu.Game/Online/Rooms/GetRoomLeaderboardRequest.cs +++ b/osu.Game/Online/Rooms/GetRoomLeaderboardRequest.cs @@ -7,9 +7,9 @@ namespace osu.Game.Online.Rooms { public class GetRoomLeaderboardRequest : APIRequest { - private readonly int roomId; + private readonly long roomId; - public GetRoomLeaderboardRequest(int roomId) + public GetRoomLeaderboardRequest(long roomId) { this.roomId = roomId; } diff --git a/osu.Game/Online/Rooms/GetRoomRequest.cs b/osu.Game/Online/Rooms/GetRoomRequest.cs index ce117075c7..853873901e 100644 --- a/osu.Game/Online/Rooms/GetRoomRequest.cs +++ b/osu.Game/Online/Rooms/GetRoomRequest.cs @@ -7,9 +7,9 @@ namespace osu.Game.Online.Rooms { public class GetRoomRequest : APIRequest { - public readonly int RoomId; + public readonly long RoomId; - public GetRoomRequest(int roomId) + public GetRoomRequest(long roomId) { RoomId = roomId; } diff --git a/osu.Game/Online/Rooms/IndexPlaylistScoresRequest.cs b/osu.Game/Online/Rooms/IndexPlaylistScoresRequest.cs index 43f80a2dc4..abce2093e3 100644 --- a/osu.Game/Online/Rooms/IndexPlaylistScoresRequest.cs +++ b/osu.Game/Online/Rooms/IndexPlaylistScoresRequest.cs @@ -15,8 +15,8 @@ namespace osu.Game.Online.Rooms /// public class IndexPlaylistScoresRequest : APIRequest { - public readonly int RoomId; - public readonly int PlaylistItemId; + public readonly long RoomId; + public readonly long PlaylistItemId; [CanBeNull] public readonly Cursor Cursor; @@ -24,13 +24,13 @@ namespace osu.Game.Online.Rooms [CanBeNull] public readonly IndexScoresParams IndexParams; - public IndexPlaylistScoresRequest(int roomId, int playlistItemId) + public IndexPlaylistScoresRequest(long roomId, long playlistItemId) { RoomId = roomId; PlaylistItemId = playlistItemId; } - public IndexPlaylistScoresRequest(int roomId, int playlistItemId, [NotNull] Cursor cursor, [NotNull] IndexScoresParams indexParams) + public IndexPlaylistScoresRequest(long roomId, long playlistItemId, [NotNull] Cursor cursor, [NotNull] IndexScoresParams indexParams) : this(roomId, playlistItemId) { Cursor = cursor; diff --git a/osu.Game/Online/Rooms/MultiplayerScore.cs b/osu.Game/Online/Rooms/MultiplayerScore.cs index 677a3d3026..30c1d2f826 100644 --- a/osu.Game/Online/Rooms/MultiplayerScore.cs +++ b/osu.Game/Online/Rooms/MultiplayerScore.cs @@ -18,7 +18,7 @@ namespace osu.Game.Online.Rooms public class MultiplayerScore { [JsonProperty("id")] - public int ID { get; set; } + public long ID { get; set; } [JsonProperty("user")] public User User { get; set; } diff --git a/osu.Game/Online/Rooms/PlaylistItem.cs b/osu.Game/Online/Rooms/PlaylistItem.cs index ada2140ca6..61982101c1 100644 --- a/osu.Game/Online/Rooms/PlaylistItem.cs +++ b/osu.Game/Online/Rooms/PlaylistItem.cs @@ -15,7 +15,7 @@ namespace osu.Game.Online.Rooms public class PlaylistItem : IEquatable { [JsonProperty("id")] - public int ID { get; set; } + public long ID { get; set; } [JsonProperty("beatmap_id")] public int BeatmapID { get; set; } diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index 763ba25d52..997f45ce52 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -17,7 +17,7 @@ namespace osu.Game.Online.Rooms { [Cached] [JsonProperty("id")] - public readonly Bindable RoomID = new Bindable(); + public readonly Bindable RoomID = new Bindable(); [Cached] [JsonProperty("name")] diff --git a/osu.Game/Online/Rooms/ShowPlaylistUserScoreRequest.cs b/osu.Game/Online/Rooms/ShowPlaylistUserScoreRequest.cs index 3f728a5417..ba3e3c6349 100644 --- a/osu.Game/Online/Rooms/ShowPlaylistUserScoreRequest.cs +++ b/osu.Game/Online/Rooms/ShowPlaylistUserScoreRequest.cs @@ -7,11 +7,11 @@ namespace osu.Game.Online.Rooms { public class ShowPlaylistUserScoreRequest : APIRequest { - private readonly int roomId; - private readonly int playlistItemId; + private readonly long roomId; + private readonly long playlistItemId; private readonly long userId; - public ShowPlaylistUserScoreRequest(int roomId, int playlistItemId, long userId) + public ShowPlaylistUserScoreRequest(long roomId, long playlistItemId, long userId) { this.roomId = roomId; this.playlistItemId = playlistItemId; diff --git a/osu.Game/Online/Rooms/SubmitRoomScoreRequest.cs b/osu.Game/Online/Rooms/SubmitRoomScoreRequest.cs index 5a78b9fabd..9e432fa99e 100644 --- a/osu.Game/Online/Rooms/SubmitRoomScoreRequest.cs +++ b/osu.Game/Online/Rooms/SubmitRoomScoreRequest.cs @@ -11,12 +11,12 @@ namespace osu.Game.Online.Rooms { public class SubmitRoomScoreRequest : APIRequest { - private readonly int scoreId; - private readonly int roomId; - private readonly int playlistItemId; + private readonly long scoreId; + private readonly long roomId; + private readonly long playlistItemId; private readonly ScoreInfo scoreInfo; - public SubmitRoomScoreRequest(int scoreId, int roomId, int playlistItemId, ScoreInfo scoreInfo) + public SubmitRoomScoreRequest(long scoreId, long roomId, long playlistItemId, ScoreInfo scoreInfo) { this.scoreId = scoreId; this.roomId = roomId; diff --git a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs index 2ed259e2b8..227a772b2d 100644 --- a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs +++ b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs @@ -116,7 +116,7 @@ namespace osu.Game.Screens.OnlinePlay.Components joinedRoom.Value = null; } - private readonly HashSet ignoredRooms = new HashSet(); + private readonly HashSet ignoredRooms = new HashSet(); private void onRoomsReceived(List received) { diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/MatchChatDisplay.cs b/osu.Game/Screens/OnlinePlay/Match/Components/MatchChatDisplay.cs index 6da2866236..a96d64cb5d 100644 --- a/osu.Game/Screens/OnlinePlay/Match/Components/MatchChatDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Match/Components/MatchChatDisplay.cs @@ -11,7 +11,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components public class MatchChatDisplay : StandAloneChatDisplay { [Resolved(typeof(Room), nameof(Room.RoomID))] - private Bindable roomId { get; set; } + private Bindable roomId { get; set; } [Resolved(typeof(Room), nameof(Room.ChannelId))] private Bindable channelId { get; set; } diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs b/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs index 50869f42ff..134e083c42 100644 --- a/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs +++ b/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs @@ -15,7 +15,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components public class MatchLeaderboard : Leaderboard { [Resolved(typeof(Room), nameof(Room.RoomID))] - private Bindable roomId { get; set; } + private Bindable roomId { get; set; } [BackgroundDependencyLoader] private void load() diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index f0064ae0b4..3199232f6f 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -357,7 +357,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match public class CreateOrUpdateButton : TriangleButton { [Resolved(typeof(Room), nameof(Room.RoomID))] - private Bindable roomId { get; set; } + private Bindable roomId { get; set; } protected override void LoadComplete() { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerResultsScreen.cs index e3b47b3254..140b3c45d8 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerResultsScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerResultsScreen.cs @@ -9,7 +9,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { public class MultiplayerResultsScreen : PlaylistsResultsScreen { - public MultiplayerResultsScreen(ScoreInfo score, int roomId, PlaylistItem playlistItem) + public MultiplayerResultsScreen(ScoreInfo score, long roomId, PlaylistItem playlistItem) : base(score, roomId, playlistItem, false, false) { } diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index b2f3e4a1d9..239db18a07 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -13,7 +13,7 @@ namespace osu.Game.Screens.OnlinePlay public class OnlinePlayComposite : CompositeDrawable { [Resolved(typeof(Room))] - protected Bindable RoomID { get; private set; } + protected Bindable RoomID { get; private set; } [Resolved(typeof(Room), nameof(Room.Name))] protected Bindable RoomName { get; private set; } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs index 38eae2346a..ddc88261f7 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs @@ -24,7 +24,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists public Action Exited; [Resolved(typeof(Room), nameof(Room.RoomID))] - protected Bindable RoomId { get; private set; } + protected Bindable RoomId { get; private set; } protected readonly PlaylistItem PlaylistItem; diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs index e13c8a9f82..2b252f9db7 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs @@ -19,7 +19,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { public class PlaylistsResultsScreen : ResultsScreen { - private readonly int roomId; + private readonly long roomId; private readonly PlaylistItem playlistItem; protected LoadingSpinner LeftSpinner { get; private set; } @@ -32,7 +32,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists [Resolved] private IAPIProvider api { get; set; } - public PlaylistsResultsScreen(ScoreInfo score, int roomId, PlaylistItem playlistItem, bool allowRetry, bool allowWatchingReplay = true) + public PlaylistsResultsScreen(ScoreInfo score, long roomId, PlaylistItem playlistItem, bool allowRetry, bool allowWatchingReplay = true) : base(score, allowRetry, allowWatchingReplay) { this.roomId = roomId; diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index 88731a10bc..9ccf4775d0 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -25,7 +25,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists public override string ShortTitle => "playlist"; [Resolved(typeof(Room), nameof(Room.RoomID))] - private Bindable roomId { get; set; } + private Bindable roomId { get; set; } private MatchSettingsOverlay settingsOverlay; private MatchLeaderboard leaderboard; From ffa90c1a2373b7d1c774f5d355a43452c9d19667 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 16 Feb 2021 20:23:19 +0900 Subject: [PATCH 0596/1791] Remove whitespace --- osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs b/osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs index c7d4ccd12e..1fcf7f2277 100644 --- a/osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs +++ b/osu.Game/Screens/OnlinePlay/Components/RoomLocalUserInfo.cs @@ -56,7 +56,6 @@ namespace osu.Game.Screens.OnlinePlay.Components attemptDisplay.Text += $" ({remaining} remaining)"; } } - else { attemptDisplay.Text = string.Empty; From 100097d78f0b28c66a86d9d8a4b8bbe309964429 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 16 Feb 2021 21:32:35 +0900 Subject: [PATCH 0597/1791] Fix playlist not being handled correctly for non-joined cases --- .../Multiplayer/StatefulMultiplayerClient.cs | 8 ++++---- .../Multiplayer/Match/BeatmapSelectionControl.cs | 14 +++++++++----- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index f5f4c3a8ba..e9eb80e6a1 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -539,10 +539,7 @@ namespace osu.Game.Online.Multiplayer var allowedMods = settings.AllowedMods.Select(m => m.ToMod(ruleset)); // Update an existing playlist item from the API room, or create a new item. - var playlistItem = apiRoom.Playlist.FirstOrDefault(i => i.ID == settings.PlaylistItemId); - - if (playlistItem == null) - apiRoom.Playlist.Add(playlistItem = new PlaylistItem()); + var playlistItem = apiRoom.Playlist.FirstOrDefault(i => i.ID == settings.PlaylistItemId) ?? new PlaylistItem(); playlistItem.ID = settings.PlaylistItemId; playlistItem.Beatmap.Value = beatmap; @@ -552,6 +549,9 @@ namespace osu.Game.Online.Multiplayer playlistItem.AllowedMods.Clear(); playlistItem.AllowedMods.AddRange(allowedMods); + if (!apiRoom.Playlist.Contains(playlistItem)) + apiRoom.Playlist.Add(playlistItem); + CurrentMatchPlayingItem.Value = playlistItem; } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs index 769596956b..8d394f2c2b 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs @@ -2,8 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System.Diagnostics; +using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; @@ -62,7 +62,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match base.LoadComplete(); Debug.Assert(SelectedItem != null); - SelectedItem.BindValueChanged(onSelectedItemChanged, true); + SelectedItem.BindValueChanged(_ => updateBeatmap()); + Playlist.BindCollectionChanged((_, __) => updateBeatmap(), true); Host.BindValueChanged(host => { @@ -73,12 +74,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match }, true); } - private void onSelectedItemChanged(ValueChangedEvent selectedItem) + private void updateBeatmap() { - if (selectedItem.NewValue == null) + Debug.Assert(SelectedItem != null); + PlaylistItem item = SelectedItem.Value ?? Playlist.FirstOrDefault(); + + if (item == null) beatmapPanelContainer.Clear(); else - beatmapPanelContainer.Child = new DrawableRoomPlaylistItem(selectedItem.NewValue, false, false); + beatmapPanelContainer.Child = new DrawableRoomPlaylistItem(item, false, false); } } } From f61b8e6154c596a33f43dceed8f55b35ce479f58 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 16 Feb 2021 21:32:38 +0900 Subject: [PATCH 0598/1791] Change to long --- osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs index 04752f4e6f..473382cf5f 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs @@ -40,7 +40,7 @@ namespace osu.Game.Online.Multiplayer /// Only used for client-side mutation. /// [Key(6)] - public int PlaylistItemId { get; set; } + public long PlaylistItemId { get; set; } public bool Equals(MultiplayerRoomSettings other) => BeatmapID == other.BeatmapID From 8f72631c314f576fb5e9b8ff0473ca23906a4f8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 16 Feb 2021 21:48:19 +0100 Subject: [PATCH 0599/1791] Fix typo in comment --- .../Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 2f50bee677..3f3fee1b79 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -290,7 +290,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer return; // update local mods based on room's reported status for the local user (omitting the base call implementation). - // this makes the server authoritative, and avoids the local user potentially settings mods that the server is not aware of (ie. if the match was started during the selection being changed). + // this makes the server authoritative, and avoids the local user potentially setting mods that the server is not aware of (ie. if the match was started during the selection being changed). var ruleset = Ruleset.Value.CreateInstance(); Mods.Value = client.LocalUser.Mods.Select(m => m.ToMod(ruleset)).Concat(SelectedItem.Value.RequiredMods).ToList(); } From 3b4e02e5c78fd77325f188c0379785e47b61d6aa Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 17 Feb 2021 07:29:45 +0300 Subject: [PATCH 0600/1791] Fix user population not immediate on bracket loading --- osu.Game.Tournament/TournamentGameBase.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 4224da4bbe..327d8f67b8 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -152,7 +152,7 @@ namespace osu.Game.Tournament { if (string.IsNullOrEmpty(p.Username) || p.Statistics == null) { - PopulateUser(p); + PopulateUser(p, immediate: true); addedInfo = true; } } @@ -211,7 +211,7 @@ namespace osu.Game.Tournament return addedInfo; } - public void PopulateUser(User user, Action success = null, Action failure = null) + public void PopulateUser(User user, Action success = null, Action failure = null, bool immediate = false) { var req = new GetUserRequest(user.Id, Ruleset.Value); @@ -231,7 +231,10 @@ namespace osu.Game.Tournament failure?.Invoke(); }; - API.Queue(req); + if (immediate) + API.Perform(req); + else + API.Queue(req); } protected override void LoadComplete() From 705e9267497bc1eec01da9d73ec2630ad2be327d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 17 Feb 2021 07:48:23 +0300 Subject: [PATCH 0601/1791] Fix attempting to populate users with invalid IDs --- osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs index 582f72429b..263bbc533c 100644 --- a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs @@ -277,7 +277,8 @@ namespace osu.Game.Tournament.Screens.Editors userId.Value = user.Id.ToString(); userId.BindValueChanged(idString => { - int.TryParse(idString.NewValue, out var parsed); + if (!(int.TryParse(idString.NewValue, out var parsed))) + return; user.Id = parsed; From 85ebc8e06cd45be8e66f40c059dac6b20c926cc8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 17 Feb 2021 07:49:28 +0300 Subject: [PATCH 0602/1791] Fix potentially overwriting user ID from failed request --- osu.Game.Tournament/TournamentGameBase.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 327d8f67b8..0b101f050f 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -225,11 +225,7 @@ namespace osu.Game.Tournament success?.Invoke(); }; - req.Failure += _ => - { - user.Id = 1; - failure?.Invoke(); - }; + req.Failure += _ => failure?.Invoke(); if (immediate) API.Perform(req); From 9a7b6ebe5058ea4afb2fc17ad6b62b838caff187 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 17 Feb 2021 14:30:52 +0900 Subject: [PATCH 0603/1791] Fix missed occurrence --- osu.Game/Online/API/Requests/Responses/APIUserScoreAggregate.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIUserScoreAggregate.cs b/osu.Game/Online/API/Requests/Responses/APIUserScoreAggregate.cs index bcc8721400..172fa3a583 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUserScoreAggregate.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUserScoreAggregate.cs @@ -22,7 +22,7 @@ namespace osu.Game.Online.API.Requests.Responses public double? PP { get; set; } [JsonProperty(@"room_id")] - public int RoomID { get; set; } + public long RoomID { get; set; } [JsonProperty("total_score")] public long TotalScore { get; set; } From a845e96b7a8a6eb55615c38567e5a1716b334008 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 17 Feb 2021 08:50:48 +0300 Subject: [PATCH 0604/1791] Replace `Ranks.Global` completely with a `GlobalRank` property --- osu.Desktop/DiscordRichPresence.cs | 2 +- .../TestSceneMultiplayerParticipantsList.cs | 10 ++-------- .../Visual/Online/TestSceneRankGraph.cs | 10 +++++----- .../Visual/Online/TestSceneUserProfileOverlay.cs | 3 ++- osu.Game.Tournament.Tests/TournamentTestScene.cs | 10 +++++----- osu.Game.Tournament/Models/TournamentTeam.cs | 2 +- .../Screens/TeamIntro/SeedingScreen.cs | 2 +- osu.Game.Tournament/TournamentGameBase.cs | 2 +- .../Profile/Header/CentreHeaderContainer.cs | 2 +- .../Profile/Header/DetailHeaderContainer.cs | 2 +- .../Multiplayer/Participants/ParticipantPanel.cs | 2 +- osu.Game/Users/UserStatistics.cs | 16 +++++----------- 12 files changed, 26 insertions(+), 37 deletions(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index 63b12fb84b..832d26b0ef 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -105,7 +105,7 @@ namespace osu.Desktop if (privacyMode.Value == DiscordRichPresenceMode.Limited) presence.Assets.LargeImageText = string.Empty; else - presence.Assets.LargeImageText = $"{user.Value.Username}" + (user.Value.Statistics?.Ranks.Global > 0 ? $" (rank #{user.Value.Statistics.Ranks.Global:N0})" : string.Empty); + presence.Assets.LargeImageText = $"{user.Value.Username}" + (user.Value.Statistics?.GlobalRank > 0 ? $" (rank #{user.Value.Statistics.GlobalRank:N0})" : string.Empty); // update ruleset presence.Assets.SmallImageKey = ruleset.Value.ID <= 3 ? $"mode_{ruleset.Value.ID}" : "mode_custom"; diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs index 1e14bbbbea..e713cff233 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs @@ -160,10 +160,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { { Ruleset.Value.ShortName, - new UserStatistics - { - Ranks = new UserStatistics.UserRanks { Global = RNG.Next(1, 100000) } - } + new UserStatistics { GlobalRank = RNG.Next(1, 100000), } } }, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", @@ -207,10 +204,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { { Ruleset.Value.ShortName, - new UserStatistics - { - Ranks = new UserStatistics.UserRanks { Global = RNG.Next(1, 100000) } - } + new UserStatistics { GlobalRank = RNG.Next(1, 100000), } } }, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankGraph.cs b/osu.Game.Tests/Visual/Online/TestSceneRankGraph.cs index 3b31192259..5bf9e31309 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankGraph.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankGraph.cs @@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual.Online { graph.Statistics.Value = new UserStatistics { - Ranks = new UserStatistics.UserRanks { Global = 123456 }, + GlobalRank = 123456, PP = 12345, }; }); @@ -79,7 +79,7 @@ namespace osu.Game.Tests.Visual.Online { graph.Statistics.Value = new UserStatistics { - Ranks = new UserStatistics.UserRanks { Global = 89000 }, + GlobalRank = 89000, PP = 12345, RankHistory = new User.RankHistoryData { @@ -92,7 +92,7 @@ namespace osu.Game.Tests.Visual.Online { graph.Statistics.Value = new UserStatistics { - Ranks = new UserStatistics.UserRanks { Global = 89000 }, + GlobalRank = 89000, PP = 12345, RankHistory = new User.RankHistoryData { @@ -105,7 +105,7 @@ namespace osu.Game.Tests.Visual.Online { graph.Statistics.Value = new UserStatistics { - Ranks = new UserStatistics.UserRanks { Global = 12000 }, + GlobalRank = 12000, PP = 12345, RankHistory = new User.RankHistoryData { @@ -118,7 +118,7 @@ namespace osu.Game.Tests.Visual.Online { graph.Statistics.Value = new UserStatistics { - Ranks = new UserStatistics.UserRanks { Global = 12000 }, + GlobalRank = 12000, PP = 12345, RankHistory = new User.RankHistoryData { diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs index 7ade24f4de..b52cc6edb6 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs @@ -33,7 +33,8 @@ namespace osu.Game.Tests.Visual.Online ProfileOrder = new[] { "me" }, Statistics = new UserStatistics { - Ranks = new UserStatistics.UserRanks { Global = 2148, Country = 1 }, + GlobalRank = 2148, + Ranks = new UserStatistics.UserRanks { Country = 1, }, PP = 4567.89m, Level = new UserStatistics.LevelInfo { diff --git a/osu.Game.Tournament.Tests/TournamentTestScene.cs b/osu.Game.Tournament.Tests/TournamentTestScene.cs index 47d2160561..cdfd19c157 100644 --- a/osu.Game.Tournament.Tests/TournamentTestScene.cs +++ b/osu.Game.Tournament.Tests/TournamentTestScene.cs @@ -113,11 +113,11 @@ namespace osu.Game.Tournament.Tests }, Players = { - new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 12 } } }, - new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 16 } } }, - new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 20 } } }, - new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 24 } } }, - new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 30 } } }, + new User { Username = "Hello", Statistics = new UserStatistics { GlobalRank = 12 } }, + new User { Username = "Hello", Statistics = new UserStatistics { GlobalRank = 16 } }, + new User { Username = "Hello", Statistics = new UserStatistics { GlobalRank = 20 } }, + new User { Username = "Hello", Statistics = new UserStatistics { GlobalRank = 24 } }, + new User { Username = "Hello", Statistics = new UserStatistics { GlobalRank = 30 } }, } } }, diff --git a/osu.Game.Tournament/Models/TournamentTeam.cs b/osu.Game.Tournament/Models/TournamentTeam.cs index 7fca75cea4..7074ae413c 100644 --- a/osu.Game.Tournament/Models/TournamentTeam.cs +++ b/osu.Game.Tournament/Models/TournamentTeam.cs @@ -36,7 +36,7 @@ namespace osu.Game.Tournament.Models { get { - var ranks = Players.Select(p => p.Statistics?.Ranks.Global) + var ranks = Players.Select(p => p.Statistics?.GlobalRank) .Where(i => i.HasValue) .Select(i => i.Value) .ToArray(); diff --git a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs index 55fc80dba2..4f66d89b7f 100644 --- a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs +++ b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs @@ -250,7 +250,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro }; foreach (var p in team.Players) - fill.Add(new RowDisplay(p.Username, p.Statistics?.Ranks.Global?.ToString("\\##,0") ?? "-")); + fill.Add(new RowDisplay(p.Username, p.Statistics?.GlobalRank?.ToString("\\##,0") ?? "-")); } internal class RowDisplay : CompositeDrawable diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 0b101f050f..3a2a880811 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -150,7 +150,7 @@ namespace osu.Game.Tournament { foreach (var p in t.Players) { - if (string.IsNullOrEmpty(p.Username) || p.Statistics == null) + if (string.IsNullOrEmpty(p.Username) || p.Statistics?.GlobalRank == null) { PopulateUser(p, immediate: true); addedInfo = true; diff --git a/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs index 04a1040e06..9285e2d875 100644 --- a/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs @@ -144,7 +144,7 @@ namespace osu.Game.Overlays.Profile.Header private void updateDisplay(User user) { - hiddenDetailGlobal.Content = user?.Statistics?.Ranks.Global?.ToString("\\##,##0") ?? "-"; + hiddenDetailGlobal.Content = user?.Statistics?.GlobalRank?.ToString("\\##,##0") ?? "-"; hiddenDetailCountry.Content = user?.Statistics?.Ranks.Country?.ToString("\\##,##0") ?? "-"; } } diff --git a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs index cf6ae1a3fc..05a0508e1f 100644 --- a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs @@ -176,7 +176,7 @@ namespace osu.Game.Overlays.Profile.Header foreach (var scoreRankInfo in scoreRankInfos) scoreRankInfo.Value.RankCount = user?.Statistics?.GradesCount[scoreRankInfo.Key] ?? 0; - detailGlobalRank.Content = user?.Statistics?.Ranks.Global?.ToString("\\##,##0") ?? "-"; + detailGlobalRank.Content = user?.Statistics?.GlobalRank?.ToString("\\##,##0") ?? "-"; detailCountryRank.Content = user?.Statistics?.Ranks.Country?.ToString("\\##,##0") ?? "-"; rankGraph.Statistics.Value = user?.Statistics; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index c4d11676e7..25bc314f1b 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs @@ -165,7 +165,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants var ruleset = rulesets.GetRuleset(Room.Settings.RulesetID); - var currentModeRank = User.User?.RulesetsStatistics?.GetValueOrDefault(ruleset.ShortName)?.Ranks.Global; + var currentModeRank = User.User?.RulesetsStatistics?.GetValueOrDefault(ruleset.ShortName)?.GlobalRank; userRankText.Text = currentModeRank != null ? $"#{currentModeRank.Value:N0}" : string.Empty; userStateDisplay.UpdateStatus(User.State, User.BeatmapAvailability); diff --git a/osu.Game/Users/UserStatistics.cs b/osu.Game/Users/UserStatistics.cs index 1fed908c39..e50ca57d90 100644 --- a/osu.Game/Users/UserStatistics.cs +++ b/osu.Game/Users/UserStatistics.cs @@ -26,17 +26,14 @@ namespace osu.Game.Users public int Progress; } + [JsonProperty(@"global_rank")] + public int? GlobalRank; + + // eventually UserRanks object will be completely replaced with separate global rank (exists) and country rank properties + // see https://github.com/ppy/osu-web/blob/cb79bb72186c8f1a25f6a6f5ef315123decb4231/app/Transformers/UserStatisticsTransformer.php#L53. [JsonProperty(@"rank")] public UserRanks Ranks; - // eventually UserRanks object will be completely replaced with separate global and country rank properties, see https://github.com/ppy/osu-web/blob/cb79bb72186c8f1a25f6a6f5ef315123decb4231/app/Transformers/UserStatisticsTransformer.php#L53. - // but for now, always point UserRanks.Global to the global_rank property, as that is included solely for requests like GetUsersRequest. - [JsonProperty(@"global_rank")] - private int? globalRank - { - set => Ranks.Global = value; - } - [JsonProperty(@"pp")] public decimal? PP; @@ -120,9 +117,6 @@ namespace osu.Game.Users public struct UserRanks { - [JsonProperty(@"global")] - public int? Global; - [JsonProperty(@"country")] public int? Country; } From fb0e9d6760c2877ae97b40ad62d601b9e6774369 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 17 Feb 2021 16:44:39 +0900 Subject: [PATCH 0605/1791] Add played property to playlist item --- osu.Game/Online/Rooms/PlaylistItem.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Online/Rooms/PlaylistItem.cs b/osu.Game/Online/Rooms/PlaylistItem.cs index 61982101c1..1d409d4b56 100644 --- a/osu.Game/Online/Rooms/PlaylistItem.cs +++ b/osu.Game/Online/Rooms/PlaylistItem.cs @@ -23,6 +23,12 @@ namespace osu.Game.Online.Rooms [JsonProperty("ruleset_id")] public int RulesetID { get; set; } + /// + /// Whether this is still a valid selection for the . + /// + [JsonProperty("expired")] + public bool Expired { get; set; } + [JsonIgnore] public readonly Bindable Beatmap = new Bindable(); From 61bf9a64bb117483093318b99ce135cd50a2e8c0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 17 Feb 2021 11:21:33 +0300 Subject: [PATCH 0606/1791] Revert failed user requests changes with returning user ID instead --- osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs | 3 +-- osu.Game.Tournament/TournamentGameBase.cs | 8 +++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs index 263bbc533c..582f72429b 100644 --- a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs @@ -277,8 +277,7 @@ namespace osu.Game.Tournament.Screens.Editors userId.Value = user.Id.ToString(); userId.BindValueChanged(idString => { - if (!(int.TryParse(idString.NewValue, out var parsed))) - return; + int.TryParse(idString.NewValue, out var parsed); user.Id = parsed; diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 0b101f050f..ffda101ee0 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -217,6 +217,8 @@ namespace osu.Game.Tournament req.Success += res => { + user.Id = res.Id; + user.Username = res.Username; user.Statistics = res.Statistics; user.Country = res.Country; @@ -225,7 +227,11 @@ namespace osu.Game.Tournament success?.Invoke(); }; - req.Failure += _ => failure?.Invoke(); + req.Failure += _ => + { + user.Id = 1; + failure?.Invoke(); + }; if (immediate) API.Perform(req); From 0d1149911c44f16b1017c96ed79638fbcfe00a5c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 17 Feb 2021 17:33:10 +0900 Subject: [PATCH 0607/1791] Don't display expired playlist items --- osu.Game/Online/Rooms/Room.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index 997f45ce52..aaaa712860 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -148,6 +148,12 @@ namespace osu.Game.Online.Rooms if (EndDate.Value != null && DateTimeOffset.Now >= EndDate.Value) Status.Value = new RoomStatusEnded(); + // Todo: This is not the best way/place to do this, but the intention is to display all playlist items when the room has ended, + // and display only the non-expired playlist items while the room is still active. + // In order to achieve this, all expired items are removed from the source Room. + if (!(Status.Value is RoomStatusEnded)) + other.Playlist.RemoveAll(i => i.Expired); + if (!Playlist.SequenceEqual(other.Playlist)) { Playlist.Clear(); From 70a995919cde7ae34501afac229872cf7317e693 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 17 Feb 2021 17:58:24 +0900 Subject: [PATCH 0608/1791] Update comments --- osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs | 3 --- osu.Game/Online/Rooms/Room.cs | 4 ++-- .../OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs | 2 ++ 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs index 473382cf5f..7d6c76bc2f 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerRoomSettings.cs @@ -36,9 +36,6 @@ namespace osu.Game.Online.Multiplayer [Key(5)] public IEnumerable AllowedMods { get; set; } = Enumerable.Empty(); - /// - /// Only used for client-side mutation. - /// [Key(6)] public long PlaylistItemId { get; set; } diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index aaaa712860..00a7979f2c 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -149,8 +149,8 @@ namespace osu.Game.Online.Rooms Status.Value = new RoomStatusEnded(); // Todo: This is not the best way/place to do this, but the intention is to display all playlist items when the room has ended, - // and display only the non-expired playlist items while the room is still active. - // In order to achieve this, all expired items are removed from the source Room. + // and display only the non-expired playlist items while the room is still active. In order to achieve this, all expired items are removed from the source Room. + // More refactoring is required before this can be done locally instead - DrawableRoomPlaylist is currently directly bound to the playlist to display items in the room. if (!(Status.Value is RoomStatusEnded)) other.Playlist.RemoveAll(i => i.Expired); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs index 8d394f2c2b..3cf0767cf8 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs @@ -77,6 +77,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match private void updateBeatmap() { Debug.Assert(SelectedItem != null); + + // When the selected item is null, the match hasn't yet been created. Use the playlist directly, which is mutated by song selection. PlaylistItem item = SelectedItem.Value ?? Playlist.FirstOrDefault(); if (item == null) From 604add04e495cbed44991962f3fa4e1bcb8b1451 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 17 Feb 2021 19:06:37 +0900 Subject: [PATCH 0609/1791] Fix song select mods being reset incorrectly --- osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs index b201c62b7f..c60743a226 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using Humanizer; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -31,6 +32,10 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(typeof(Room), nameof(Room.Playlist))] protected BindableList Playlist { get; private set; } + [CanBeNull] + [Resolved(CanBeNull = true)] + private IBindable selectedItem { get; set; } + private readonly Bindable> freeMods = new Bindable>(Array.Empty()); private readonly FreeModSelectOverlay freeModSelectOverlay; @@ -66,8 +71,8 @@ namespace osu.Game.Screens.OnlinePlay // At this point, Mods contains both the required and allowed mods. For selection purposes, it should only contain the required mods. // Similarly, freeMods is currently empty but should only contain the allowed mods. - Mods.Value = Playlist.FirstOrDefault()?.RequiredMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty(); - freeMods.Value = Playlist.FirstOrDefault()?.AllowedMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty(); + Mods.Value = selectedItem?.Value?.RequiredMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty(); + freeMods.Value = selectedItem?.Value?.AllowedMods.Select(m => m.CreateCopy()).ToArray() ?? Array.Empty(); Ruleset.BindValueChanged(onRulesetChanged); } From c1620ce21b6e3194a200e980bb79891c0406be24 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Feb 2021 19:19:49 +0900 Subject: [PATCH 0610/1791] Fix intro beatmap always being imported even if already in a good state --- osu.Game/Screens/Menu/IntroScreen.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index b8b962be6c..71b83d4aab 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -111,12 +111,10 @@ namespace osu.Game.Screens.Menu { setInfo = beatmaps.QueryBeatmapSet(b => b.Hash == BeatmapHash); - if (setInfo != null) - { - initialBeatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0]); - } + if (setInfo == null) + return false; - return UsingThemedIntro; + return (initialBeatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0])) != null; } } From c1db33e0753e665334abd833330e13e5c3ee5c74 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Feb 2021 17:04:43 +0900 Subject: [PATCH 0611/1791] Improve some xmldoc on ArchiveModelManager for methods which are not going to trigger user interactive flow --- osu.Game/Database/ArchiveModelManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 232acba4a3..cc107d61c6 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -226,7 +226,7 @@ namespace osu.Game.Database public Action> PresentImport; /// - /// Import an item from an . + /// Silently import an item from an . /// /// The archive to be imported. /// An optional cancellation token. @@ -303,7 +303,7 @@ namespace osu.Game.Database } /// - /// Import an item from a . + /// Silently import an item from a . /// /// The model to be imported. /// An optional archive to use for model population. From 0196ee882a5c6a956827e84fc79fb824de51a07c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Feb 2021 19:09:38 +0900 Subject: [PATCH 0612/1791] Redirect batch imports to a separate task scheduler to avoid contention with interactive actions --- ...eneOnlinePlayBeatmapAvailabilityTracker.cs | 4 +-- osu.Game/Database/ArchiveModelManager.cs | 36 ++++++++++++++----- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index d3475de157..3ffb512b7f 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -165,10 +165,10 @@ namespace osu.Game.Tests.Online { } - public override async Task Import(BeatmapSetInfo item, ArchiveReader archive = null, CancellationToken cancellationToken = default) + public override async Task Import(BeatmapSetInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) { await AllowImport.Task; - return await (CurrentImportTask = base.Import(item, archive, cancellationToken)); + return await (CurrentImportTask = base.Import(item, archive, lowPriority, cancellationToken)); } } diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index cc107d61c6..03b8db2cb8 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -38,6 +38,11 @@ namespace osu.Game.Database { private const int import_queue_request_concurrency = 1; + /// + /// The size of a batch import operation before considering it a lower priority operation. + /// + private const int low_priority_import_batch_size = 1; + /// /// A singleton scheduler shared by all . /// @@ -47,6 +52,13 @@ namespace osu.Game.Database /// private static readonly ThreadedTaskScheduler import_scheduler = new ThreadedTaskScheduler(import_queue_request_concurrency, nameof(ArchiveModelManager)); + /// + /// A second scheduler for lower priority imports. + /// For simplicity, these will just run in parallel with normal priority imports, but a future refactor would see this implemented via a custom scheduler/queue. + /// See https://gist.github.com/peppy/f0e118a14751fc832ca30dd48ba3876b for an incomplete version of this. + /// + private static readonly ThreadedTaskScheduler import_scheduler_low_priority = new ThreadedTaskScheduler(import_queue_request_concurrency, nameof(ArchiveModelManager)); + /// /// Set an endpoint for notifications to be posted to. /// @@ -103,8 +115,11 @@ namespace osu.Game.Database /// /// Import one or more items from filesystem . - /// This will post notifications tracking progress. /// + /// + /// This will be treated as a low priority import if more than one path is specified; use to always import at standard priority. + /// This will post notifications tracking progress. + /// /// One or more archive locations on disk. public Task Import(params string[] paths) { @@ -133,13 +148,15 @@ namespace osu.Game.Database var imported = new List(); + bool isLowPriorityImport = tasks.Length > low_priority_import_batch_size; + await Task.WhenAll(tasks.Select(async task => { notification.CancellationToken.ThrowIfCancellationRequested(); try { - var model = await Import(task, notification.CancellationToken); + var model = await Import(task, isLowPriorityImport, notification.CancellationToken); lock (imported) { @@ -193,15 +210,16 @@ namespace osu.Game.Database /// Note that this bypasses the UI flow and should only be used for special cases or testing. /// /// The containing data about the to import. + /// Whether this is a low priority import. /// An optional cancellation token. /// The imported model, if successful. - internal async Task Import(ImportTask task, CancellationToken cancellationToken = default) + internal async Task Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); TModel import; using (ArchiveReader reader = task.GetReader()) - import = await Import(reader, cancellationToken); + import = await Import(reader, lowPriority, cancellationToken); // 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. @@ -229,8 +247,9 @@ namespace osu.Game.Database /// Silently import an item from an . /// /// The archive to be imported. + /// Whether this is a low priority import. /// An optional cancellation token. - public Task Import(ArchiveReader archive, CancellationToken cancellationToken = default) + public Task Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -253,7 +272,7 @@ namespace osu.Game.Database return null; } - return Import(model, archive, cancellationToken); + return Import(model, archive, lowPriority, cancellationToken); } /// @@ -307,8 +326,9 @@ namespace osu.Game.Database /// /// The model to be imported. /// An optional archive to use for model population. + /// Whether this is a low priority import. /// An optional cancellation token. - public virtual async Task Import(TModel item, ArchiveReader archive = null, CancellationToken cancellationToken = default) => await Task.Factory.StartNew(async () => + public virtual async Task Import(TModel item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) => await Task.Factory.StartNew(async () => { cancellationToken.ThrowIfCancellationRequested(); @@ -383,7 +403,7 @@ namespace osu.Game.Database flushEvents(true); return item; - }, cancellationToken, TaskCreationOptions.HideScheduler, import_scheduler).Unwrap(); + }, cancellationToken, TaskCreationOptions.HideScheduler, lowPriority ? import_scheduler_low_priority : import_scheduler).Unwrap(); /// /// Exports an item to a legacy (.zip based) package. From 172e2e9b3ffabfcc7fb7017529f9eecd103e2d62 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Feb 2021 20:40:15 +0900 Subject: [PATCH 0613/1791] Fix audio previews not being adjusted in volume correctly --- osu.Game/Audio/PreviewTrackManager.cs | 3 +++ osu.Game/OsuGameBase.cs | 7 ++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Audio/PreviewTrackManager.cs b/osu.Game/Audio/PreviewTrackManager.cs index 8d02af6574..d88fd1e62b 100644 --- a/osu.Game/Audio/PreviewTrackManager.cs +++ b/osu.Game/Audio/PreviewTrackManager.cs @@ -27,6 +27,8 @@ namespace osu.Game.Audio protected TrackManagerPreviewTrack CurrentTrack; + private readonly BindableNumber globalTrackVolumeAdjust = new BindableNumber(OsuGameBase.GLOBAL_TRACK_VOLUME_ADJUST); + [BackgroundDependencyLoader] private void load() { @@ -35,6 +37,7 @@ namespace osu.Game.Audio trackStore = new PreviewTrackStore(new OnlineStore()); audio.AddItem(trackStore); + trackStore.AddAdjustment(AdjustableProperty.Volume, globalTrackVolumeAdjust); trackStore.AddAdjustment(AdjustableProperty.Volume, audio.VolumeTrack); } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 00b436931a..3d24f245f9 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -156,7 +156,12 @@ namespace osu.Game protected override UserInputManager CreateUserInputManager() => new OsuUserInputManager(); - private readonly BindableNumber globalTrackVolumeAdjust = new BindableNumber(0.5f); + /// + /// The maximum volume at which audio tracks should playback. This can be set lower than 1 to create some head-room for sound effects. + /// + internal const double GLOBAL_TRACK_VOLUME_ADJUST = 0.5; + + private readonly BindableNumber globalTrackVolumeAdjust = new BindableNumber(GLOBAL_TRACK_VOLUME_ADJUST); [BackgroundDependencyLoader] private void load() From 403536ef80c64d475f56bb6fc290d849b49a491c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Feb 2021 21:09:20 +0900 Subject: [PATCH 0614/1791] Fix ModDisplay potentially being operated on before loaded completely Closes https://github.com/ppy/osu/issues/11810. --- osu.Game/Screens/Play/HUD/ModDisplay.cs | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ModDisplay.cs b/osu.Game/Screens/Play/HUD/ModDisplay.cs index 68d019bf71..7359f04dcf 100644 --- a/osu.Game/Screens/Play/HUD/ModDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ModDisplay.cs @@ -72,19 +72,6 @@ namespace osu.Game.Screens.Play.HUD } }, }; - - Current.ValueChanged += mods => - { - iconsContainer.Clear(); - - foreach (Mod mod in mods.NewValue) - { - iconsContainer.Add(new ModIcon(mod) { Scale = new Vector2(0.6f) }); - } - - if (IsLoaded) - appearTransform(); - }; } protected override void Dispose(bool isDisposing) @@ -97,7 +84,16 @@ namespace osu.Game.Screens.Play.HUD { base.LoadComplete(); - appearTransform(); + Current.BindValueChanged(mods => + { + iconsContainer.Clear(); + + foreach (Mod mod in mods.NewValue) + iconsContainer.Add(new ModIcon(mod) { Scale = new Vector2(0.6f) }); + + appearTransform(); + }, true); + iconsContainer.FadeInFromZero(fade_duration, Easing.OutQuint); } From 2a1bb2f578ac8b74e6a7d5e7a949a7e995acc6c1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 17 Feb 2021 21:38:01 +0900 Subject: [PATCH 0615/1791] Fix selected item potentially changing during gameplay --- .../Multiplayer/MultiplayerMatchSubScreen.cs | 51 ++++++++++++------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index c1930c525c..b5eff04532 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -46,7 +46,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer private MultiplayerMatchSettingsOverlay settingsOverlay; private Drawable userModsSection; - private IBindable isConnected; + private readonly IBindable isConnected = new Bindable(); + private readonly IBindable matchCurrentItem = new Bindable(); [CanBeNull] private IDisposable readyClickOperation; @@ -268,8 +269,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { base.LoadComplete(); - SelectedItem.BindValueChanged(onSelectedItemChanged); - SelectedItem.BindTo(client.CurrentMatchPlayingItem); + matchCurrentItem.BindTo(client.CurrentMatchPlayingItem); + matchCurrentItem.BindValueChanged(onCurrentItemChanged, true); BeatmapAvailability.BindValueChanged(updateBeatmapAvailability, true); UserMods.BindValueChanged(onUserModsChanged); @@ -277,7 +278,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer client.LoadRequested += onLoadRequested; client.RoomUpdated += onRoomUpdated; - isConnected = client.IsConnected.GetBoundCopy(); + isConnected.BindTo(client.IsConnected); isConnected.BindValueChanged(connected => { if (!connected.NewValue) @@ -285,6 +286,33 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer }, true); } + private void onCurrentItemChanged(ValueChangedEvent item) + { + if (client?.LocalUser == null) + return; + + // If we're about to enter gameplay, schedule the item to be set at a later time. + if (client.LocalUser.State > MultiplayerUserState.Ready) + { + Schedule(() => onCurrentItemChanged(item)); + return; + } + + SelectedItem.Value = item.NewValue; + + if (item.NewValue?.AllowedMods.Any() != true) + { + userModsSection.Hide(); + userModsSelectOverlay.Hide(); + userModsSelectOverlay.IsValidMod = _ => false; + } + else + { + userModsSection.Show(); + userModsSelectOverlay.IsValidMod = m => item.NewValue.AllowedMods.Any(a => a.GetType() == m.GetType()); + } + } + protected override void UpdateMods() { if (SelectedItem.Value == null || client.LocalUser == null) @@ -313,21 +341,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer return base.OnBackButton(); } - private void onSelectedItemChanged(ValueChangedEvent item) - { - if (item.NewValue?.AllowedMods.Any() != true) - { - userModsSection.Hide(); - userModsSelectOverlay.Hide(); - userModsSelectOverlay.IsValidMod = _ => false; - } - else - { - userModsSection.Show(); - userModsSelectOverlay.IsValidMod = m => item.NewValue.AllowedMods.Any(a => a.GetType() == m.GetType()); - } - } - private ModSettingChangeTracker modSettingChangeTracker; private ScheduledDelegate debouncedModSettingsUpdate; From 6ef235c4c5290f3254ed247b2f20054dde1aceaf Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 17 Feb 2021 21:42:22 +0900 Subject: [PATCH 0616/1791] Fix beatmap panel flickering multiple times --- .../Match/BeatmapSelectionControl.cs | 17 ++------ .../Screens/OnlinePlay/OnlinePlayComposite.cs | 41 +++++++++++++++++-- 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs index 3cf0767cf8..3af0d5b715 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs @@ -1,15 +1,12 @@ // 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.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using osu.Framework.Screens; using osu.Game.Online.API; -using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Match.Components; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match @@ -61,10 +58,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { base.LoadComplete(); - Debug.Assert(SelectedItem != null); - SelectedItem.BindValueChanged(_ => updateBeatmap()); - Playlist.BindCollectionChanged((_, __) => updateBeatmap(), true); - + SelectedItem.BindValueChanged(_ => updateBeatmap(), true); Host.BindValueChanged(host => { if (RoomID.Value == null || host.NewValue?.Equals(api.LocalUser.Value) == true) @@ -76,15 +70,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match private void updateBeatmap() { - Debug.Assert(SelectedItem != null); - - // When the selected item is null, the match hasn't yet been created. Use the playlist directly, which is mutated by song selection. - PlaylistItem item = SelectedItem.Value ?? Playlist.FirstOrDefault(); - - if (item == null) + if (SelectedItem.Value == null) beatmapPanelContainer.Clear(); else - beatmapPanelContainer.Child = new DrawableRoomPlaylistItem(item, false, false); + beatmapPanelContainer.Child = new DrawableRoomPlaylistItem(SelectedItem.Value, false, false); } } } diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index a7058d0ede..f203ef927c 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Specialized; +using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -57,11 +59,44 @@ namespace osu.Game.Screens.OnlinePlay protected Bindable Duration { get; private set; } /// - /// The currently selected item in the . - /// May be null if this is not inside a . + /// The currently selected item in the , or the first item from + /// if this is not within a . /// + protected IBindable SelectedItem => selectedItem; + + private readonly Bindable selectedItem = new Bindable(); + [CanBeNull] [Resolved(CanBeNull = true)] - protected IBindable SelectedItem { get; private set; } + private IBindable subScreenSelectedItem { get; set; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + if (subScreenSelectedItem != null) + subScreenSelectedItem.BindValueChanged(onSelectedItemChanged, true); + else + Playlist.BindCollectionChanged(onPlaylistChanged, true); + } + + /// + /// Invoked when the selected item from within a changes. + /// Does not occur when this is outside a . + /// + private void onSelectedItemChanged(ValueChangedEvent item) + { + // If the room hasn't been created yet, fall-back to the first item from the playlist. + selectedItem.Value = RoomID.Value == null ? Playlist.FirstOrDefault() : item.NewValue; + } + + /// + /// Invoked when the playlist changes. + /// Does not occur when this is inside a . + /// + private void onPlaylistChanged(object sender, NotifyCollectionChangedEventArgs e) + { + selectedItem.Value = Playlist.FirstOrDefault(); + } } } From 3208b2c5bf096bb25eab7946fb8475a1113b81e6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Feb 2021 23:13:51 +0900 Subject: [PATCH 0617/1791] Fix potential nullref if mods are never set --- osu.Game/Screens/Play/HUD/ModDisplay.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ModDisplay.cs b/osu.Game/Screens/Play/HUD/ModDisplay.cs index 7359f04dcf..cffdb21fb8 100644 --- a/osu.Game/Screens/Play/HUD/ModDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ModDisplay.cs @@ -88,10 +88,13 @@ namespace osu.Game.Screens.Play.HUD { iconsContainer.Clear(); - foreach (Mod mod in mods.NewValue) - iconsContainer.Add(new ModIcon(mod) { Scale = new Vector2(0.6f) }); + if (mods.NewValue != null) + { + foreach (Mod mod in mods.NewValue) + iconsContainer.Add(new ModIcon(mod) { Scale = new Vector2(0.6f) }); - appearTransform(); + appearTransform(); + } }, true); iconsContainer.FadeInFromZero(fade_duration, Easing.OutQuint); From e7308193e7bd33a8c6e38f90d7dca112bda6e084 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Feb 2021 13:03:29 +0900 Subject: [PATCH 0618/1791] Add xmldoc explaining how PreviewTime is intended to work --- osu.Game/Beatmaps/BeatmapMetadata.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index 39b3c23ddd..367f612dc8 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -51,7 +51,12 @@ namespace osu.Game.Beatmaps [JsonProperty(@"tags")] public string Tags { get; set; } + /// + /// The time in milliseconds to begin playing the track for preview purposes. + /// If -1, the track should begin playing at 40% of its length. + /// public int PreviewTime { get; set; } + public string AudioFile { get; set; } public string BackgroundFile { get; set; } From 90dce5204218ac9132e929d374a123f3da7abe34 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Feb 2021 14:10:28 +0900 Subject: [PATCH 0619/1791] Fix potential crash from cross-thread drawable manipulation in CollectionFilterDropdown --- osu.Game/Collections/CollectionFilterDropdown.cs | 13 ++++++------- osu.Game/Screens/Select/FilterControl.cs | 2 +- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/osu.Game/Collections/CollectionFilterDropdown.cs b/osu.Game/Collections/CollectionFilterDropdown.cs index ec0e9d5a89..3e55ecb084 100644 --- a/osu.Game/Collections/CollectionFilterDropdown.cs +++ b/osu.Game/Collections/CollectionFilterDropdown.cs @@ -41,19 +41,18 @@ namespace osu.Game.Collections ItemSource = filters; } - [BackgroundDependencyLoader(permitNulls: true)] - private void load([CanBeNull] CollectionManager collectionManager) + [Resolved(CanBeNull = true)] + private CollectionManager collectionManager { get; set; } + + protected override void LoadComplete() { + base.LoadComplete(); + if (collectionManager != null) collections.BindTo(collectionManager.Collections); collections.CollectionChanged += (_, __) => collectionsChanged(); collectionsChanged(); - } - - protected override void LoadComplete() - { - base.LoadComplete(); Current.BindValueChanged(filterChanged, true); } diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 952a5d1eaa..eafd8a87d1 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -44,7 +44,7 @@ namespace osu.Game.Screens.Select Sort = sortMode.Value, AllowConvertedBeatmaps = showConverted.Value, Ruleset = ruleset.Value, - Collection = collectionDropdown?.Current.Value.Collection + Collection = collectionDropdown?.Current.Value?.Collection }; if (!minimumStars.IsDefault) From 49589b64c34792ac07d19f8f3d4efb97fd4c7aad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Feb 2021 14:55:15 +0900 Subject: [PATCH 0620/1791] Intro track should not restart from preview point --- osu.Game/Screens/Menu/IntroScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 71b83d4aab..71f3b60026 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -168,7 +168,7 @@ namespace osu.Game.Screens.Menu { // Only start the current track if it is the menu music. A beatmap's track is started when entering the Main Menu. if (UsingThemedIntro) - Track.Restart(); + Track.Start(); } protected override void LogoArriving(OsuLogo logo, bool resuming) From dfedea9ea2abf6c09dead571889915a37570c0ff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Feb 2021 14:55:44 +0900 Subject: [PATCH 0621/1791] Move preview point logic to a specific method in WorkingBeatmap --- osu.Game/Beatmaps/WorkingBeatmap.cs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 30382c444f..aab8ff6bd6 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -266,6 +266,26 @@ namespace osu.Game.Beatmaps [NotNull] public Track LoadTrack() => loadedTrack = GetBeatmapTrack() ?? GetVirtualTrack(1000); + /// + /// Reads the correct track restart point from beatmap metadata and sets looping to enabled. + /// + public void PrepareTrackForPreviewLooping() + { + Track.Looping = true; + Track.RestartPoint = Metadata.PreviewTime; + + if (Track.RestartPoint == -1) + { + if (!Track.IsLoaded) + { + // force length to be populated (https://github.com/ppy/osu-framework/issues/4202) + Track.Seek(Track.CurrentTime); + } + + Track.RestartPoint = 0.4f * Track.Length; + } + } + /// /// Transfer a valid audio track into this working beatmap. Used as an optimisation to avoid reload / track swap /// across difficulties in the same beatmap set. From 421cdb6650246d249d685812c8ea7dbd3c07a015 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Feb 2021 15:01:11 +0900 Subject: [PATCH 0622/1791] Consume new method in existing usages (and remove some unnecessary set/unset code) --- osu.Game/Screens/Menu/MainMenu.cs | 11 ++++++----- osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs | 7 +------ osu.Game/Screens/Select/SongSelect.cs | 7 +++---- 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 97fd58318b..424e6d2cd5 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -179,14 +179,15 @@ namespace osu.Game.Screens.Menu base.OnEntering(last); buttons.FadeInFromZero(500); - var metadata = Beatmap.Value.Metadata; - if (last is IntroScreen && musicController.TrackLoaded) { - if (!musicController.CurrentTrack.IsRunning) + var track = musicController.CurrentTrack; + + // presume the track is the current beatmap's track. not sure how correct this assumption is but it has worked until now. + if (!track.IsRunning) { - musicController.CurrentTrack.Seek(metadata.PreviewTime != -1 ? metadata.PreviewTime : 0.4f * musicController.CurrentTrack.Length); - musicController.CurrentTrack.Start(); + Beatmap.Value.PrepareTrackForPreviewLooping(); + track.Restart(); } } diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index e755f8c405..86422085a1 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -173,9 +173,7 @@ namespace osu.Game.Screens.OnlinePlay.Match if (track != null) { - track.RestartPoint = Beatmap.Value.Metadata.PreviewTime; - track.Looping = true; - + Beatmap.Value.PrepareTrackForPreviewLooping(); music?.EnsurePlayingSomething(); } } @@ -185,10 +183,7 @@ namespace osu.Game.Screens.OnlinePlay.Match var track = Beatmap?.Value?.Track; if (track != null) - { track.Looping = false; - track.RestartPoint = 0; - } } } } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index edbab083cd..b7f7c40539 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -648,8 +648,9 @@ namespace osu.Game.Screens.Select { Debug.Assert(!isHandlingLooping); - music.CurrentTrack.Looping = isHandlingLooping = true; + isHandlingLooping = true; + ensureTrackLooping(Beatmap.Value, TrackChangeDirection.None); music.TrackChanged += ensureTrackLooping; } @@ -665,7 +666,7 @@ namespace osu.Game.Screens.Select } private void ensureTrackLooping(WorkingBeatmap beatmap, TrackChangeDirection changeDirection) - => music.CurrentTrack.Looping = true; + => beatmap.PrepareTrackForPreviewLooping(); public override bool OnBackButton() { @@ -719,8 +720,6 @@ namespace osu.Game.Screens.Select bool isNewTrack = !lastTrack.TryGetTarget(out var last) || last != track; - track.RestartPoint = Beatmap.Value.Metadata.PreviewTime; - if (!track.IsRunning && (music.UserPauseRequested != true || isNewTrack)) music.Play(true); From 56e9e10ff584742f80c21971771874c7f21ab745 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 18 Feb 2021 15:30:31 +0900 Subject: [PATCH 0623/1791] Make server authoritative in playlist item id --- osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index e9eb80e6a1..416162779d 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -217,7 +217,6 @@ namespace osu.Game.Online.Multiplayer RulesetID = item.GetOr(existingPlaylistItem).RulesetID, RequiredMods = item.HasValue ? item.Value.AsNonNull().RequiredMods.Select(m => new APIMod(m)).ToList() : Room.Settings.RequiredMods, AllowedMods = item.HasValue ? item.Value.AsNonNull().AllowedMods.Select(m => new APIMod(m)).ToList() : Room.Settings.AllowedMods, - PlaylistItemId = Room.Settings.PlaylistItemId, }); } From 143e1456701ad4808c55cbb75788e81dc3df9c9a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Feb 2021 15:42:26 +0900 Subject: [PATCH 0624/1791] Update implementation of AdjustableAudioComponents --- .../Rulesets/TestSceneDrawableRulesetDependencies.cs | 4 ++-- osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs | 4 ++-- osu.Game/Skinning/PoolableSkinnableSample.cs | 4 ++-- osu.Game/Skinning/SkinnableSound.cs | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs b/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs index 787f72ba79..4aebed0d31 100644 --- a/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs +++ b/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs @@ -118,9 +118,9 @@ 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 AddAdjustment(AdjustableProperty type, IBindable adjustBindable) => throw new NotImplementedException(); - public void RemoveAdjustment(AdjustableProperty type, BindableNumber 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..bbaca7c80f 100644 --- a/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs +++ b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs @@ -110,9 +110,9 @@ namespace osu.Game.Rulesets.UI public IEnumerable GetAvailableResources() => throw new NotSupportedException(); - public void AddAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => throw new NotSupportedException(); + public void AddAdjustment(AdjustableProperty type, IBindable adjustBindable) => throw new NotSupportedException(); - public void RemoveAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => throw new NotSupportedException(); + public void RemoveAdjustment(AdjustableProperty type, IBindable adjustBindable) => throw new NotSupportedException(); public void RemoveAllAdjustments(AdjustableProperty type) => throw new NotSupportedException(); diff --git a/osu.Game/Skinning/PoolableSkinnableSample.cs b/osu.Game/Skinning/PoolableSkinnableSample.cs index 9025fdbd0f..abff57091b 100644 --- a/osu.Game/Skinning/PoolableSkinnableSample.cs +++ b/osu.Game/Skinning/PoolableSkinnableSample.cs @@ -165,9 +165,9 @@ namespace osu.Game.Skinning public BindableNumber Tempo => sampleContainer.Tempo; - public void AddAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => sampleContainer.AddAdjustment(type, adjustBindable); + public void AddAdjustment(AdjustableProperty type, IBindable adjustBindable) => sampleContainer.AddAdjustment(type, adjustBindable); - public void RemoveAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => sampleContainer.RemoveAdjustment(type, adjustBindable); + public void RemoveAdjustment(AdjustableProperty type, IBindable adjustBindable) => sampleContainer.RemoveAdjustment(type, adjustBindable); public void RemoveAllAdjustments(AdjustableProperty type) => sampleContainer.RemoveAllAdjustments(type); diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index b3db2d6558..d3dfcb1dc0 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -176,10 +176,10 @@ namespace osu.Game.Skinning public BindableNumber Tempo => samplesContainer.Tempo; - public void AddAdjustment(AdjustableProperty type, BindableNumber adjustBindable) + public void AddAdjustment(AdjustableProperty type, IBindable adjustBindable) => samplesContainer.AddAdjustment(type, adjustBindable); - public void RemoveAdjustment(AdjustableProperty type, BindableNumber adjustBindable) + public void RemoveAdjustment(AdjustableProperty type, IBindable adjustBindable) => samplesContainer.RemoveAdjustment(type, adjustBindable); public void RemoveAllAdjustments(AdjustableProperty type) From e911760318edfb87b86bd6f5b74da4e285c441f5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 18 Feb 2021 15:47:33 +0900 Subject: [PATCH 0625/1791] Split OnlinePlayComposite to remove if-statement --- .../Match/BeatmapSelectionControl.cs | 2 +- .../Screens/OnlinePlay/OnlinePlayComposite.cs | 36 ++++-------------- .../OnlinePlay/RoomSubScreenComposite.cs | 38 +++++++++++++++++++ 3 files changed, 46 insertions(+), 30 deletions(-) create mode 100644 osu.Game/Screens/OnlinePlay/RoomSubScreenComposite.cs diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs index 3af0d5b715..ebe63e26d6 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/BeatmapSelectionControl.cs @@ -11,7 +11,7 @@ using osu.Game.Screens.OnlinePlay.Match.Components; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { - public class BeatmapSelectionControl : OnlinePlayComposite + public class BeatmapSelectionControl : RoomSubScreenComposite { [Resolved] private MultiplayerMatchSubScreen matchSubScreen { get; set; } diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index f203ef927c..eb0b23f13f 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -2,9 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Specialized; using System.Linq; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; @@ -14,6 +12,9 @@ using osu.Game.Users; namespace osu.Game.Screens.OnlinePlay { + /// + /// A that exposes bindables for properties. + /// public class OnlinePlayComposite : CompositeDrawable { [Resolved(typeof(Room))] @@ -62,41 +63,18 @@ namespace osu.Game.Screens.OnlinePlay /// The currently selected item in the , or the first item from /// if this is not within a . /// - protected IBindable SelectedItem => selectedItem; - - private readonly Bindable selectedItem = new Bindable(); - - [CanBeNull] - [Resolved(CanBeNull = true)] - private IBindable subScreenSelectedItem { get; set; } + protected readonly Bindable SelectedItem = new Bindable(); protected override void LoadComplete() { base.LoadComplete(); - if (subScreenSelectedItem != null) - subScreenSelectedItem.BindValueChanged(onSelectedItemChanged, true); - else - Playlist.BindCollectionChanged(onPlaylistChanged, true); + Playlist.BindCollectionChanged((_, __) => UpdateSelectedItem(), true); } - /// - /// Invoked when the selected item from within a changes. - /// Does not occur when this is outside a . - /// - private void onSelectedItemChanged(ValueChangedEvent item) + protected virtual void UpdateSelectedItem() { - // If the room hasn't been created yet, fall-back to the first item from the playlist. - selectedItem.Value = RoomID.Value == null ? Playlist.FirstOrDefault() : item.NewValue; - } - - /// - /// Invoked when the playlist changes. - /// Does not occur when this is inside a . - /// - private void onPlaylistChanged(object sender, NotifyCollectionChangedEventArgs e) - { - selectedItem.Value = Playlist.FirstOrDefault(); + SelectedItem.Value = Playlist.FirstOrDefault(); } } } diff --git a/osu.Game/Screens/OnlinePlay/RoomSubScreenComposite.cs b/osu.Game/Screens/OnlinePlay/RoomSubScreenComposite.cs new file mode 100644 index 0000000000..4cfd881aa3 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/RoomSubScreenComposite.cs @@ -0,0 +1,38 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Game.Online.Rooms; +using osu.Game.Screens.OnlinePlay.Match; + +namespace osu.Game.Screens.OnlinePlay +{ + /// + /// An with additional logic tracking the currently-selected inside a . + /// + public class RoomSubScreenComposite : OnlinePlayComposite + { + [Resolved] + private IBindable subScreenSelectedItem { get; set; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + subScreenSelectedItem.BindValueChanged(_ => UpdateSelectedItem(), true); + } + + protected override void UpdateSelectedItem() + { + if (RoomID.Value == null) + { + // If the room hasn't been created yet, fall-back to the base logic. + base.UpdateSelectedItem(); + return; + } + + SelectedItem.Value = subScreenSelectedItem.Value; + } + } +} From 46ba5de32c5e0dc63d94ab618121a61df655c2a9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 18 Feb 2021 16:19:36 +0900 Subject: [PATCH 0626/1791] Fix collections being imported from BDL thread --- osu.Game/Collections/CollectionManager.cs | 48 +++++++++++++---------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index 569ac749a4..a65d9a415d 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -139,35 +139,43 @@ namespace osu.Game.Collections PostNotification?.Invoke(notification); var collection = readCollections(stream, notification); - bool importCompleted = false; - - Schedule(() => - { - importCollections(collection); - importCompleted = true; - }); - - while (!IsDisposed && !importCompleted) - await Task.Delay(10); + await importCollections(collection); notification.CompletionText = $"Imported {collection.Count} collections"; notification.State = ProgressNotificationState.Completed; } - private void importCollections(List newCollections) + private Task importCollections(List newCollections) { - foreach (var newCol in newCollections) - { - var existing = Collections.FirstOrDefault(c => c.Name == newCol.Name); - if (existing == null) - Collections.Add(existing = new BeatmapCollection { Name = { Value = newCol.Name.Value } }); + var tcs = new TaskCompletionSource(); - foreach (var newBeatmap in newCol.Beatmaps) + Schedule(() => + { + try { - if (!existing.Beatmaps.Contains(newBeatmap)) - existing.Beatmaps.Add(newBeatmap); + foreach (var newCol in newCollections) + { + var existing = Collections.FirstOrDefault(c => c.Name == newCol.Name); + if (existing == null) + Collections.Add(existing = new BeatmapCollection { Name = { Value = newCol.Name.Value } }); + + foreach (var newBeatmap in newCol.Beatmaps) + { + if (!existing.Beatmaps.Contains(newBeatmap)) + existing.Beatmaps.Add(newBeatmap); + } + } + + tcs.SetResult(true); } - } + catch (Exception e) + { + Logger.Error(e, "Failed to import collection."); + tcs.SetException(e); + } + }); + + return tcs.Task; } private List readCollections(Stream stream, ProgressNotification notification = null) From c3a98b6ad15475a86565c2501fe0faeb2e898b14 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Feb 2021 16:59:43 +0900 Subject: [PATCH 0627/1791] Fix carousel items' borders getting blown out when selected and hovered I tried restructuring the hierarchy to avoid needing this added property (moving the hover layer out of the border container) but this leads to some subpixel leakage outside the borders which looks even worse. Closes #6915. --- .../Screens/Select/Carousel/CarouselHeader.cs | 34 +++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselHeader.cs b/osu.Game/Screens/Select/Carousel/CarouselHeader.cs index 947334c747..73324894ee 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselHeader.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselHeader.cs @@ -24,9 +24,13 @@ namespace osu.Game.Screens.Select.Carousel public Container BorderContainer; public readonly Bindable State = new Bindable(CarouselItemState.NotSelected); + private HoverLayer hoverLayer; protected override Container Content { get; } = new Container { RelativeSizeAxes = Axes.Both }; + private const float corner_radius = 10; + private const float border_thickness = 2.5f; + public CarouselHeader() { RelativeSizeAxes = Axes.X; @@ -36,12 +40,12 @@ namespace osu.Game.Screens.Select.Carousel { RelativeSizeAxes = Axes.Both, Masking = true, - CornerRadius = 10, + CornerRadius = corner_radius, BorderColour = new Color4(221, 255, 255, 255), Children = new Drawable[] { Content, - new HoverLayer() + hoverLayer = new HoverLayer() } }; } @@ -59,6 +63,8 @@ namespace osu.Game.Screens.Select.Carousel { case CarouselItemState.Collapsed: case CarouselItemState.NotSelected: + hoverLayer.InsetForBorder = false; + BorderContainer.BorderThickness = 0; BorderContainer.EdgeEffect = new EdgeEffectParameters { @@ -70,7 +76,9 @@ namespace osu.Game.Screens.Select.Carousel break; case CarouselItemState.Selected: - BorderContainer.BorderThickness = 2.5f; + hoverLayer.InsetForBorder = true; + + BorderContainer.BorderThickness = border_thickness; BorderContainer.EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow, @@ -107,6 +115,26 @@ namespace osu.Game.Screens.Select.Carousel sampleHover = audio.Samples.Get("SongSelect/song-ping"); } + public bool InsetForBorder + { + set + { + if (value) + { + // apply same border as above to avoid applying additive overlay to it (and blowing out the colour). + Masking = true; + CornerRadius = corner_radius; + BorderThickness = border_thickness; + } + else + { + BorderThickness = 0; + CornerRadius = 0; + Masking = false; + } + } + } + protected override bool OnHover(HoverEvent e) { box.FadeIn(100, Easing.OutQuint); From b713eb2eae259e086ca3b62ff6e3d7b79bed06a4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Feb 2021 17:13:48 +0900 Subject: [PATCH 0628/1791] Make field readonly --- osu.Game/Screens/Select/Carousel/CarouselHeader.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselHeader.cs b/osu.Game/Screens/Select/Carousel/CarouselHeader.cs index 73324894ee..2fbf64de29 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselHeader.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselHeader.cs @@ -24,7 +24,8 @@ namespace osu.Game.Screens.Select.Carousel public Container BorderContainer; public readonly Bindable State = new Bindable(CarouselItemState.NotSelected); - private HoverLayer hoverLayer; + + private readonly HoverLayer hoverLayer; protected override Container Content { get; } = new Container { RelativeSizeAxes = Axes.Both }; From 668cc144f68c339ab03a4601c4d36faae86b051e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 18 Feb 2021 17:39:01 +0900 Subject: [PATCH 0629/1791] Fix test failures + multiple filter operations firing --- .../Collections/CollectionFilterDropdown.cs | 22 ++++++++++++++----- .../Collections/CollectionFilterMenuItem.cs | 8 ++++++- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/osu.Game/Collections/CollectionFilterDropdown.cs b/osu.Game/Collections/CollectionFilterDropdown.cs index 3e55ecb084..aad8400faa 100644 --- a/osu.Game/Collections/CollectionFilterDropdown.cs +++ b/osu.Game/Collections/CollectionFilterDropdown.cs @@ -29,6 +29,14 @@ namespace osu.Game.Collections /// protected virtual bool ShowManageCollectionsItem => true; + private readonly BindableWithCurrent current = new BindableWithCurrent(); + + public new Bindable Current + { + get => current.Current; + set => current.Current = value; + } + private readonly IBindableList collections = new BindableList(); private readonly IBindableList beatmaps = new BindableList(); private readonly BindableList filters = new BindableList(); @@ -36,14 +44,15 @@ namespace osu.Game.Collections [Resolved(CanBeNull = true)] private ManageCollectionsDialog manageCollectionsDialog { get; set; } + [Resolved(CanBeNull = true)] + private CollectionManager collectionManager { get; set; } + public CollectionFilterDropdown() { ItemSource = filters; + Current.Value = new AllBeatmapsCollectionFilterMenuItem(); } - [Resolved(CanBeNull = true)] - private CollectionManager collectionManager { get; set; } - protected override void LoadComplete() { base.LoadComplete(); @@ -51,9 +60,12 @@ namespace osu.Game.Collections if (collectionManager != null) collections.BindTo(collectionManager.Collections); - collections.CollectionChanged += (_, __) => collectionsChanged(); - collectionsChanged(); + // Dropdown has logic which triggers a change on the bindable with every change to the contained items. + // This is not desirable here, as it leads to multiple filter operations running even though nothing has changed. + // An extra bindable is enough to subvert this behaviour. + base.Current.BindTo(Current); + collections.BindCollectionChanged((_, __) => collectionsChanged(), true); Current.BindValueChanged(filterChanged, true); } diff --git a/osu.Game/Collections/CollectionFilterMenuItem.cs b/osu.Game/Collections/CollectionFilterMenuItem.cs index 4a489d2945..fe79358223 100644 --- a/osu.Game/Collections/CollectionFilterMenuItem.cs +++ b/osu.Game/Collections/CollectionFilterMenuItem.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using JetBrains.Annotations; using osu.Framework.Bindables; @@ -9,7 +10,7 @@ namespace osu.Game.Collections /// /// A filter. /// - public class CollectionFilterMenuItem + public class CollectionFilterMenuItem : IEquatable { /// /// The collection to filter beatmaps from. @@ -33,6 +34,11 @@ namespace osu.Game.Collections Collection = collection; CollectionName = Collection?.Name.GetBoundCopy() ?? new Bindable("All beatmaps"); } + + public bool Equals(CollectionFilterMenuItem other) + => other != null && CollectionName.Value == other.CollectionName.Value; + + public override int GetHashCode() => CollectionName.Value.GetHashCode(); } public class AllBeatmapsCollectionFilterMenuItem : CollectionFilterMenuItem From 71316bbee568c7ded598d502204054db85ffbb53 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Feb 2021 17:45:58 +0900 Subject: [PATCH 0630/1791] Allow using OnlineViewContainer without deriving it --- osu.Game/Online/OnlineViewContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/OnlineViewContainer.cs b/osu.Game/Online/OnlineViewContainer.cs index c9fb70f0cc..8868f90524 100644 --- a/osu.Game/Online/OnlineViewContainer.cs +++ b/osu.Game/Online/OnlineViewContainer.cs @@ -15,7 +15,7 @@ namespace osu.Game.Online /// A for displaying online content which require a local user to be logged in. /// Shows its children only when the local user is logged in and supports displaying a placeholder if not. /// - public abstract class OnlineViewContainer : Container + public class OnlineViewContainer : Container { protected LoadingSpinner LoadingSpinner { get; private set; } @@ -30,7 +30,7 @@ namespace osu.Game.Online [Resolved] protected IAPIProvider API { get; private set; } - protected OnlineViewContainer(string placeholderMessage) + public OnlineViewContainer(string placeholderMessage) { this.placeholderMessage = placeholderMessage; } From c3f66a0c745ce7c53fbf948625f5ad620dd2d730 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Feb 2021 17:46:07 +0900 Subject: [PATCH 0631/1791] Add login placeholder for chat overlay --- osu.Game/Overlays/ChatOverlay.cs | 73 ++++++++++++++++---------------- 1 file changed, 36 insertions(+), 37 deletions(-) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 8bc7e21047..f5dd4fae2c 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -24,6 +24,7 @@ using osu.Game.Overlays.Chat.Tabs; using osuTK.Input; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osu.Game.Online; namespace osu.Game.Overlays { @@ -118,40 +119,47 @@ namespace osu.Game.Overlays { RelativeSizeAxes = Axes.Both, }, - currentChannelContainer = new Container + new OnlineViewContainer("Sign in to chat") { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding - { - Bottom = textbox_height - }, - }, - new Container - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.X, - Height = textbox_height, - Padding = new MarginPadding - { - Top = padding * 2, - Bottom = padding * 2, - Left = ChatLine.LEFT_PADDING + padding * 2, - Right = padding * 2, - }, Children = new Drawable[] { - textbox = new FocusedTextBox + currentChannelContainer = new Container { RelativeSizeAxes = Axes.Both, - Height = 1, - PlaceholderText = "type your message", - ReleaseFocusOnCommit = false, - HoldFocus = true, - } - } - }, - loading = new LoadingSpinner(), + Padding = new MarginPadding + { + Bottom = textbox_height + }, + }, + new Container + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + Height = textbox_height, + Padding = new MarginPadding + { + Top = padding * 2, + Bottom = padding * 2, + Left = ChatLine.LEFT_PADDING + padding * 2, + Right = padding * 2, + }, + Children = new Drawable[] + { + textbox = new FocusedTextBox + { + RelativeSizeAxes = Axes.Both, + Height = 1, + PlaceholderText = "type your message", + ReleaseFocusOnCommit = false, + HoldFocus = true, + } + } + }, + loading = new LoadingSpinner(), + }, + } } }, tabsArea = new TabsArea @@ -184,9 +192,7 @@ namespace osu.Game.Overlays }, }, }; - textbox.OnCommit += postMessage; - ChannelTabControl.Current.ValueChanged += current => channelManager.CurrentChannel.Value = current.NewValue; ChannelTabControl.ChannelSelectorActive.ValueChanged += active => ChannelSelectionOverlay.State.Value = active.NewValue ? Visibility.Visible : Visibility.Hidden; ChannelSelectionOverlay.State.ValueChanged += state => @@ -203,10 +209,8 @@ namespace osu.Game.Overlays else textbox.HoldFocus = true; }; - ChannelSelectionOverlay.OnRequestJoin = channel => channelManager.JoinChannel(channel); ChannelSelectionOverlay.OnRequestLeave = channelManager.LeaveChannel; - ChatHeight = config.GetBindable(OsuSetting.ChatDisplayHeight); ChatHeight.BindValueChanged(height => { @@ -214,9 +218,7 @@ namespace osu.Game.Overlays channelSelectionContainer.Height = 1f - height.NewValue; tabBackground.FadeTo(height.NewValue == 1f ? 1f : 0.8f, 200); }, true); - chatBackground.Colour = colours.ChatBlue; - loading.Show(); // This is a relatively expensive (and blocking) operation. @@ -226,13 +228,10 @@ namespace osu.Game.Overlays { // TODO: consider scheduling bindable callbacks to not perform when overlay is not present. channelManager.JoinedChannels.CollectionChanged += joinedChannelsChanged; - foreach (Channel channel in channelManager.JoinedChannels) ChannelTabControl.AddChannel(channel); - channelManager.AvailableChannels.CollectionChanged += availableChannelsChanged; availableChannelsChanged(null, null); - currentChannel = channelManager.CurrentChannel.GetBoundCopy(); currentChannel.BindValueChanged(currentChannelChanged, true); }); From 58d8f0733cfed3f432879263e827fc4cc1128162 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Feb 2021 17:45:58 +0900 Subject: [PATCH 0632/1791] Allow using OnlineViewContainer without deriving it --- osu.Game/Online/OnlineViewContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/OnlineViewContainer.cs b/osu.Game/Online/OnlineViewContainer.cs index c9fb70f0cc..8868f90524 100644 --- a/osu.Game/Online/OnlineViewContainer.cs +++ b/osu.Game/Online/OnlineViewContainer.cs @@ -15,7 +15,7 @@ namespace osu.Game.Online /// A for displaying online content which require a local user to be logged in. /// Shows its children only when the local user is logged in and supports displaying a placeholder if not. /// - public abstract class OnlineViewContainer : Container + public class OnlineViewContainer : Container { protected LoadingSpinner LoadingSpinner { get; private set; } @@ -30,7 +30,7 @@ namespace osu.Game.Online [Resolved] protected IAPIProvider API { get; private set; } - protected OnlineViewContainer(string placeholderMessage) + public OnlineViewContainer(string placeholderMessage) { this.placeholderMessage = placeholderMessage; } From 0bd1964d8e7db31f84374aa079c16e43b2c33a24 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Feb 2021 18:04:41 +0900 Subject: [PATCH 0633/1791] Add login placeholder logic to OnlineOverlay A perfect implementation of this would probably leave the filter/header content visible, but that requires some re-thinking and restructuring to how the content is displayed in these overlays (ie. the header component shouldn't be inside the `ScrollContainer` as it is fixed). Supersedes and closes #10774. Closes #933. Addresses most pieces of #7417. --- osu.Game/Overlays/ChangelogOverlay.cs | 2 +- osu.Game/Overlays/NewsOverlay.cs | 2 +- osu.Game/Overlays/OnlineOverlay.cs | 15 ++++++++++++--- osu.Game/Overlays/TabbableOnlineOverlay.cs | 3 +-- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/ChangelogOverlay.cs b/osu.Game/Overlays/ChangelogOverlay.cs index 593f59555a..537dd00727 100644 --- a/osu.Game/Overlays/ChangelogOverlay.cs +++ b/osu.Game/Overlays/ChangelogOverlay.cs @@ -30,7 +30,7 @@ namespace osu.Game.Overlays protected List Streams; public ChangelogOverlay() - : base(OverlayColourScheme.Purple) + : base(OverlayColourScheme.Purple, false) { } diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs index 08e8331dd3..5beb285216 100644 --- a/osu.Game/Overlays/NewsOverlay.cs +++ b/osu.Game/Overlays/NewsOverlay.cs @@ -14,7 +14,7 @@ namespace osu.Game.Overlays private readonly Bindable article = new Bindable(null); public NewsOverlay() - : base(OverlayColourScheme.Purple) + : base(OverlayColourScheme.Purple, false) { } diff --git a/osu.Game/Overlays/OnlineOverlay.cs b/osu.Game/Overlays/OnlineOverlay.cs index 7c9f751d3b..0a5ceb1993 100644 --- a/osu.Game/Overlays/OnlineOverlay.cs +++ b/osu.Game/Overlays/OnlineOverlay.cs @@ -4,6 +4,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.UserInterface; +using osu.Game.Online; namespace osu.Game.Overlays { @@ -16,10 +17,16 @@ namespace osu.Game.Overlays protected readonly LoadingLayer Loading; private readonly Container content; - protected OnlineOverlay(OverlayColourScheme colourScheme) + protected OnlineOverlay(OverlayColourScheme colourScheme, bool requiresSignIn = true) : base(colourScheme) { - base.Content.AddRange(new Drawable[] + var mainContent = requiresSignIn + ? new OnlineViewContainer($"Sign in to view the {Header.Title.Title}") + : new Container(); + + mainContent.RelativeSizeAxes = Axes.Both; + + mainContent.AddRange(new Drawable[] { ScrollFlow = new OverlayScrollContainer { @@ -41,8 +48,10 @@ namespace osu.Game.Overlays } } }, - Loading = new LoadingLayer(true) + Loading = new LoadingLayer() }); + + base.Content.Add(mainContent); } } } diff --git a/osu.Game/Overlays/TabbableOnlineOverlay.cs b/osu.Game/Overlays/TabbableOnlineOverlay.cs index 8172e99c1b..9ceab12d3d 100644 --- a/osu.Game/Overlays/TabbableOnlineOverlay.cs +++ b/osu.Game/Overlays/TabbableOnlineOverlay.cs @@ -61,8 +61,7 @@ namespace osu.Game.Overlays LoadComponentAsync(display, loaded => { - if (API.IsLoggedIn) - Loading.Hide(); + Loading.Hide(); Child = loaded; }, (cancellationToken = new CancellationTokenSource()).Token); From 990c1b1d6ef118c755b8cdff7150601542f47535 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Feb 2021 18:19:57 +0900 Subject: [PATCH 0634/1791] Revert accidental removal of newlines --- osu.Game/Overlays/ChatOverlay.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index f5dd4fae2c..28f2287514 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -192,7 +192,9 @@ namespace osu.Game.Overlays }, }, }; + textbox.OnCommit += postMessage; + ChannelTabControl.Current.ValueChanged += current => channelManager.CurrentChannel.Value = current.NewValue; ChannelTabControl.ChannelSelectorActive.ValueChanged += active => ChannelSelectionOverlay.State.Value = active.NewValue ? Visibility.Visible : Visibility.Hidden; ChannelSelectionOverlay.State.ValueChanged += state => @@ -209,8 +211,10 @@ namespace osu.Game.Overlays else textbox.HoldFocus = true; }; + ChannelSelectionOverlay.OnRequestJoin = channel => channelManager.JoinChannel(channel); ChannelSelectionOverlay.OnRequestLeave = channelManager.LeaveChannel; + ChatHeight = config.GetBindable(OsuSetting.ChatDisplayHeight); ChatHeight.BindValueChanged(height => { @@ -218,7 +222,9 @@ namespace osu.Game.Overlays channelSelectionContainer.Height = 1f - height.NewValue; tabBackground.FadeTo(height.NewValue == 1f ? 1f : 0.8f, 200); }, true); + chatBackground.Colour = colours.ChatBlue; + loading.Show(); // This is a relatively expensive (and blocking) operation. @@ -228,10 +234,13 @@ namespace osu.Game.Overlays { // TODO: consider scheduling bindable callbacks to not perform when overlay is not present. channelManager.JoinedChannels.CollectionChanged += joinedChannelsChanged; + foreach (Channel channel in channelManager.JoinedChannels) ChannelTabControl.AddChannel(channel); + channelManager.AvailableChannels.CollectionChanged += availableChannelsChanged; availableChannelsChanged(null, null); + currentChannel = channelManager.CurrentChannel.GetBoundCopy(); currentChannel.BindValueChanged(currentChannelChanged, true); }); From 00574a528868d1ef31907484e71623969c0cfeaf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Feb 2021 18:32:28 +0900 Subject: [PATCH 0635/1791] 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 0636/1791] 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 0637/1791] 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 0638/1791] 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 a01896a652ee042cc66149a99ccf5b76dddef535 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 18 Feb 2021 13:04:22 +0300 Subject: [PATCH 0639/1791] Fix misordered hit error in score meter types --- osu.Game/Configuration/ScoreMeterType.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Configuration/ScoreMeterType.cs b/osu.Game/Configuration/ScoreMeterType.cs index b9499c758e..ddbd2327c2 100644 --- a/osu.Game/Configuration/ScoreMeterType.cs +++ b/osu.Game/Configuration/ScoreMeterType.cs @@ -16,12 +16,12 @@ namespace osu.Game.Configuration [Description("Hit Error (right)")] HitErrorRight, - [Description("Hit Error (bottom)")] - HitErrorBottom, - [Description("Hit Error (left+right)")] HitErrorBoth, + [Description("Hit Error (bottom)")] + HitErrorBottom, + [Description("Colour (left)")] ColourLeft, From d85a4a22e525cc4914c12bbe067e79f7ef8ba8cb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Feb 2021 19:19:28 +0900 Subject: [PATCH 0640/1791] Allow beatmap imports from any derived version of SongSelect, rather than only PlaySongSelect --- osu.Game/OsuGame.cs | 2 +- osu.Game/PerformFromMenuRunner.cs | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 15785ea6bd..771bcd2310 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -383,7 +383,7 @@ namespace osu.Game Ruleset.Value = selection.Ruleset; Beatmap.Value = BeatmapManager.GetWorkingBeatmap(selection); - }, validScreens: new[] { typeof(PlaySongSelect) }); + }, validScreens: new[] { typeof(SongSelect) }); } /// diff --git a/osu.Game/PerformFromMenuRunner.cs b/osu.Game/PerformFromMenuRunner.cs index 7999023998..3df9ca5305 100644 --- a/osu.Game/PerformFromMenuRunner.cs +++ b/osu.Game/PerformFromMenuRunner.cs @@ -82,7 +82,9 @@ namespace osu.Game game?.CloseAllOverlays(false); // we may already be at the target screen type. - if (validScreens.Contains(current.GetType()) && !beatmap.Disabled) + var type = current.GetType(); + + if (validScreens.Any(t => type.IsAssignableFrom(t)) && !beatmap.Disabled) { finalAction(current); Cancel(); @@ -91,13 +93,14 @@ namespace osu.Game while (current != null) { - if (validScreens.Contains(current.GetType())) + if (validScreens.Any(t => type.IsAssignableFrom(t))) { current.MakeCurrent(); break; } current = current.GetParentScreen(); + type = current?.GetType(); } } From 8a1a4ea2d42874d1dc752d4ec6a3371d02940054 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 18 Feb 2021 19:33:04 +0900 Subject: [PATCH 0641/1791] Set Current directly --- osu.Game/Collections/CollectionFilterDropdown.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Collections/CollectionFilterDropdown.cs b/osu.Game/Collections/CollectionFilterDropdown.cs index aad8400faa..bb743d4ccc 100644 --- a/osu.Game/Collections/CollectionFilterDropdown.cs +++ b/osu.Game/Collections/CollectionFilterDropdown.cs @@ -63,7 +63,7 @@ namespace osu.Game.Collections // Dropdown has logic which triggers a change on the bindable with every change to the contained items. // This is not desirable here, as it leads to multiple filter operations running even though nothing has changed. // An extra bindable is enough to subvert this behaviour. - base.Current.BindTo(Current); + base.Current = Current; collections.BindCollectionChanged((_, __) => collectionsChanged(), true); Current.BindValueChanged(filterChanged, true); From e14a59f272f7c5feaab972493c2fc8cf1a91c3e6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 18 Feb 2021 15:26:59 +0300 Subject: [PATCH 0642/1791] Fix creating ruleset instances per LINQ select --- .../OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index 25bc314f1b..5bef934e6a 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs @@ -163,7 +163,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants const double fade_time = 50; - var ruleset = rulesets.GetRuleset(Room.Settings.RulesetID); + var ruleset = rulesets.GetRuleset(Room.Settings.RulesetID).CreateInstance(); var currentModeRank = User.User?.RulesetsStatistics?.GetValueOrDefault(ruleset.ShortName)?.GlobalRank; userRankText.Text = currentModeRank != null ? $"#{currentModeRank.Value:N0}" : string.Empty; @@ -177,7 +177,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants // If the mods are updated at the end of the frame, the flow container will skip a reflow cycle: https://github.com/ppy/osu-framework/issues/4187 // This looks particularly jarring here, so re-schedule the update to that start of our frame as a fix. - Schedule(() => userModsDisplay.Current.Value = User.Mods.Select(m => m.ToMod(ruleset.CreateInstance())).ToList()); + Schedule(() => userModsDisplay.Current.Value = User.Mods.Select(m => m.ToMod(ruleset)).ToList()); } public MenuItem[] ContextMenuItems From a407bfe73bc0e4aefce7a30a00a4daf6cb30c481 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 18 Feb 2021 15:37:52 +0300 Subject: [PATCH 0643/1791] Privatize `UserRanks` and expose a similar `CountryRank` field instead --- .../Visual/Online/TestSceneUserProfileOverlay.cs | 2 +- .../Profile/Header/CentreHeaderContainer.cs | 2 +- .../Profile/Header/DetailHeaderContainer.cs | 2 +- osu.Game/Users/UserStatistics.cs | 13 +++++++++---- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs index b52cc6edb6..03d079261d 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs @@ -34,7 +34,7 @@ namespace osu.Game.Tests.Visual.Online Statistics = new UserStatistics { GlobalRank = 2148, - Ranks = new UserStatistics.UserRanks { Country = 1, }, + CountryRank = 1, PP = 4567.89m, Level = new UserStatistics.LevelInfo { diff --git a/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs index 9285e2d875..62ebee7677 100644 --- a/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs @@ -145,7 +145,7 @@ namespace osu.Game.Overlays.Profile.Header private void updateDisplay(User user) { hiddenDetailGlobal.Content = user?.Statistics?.GlobalRank?.ToString("\\##,##0") ?? "-"; - hiddenDetailCountry.Content = user?.Statistics?.Ranks.Country?.ToString("\\##,##0") ?? "-"; + hiddenDetailCountry.Content = user?.Statistics?.CountryRank?.ToString("\\##,##0") ?? "-"; } } } diff --git a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs index 05a0508e1f..574aef02fd 100644 --- a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs @@ -177,7 +177,7 @@ namespace osu.Game.Overlays.Profile.Header scoreRankInfo.Value.RankCount = user?.Statistics?.GradesCount[scoreRankInfo.Key] ?? 0; detailGlobalRank.Content = user?.Statistics?.GlobalRank?.ToString("\\##,##0") ?? "-"; - detailCountryRank.Content = user?.Statistics?.Ranks.Country?.ToString("\\##,##0") ?? "-"; + detailCountryRank.Content = user?.Statistics?.CountryRank?.ToString("\\##,##0") ?? "-"; rankGraph.Statistics.Value = user?.Statistics; } diff --git a/osu.Game/Users/UserStatistics.cs b/osu.Game/Users/UserStatistics.cs index e50ca57d90..90c1d40848 100644 --- a/osu.Game/Users/UserStatistics.cs +++ b/osu.Game/Users/UserStatistics.cs @@ -29,10 +29,15 @@ namespace osu.Game.Users [JsonProperty(@"global_rank")] public int? GlobalRank; - // eventually UserRanks object will be completely replaced with separate global rank (exists) and country rank properties - // see https://github.com/ppy/osu-web/blob/cb79bb72186c8f1a25f6a6f5ef315123decb4231/app/Transformers/UserStatisticsTransformer.php#L53. + public int? CountryRank; + [JsonProperty(@"rank")] - public UserRanks Ranks; + private UserRanks ranks + { + // eventually that will also become an own json property instead of reading from a `rank` object. + // see https://github.com/ppy/osu-web/blob/cb79bb72186c8f1a25f6a6f5ef315123decb4231/app/Transformers/UserStatisticsTransformer.php#L53. + set => CountryRank = value.Country; + } [JsonProperty(@"pp")] public decimal? PP; @@ -115,7 +120,7 @@ namespace osu.Game.Users } } - public struct UserRanks + private struct UserRanks { [JsonProperty(@"country")] public int? Country; From f6df5a9d2b67272e6a1e42a6cd71dad5777c0e02 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 18 Feb 2021 15:55:45 +0300 Subject: [PATCH 0644/1791] Suppress false warning --- osu.Game/Users/UserStatistics.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Users/UserStatistics.cs b/osu.Game/Users/UserStatistics.cs index 90c1d40848..4b1e46d51a 100644 --- a/osu.Game/Users/UserStatistics.cs +++ b/osu.Game/Users/UserStatistics.cs @@ -122,8 +122,10 @@ namespace osu.Game.Users private struct UserRanks { +#pragma warning disable 649 [JsonProperty(@"country")] public int? Country; +#pragma warning restore 649 } public RankHistoryData RankHistory; From 10ec4cd8e07950b8a48e8da8931e3e39b16559b8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Feb 2021 22:38:17 +0900 Subject: [PATCH 0645/1791] Revert change to loading layer's default state --- osu.Game/Overlays/OnlineOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/OnlineOverlay.cs b/osu.Game/Overlays/OnlineOverlay.cs index 0a5ceb1993..de33e4a1bc 100644 --- a/osu.Game/Overlays/OnlineOverlay.cs +++ b/osu.Game/Overlays/OnlineOverlay.cs @@ -48,7 +48,7 @@ namespace osu.Game.Overlays } } }, - Loading = new LoadingLayer() + Loading = new LoadingLayer(true) }); base.Content.Add(mainContent); From 4caca9653ab02ddbb9dea6105bb5cbeb807fae2b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Feb 2021 10:39:56 +0900 Subject: [PATCH 0646/1791] 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 e30416bc1c..bc5ba57d71 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 72f680f6f8..fef2f567df 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -29,7 +29,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 137c96a72d..0d473290e6 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -91,7 +91,7 @@ - + From bc10fcafae2d57b6bb07d6bf666ee72577e5b8f9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Feb 2021 13:23:01 +0900 Subject: [PATCH 0647/1791] Remove now unnecessary schedule --- .../Multiplayer/MultiplayerMatchSubScreen.cs | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index b5eff04532..cb2b6dda95 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -47,7 +47,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer private Drawable userModsSection; private readonly IBindable isConnected = new Bindable(); - private readonly IBindable matchCurrentItem = new Bindable(); [CanBeNull] private IDisposable readyClickOperation; @@ -269,8 +268,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { base.LoadComplete(); - matchCurrentItem.BindTo(client.CurrentMatchPlayingItem); - matchCurrentItem.BindValueChanged(onCurrentItemChanged, true); + SelectedItem.BindTo(client.CurrentMatchPlayingItem); + SelectedItem.BindValueChanged(onSelectedItemChanged, true); BeatmapAvailability.BindValueChanged(updateBeatmapAvailability, true); UserMods.BindValueChanged(onUserModsChanged); @@ -286,20 +285,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer }, true); } - private void onCurrentItemChanged(ValueChangedEvent item) + private void onSelectedItemChanged(ValueChangedEvent item) { if (client?.LocalUser == null) return; - // If we're about to enter gameplay, schedule the item to be set at a later time. - if (client.LocalUser.State > MultiplayerUserState.Ready) - { - Schedule(() => onCurrentItemChanged(item)); - return; - } - - SelectedItem.Value = item.NewValue; - if (item.NewValue?.AllowedMods.Any() != true) { userModsSection.Hide(); From 841c2c56d961e7eb76cb48f4fe2686f9250e6e7a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Feb 2021 13:30:42 +0900 Subject: [PATCH 0648/1791] Remove confusing pp_rank include (will be removed osu-web side too) --- osu.Game/Users/UserStatistics.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Users/UserStatistics.cs b/osu.Game/Users/UserStatistics.cs index 4b1e46d51a..70969ea737 100644 --- a/osu.Game/Users/UserStatistics.cs +++ b/osu.Game/Users/UserStatistics.cs @@ -42,9 +42,6 @@ namespace osu.Game.Users [JsonProperty(@"pp")] public decimal? PP; - [JsonProperty(@"pp_rank")] - public int PPRank; - [JsonProperty(@"ranked_score")] public long RankedScore; From 183a481a345ea4fff4b55dd6df7fcdbc4e1dad4b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Feb 2021 13:32:32 +0900 Subject: [PATCH 0649/1791] Refactor playlist update to remove .Contains() check --- .../Multiplayer/StatefulMultiplayerClient.cs | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index 416162779d..ed97307c95 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -537,21 +537,30 @@ namespace osu.Game.Online.Multiplayer var mods = settings.RequiredMods.Select(m => m.ToMod(ruleset)); var allowedMods = settings.AllowedMods.Select(m => m.ToMod(ruleset)); - // Update an existing playlist item from the API room, or create a new item. - var playlistItem = apiRoom.Playlist.FirstOrDefault(i => i.ID == settings.PlaylistItemId) ?? new PlaylistItem(); + // Try to retrieve the existing playlist item from the API room. + var playlistItem = apiRoom.Playlist.FirstOrDefault(i => i.ID == settings.PlaylistItemId); - playlistItem.ID = settings.PlaylistItemId; - playlistItem.Beatmap.Value = beatmap; - playlistItem.Ruleset.Value = ruleset.RulesetInfo; - playlistItem.RequiredMods.Clear(); - playlistItem.RequiredMods.AddRange(mods); - playlistItem.AllowedMods.Clear(); - playlistItem.AllowedMods.AddRange(allowedMods); - - if (!apiRoom.Playlist.Contains(playlistItem)) + if (playlistItem != null) + updateItem(playlistItem); + else + { + // An existing playlist item does not exist, so append a new one. + updateItem(playlistItem = new PlaylistItem()); apiRoom.Playlist.Add(playlistItem); + } CurrentMatchPlayingItem.Value = playlistItem; + + void updateItem(PlaylistItem item) + { + item.ID = settings.PlaylistItemId; + item.Beatmap.Value = beatmap; + item.Ruleset.Value = ruleset.RulesetInfo; + item.RequiredMods.Clear(); + item.RequiredMods.AddRange(mods); + item.AllowedMods.Clear(); + item.AllowedMods.AddRange(allowedMods); + } } /// From 85a844a37820425e05df93d2d615593dbcc1989f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Feb 2021 13:40:12 +0900 Subject: [PATCH 0650/1791] Restructure class slightly --- osu.Game/Users/UserStatistics.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Users/UserStatistics.cs b/osu.Game/Users/UserStatistics.cs index 70969ea737..78e6f5a05a 100644 --- a/osu.Game/Users/UserStatistics.cs +++ b/osu.Game/Users/UserStatistics.cs @@ -39,6 +39,9 @@ namespace osu.Game.Users set => CountryRank = value.Country; } + // populated via User model, as that's where the data currently lives. + public RankHistoryData RankHistory; + [JsonProperty(@"pp")] public decimal? PP; @@ -117,14 +120,12 @@ namespace osu.Game.Users } } +#pragma warning disable 649 private struct UserRanks { -#pragma warning disable 649 [JsonProperty(@"country")] public int? Country; -#pragma warning restore 649 } - - public RankHistoryData RankHistory; +#pragma warning restore 649 } } From c0e0bd4f421764ae6418597ed30bb64d501a69e8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Feb 2021 13:57:04 +0900 Subject: [PATCH 0651/1791] Add compatibility with old server build --- osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index ed97307c95..bfd505fb19 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -94,6 +94,10 @@ namespace osu.Game.Online.Multiplayer [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; + private Room? apiRoom; [BackgroundDependencyLoader] @@ -141,6 +145,7 @@ namespace osu.Game.Online.Multiplayer { Room = joinedRoom; apiRoom = room; + defaultPlaylistItemId = apiRoom.Playlist.FirstOrDefault()?.ID ?? 0; }, cancellationSource.Token); // Update room settings. @@ -553,7 +558,7 @@ namespace osu.Game.Online.Multiplayer void updateItem(PlaylistItem item) { - item.ID = settings.PlaylistItemId; + item.ID = settings.PlaylistItemId == 0 ? defaultPlaylistItemId : settings.PlaylistItemId; item.Beatmap.Value = beatmap; item.Ruleset.Value = ruleset.RulesetInfo; item.RequiredMods.Clear(); From 87edf6787981a49c7160dac2d26beebf8b404416 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Feb 2021 14:07:39 +0900 Subject: [PATCH 0652/1791] 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 bc5ba57d71..bfdc8f6b3c 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 fef2f567df..4138fc8d6c 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 0d473290e6..783b638aa0 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -91,7 +91,7 @@ - + From 1701d69a602520fb75c7f39f5c3fe7ad7d9f641d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Feb 2021 14:33:08 +0900 Subject: [PATCH 0653/1791] Fix calls to IsAssignableFrom being back-to-front --- osu.Game/PerformFromMenuRunner.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/PerformFromMenuRunner.cs b/osu.Game/PerformFromMenuRunner.cs index 3df9ca5305..a4179c94da 100644 --- a/osu.Game/PerformFromMenuRunner.cs +++ b/osu.Game/PerformFromMenuRunner.cs @@ -84,7 +84,7 @@ namespace osu.Game // we may already be at the target screen type. var type = current.GetType(); - if (validScreens.Any(t => type.IsAssignableFrom(t)) && !beatmap.Disabled) + if (validScreens.Any(t => t.IsAssignableFrom(type)) && !beatmap.Disabled) { finalAction(current); Cancel(); @@ -93,7 +93,7 @@ namespace osu.Game while (current != null) { - if (validScreens.Any(t => type.IsAssignableFrom(t))) + if (validScreens.Any(t => t.IsAssignableFrom(type))) { current.MakeCurrent(); break; From 39059ed82d57fa526018371089dc609c1fc0b7b3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Feb 2021 14:36:51 +0900 Subject: [PATCH 0654/1791] Remove unnecessary null coalesce check --- osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 15e12eac40..da516798c8 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -166,19 +166,21 @@ namespace osu.Game.Screens.OnlinePlay.Match { updateWorkingBeatmap(); - if (SelectedItem.Value == null) + var selected = SelectedItem.Value; + + if (selected == null) return; // Remove any user mods that are no longer allowed. UserMods.Value = UserMods.Value - .Where(m => SelectedItem.Value.AllowedMods.Any(a => m.GetType() == a.GetType())) + .Where(m => selected.AllowedMods.Any(a => m.GetType() == a.GetType())) .ToList(); UpdateMods(); - Ruleset.Value = SelectedItem.Value.Ruleset.Value; + Ruleset.Value = selected.Ruleset.Value; - if (SelectedItem.Value?.AllowedMods.Any() != true) + if (selected.AllowedMods.Any() != true) { UserModsSection?.Hide(); userModsSelectOverlay.Hide(); @@ -187,7 +189,7 @@ namespace osu.Game.Screens.OnlinePlay.Match else { UserModsSection?.Show(); - userModsSelectOverlay.IsValidMod = m => SelectedItem.Value.AllowedMods.Any(a => a.GetType() == m.GetType()); + userModsSelectOverlay.IsValidMod = m => selected.AllowedMods.Any(a => a.GetType() == m.GetType()); } } From 484968d797852ce71cb412ade2e43e11b267cd0f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Feb 2021 14:46:10 +0900 Subject: [PATCH 0655/1791] Fix weird bool check Co-authored-by: Dan Balasescu --- osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 52705302aa..4a689314db 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -172,7 +172,7 @@ namespace osu.Game.Screens.OnlinePlay.Match Ruleset.Value = selected.Ruleset.Value; - if (selected.AllowedMods.Any() != true) + if (!selected.AllowedMods.Any()) { UserModsSection?.Hide(); userModsSelectOverlay.Hide(); From ee9e6fff402b146f2c99b8a872f3fe3f5cfc703f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Feb 2021 15:09:41 +0900 Subject: [PATCH 0656/1791] Add bindable flow for expanded leaderboard state --- .../Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs | 1 + osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs | 4 ++++ osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs | 2 ++ 3 files changed, 7 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index aab69d687a..026c302642 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -85,6 +85,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestScoreUpdates() { AddRepeatStep("update state", () => streamingClient.RandomlyUpdateState(), 100); + AddToggleStep("switch compact mode", expanded => leaderboard.Expanded.Value = expanded); } [Test] diff --git a/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs index 7b94bf19ec..20e24ed945 100644 --- a/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using JetBrains.Annotations; +using osu.Framework.Bindables; using osu.Framework.Caching; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -16,6 +17,8 @@ namespace osu.Game.Screens.Play.HUD { private readonly Cached sorting = new Cached(); + public Bindable Expanded = new Bindable(); + public GameplayLeaderboard() { Width = GameplayLeaderboardScore.EXTENDED_WIDTH + GameplayLeaderboardScore.SHEAR_WIDTH; @@ -49,6 +52,7 @@ namespace osu.Game.Screens.Play.HUD { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, + Expanded = { BindTarget = Expanded }, }; base.Add(drawable); diff --git a/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs b/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs index cb20deb272..f738f91e63 100644 --- a/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs +++ b/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs @@ -30,6 +30,8 @@ namespace osu.Game.Screens.Play.HUD private const float panel_shear = 0.15f; + public Bindable Expanded = new Bindable(); + private OsuSpriteText positionText, scoreText, accuracyText, comboText, usernameText; public BindableDouble TotalScore { get; } = new BindableDouble(); From 43c35c5118045f9c52a1755698073415f455d03b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Feb 2021 15:15:31 +0900 Subject: [PATCH 0657/1791] Show local user in test scene --- .../Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index 026c302642..1ee848b902 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -13,6 +13,7 @@ using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Database; using osu.Game.Online; +using osu.Game.Online.API; using osu.Game.Online.Spectator; using osu.Game.Replays.Legacy; using osu.Game.Rulesets.Osu.Scoring; @@ -50,6 +51,8 @@ namespace osu.Game.Tests.Visual.Multiplayer [SetUpSteps] public override void SetUpSteps() { + AddStep("set local user", () => ((DummyAPIAccess)API).LocalUser.Value = lookupCache.GetUserAsync(1).Result); + AddStep("create leaderboard", () => { leaderboard?.Expire(); From 691cfa5bc3bdceda779441dcd99a2437bd2beee8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Feb 2021 16:46:30 +0900 Subject: [PATCH 0658/1791] Add expanded/compact display modes for GameplayLeaderboard --- .../Screens/Play/HUD/GameplayLeaderboard.cs | 2 - .../Play/HUD/GameplayLeaderboardScore.cs | 347 +++++++++++------- 2 files changed, 210 insertions(+), 139 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs index 20e24ed945..34efeab54c 100644 --- a/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs @@ -50,8 +50,6 @@ namespace osu.Game.Screens.Play.HUD { var drawable = new GameplayLeaderboardScore(user, isTracked) { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, Expanded = { BindTarget = Expanded }, }; diff --git a/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs b/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs index f738f91e63..10476e5565 100644 --- a/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs +++ b/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs @@ -20,16 +20,31 @@ namespace osu.Game.Screens.Play.HUD { public class GameplayLeaderboardScore : CompositeDrawable, ILeaderboardScore { - public const float EXTENDED_WIDTH = 255f; + public const float EXTENDED_WIDTH = regular_width + top_player_left_width_extension; private const float regular_width = 235f; + // a bit hand-wavy, but there's a lot of hard-coded paddings in each of the grid's internals. + private const float compact_width = 77.5f; + + private const float top_player_left_width_extension = 20f; + public const float PANEL_HEIGHT = 35f; public const float SHEAR_WIDTH = PANEL_HEIGHT * panel_shear; private const float panel_shear = 0.15f; + private const float rank_text_width = 35f; + + private const float score_components_width = 85f; + + private const float avatar_size = 25f; + + private const double panel_transition_duration = 500; + + private const double text_transition_duration = 200; + public Bindable Expanded = new Bindable(); private OsuSpriteText positionText, scoreText, accuracyText, comboText, usernameText; @@ -65,8 +80,15 @@ namespace osu.Game.Screens.Play.HUD private readonly bool trackedPlayer; private Container mainFillContainer; + private Box centralFill; + private Container backgroundPaddingAdjustContainer; + + private GridContainer gridContainer; + + private Container scoreComponents; + /// /// Creates a new . /// @@ -77,7 +99,8 @@ namespace osu.Game.Screens.Play.HUD User = user; this.trackedPlayer = trackedPlayer; - Size = new Vector2(EXTENDED_WIDTH, PANEL_HEIGHT); + AutoSizeAxes = Axes.X; + Height = PANEL_HEIGHT; } [BackgroundDependencyLoader] @@ -87,147 +110,167 @@ namespace osu.Game.Screens.Play.HUD InternalChildren = new Drawable[] { - mainFillContainer = new Container + new Container { - Width = regular_width, + AutoSizeAxes = Axes.X, RelativeSizeAxes = Axes.Y, - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Masking = true, - CornerRadius = 5f, - Shear = new Vector2(panel_shear, 0f), - Child = new Box + Margin = new MarginPadding { Left = top_player_left_width_extension }, + Children = new Drawable[] { - Alpha = 0.5f, - RelativeSizeAxes = Axes.Both, - } - }, - new GridContainer - { - Width = regular_width, - RelativeSizeAxes = Axes.Y, - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - ColumnDimensions = new[] - { - new Dimension(GridSizeMode.Absolute, 35f), - new Dimension(), - new Dimension(GridSizeMode.Absolute, 85f), - }, - Content = new[] - { - new Drawable[] + backgroundPaddingAdjustContainer = new Container { - positionText = new OsuSpriteText + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - Padding = new MarginPadding { Right = SHEAR_WIDTH / 2 }, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Colour = Color4.White, - Font = OsuFont.Torus.With(size: 14, weight: FontWeight.Bold), - Shadow = false, - }, - new Container - { - Padding = new MarginPadding { Horizontal = SHEAR_WIDTH / 3 }, - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + mainFillContainer = new Container { - new Container + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 5f, + Shear = new Vector2(panel_shear, 0f), + Children = new Drawable[] { - Masking = true, - CornerRadius = 5f, - Shear = new Vector2(panel_shear, 0f), - RelativeSizeAxes = Axes.Both, - Children = new[] + new Box { - centralFill = new Box - { - Alpha = 0.5f, - RelativeSizeAxes = Axes.Both, - Colour = Color4Extensions.FromHex("3399cc"), - }, - } - }, - new FillFlowContainer - { - Padding = new MarginPadding { Left = SHEAR_WIDTH }, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(4f, 0f), - Children = new Drawable[] - { - avatarContainer = new CircularContainer - { - Masking = true, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Size = new Vector2(25f), - Children = new Drawable[] - { - new Box - { - Name = "Placeholder while avatar loads", - Alpha = 0.3f, - RelativeSizeAxes = Axes.Both, - Colour = colours.Gray4, - } - } - }, - usernameText = new OsuSpriteText - { - RelativeSizeAxes = Axes.X, - Width = 0.6f, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Colour = Color4.White, - Font = OsuFont.Torus.With(size: 14, weight: FontWeight.SemiBold), - Text = User?.Username, - Truncate = true, - Shadow = false, - } - } - }, - } - }, - new Container - { - Padding = new MarginPadding { Top = 2f, Right = 17.5f, Bottom = 5f }, - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Colour = Color4.White, - Children = new Drawable[] - { - scoreText = new OsuSpriteText - { - Spacing = new Vector2(-1f, 0f), - Font = OsuFont.Torus.With(size: 16, weight: FontWeight.SemiBold, fixedWidth: true), - Shadow = false, - }, - accuracyText = new OsuSpriteText - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold, fixedWidth: true), - Spacing = new Vector2(-1f, 0f), - Shadow = false, - }, - comboText = new OsuSpriteText - { - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - Spacing = new Vector2(-1f, 0f), - Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold, fixedWidth: true), - Shadow = false, + Alpha = 0.5f, + RelativeSizeAxes = Axes.Both, + }, }, }, } + }, + gridContainer = new GridContainer + { + RelativeSizeAxes = Axes.Y, + Width = compact_width, // will be updated by expanded state. + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.Absolute, rank_text_width), + new Dimension(), + new Dimension(GridSizeMode.AutoSize, maxSize: score_components_width), + }, + Content = new[] + { + new Drawable[] + { + positionText = new OsuSpriteText + { + Padding = new MarginPadding { Right = SHEAR_WIDTH / 2 }, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = Color4.White, + Font = OsuFont.Torus.With(size: 14, weight: FontWeight.Bold), + Shadow = false, + }, + new Container + { + Padding = new MarginPadding { Horizontal = SHEAR_WIDTH / 3 }, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Container + { + Masking = true, + CornerRadius = 5f, + Shear = new Vector2(panel_shear, 0f), + RelativeSizeAxes = Axes.Both, + Children = new[] + { + centralFill = new Box + { + Alpha = 0.5f, + RelativeSizeAxes = Axes.Both, + Colour = Color4Extensions.FromHex("3399cc"), + }, + } + }, + new FillFlowContainer + { + Padding = new MarginPadding { Left = SHEAR_WIDTH }, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(4f, 0f), + Children = new Drawable[] + { + avatarContainer = new CircularContainer + { + Masking = true, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Size = new Vector2(avatar_size), + Children = new Drawable[] + { + new Box + { + Name = "Placeholder while avatar loads", + Alpha = 0.3f, + RelativeSizeAxes = Axes.Both, + Colour = colours.Gray4, + } + } + }, + usernameText = new OsuSpriteText + { + RelativeSizeAxes = Axes.X, + Width = 0.6f, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Colour = Color4.White, + Font = OsuFont.Torus.With(size: 14, weight: FontWeight.SemiBold), + Text = User?.Username, + Truncate = true, + Shadow = false, + } + } + }, + } + }, + scoreComponents = new Container + { + Padding = new MarginPadding { Top = 2f, Right = 17.5f, Bottom = 5f }, + AlwaysPresent = true, // required to smoothly animate autosize after hidden early. + Masking = true, + RelativeSizeAxes = Axes.Y, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Colour = Color4.White, + Children = new Drawable[] + { + scoreText = new OsuSpriteText + { + Spacing = new Vector2(-1f, 0f), + Font = OsuFont.Torus.With(size: 16, weight: FontWeight.SemiBold, fixedWidth: true), + Shadow = false, + }, + accuracyText = new OsuSpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold, fixedWidth: true), + Spacing = new Vector2(-1f, 0f), + Shadow = false, + }, + comboText = new OsuSpriteText + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Spacing = new Vector2(-1f, 0f), + Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold, fixedWidth: true), + Shadow = false, + }, + }, + } + } + } } } - } + }, }; LoadComponentAsync(new DrawableAvatar(User), avatarContainer.Add); @@ -243,18 +286,43 @@ namespace osu.Game.Screens.Play.HUD base.LoadComplete(); updateState(); + Expanded.BindValueChanged(changeExpandedState, true); + FinishTransforms(true); } - private const double panel_transition_duration = 500; + private void changeExpandedState(ValueChangedEvent expanded) + { + scoreComponents.ClearTransforms(); + + if (expanded.NewValue) + { + gridContainer.ResizeWidthTo(regular_width, panel_transition_duration, Easing.OutQuint); + + scoreComponents.ResizeWidthTo(score_components_width, panel_transition_duration, Easing.OutQuint); + scoreComponents.FadeIn(panel_transition_duration, Easing.OutQuint); + + usernameText.FadeIn(panel_transition_duration, Easing.OutQuint); + } + else + { + gridContainer.ResizeWidthTo(compact_width, panel_transition_duration, Easing.OutQuint); + + scoreComponents.ResizeWidthTo(0, panel_transition_duration, Easing.OutQuint); + scoreComponents.FadeOut(text_transition_duration, Easing.OutQuint); + + usernameText.FadeOut(text_transition_duration, Easing.OutQuint); + } + } private void updateState() { + bool widthExtension = false; + if (HasQuit.Value) { // we will probably want to display this in a better way once we have a design. // and also show states other than quit. - mainFillContainer.ResizeWidthTo(regular_width, panel_transition_duration, Easing.OutElastic); panelColour = Color4.Gray; textColour = Color4.White; return; @@ -262,22 +330,29 @@ namespace osu.Game.Screens.Play.HUD if (scorePosition == 1) { - mainFillContainer.ResizeWidthTo(EXTENDED_WIDTH, panel_transition_duration, Easing.OutElastic); + widthExtension = true; panelColour = Color4Extensions.FromHex("7fcc33"); textColour = Color4.White; } else if (trackedPlayer) { - mainFillContainer.ResizeWidthTo(EXTENDED_WIDTH, panel_transition_duration, Easing.OutElastic); + widthExtension = true; panelColour = Color4Extensions.FromHex("ffd966"); textColour = Color4Extensions.FromHex("2e576b"); } else { - mainFillContainer.ResizeWidthTo(regular_width, panel_transition_duration, Easing.OutElastic); panelColour = Color4Extensions.FromHex("3399cc"); textColour = Color4.White; } + + this.TransformTo(nameof(SizeContainerLeftPadding), widthExtension ? -top_player_left_width_extension : 0, panel_transition_duration, Easing.OutElastic); + } + + public float SizeContainerLeftPadding + { + get => backgroundPaddingAdjustContainer.Padding.Left; + set => backgroundPaddingAdjustContainer.Padding = new MarginPadding { Left = value }; } private Color4 panelColour @@ -289,8 +364,6 @@ namespace osu.Game.Screens.Play.HUD } } - private const double text_transition_duration = 200; - private Color4 textColour { set From 772471a6d826a730ca17176f2c542b7e4d2ba44a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 19 Feb 2021 09:34:39 +0300 Subject: [PATCH 0659/1791] 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 0660/1791] 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 9d02f589fe70bfa9b3d1b2ed9f46400b497250a1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Feb 2021 16:51:34 +0900 Subject: [PATCH 0661/1791] Compact leaderboard during gameplay --- .../Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs index 04d9e0a72a..ffcf248575 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs @@ -89,6 +89,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer Debug.Assert(client.Room != null); } + protected override void LoadComplete() + { + base.LoadComplete(); + + ((IBindable)leaderboard.Expanded).BindTo(IsBreakTime); + } + protected override void StartGameplay() { // block base call, but let the server know we are ready to start. From 52ebe343473007a06436cfe4c3969a129ef6c287 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Feb 2021 17:15:38 +0900 Subject: [PATCH 0662/1791] Update TestScenePause exit from fail test to actually fail --- .../Visual/Gameplay/TestScenePause.cs | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index aa56c636ab..1214a33084 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -140,7 +140,7 @@ namespace osu.Game.Tests.Visual.Gameplay confirmClockRunning(false); - pauseFromUserExitKey(); + AddStep("pause via forced pause", () => Player.Pause()); confirmPausedWithNoOverlay(); AddAssert("fail overlay still shown", () => Player.FailOverlayVisible); @@ -149,11 +149,28 @@ namespace osu.Game.Tests.Visual.Gameplay } [Test] - public void TestExitFromFailedGameplay() + public void TestExitFromFailedGameplayAfterFailAnimation() { AddUntilStep("wait for fail", () => Player.HasFailed); - AddStep("exit", () => Player.Exit()); + AddUntilStep("wait for fail overlay shown", () => Player.FailOverlayVisible); + confirmClockRunning(false); + + AddStep("exit via user pause", () => Player.ExitViaPause()); + confirmExited(); + } + + [Test] + public void TestExitFromFailedGameplayDuringFailAnimation() + { + AddUntilStep("wait for fail", () => Player.HasFailed); + + // will finish the fail animation and show the fail/pause screen. + AddStep("attempt exit via pause key", () => Player.ExitViaPause()); + AddAssert("fail overlay shown", () => Player.FailOverlayVisible); + + // will actually exit. + AddStep("exit via pause key", () => Player.ExitViaPause()); confirmExited(); } From 82cc06ca57d1b8ba63739799c7db5120cfe3ae7e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Feb 2021 17:26:54 +0900 Subject: [PATCH 0663/1791] Fix new logic not considering fail overlay correctly --- osu.Game/Screens/Play/Player.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 8f2c1d1b92..e4fc92b5f2 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -496,12 +496,13 @@ namespace osu.Game.Screens.Play return; } - bool pauseDialogShown = PauseOverlay.State.Value == Visibility.Visible; + bool pauseOrFailDialogVisible = + PauseOverlay.State.Value == Visibility.Visible || FailOverlay.State.Value == Visibility.Visible; - if (showDialogFirst && !pauseDialogShown) + if (showDialogFirst && !pauseOrFailDialogVisible) { // if the fail animation is currently in progress, accelerate it (it will show the pause dialog on completion). - if (ValidForResume && HasFailed && !FailOverlay.IsPresent) + if (ValidForResume && HasFailed) { failAnimation.FinishTransforms(true); return; From ddd1dcff88428460cfcde74c963196a0518924fe Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 19 Feb 2021 11:33:26 +0300 Subject: [PATCH 0664/1791] 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 0665/1791] 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 0666/1791] 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 0667/1791] 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 0668/1791] 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 0669/1791] 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 0670/1791] 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 d2ec151c67d09a8a442960ffce216ac5f04a81e2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 20 Feb 2021 14:19:44 +0900 Subject: [PATCH 0671/1791] Add failing test for pausing when pause support is disabled --- osu.Game.Tests/Visual/Gameplay/TestScenePause.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index 1214a33084..bddc7ab731 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -90,6 +90,15 @@ namespace osu.Game.Tests.Visual.Gameplay resumeAndConfirm(); } + [Test] + public void TestUserPauseWhenPauseNotAllowed() + { + AddStep("disable pause support", () => Player.Configuration.AllowPause = false); + + pauseFromUserExitKey(); + confirmExited(); + } + [Test] public void TestUserPauseDuringCooldownTooSoon() { From 38a21249213912af02c88c08037026e94ab11de3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 20 Feb 2021 13:35:25 +0900 Subject: [PATCH 0672/1791] Support instant exit if pausing is not allowed in the current game mode --- osu.Game/Screens/Play/Player.cs | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index e4fc92b5f2..1e130b7f88 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -508,10 +508,14 @@ namespace osu.Game.Screens.Play return; } - // in the case a dialog needs to be shown, attempt to pause and show it. - // this may fail (see internal checks in Pause()) at which point the exit attempt will be aborted. - Pause(); - return; + // there's a chance the pausing is not supported in the current state, at which point immediate exit should be preferred. + if (pausingSupportedByCurrentState) + { + // in the case a dialog needs to be shown, attempt to pause and show it. + // this may fail (see internal checks in Pause()) but the fail cases are temporary, so don't fall through to Exit(). + Pause(); + return; + } } this.Exit(); @@ -670,15 +674,17 @@ namespace osu.Game.Screens.Play private double? lastPauseActionTime; - private bool canPause => + /// + /// A set of conditionals which defines whether the current game state and configuration allows for + /// pausing to be attempted via . If false, the game should generally exit if a user pause + /// is attempted. + /// + private bool pausingSupportedByCurrentState => // must pass basic screen conditions (beatmap loaded, instance allows pause) LoadedBeatmapSuccessfully && Configuration.AllowPause && ValidForResume // replays cannot be paused and exit immediately && !DrawableRuleset.HasReplayLoaded.Value - // cannot pause if we are already in a fail state - && !HasFailed - // cannot pause if already paused (or in a cooldown state) unless we are in a resuming state. - && (IsResuming || (GameplayClockContainer.IsPaused.Value == false && !pauseCooldownActive)); + && !HasFailed; private bool pauseCooldownActive => lastPauseActionTime.HasValue && GameplayClockContainer.GameplayClock.CurrentTime < lastPauseActionTime + pause_cooldown; @@ -693,7 +699,10 @@ namespace osu.Game.Screens.Play public void Pause() { - if (!canPause) return; + if (!pausingSupportedByCurrentState) return; + + if (!IsResuming && pauseCooldownActive) + return; if (IsResuming) { From 9d229a5ec2ae17f70f68ec9d77fb99b2a6ebeaf4 Mon Sep 17 00:00:00 2001 From: Samuel Cattini-Schultz Date: Sat, 20 Feb 2021 16:27:58 +1100 Subject: [PATCH 0673/1791] Add tests for clockrate adjusted difficulty calculations --- .../CatchDifficultyCalculatorTest.cs | 5 +++++ .../ManiaDifficultyCalculatorTest.cs | 5 +++++ osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs | 6 ++++++ .../TaikoDifficultyCalculatorTest.cs | 6 ++++++ 4 files changed, 22 insertions(+) diff --git a/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs index ee416e5a38..f4ee3f5a42 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs @@ -4,6 +4,7 @@ using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Difficulty; +using osu.Game.Rulesets.Catch.Mods; using osu.Game.Rulesets.Difficulty; using osu.Game.Tests.Beatmaps; @@ -17,6 +18,10 @@ namespace osu.Game.Rulesets.Catch.Tests public void Test(double expected, string name) => base.Test(expected, name); + [TestCase(5.0565038923984691d, "diffcalc-test")] + public void TestClockRateAdjusted(double expected, string name) + => Test(expected, name, new CatchModDoubleTime()); + protected override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchDifficultyCalculator(new CatchRuleset(), beatmap); protected override Ruleset CreateRuleset() => new CatchRuleset(); diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs index a25551f854..09ca04be8a 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs @@ -5,6 +5,7 @@ using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mania.Difficulty; +using osu.Game.Rulesets.Mania.Mods; using osu.Game.Tests.Beatmaps; namespace osu.Game.Rulesets.Mania.Tests @@ -17,6 +18,10 @@ namespace osu.Game.Rulesets.Mania.Tests public void Test(double expected, string name) => base.Test(expected, name); + [TestCase(2.7646128945056723d, "diffcalc-test")] + public void TestClockRateAdjusted(double expected, string name) + => Test(expected, name, new ManiaModDoubleTime()); + protected override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new ManiaDifficultyCalculator(new ManiaRuleset(), beatmap); protected override Ruleset CreateRuleset() => new ManiaRuleset(); diff --git a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs index 85a41137d4..a365ea10d4 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs @@ -5,6 +5,7 @@ using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Osu.Difficulty; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Tests.Beatmaps; namespace osu.Game.Rulesets.Osu.Tests @@ -19,6 +20,11 @@ namespace osu.Game.Rulesets.Osu.Tests public void Test(double expected, string name) => base.Test(expected, name); + [TestCase(8.6228371119393064d, "diffcalc-test")] + [TestCase(1.2864585434597433d, "zero-length-sliders")] + public void TestClockRateAdjusted(double expected, string name) + => Test(expected, name, new OsuModDoubleTime()); + protected override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new OsuDifficultyCalculator(new OsuRuleset(), beatmap); protected override Ruleset CreateRuleset() => new OsuRuleset(); diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs index 71b3c23b50..eb21c02d5f 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs @@ -5,6 +5,7 @@ using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Taiko.Difficulty; +using osu.Game.Rulesets.Taiko.Mods; using osu.Game.Tests.Beatmaps; namespace osu.Game.Rulesets.Taiko.Tests @@ -18,6 +19,11 @@ namespace osu.Game.Rulesets.Taiko.Tests public void Test(double expected, string name) => base.Test(expected, name); + [TestCase(3.1473940254109078d, "diffcalc-test")] + [TestCase(3.1473940254109078d, "diffcalc-test-strong")] + public void TestClockRateAdjusted(double expected, string name) + => Test(expected, name, new TaikoModDoubleTime()); + protected override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new TaikoDifficultyCalculator(new TaikoRuleset(), beatmap); protected override Ruleset CreateRuleset() => new TaikoRuleset(); From 3b7ebfa2acad63c3a426610450d681eab9947450 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 20 Feb 2021 17:17:31 +0900 Subject: [PATCH 0674/1791] 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 442347df8eee74c903a4564ab8ec92a8b20c7964 Mon Sep 17 00:00:00 2001 From: Samuel Cattini-Schultz Date: Fri, 19 Feb 2021 18:04:25 +1100 Subject: [PATCH 0675/1791] Fix clockrate adjusted difficulty calculations bug in strain decay When starting a new section, the starting strain value was calculated using the unadjusted timing value, meaning decay curves were essentially being stretched or squashed according to the clockrate. This caused incorrect strain peaks for any section where the peak occurs at the start of the section (none of the objects in the section added enough strain after decay to exceed the starting strain). This bug caused star ratings with clockrates above 1 to be lower than they should and below 1 to be higher than they should. --- osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs | 4 ++-- osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs | 2 +- .../Difficulty/Preprocessing/DifficultyHitObject.cs | 6 ++++++ osu.Game/Rulesets/Difficulty/Skills/Skill.cs | 6 +++--- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs b/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs index 7ebc1ff752..56fb138b1f 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs @@ -71,8 +71,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills } protected override double GetPeakStrain(double offset) - => applyDecay(individualStrain, offset - Previous[0].BaseObject.StartTime, individual_decay_base) - + applyDecay(overallStrain, offset - Previous[0].BaseObject.StartTime, overall_decay_base); + => applyDecay(individualStrain, offset - Previous[0].StartTime, individual_decay_base) + + applyDecay(overallStrain, offset - Previous[0].StartTime, overall_decay_base); private double applyDecay(double value, double deltaTime, double decayBase) => value * Math.Pow(decayBase, deltaTime / 1000); diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index f15e5e1df0..8c2292dcaa 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -83,7 +83,7 @@ namespace osu.Game.Rulesets.Difficulty foreach (Skill s in skills) { s.SaveCurrentPeak(); - s.StartNewSectionFrom(currentSectionEnd); + s.StartNewSectionFrom(currentSectionEnd / clockRate); } currentSectionEnd += sectionLength; diff --git a/osu.Game/Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs b/osu.Game/Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs index ebbffb5143..fa578d55f0 100644 --- a/osu.Game/Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs +++ b/osu.Game/Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs @@ -25,6 +25,11 @@ namespace osu.Game.Rulesets.Difficulty.Preprocessing /// public readonly double DeltaTime; + /// + /// Start time of . + /// + public readonly double StartTime; + /// /// Creates a new . /// @@ -36,6 +41,7 @@ namespace osu.Game.Rulesets.Difficulty.Preprocessing BaseObject = hitObject; LastObject = lastObject; DeltaTime = (hitObject.StartTime - lastObject.StartTime) / clockRate; + StartTime = hitObject.StartTime / clockRate; } } } diff --git a/osu.Game/Rulesets/Difficulty/Skills/Skill.cs b/osu.Game/Rulesets/Difficulty/Skills/Skill.cs index 1063a24b27..44ce78c8e3 100644 --- a/osu.Game/Rulesets/Difficulty/Skills/Skill.cs +++ b/osu.Game/Rulesets/Difficulty/Skills/Skill.cs @@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.Difficulty.Skills /// /// Sets the initial strain level for a new section. /// - /// The beginning of the new section in milliseconds. + /// The beginning of the new section in milliseconds, adjusted by clockrate. public void StartNewSectionFrom(double time) { // The maximum strain of the new section is not zero by default, strain decays as usual regardless of section boundaries. @@ -87,9 +87,9 @@ namespace osu.Game.Rulesets.Difficulty.Skills /// /// Retrieves the peak strain at a point in time. /// - /// The time to retrieve the peak strain at. + /// The time to retrieve the peak strain at, adjusted by clockrate. /// The peak strain. - protected virtual double GetPeakStrain(double time) => CurrentStrain * strainDecay(time - Previous[0].BaseObject.StartTime); + protected virtual double GetPeakStrain(double time) => CurrentStrain * strainDecay(time - Previous[0].StartTime); /// /// Returns the calculated difficulty value representing all processed s. From 417bb07b366394a41bef13dcc2a1c2a79891bfbd Mon Sep 17 00:00:00 2001 From: Samuel Cattini-Schultz Date: Sat, 20 Feb 2021 16:52:19 +1100 Subject: [PATCH 0676/1791] Update tests with fixed diffcalc values --- .../CatchDifficultyCalculatorTest.cs | 2 +- .../ManiaDifficultyCalculatorTest.cs | 2 +- osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs | 4 ++-- .../TaikoDifficultyCalculatorTest.cs | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs index f4ee3f5a42..5580358f89 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Catch.Tests public void Test(double expected, string name) => base.Test(expected, name); - [TestCase(5.0565038923984691d, "diffcalc-test")] + [TestCase(5.169743871843191d, "diffcalc-test")] public void TestClockRateAdjusted(double expected, string name) => Test(expected, name, new CatchModDoubleTime()); diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs index 09ca04be8a..6e6500a339 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Tests public void Test(double expected, string name) => base.Test(expected, name); - [TestCase(2.7646128945056723d, "diffcalc-test")] + [TestCase(2.7879104989252959d, "diffcalc-test")] public void TestClockRateAdjusted(double expected, string name) => Test(expected, name, new ManiaModDoubleTime()); diff --git a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs index a365ea10d4..b6db989231 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs @@ -20,8 +20,8 @@ namespace osu.Game.Rulesets.Osu.Tests public void Test(double expected, string name) => base.Test(expected, name); - [TestCase(8.6228371119393064d, "diffcalc-test")] - [TestCase(1.2864585434597433d, "zero-length-sliders")] + [TestCase(8.7212283220504574d, "diffcalc-test")] + [TestCase(1.3212137310562277d, "zero-length-sliders")] public void TestClockRateAdjusted(double expected, string name) => Test(expected, name, new OsuModDoubleTime()); diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs index eb21c02d5f..dd3c6b317a 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs @@ -19,8 +19,8 @@ namespace osu.Game.Rulesets.Taiko.Tests public void Test(double expected, string name) => base.Test(expected, name); - [TestCase(3.1473940254109078d, "diffcalc-test")] - [TestCase(3.1473940254109078d, "diffcalc-test-strong")] + [TestCase(3.1704781712282624d, "diffcalc-test")] + [TestCase(3.1704781712282624d, "diffcalc-test-strong")] public void TestClockRateAdjusted(double expected, string name) => Test(expected, name, new TaikoModDoubleTime()); From 66643a97b0af5b90793435d5b6abefae582ca163 Mon Sep 17 00:00:00 2001 From: Samuel Cattini-Schultz Date: Sat, 6 Feb 2021 15:06:16 +1100 Subject: [PATCH 0677/1791] 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 cc4c5f72d893bc014d4172b1e2ba1c0930165aa4 Mon Sep 17 00:00:00 2001 From: Leon Gebler Date: Sat, 20 Feb 2021 21:48:31 +0100 Subject: [PATCH 0678/1791] Move logic to keep selection in bounds into it's own method --- .../Edit/OsuSelectionHandler.cs | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 871339ae7b..51e0e80e30 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -211,26 +211,35 @@ namespace osu.Game.Rulesets.Osu.Edit { var hitObjects = selectedMovableObjects; - Quad quad = getSurroundingQuad(hitObjects); - - Vector2 newTopLeft = quad.TopLeft + delta; - if (newTopLeft.X < 0) - delta.X -= newTopLeft.X; - if (newTopLeft.Y < 0) - delta.Y -= newTopLeft.Y; - - Vector2 newBottomRight = quad.BottomRight + delta; - if (newBottomRight.X > DrawWidth) - delta.X -= newBottomRight.X - DrawWidth; - if (newBottomRight.Y > DrawHeight) - delta.Y -= newBottomRight.Y - DrawHeight; - foreach (var h in hitObjects) h.Position += delta; + moveSelectionInBounds(); + return true; } + private void moveSelectionInBounds() + { + var hitObjects = selectedMovableObjects; + + Quad quad = getSurroundingQuad(hitObjects); + Vector2 delta = new Vector2(0); + + if (quad.TopLeft.X < 0) + delta.X -= quad.TopLeft.X; + if (quad.TopLeft.Y < 0) + delta.Y -= quad.TopLeft.Y; + + if (quad.BottomRight.X > DrawWidth) + delta.X -= quad.BottomRight.X - DrawWidth; + if (quad.BottomRight.Y > DrawHeight) + delta.Y -= quad.BottomRight.Y - DrawHeight; + + foreach (var h in hitObjects) + h.Position += delta; + } + /// /// Returns a gamefield-space quad surrounding the provided hit objects. /// From 0b8009938a4c6c84b406cf6fd15ccd76e150935f Mon Sep 17 00:00:00 2001 From: Leon Gebler Date: Sat, 20 Feb 2021 21:50:30 +0100 Subject: [PATCH 0679/1791] Prevent selection from breaking playfield bounds when scaling --- .../Edit/OsuSelectionHandler.cs | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 51e0e80e30..6c62bdd922 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -135,14 +135,21 @@ namespace osu.Game.Rulesets.Osu.Edit adjustScaleFromAnchor(ref scale, reference); var hitObjects = selectedMovableObjects; + Quad selectionQuad = getSurroundingQuad(hitObjects); + + float newWidth = selectionQuad.Width + scale.X; + float newHeight = selectionQuad.Height + scale.Y; + + if ((newHeight > DrawHeight) || (newWidth > DrawWidth)) + return false; // for the time being, allow resizing of slider paths only if the slider is // the only hit object selected. with a group selection, it's likely the user // is not looking to change the duration of the slider but expand the whole pattern. if (hitObjects.Length == 1 && hitObjects.First() is Slider slider) { - Quad quad = getSurroundingQuad(slider.Path.ControlPoints.Select(p => p.Position.Value)); - Vector2 pathRelativeDeltaScale = new Vector2(1 + scale.X / quad.Width, 1 + scale.Y / quad.Height); + Quad sliderQuad = getSurroundingQuad(slider.Path.ControlPoints.Select(p => p.Position.Value)); + Vector2 pathRelativeDeltaScale = new Vector2(1 + scale.X / sliderQuad.Width, 1 + scale.Y / sliderQuad.Height); foreach (var point in slider.Path.ControlPoints) point.Position.Value *= pathRelativeDeltaScale; @@ -153,23 +160,23 @@ namespace osu.Game.Rulesets.Osu.Edit if ((reference & Anchor.x0) > 0 && !moveSelection(new Vector2(-scale.X, 0))) return false; if ((reference & Anchor.y0) > 0 && !moveSelection(new Vector2(0, -scale.Y))) return false; - Quad quad = getSurroundingQuad(hitObjects); - foreach (var h in hitObjects) { var newPosition = h.Position; // guard against no-ops and NaN. - if (scale.X != 0 && quad.Width > 0) - newPosition.X = quad.TopLeft.X + (h.X - quad.TopLeft.X) / quad.Width * (quad.Width + scale.X); + if (scale.X != 0 && selectionQuad.Width > 0) + newPosition.X = selectionQuad.TopLeft.X + (h.X - selectionQuad.TopLeft.X) / selectionQuad.Width * (selectionQuad.Width + scale.X); - if (scale.Y != 0 && quad.Height > 0) - newPosition.Y = quad.TopLeft.Y + (h.Y - quad.TopLeft.Y) / quad.Height * (quad.Height + scale.Y); + if (scale.Y != 0 && selectionQuad.Height > 0) + newPosition.Y = selectionQuad.TopLeft.Y + (h.Y - selectionQuad.TopLeft.Y) / selectionQuad.Height * (selectionQuad.Height + scale.Y); h.Position = newPosition; } } + moveSelectionInBounds(); + return true; } From 562a4cefdb952b54d3b6c20eda47e13b78378010 Mon Sep 17 00:00:00 2001 From: Leon Gebler Date: Sun, 21 Feb 2021 12:12:32 +0100 Subject: [PATCH 0680/1791] Simplify HandleScale by extracting methods --- .../Edit/OsuSelectionHandler.cs | 67 +++++++++++-------- 1 file changed, 40 insertions(+), 27 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 6c62bdd922..9418752745 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -143,41 +143,18 @@ namespace osu.Game.Rulesets.Osu.Edit if ((newHeight > DrawHeight) || (newWidth > DrawWidth)) return false; + bool result; // for the time being, allow resizing of slider paths only if the slider is // the only hit object selected. with a group selection, it's likely the user // is not looking to change the duration of the slider but expand the whole pattern. if (hitObjects.Length == 1 && hitObjects.First() is Slider slider) - { - Quad sliderQuad = getSurroundingQuad(slider.Path.ControlPoints.Select(p => p.Position.Value)); - Vector2 pathRelativeDeltaScale = new Vector2(1 + scale.X / sliderQuad.Width, 1 + scale.Y / sliderQuad.Height); - - foreach (var point in slider.Path.ControlPoints) - point.Position.Value *= pathRelativeDeltaScale; - } + result = scaleSlider(slider, scale); else - { - // move the selection before scaling if dragging from top or left anchors. - if ((reference & Anchor.x0) > 0 && !moveSelection(new Vector2(-scale.X, 0))) return false; - if ((reference & Anchor.y0) > 0 && !moveSelection(new Vector2(0, -scale.Y))) return false; - - foreach (var h in hitObjects) - { - var newPosition = h.Position; - - // guard against no-ops and NaN. - if (scale.X != 0 && selectionQuad.Width > 0) - newPosition.X = selectionQuad.TopLeft.X + (h.X - selectionQuad.TopLeft.X) / selectionQuad.Width * (selectionQuad.Width + scale.X); - - if (scale.Y != 0 && selectionQuad.Height > 0) - newPosition.Y = selectionQuad.TopLeft.Y + (h.Y - selectionQuad.TopLeft.Y) / selectionQuad.Height * (selectionQuad.Height + scale.Y); - - h.Position = newPosition; - } - } + result = scaleHitObjects(hitObjects, reference, scale); moveSelectionInBounds(); - return true; + return result; } private static void adjustScaleFromAnchor(ref Vector2 scale, Anchor reference) @@ -214,6 +191,42 @@ namespace osu.Game.Rulesets.Osu.Edit return true; } + private bool scaleSlider(Slider slider, Vector2 scale) + { + Quad quad = getSurroundingQuad(slider.Path.ControlPoints.Select(p => p.Position.Value)); + Vector2 pathRelativeDeltaScale = new Vector2(1 + scale.X / quad.Width, 1 + scale.Y / quad.Height); + + foreach (var point in slider.Path.ControlPoints) + point.Position.Value *= pathRelativeDeltaScale; + + return true; + } + + private bool scaleHitObjects(OsuHitObject[] hitObjects, Anchor reference, Vector2 scale) + { + // move the selection before scaling if dragging from top or left anchors. + if ((reference & Anchor.x0) > 0 && !moveSelection(new Vector2(-scale.X, 0))) return false; + if ((reference & Anchor.y0) > 0 && !moveSelection(new Vector2(0, -scale.Y))) return false; + + Quad selectionQuad = getSurroundingQuad(hitObjects); + + foreach (var h in hitObjects) + { + var newPosition = h.Position; + + // guard against no-ops and NaN. + if (scale.X != 0 && selectionQuad.Width > 0) + newPosition.X = selectionQuad.TopLeft.X + (h.X - selectionQuad.TopLeft.X) / selectionQuad.Width * (selectionQuad.Width + scale.X); + + if (scale.Y != 0 && selectionQuad.Height > 0) + newPosition.Y = selectionQuad.TopLeft.Y + (h.Y - selectionQuad.TopLeft.Y) / selectionQuad.Height * (selectionQuad.Height + scale.Y); + + h.Position = newPosition; + } + + return true; + } + private bool moveSelection(Vector2 delta) { var hitObjects = selectedMovableObjects; From 2c6f92d12fb2d2a55ad8bb9f28e4f32634aadf8b Mon Sep 17 00:00:00 2001 From: Leon Gebler Date: Sun, 21 Feb 2021 17:38:50 +0100 Subject: [PATCH 0681/1791] Move bounds check from moveSelection to HandleMovement --- osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 9418752745..9ca8404a26 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -35,8 +35,12 @@ namespace osu.Game.Rulesets.Osu.Edit referenceOrigin = null; } - public override bool HandleMovement(MoveSelectionEvent moveEvent) => - moveSelection(moveEvent.InstantDelta); + public override bool HandleMovement(MoveSelectionEvent moveEvent) + { + bool result = moveSelection(moveEvent.InstantDelta); + moveSelectionInBounds(); + return result; + } /// /// During a transform, the initial origin is stored so it can be used throughout the operation. @@ -234,8 +238,6 @@ namespace osu.Game.Rulesets.Osu.Edit foreach (var h in hitObjects) h.Position += delta; - moveSelectionInBounds(); - return true; } From 33985d9e7c27a3d2ec58e5a2cd6f3068e50be7ab Mon Sep 17 00:00:00 2001 From: Leon Gebler Date: Sun, 21 Feb 2021 17:40:57 +0100 Subject: [PATCH 0682/1791] Rewrite scaling bounds check to behave more intuively --- .../Edit/OsuSelectionHandler.cs | 39 +++++++++++++------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 9ca8404a26..64dbe20c58 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -139,15 +139,8 @@ namespace osu.Game.Rulesets.Osu.Edit adjustScaleFromAnchor(ref scale, reference); var hitObjects = selectedMovableObjects; - Quad selectionQuad = getSurroundingQuad(hitObjects); - - float newWidth = selectionQuad.Width + scale.X; - float newHeight = selectionQuad.Height + scale.Y; - - if ((newHeight > DrawHeight) || (newWidth > DrawWidth)) - return false; - bool result; + // for the time being, allow resizing of slider paths only if the slider is // the only hit object selected. with a group selection, it's likely the user // is not looking to change the duration of the slider but expand the whole pattern. @@ -197,8 +190,18 @@ namespace osu.Game.Rulesets.Osu.Edit private bool scaleSlider(Slider slider, Vector2 scale) { - Quad quad = getSurroundingQuad(slider.Path.ControlPoints.Select(p => p.Position.Value)); - Vector2 pathRelativeDeltaScale = new Vector2(1 + scale.X / quad.Width, 1 + scale.Y / quad.Height); + Quad sliderQuad = getSurroundingQuad(slider.Path.ControlPoints.Select(p => p.Position.Value)); + Vector2 pathRelativeDeltaScale = new Vector2(1 + scale.X / sliderQuad.Width, 1 + scale.Y / sliderQuad.Height); + + Quad selectionQuad = getSurroundingQuad(new OsuHitObject[] { slider }); + Quad scaledQuad = new Quad(selectionQuad.TopLeft.X, selectionQuad.TopLeft.Y, selectionQuad.Width + scale.X, selectionQuad.Height + scale.Y); + (bool X, bool Y) inBounds = isQuadInBounds(scaledQuad); + + if (!inBounds.X) + pathRelativeDeltaScale.X = 1; + + if (!inBounds.Y) + pathRelativeDeltaScale.Y = 1; foreach (var point in slider.Path.ControlPoints) point.Position.Value *= pathRelativeDeltaScale; @@ -213,16 +216,18 @@ namespace osu.Game.Rulesets.Osu.Edit if ((reference & Anchor.y0) > 0 && !moveSelection(new Vector2(0, -scale.Y))) return false; Quad selectionQuad = getSurroundingQuad(hitObjects); + Quad scaledQuad = new Quad(selectionQuad.TopLeft.X, selectionQuad.TopLeft.Y, selectionQuad.Width + scale.X, selectionQuad.Height + scale.Y); + (bool X, bool Y) inBounds = isQuadInBounds(scaledQuad); foreach (var h in hitObjects) { var newPosition = h.Position; // guard against no-ops and NaN. - if (scale.X != 0 && selectionQuad.Width > 0) + if (scale.X != 0 && selectionQuad.Width > 0 && inBounds.X) newPosition.X = selectionQuad.TopLeft.X + (h.X - selectionQuad.TopLeft.X) / selectionQuad.Width * (selectionQuad.Width + scale.X); - if (scale.Y != 0 && selectionQuad.Height > 0) + if (scale.Y != 0 && selectionQuad.Height > 0 && inBounds.Y) newPosition.Y = selectionQuad.TopLeft.Y + (h.Y - selectionQuad.TopLeft.Y) / selectionQuad.Height * (selectionQuad.Height + scale.Y); h.Position = newPosition; @@ -231,6 +236,16 @@ namespace osu.Game.Rulesets.Osu.Edit return true; } + private (bool X, bool Y) isQuadInBounds(Quad quad) + { + (bool X, bool Y) result; + + result.X = (quad.TopLeft.X >= 0) && (quad.BottomRight.X < DrawWidth); + result.Y = (quad.TopLeft.Y >= 0) && (quad.BottomRight.Y < DrawHeight); + + return result; + } + private bool moveSelection(Vector2 delta) { var hitObjects = selectedMovableObjects; From 8d463987dd6c7260f25c90a31804d180c147b5df Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Feb 2021 13:21:50 +0900 Subject: [PATCH 0683/1791] 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 0684/1791] 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 0685/1791] 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 0686/1791] 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 0687/1791] 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 0688/1791] 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 0689/1791] 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 0690/1791] 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 0691/1791] 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 0692/1791] 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 0693/1791] 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 0694/1791] 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 0695/1791] 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 0696/1791] 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 0697/1791] 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 0698/1791] 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 0699/1791] 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 0700/1791] 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 0701/1791] 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 0702/1791] 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 0703/1791] 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 0704/1791] 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 0705/1791] 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 0706/1791] 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 0707/1791] 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 0708/1791] 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 0709/1791] 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 3491021f72a00eb5f4471dd6828678d2e6017315 Mon Sep 17 00:00:00 2001 From: Leon Gebler Date: Tue, 23 Feb 2021 20:58:46 +0100 Subject: [PATCH 0710/1791] Move moveSelection into HandleMovement --- .../Edit/OsuSelectionHandler.cs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 64dbe20c58..03c1676982 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -37,9 +37,13 @@ namespace osu.Game.Rulesets.Osu.Edit public override bool HandleMovement(MoveSelectionEvent moveEvent) { - bool result = moveSelection(moveEvent.InstantDelta); + var hitObjects = selectedMovableObjects; + + foreach (var h in hitObjects) + h.Position += moveEvent.InstantDelta; + moveSelectionInBounds(); - return result; + return true; } /// @@ -246,16 +250,6 @@ namespace osu.Game.Rulesets.Osu.Edit return result; } - private bool moveSelection(Vector2 delta) - { - var hitObjects = selectedMovableObjects; - - foreach (var h in hitObjects) - h.Position += delta; - - return true; - } - private void moveSelectionInBounds() { var hitObjects = selectedMovableObjects; From 71b30bdbbb7de4988ca31afe8110a477f98ab4b2 Mon Sep 17 00:00:00 2001 From: Leon Gebler Date: Tue, 23 Feb 2021 00:16:35 +0100 Subject: [PATCH 0711/1791] Adjust tuple usage --- osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 03c1676982..e1ee0612fa 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -199,12 +199,12 @@ namespace osu.Game.Rulesets.Osu.Edit Quad selectionQuad = getSurroundingQuad(new OsuHitObject[] { slider }); Quad scaledQuad = new Quad(selectionQuad.TopLeft.X, selectionQuad.TopLeft.Y, selectionQuad.Width + scale.X, selectionQuad.Height + scale.Y); - (bool X, bool Y) inBounds = isQuadInBounds(scaledQuad); + (bool xInBounds, bool yInBounds) = isQuadInBounds(scaledQuad); - if (!inBounds.X) + if (!xInBounds) pathRelativeDeltaScale.X = 1; - if (!inBounds.Y) + if (!yInBounds) pathRelativeDeltaScale.Y = 1; foreach (var point in slider.Path.ControlPoints) @@ -221,17 +221,17 @@ namespace osu.Game.Rulesets.Osu.Edit Quad selectionQuad = getSurroundingQuad(hitObjects); Quad scaledQuad = new Quad(selectionQuad.TopLeft.X, selectionQuad.TopLeft.Y, selectionQuad.Width + scale.X, selectionQuad.Height + scale.Y); - (bool X, bool Y) inBounds = isQuadInBounds(scaledQuad); + (bool xInBounds, bool yInBounds) = isQuadInBounds(scaledQuad); foreach (var h in hitObjects) { var newPosition = h.Position; // guard against no-ops and NaN. - if (scale.X != 0 && selectionQuad.Width > 0 && inBounds.X) + if (scale.X != 0 && selectionQuad.Width > 0 && xInBounds) newPosition.X = selectionQuad.TopLeft.X + (h.X - selectionQuad.TopLeft.X) / selectionQuad.Width * (selectionQuad.Width + scale.X); - if (scale.Y != 0 && selectionQuad.Height > 0 && inBounds.Y) + if (scale.Y != 0 && selectionQuad.Height > 0 && yInBounds) newPosition.Y = selectionQuad.TopLeft.Y + (h.Y - selectionQuad.TopLeft.Y) / selectionQuad.Height * (selectionQuad.Height + scale.Y); h.Position = newPosition; From 2a4139a2070df2f324526c651f9844fa58be1288 Mon Sep 17 00:00:00 2001 From: Leon Gebler Date: Tue, 23 Feb 2021 00:25:40 +0100 Subject: [PATCH 0712/1791] Refactor isQuadInBounds --- osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index e1ee0612fa..ede756ab47 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -242,12 +242,10 @@ namespace osu.Game.Rulesets.Osu.Edit private (bool X, bool Y) isQuadInBounds(Quad quad) { - (bool X, bool Y) result; + bool xInBounds = (quad.TopLeft.X >= 0) && (quad.BottomRight.X < DrawWidth); + bool yInBounds = (quad.TopLeft.Y >= 0) && (quad.BottomRight.Y < DrawHeight); - result.X = (quad.TopLeft.X >= 0) && (quad.BottomRight.X < DrawWidth); - result.Y = (quad.TopLeft.Y >= 0) && (quad.BottomRight.Y < DrawHeight); - - return result; + return (xInBounds, yInBounds); } private void moveSelectionInBounds() From 877e19421bfd44426bfcb15956fd75cc2dfabea0 Mon Sep 17 00:00:00 2001 From: Leon Gebler Date: Tue, 23 Feb 2021 16:36:51 +0100 Subject: [PATCH 0713/1791] Refactor movement while scaling --- osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index ede756ab47..28e6347f4f 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -216,11 +216,11 @@ namespace osu.Game.Rulesets.Osu.Edit private bool scaleHitObjects(OsuHitObject[] hitObjects, Anchor reference, Vector2 scale) { // move the selection before scaling if dragging from top or left anchors. - if ((reference & Anchor.x0) > 0 && !moveSelection(new Vector2(-scale.X, 0))) return false; - if ((reference & Anchor.y0) > 0 && !moveSelection(new Vector2(0, -scale.Y))) return false; + float xOffset = ((reference & Anchor.x0) > 0) ? -scale.X : 0; + float yOffset = ((reference & Anchor.y0) > 0) ? -scale.Y : 0; Quad selectionQuad = getSurroundingQuad(hitObjects); - Quad scaledQuad = new Quad(selectionQuad.TopLeft.X, selectionQuad.TopLeft.Y, selectionQuad.Width + scale.X, selectionQuad.Height + scale.Y); + Quad scaledQuad = new Quad(selectionQuad.TopLeft.X + xOffset, selectionQuad.TopLeft.Y + yOffset, selectionQuad.Width + scale.X, selectionQuad.Height + scale.Y); (bool xInBounds, bool yInBounds) = isQuadInBounds(scaledQuad); foreach (var h in hitObjects) @@ -229,10 +229,10 @@ namespace osu.Game.Rulesets.Osu.Edit // guard against no-ops and NaN. if (scale.X != 0 && selectionQuad.Width > 0 && xInBounds) - newPosition.X = selectionQuad.TopLeft.X + (h.X - selectionQuad.TopLeft.X) / selectionQuad.Width * (selectionQuad.Width + scale.X); + newPosition.X = selectionQuad.TopLeft.X + xOffset + (h.X - selectionQuad.TopLeft.X) / selectionQuad.Width * (selectionQuad.Width + scale.X); if (scale.Y != 0 && selectionQuad.Height > 0 && yInBounds) - newPosition.Y = selectionQuad.TopLeft.Y + (h.Y - selectionQuad.TopLeft.Y) / selectionQuad.Height * (selectionQuad.Height + scale.Y); + newPosition.Y = selectionQuad.TopLeft.Y + yOffset + (h.Y - selectionQuad.TopLeft.Y) / selectionQuad.Height * (selectionQuad.Height + scale.Y); h.Position = newPosition; } 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 0714/1791] 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 0715/1791] 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 0716/1791] 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 0717/1791] 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 0718/1791] 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 0719/1791] 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 0720/1791] 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 0721/1791] 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 0722/1791] 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 0723/1791] 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 0724/1791] 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 0725/1791] 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 0726/1791] 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 0727/1791] 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 0728/1791] 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 0729/1791] 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 0730/1791] 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 0731/1791] 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 0732/1791] 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 0733/1791] 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 0734/1791] 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 0735/1791] 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 0736/1791] 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 0737/1791] 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 0738/1791] 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 0739/1791] 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 0740/1791] 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 0741/1791] 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 0742/1791] 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 0743/1791] 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 0744/1791] 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 0745/1791] 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 0746/1791] 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 0747/1791] 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 0748/1791] 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 0749/1791] 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 0750/1791] 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 0751/1791] 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 0752/1791] 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 0753/1791] 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 0754/1791] 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 0755/1791] 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 0756/1791] 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 0757/1791] 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 0758/1791] 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 0759/1791] 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 0760/1791] 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 0761/1791] 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 0762/1791] 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 0763/1791] 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 0764/1791] 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 0765/1791] 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 0766/1791] 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 0767/1791] 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 0768/1791] 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 0769/1791] 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 0770/1791] 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 0771/1791] 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 0772/1791] 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 0773/1791] 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 0774/1791] 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 0775/1791] 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 0776/1791] 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 0777/1791] 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 0778/1791] 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 0779/1791] 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 0780/1791] 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 0781/1791] 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 0782/1791] 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 0783/1791] 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 0784/1791] 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 0785/1791] 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 0786/1791] 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 0787/1791] 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 0788/1791] 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 0789/1791] 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 0790/1791] 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 0791/1791] 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 0792/1791] 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 0793/1791] 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 0794/1791] 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 0795/1791] 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 0796/1791] 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 0797/1791] 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 0798/1791] 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 0799/1791] 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 0800/1791] 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 0801/1791] 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 0802/1791] 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 0803/1791] 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 0804/1791] 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 0805/1791] 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 0806/1791] 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 0807/1791] 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 0808/1791] 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 0809/1791] 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 0810/1791] 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 0811/1791] 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 0812/1791] 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 0813/1791] 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 0814/1791] 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 0815/1791] 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 0816/1791] 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 0817/1791] 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 0818/1791] 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 0819/1791] 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 0820/1791] 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 0821/1791] 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 0822/1791] 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 0823/1791] 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 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 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 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 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 0824/1791] 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 0825/1791] 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 0826/1791] 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 0827/1791] 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 0828/1791] 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 0829/1791] 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 0830/1791] 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 0831/1791] 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 0832/1791] 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 0833/1791] 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 0834/1791] 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 0835/1791] 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 0836/1791] 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 0837/1791] 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 0838/1791] 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 0839/1791] 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 0840/1791] 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 0841/1791] 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 0842/1791] 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 0843/1791] 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 0844/1791] 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 0845/1791] 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 0846/1791] 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 0847/1791] 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 0848/1791] 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 0849/1791] 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 0850/1791] 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 0851/1791] 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 0852/1791] 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 0853/1791] 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 0854/1791] 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 0855/1791] 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 0856/1791] 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 0857/1791] 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 0858/1791] 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 0859/1791] 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 0860/1791] 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 0861/1791] 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 0862/1791] 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 0863/1791] 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 0864/1791] 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 0865/1791] 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 0866/1791] 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 0867/1791] 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 0868/1791] 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 0869/1791] 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 0870/1791] 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 0871/1791] 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 0872/1791] 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 0873/1791] 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 0874/1791] 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 0875/1791] 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 0876/1791] 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 0877/1791] 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 0878/1791] 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 115c186cb7a624065620391a83be90c502827bb2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 7 Mar 2021 02:16:10 +0300 Subject: [PATCH 0879/1791] Move hit circle font from osu! ruleset --- osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs | 2 -- osu.Game/Skinning/LegacySkinConfiguration.cs | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs index 63c9b53278..75a62a6f8e 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs @@ -5,8 +5,6 @@ namespace osu.Game.Rulesets.Osu.Skinning { public enum OsuSkinConfiguration { - HitCirclePrefix, - HitCircleOverlap, SliderBorderSize, SliderPathRadius, AllowSliderBallTint, diff --git a/osu.Game/Skinning/LegacySkinConfiguration.cs b/osu.Game/Skinning/LegacySkinConfiguration.cs index 84a834ec22..20d1da8aaa 100644 --- a/osu.Game/Skinning/LegacySkinConfiguration.cs +++ b/osu.Game/Skinning/LegacySkinConfiguration.cs @@ -19,6 +19,8 @@ namespace osu.Game.Skinning ComboOverlap, ScorePrefix, ScoreOverlap, + HitCirclePrefix, + HitCircleOverlap, AnimationFramerate, LayeredHitSounds } From 91741564e8e06926a36b49f1c15dba97a03564da Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 7 Mar 2021 02:15:23 +0300 Subject: [PATCH 0880/1791] Add legacy font enum and extensions --- osu.Game/Skinning/LegacyFont.cs | 15 +++++++ osu.Game/Skinning/LegacySkinExtensions.cs | 48 ++++++++++++++++++++++- 2 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 osu.Game/Skinning/LegacyFont.cs diff --git a/osu.Game/Skinning/LegacyFont.cs b/osu.Game/Skinning/LegacyFont.cs new file mode 100644 index 0000000000..d1971cb84c --- /dev/null +++ b/osu.Game/Skinning/LegacyFont.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. + +namespace osu.Game.Skinning +{ + /// + /// The type of legacy font to use for s. + /// + public enum LegacyFont + { + Score, + Combo, + HitCircle, + } +} diff --git a/osu.Game/Skinning/LegacySkinExtensions.cs b/osu.Game/Skinning/LegacySkinExtensions.cs index a7c084998d..d08f50bccb 100644 --- a/osu.Game/Skinning/LegacySkinExtensions.cs +++ b/osu.Game/Skinning/LegacySkinExtensions.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.Collections.Generic; using System.Linq; using osu.Framework.Allocation; @@ -63,8 +64,51 @@ namespace osu.Game.Skinning } } - public static bool HasFont(this ISkin source, string fontPrefix) - => source.GetTexture($"{fontPrefix}-0") != null; + public static bool HasFont(this ISkin source, LegacyFont font) + { + return source.GetTexture($"{source.GetFontPrefix(font)}-0") != null; + } + + public static string GetFontPrefix(this ISkin source, LegacyFont font) + { + switch (font) + { + case LegacyFont.Score: + return source.GetConfig(LegacySetting.ScorePrefix)?.Value ?? "score"; + + case LegacyFont.Combo: + return source.GetConfig(LegacySetting.ComboPrefix)?.Value ?? "score"; + + case LegacyFont.HitCircle: + return source.GetConfig(LegacySetting.HitCirclePrefix)?.Value ?? "default"; + + default: + throw new ArgumentOutOfRangeException(nameof(font)); + } + } + + /// + /// Returns the numeric overlap of number sprites to use. + /// A positive number will bring the number sprites closer together, while a negative number + /// will split them apart more. + /// + public static float GetFontOverlap(this ISkin source, LegacyFont font) + { + switch (font) + { + case LegacyFont.Score: + return source.GetConfig(LegacySetting.ScoreOverlap)?.Value ?? -2f; + + case LegacyFont.Combo: + return source.GetConfig(LegacySetting.ComboOverlap)?.Value ?? -2f; + + case LegacyFont.HitCircle: + return source.GetConfig(LegacySetting.HitCircleOverlap)?.Value ?? -2f; + + default: + throw new ArgumentOutOfRangeException(nameof(font)); + } + } public class SkinnableTextureAnimation : TextureAnimation { From 64d1cb519324df2663aa85502ba6bed305ae1b0f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 7 Mar 2021 02:30:16 +0300 Subject: [PATCH 0881/1791] Remove text skin components in favour of plain `LegacySpriteText`s --- osu.Game/Skinning/HUDSkinComponents.cs | 2 -- osu.Game/Skinning/LegacySkin.cs | 21 +-------------------- 2 files changed, 1 insertion(+), 22 deletions(-) diff --git a/osu.Game/Skinning/HUDSkinComponents.cs b/osu.Game/Skinning/HUDSkinComponents.cs index b01be2d5a0..a345e060e5 100644 --- a/osu.Game/Skinning/HUDSkinComponents.cs +++ b/osu.Game/Skinning/HUDSkinComponents.cs @@ -9,7 +9,5 @@ namespace osu.Game.Skinning ScoreCounter, AccuracyCounter, HealthDisplay, - ScoreText, - ComboText, } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index e5d0217671..83854c592b 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -18,7 +18,6 @@ using osu.Game.Beatmaps.Formats; using osu.Game.IO; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play.HUD; -using osuTK; using osuTK.Graphics; namespace osu.Game.Skinning @@ -327,19 +326,13 @@ namespace osu.Game.Skinning return null; } - private string scorePrefix => GetConfig(LegacySkinConfiguration.LegacySetting.ScorePrefix)?.Value ?? "score"; - - private string comboPrefix => GetConfig(LegacySkinConfiguration.LegacySetting.ComboPrefix)?.Value ?? "score"; - - private bool hasScoreFont => this.HasFont(scorePrefix); - public override Drawable GetDrawableComponent(ISkinComponent component) { switch (component) { case HUDSkinComponent hudComponent: { - if (!hasScoreFont) + if (!this.HasFont(LegacyFont.Score)) return null; switch (hudComponent.Component) @@ -355,18 +348,6 @@ namespace osu.Game.Skinning case HUDSkinComponents.HealthDisplay: return new LegacyHealthDisplay(this); - - case HUDSkinComponents.ComboText: - return new LegacySpriteText(this, comboPrefix) - { - Spacing = new Vector2(-(GetConfig(LegacySkinConfiguration.LegacySetting.ComboOverlap)?.Value ?? -2), 0) - }; - - case HUDSkinComponents.ScoreText: - return new LegacySpriteText(this, scorePrefix) - { - Spacing = new Vector2(-(GetConfig(LegacySkinConfiguration.LegacySetting.ScoreOverlap)?.Value ?? -2), 0) - }; } return null; From 2a2ee3fa5ea86bbfc875f5659c01b3463adb93fb Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 7 Mar 2021 02:28:08 +0300 Subject: [PATCH 0882/1791] Update legacy sprite text constructor --- osu.Game/Skinning/LegacyRollingCounter.cs | 23 +++++------------------ osu.Game/Skinning/LegacySpriteText.cs | 7 +++++-- 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/osu.Game/Skinning/LegacyRollingCounter.cs b/osu.Game/Skinning/LegacyRollingCounter.cs index 8aa9d4e9af..0261db0e64 100644 --- a/osu.Game/Skinning/LegacyRollingCounter.cs +++ b/osu.Game/Skinning/LegacyRollingCounter.cs @@ -4,7 +4,6 @@ using System; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; -using osuTK; namespace osu.Game.Skinning { @@ -14,9 +13,7 @@ namespace osu.Game.Skinning public class LegacyRollingCounter : RollingCounter { private readonly ISkin skin; - - private readonly string fontName; - private readonly float fontOverlap; + private readonly LegacyFont font; protected override bool IsRollingProportional => true; @@ -24,17 +21,11 @@ namespace osu.Game.Skinning /// Creates a new . /// /// The from which to get counter number sprites. - /// The name of the legacy font to use. - /// - /// The numeric overlap of number sprites to use. - /// A positive number will bring the number sprites closer together, while a negative number - /// will split them apart more. - /// - public LegacyRollingCounter(ISkin skin, string fontName, float fontOverlap) + /// The legacy font to use for the counter. + public LegacyRollingCounter(ISkin skin, LegacyFont font) { this.skin = skin; - this.fontName = fontName; - this.fontOverlap = fontOverlap; + this.font = font; } protected override double GetProportionalDuration(int currentValue, int newValue) @@ -42,10 +33,6 @@ namespace osu.Game.Skinning return Math.Abs(newValue - currentValue) * 75.0; } - protected sealed override OsuSpriteText CreateSpriteText() => - new LegacySpriteText(skin, fontName) - { - Spacing = new Vector2(-fontOverlap, 0f) - }; + protected sealed override OsuSpriteText CreateSpriteText() => new LegacySpriteText(skin, font); } } diff --git a/osu.Game/Skinning/LegacySpriteText.cs b/osu.Game/Skinning/LegacySpriteText.cs index 5d0e312f7c..c55400e219 100644 --- a/osu.Game/Skinning/LegacySpriteText.cs +++ b/osu.Game/Skinning/LegacySpriteText.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using osu.Framework.Graphics.Sprites; using osu.Framework.Text; using osu.Game.Graphics.Sprites; +using osuTK; namespace osu.Game.Skinning { @@ -16,12 +17,14 @@ namespace osu.Game.Skinning protected override char[] FixedWidthExcludeCharacters => new[] { ',', '.', '%', 'x' }; - public LegacySpriteText(ISkin skin, string font = "score") + public LegacySpriteText(ISkin skin, LegacyFont font) { Shadow = false; UseFullGlyphHeight = false; - Font = new FontUsage(font, 1, fixedWidth: true); + Font = new FontUsage(skin.GetFontPrefix(font), 1, fixedWidth: true); + Spacing = new Vector2(-skin.GetFontOverlap(font), 0); + glyphStore = new LegacyGlyphStore(skin); } From 43c1e1d217388f56fe5e1e616fb000c65f7d2382 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 7 Mar 2021 02:18:31 +0300 Subject: [PATCH 0883/1791] Update existing usages Resolve post-conflict issues --- .../Legacy/CatchLegacySkinTransformer.cs | 4 +--- .../Legacy/LegacyCatchComboCounter.cs | 8 ++------ .../Legacy/OsuLegacySkinTransformer.cs | 17 +++++++---------- .../Screens/Play/HUD/LegacyComboCounter.cs | 19 ++++++++----------- osu.Game/Skinning/LegacyAccuracyCounter.cs | 8 +++++--- osu.Game/Skinning/LegacyScoreCounter.cs | 8 +++++--- 6 files changed, 28 insertions(+), 36 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs index 41fd0fe776..1b48832ed6 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs @@ -5,7 +5,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Skinning; using osuTK.Graphics; -using static osu.Game.Skinning.LegacySkinConfiguration; namespace osu.Game.Rulesets.Catch.Skinning.Legacy { @@ -14,7 +13,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy /// /// For simplicity, let's use legacy combo font texture existence as a way to identify legacy skins from default. /// - private bool providesComboCounter => this.HasFont(GetConfig(LegacySetting.ComboPrefix)?.Value ?? "score"); + private bool providesComboCounter => this.HasFont(LegacyFont.Combo); public CatchLegacySkinTransformer(ISkinSource source) : base(source) @@ -69,7 +68,6 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy this.GetAnimation("fruit-ryuuta", true, true, true); case CatchSkinComponents.CatchComboCounter: - if (providesComboCounter) return new LegacyCatchComboCounter(Source); diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatchComboCounter.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatchComboCounter.cs index f797ae75c2..28ee7bd813 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatchComboCounter.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatchComboCounter.cs @@ -7,7 +7,6 @@ using osu.Game.Rulesets.Catch.UI; using osu.Game.Skinning; using osuTK; using osuTK.Graphics; -using static osu.Game.Skinning.LegacySkinConfiguration; namespace osu.Game.Rulesets.Catch.Skinning.Legacy { @@ -22,9 +21,6 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy public LegacyCatchComboCounter(ISkin skin) { - var fontName = skin.GetConfig(LegacySetting.ComboPrefix)?.Value ?? "score"; - var fontOverlap = skin.GetConfig(LegacySetting.ComboOverlap)?.Value ?? -2f; - AutoSizeAxes = Axes.Both; Alpha = 0f; @@ -34,7 +30,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy InternalChildren = new Drawable[] { - explosion = new LegacyRollingCounter(skin, fontName, fontOverlap) + explosion = new LegacyRollingCounter(skin, LegacyFont.Combo) { Alpha = 0.65f, Blending = BlendingParameters.Additive, @@ -42,7 +38,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy Origin = Anchor.Centre, Scale = new Vector2(1.5f), }, - counter = new LegacyRollingCounter(skin, fontName, fontOverlap) + counter = new LegacyRollingCounter(skin, LegacyFont.Combo) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index d74f885573..ffe238c507 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -97,17 +97,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy return null; case OsuSkinComponents.HitCircleText: - var font = GetConfig(OsuSkinConfiguration.HitCirclePrefix)?.Value ?? "default"; - var overlap = GetConfig(OsuSkinConfiguration.HitCircleOverlap)?.Value ?? -2; + if (!this.HasFont(LegacyFont.HitCircle)) + return null; - return !this.HasFont(font) - ? null - : new LegacySpriteText(Source, font) - { - // stable applies a blanket 0.8x scale to hitcircle fonts - Scale = new Vector2(0.8f), - Spacing = new Vector2(-overlap, 0) - }; + return new LegacySpriteText(Source, LegacyFont.HitCircle) + { + // stable applies a blanket 0.8x scale to hitcircle fonts + Scale = new Vector2(0.8f), + }; case OsuSkinComponents.SpinnerBody: bool hasBackground = Source.GetTexture("spinner-background") != null; diff --git a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs index 81183a425a..b4604c0d01 100644 --- a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs @@ -6,7 +6,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics.Sprites; using osu.Game.Skinning; using osuTK; @@ -84,16 +83,16 @@ namespace osu.Game.Screens.Play.HUD { InternalChildren = new[] { - popOutCount = createSpriteText().With(s => + popOutCount = new LegacySpriteText(skin, LegacyFont.Combo) { - s.Alpha = 0; - s.Margin = new MarginPadding(0.05f); - s.Blending = BlendingParameters.Additive; - }), - displayedCountSpriteText = createSpriteText().With(s => + Alpha = 0, + Margin = new MarginPadding(0.05f), + Blending = BlendingParameters.Additive, + }, + displayedCountSpriteText = new LegacySpriteText(skin, LegacyFont.Combo) { - s.Alpha = 0; - }) + Alpha = 0, + }, }; Current.ValueChanged += combo => updateCount(combo.NewValue == 0); @@ -247,7 +246,5 @@ namespace osu.Game.Screens.Play.HUD double difference = currentValue > newValue ? currentValue - newValue : newValue - currentValue; return difference * rolling_duration; } - - private OsuSpriteText createSpriteText() => (OsuSpriteText)skin.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ComboText)); } } diff --git a/osu.Game/Skinning/LegacyAccuracyCounter.cs b/osu.Game/Skinning/LegacyAccuracyCounter.cs index 5eda374337..7d6f1dc916 100644 --- a/osu.Game/Skinning/LegacyAccuracyCounter.cs +++ b/osu.Game/Skinning/LegacyAccuracyCounter.cs @@ -29,9 +29,11 @@ namespace osu.Game.Skinning [Resolved(canBeNull: true)] private HUDOverlay hud { get; set; } - protected sealed override OsuSpriteText CreateSpriteText() - => (OsuSpriteText)skin?.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreText)) - ?.With(s => s.Anchor = s.Origin = Anchor.TopRight); + protected sealed override OsuSpriteText CreateSpriteText() => new LegacySpriteText(skin, LegacyFont.Score) + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }; protected override void Update() { diff --git a/osu.Game/Skinning/LegacyScoreCounter.cs b/osu.Game/Skinning/LegacyScoreCounter.cs index 5bffeff5a8..1d330ef495 100644 --- a/osu.Game/Skinning/LegacyScoreCounter.cs +++ b/osu.Game/Skinning/LegacyScoreCounter.cs @@ -33,8 +33,10 @@ namespace osu.Game.Skinning Margin = new MarginPadding(10); } - protected sealed override OsuSpriteText CreateSpriteText() - => (OsuSpriteText)skin.GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreText)) - .With(s => s.Anchor = s.Origin = Anchor.TopRight); + protected sealed override OsuSpriteText CreateSpriteText() => new LegacySpriteText(skin, LegacyFont.Score) + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }; } } From 5999e4ba3361f54828eaa25f1539ca0e2b991279 Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 9 Nov 2020 15:16:35 -0800 Subject: [PATCH 0884/1791] 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 0885/1791] 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 0886/1791] 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 0887/1791] 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 0888/1791] 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 0889/1791] 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 0890/1791] 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 0891/1791] 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 0892/1791] 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 0893/1791] 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 0894/1791] 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 0895/1791] 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 0896/1791] 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 0897/1791] 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 0898/1791] 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 0899/1791] 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 0900/1791] 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 0901/1791] 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 0902/1791] 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 0903/1791] 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 0904/1791] 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 0905/1791] 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 0906/1791] 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 0907/1791] 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 0908/1791] 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 0909/1791] 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 0910/1791] 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 0911/1791] 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 0912/1791] 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 0913/1791] 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 0914/1791] 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 0915/1791] 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 0916/1791] 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 0917/1791] 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 0918/1791] 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 0919/1791] 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 0920/1791] 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 0921/1791] 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 0922/1791] 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 0923/1791] 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 8046b5a818f8d2a69d7199e6cff6fa2a1db024d8 Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Wed, 17 Mar 2021 17:35:49 +0800 Subject: [PATCH 0924/1791] set text to platform clipboard on copy --- osu.Game/Screens/Edit/Editor.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 0ba202b082..88383bd3ed 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -21,6 +21,7 @@ using osu.Framework.Screens; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Extensions; using osu.Game.Graphics; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; @@ -29,6 +30,7 @@ using osu.Game.IO.Serialization; using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Components.Menus; using osu.Game.Screens.Edit.Components.Timelines.Summary; @@ -60,6 +62,9 @@ namespace osu.Game.Screens.Edit protected bool HasUnsavedChanges => lastSavedHash != changeHandler.CurrentStateHash; + [Resolved] + private GameHost host { get; set; } + [Resolved] private BeatmapManager beatmapManager { get; set; } @@ -104,7 +109,7 @@ namespace osu.Game.Screens.Edit private MusicController music { get; set; } [BackgroundDependencyLoader] - private void load(OsuColour colours, GameHost host, OsuConfigManager config) + private void load(OsuColour colours, OsuConfigManager config) { if (Beatmap.Value is DummyWorkingBeatmap) { @@ -542,8 +547,12 @@ namespace osu.Game.Screens.Edit protected void Copy() { if (editorBeatmap.SelectedHitObjects.Count == 0) + { + host.GetClipboard()?.SetText($"{clock.CurrentTime.ToEditorFormattedString()} - "); return; + } + host.GetClipboard()?.SetText($"{editorBeatmap.SelectedHitObjects.FirstOrDefault().StartTime.ToEditorFormattedString()} ({string.Join(',', editorBeatmap.SelectedHitObjects.Select(h => ((h as IHasComboInformation)?.IndexInCurrentCombo + 1 ?? 0).ToString()))}) - "); clipboard.Value = new ClipboardContent(editorBeatmap).Serialize(); } From f7ec79c5f42cd90f3201ae8855bc4d53fc5594fc Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 17 Mar 2021 19:02:25 +0900 Subject: [PATCH 0925/1791] 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 133ff085a53711229834c0c3b3944f1c29d91b73 Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Wed, 17 Mar 2021 18:06:40 +0800 Subject: [PATCH 0926/1791] refactor code --- osu.Game/Screens/Edit/Editor.cs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 88383bd3ed..fdb31a8b8c 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Text; using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -546,14 +547,24 @@ namespace osu.Game.Screens.Edit protected void Copy() { + var builder = new StringBuilder(); + const string suffix = " - "; + if (editorBeatmap.SelectedHitObjects.Count == 0) { - host.GetClipboard()?.SetText($"{clock.CurrentTime.ToEditorFormattedString()} - "); - return; + builder.Append(clock.CurrentTime.ToEditorFormattedString()); + } + else + { + var orderedHitObjects = editorBeatmap.SelectedHitObjects.OrderBy(h => h.StartTime); + builder.Append(orderedHitObjects.FirstOrDefault().StartTime.ToEditorFormattedString()); + builder.Append($" ({string.Join(',', orderedHitObjects.Cast().Select(h => h.IndexInCurrentCombo + 1))})"); + + clipboard.Value = new ClipboardContent(editorBeatmap).Serialize(); } - host.GetClipboard()?.SetText($"{editorBeatmap.SelectedHitObjects.FirstOrDefault().StartTime.ToEditorFormattedString()} ({string.Join(',', editorBeatmap.SelectedHitObjects.Select(h => ((h as IHasComboInformation)?.IndexInCurrentCombo + 1 ?? 0).ToString()))}) - "); - clipboard.Value = new ClipboardContent(editorBeatmap).Serialize(); + builder.Append(suffix); + host.GetClipboard()?.SetText(builder.ToString()); } protected void Paste() From e59b8b4ce66a6b30057add268d1620d5af8be231 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 17 Mar 2021 19:07:29 +0900 Subject: [PATCH 0927/1791] 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 51e0304c54a604bab6d6c8007c59ec755b115b2d Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Wed, 17 Mar 2021 18:31:09 +0800 Subject: [PATCH 0928/1791] properly format strings per ruleset --- osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs | 2 ++ osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs | 2 ++ osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs | 2 ++ osu.Game/Rulesets/Objects/HitObject.cs | 2 ++ osu.Game/Screens/Edit/Editor.cs | 2 +- 5 files changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs index ae45182960..631b50d686 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs @@ -120,5 +120,7 @@ namespace osu.Game.Rulesets.Catch.Objects } protected override HitWindows CreateHitWindows() => HitWindows.Empty; + + public override string ToEditorString() => (IndexInCurrentCombo + 1).ToString(); } } diff --git a/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs index 27bf50493d..c43d223335 100644 --- a/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs @@ -22,6 +22,8 @@ namespace osu.Game.Rulesets.Mania.Objects protected override HitWindows CreateHitWindows() => new ManiaHitWindows(); + public override string ToEditorString() => $"{StartTime}|{Column}"; + #region LegacyBeatmapEncoder float IHasXPosition.X => Column; diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index 22b64af3df..e784d13084 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -130,5 +130,7 @@ namespace osu.Game.Rulesets.Osu.Objects } protected override HitWindows CreateHitWindows() => new OsuHitWindows(); + + public override string ToEditorString() => (IndexInCurrentCombo + 1).ToString(); } } diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 826d411822..fa7b2811cc 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -168,6 +168,8 @@ namespace osu.Game.Rulesets.Objects /// [NotNull] protected virtual HitWindows CreateHitWindows() => new HitWindows(); + + public virtual string ToEditorString() => string.Empty; } public static class HitObjectExtensions diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index fdb31a8b8c..a6e84d59a7 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -558,7 +558,7 @@ namespace osu.Game.Screens.Edit { var orderedHitObjects = editorBeatmap.SelectedHitObjects.OrderBy(h => h.StartTime); builder.Append(orderedHitObjects.FirstOrDefault().StartTime.ToEditorFormattedString()); - builder.Append($" ({string.Join(',', orderedHitObjects.Cast().Select(h => h.IndexInCurrentCombo + 1))})"); + builder.Append($" ({string.Join(',', orderedHitObjects.Select(h => h.ToEditorString()))})"); clipboard.Value = new ClipboardContent(editorBeatmap).Serialize(); } 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 0929/1791] 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 0930/1791] 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 0931/1791] 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 0932/1791] 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 0933/1791] 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 0934/1791] 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 0935/1791] 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 0936/1791] 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 0937/1791] 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 0938/1791] 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 0939/1791] 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 0940/1791] 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 0941/1791] 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 0942/1791] 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 0943/1791] 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 0944/1791] 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 0945/1791] 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 0946/1791] 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 0947/1791] 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 0948/1791] 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 0949/1791] 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 0950/1791] 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 0951/1791] 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 0952/1791] 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 0953/1791] 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 0954/1791] 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 0955/1791] 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 563a0584d589bf0dce7c29fffc03268b097db485 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Mar 2021 18:48:51 +0900 Subject: [PATCH 0956/1791] Implement editor timeline stacking support --- .../Timeline/TimelineBlueprintContainer.cs | 50 ++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index 1fc529910b..4522418e87 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -2,6 +2,8 @@ // 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.Bindables; using osu.Framework.Graphics; @@ -121,6 +123,46 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } base.Update(); + + updateStacking(); + } + + private void updateStacking() + { + // because only blueprints of objects which are alive (via pooling) are displayed in the timeline, it's feasible to do this every-update. + + const int stack_offset = 5; + + // after the stack gets this tall, we can presume there is space underneath to draw subsequent blueprints. + const int stack_reset_count = 3; + + Stack currentConcurrentObjects = new Stack(); + + foreach (var b in SelectionBlueprints.Reverse()) + { + while (currentConcurrentObjects.TryPeek(out double stackEndTime)) + { + if (Precision.AlmostBigger(stackEndTime, b.HitObject.StartTime, 1)) + break; + + currentConcurrentObjects.Pop(); + } + + b.Y = -(stack_offset * currentConcurrentObjects.Count); + + var bEndTime = b.HitObject.GetEndTime(); + + // if the stack gets too high, we should have space below it to display the next batch of objects. + // importantly, we only do this if time has incremented, else a stack of hitobjects all at the same time value would start to overlap themselves. + if (!currentConcurrentObjects.TryPeek(out double nextStackEndTime) || + !Precision.AlmostEquals(nextStackEndTime, bEndTime, 1)) + { + if (currentConcurrentObjects.Count >= stack_reset_count) + currentConcurrentObjects.Clear(); + } + + currentConcurrentObjects.Push(bEndTime); + } } protected override SelectionHandler CreateSelectionHandler() => new TimelineSelectionHandler(); @@ -203,7 +245,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Box.X = Math.Min(rescaledStart, rescaledEnd); Box.Width = Math.Abs(rescaledStart - rescaledEnd); - PerformSelection?.Invoke(Box.ScreenSpaceDrawQuad.AABBFloat); + var boxScreenRect = Box.ScreenSpaceDrawQuad.AABBFloat; + + // we don't care about where the hitobjects are vertically. in cases like stacking display, they may be outside the box without this adjustment. + boxScreenRect.Y -= boxScreenRect.Height; + boxScreenRect.Height *= 2; + + PerformSelection?.Invoke(boxScreenRect); } public override void Hide() From 1c865682ae3b0d4116fa8483b1e173c67ab079a3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Mar 2021 16:25:50 +0900 Subject: [PATCH 0957/1791] 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 0958/1791] 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 0959/1791] 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 0960/1791] 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 0961/1791] 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 0962/1791] 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 0963/1791] 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 0964/1791] 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 0965/1791] 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 0966/1791] 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 0967/1791] 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 0968/1791] 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 0969/1791] 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 0970/1791] 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 0971/1791] 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 0972/1791] 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 0973/1791] 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 0974/1791] 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 0975/1791] 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 0976/1791] 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 0977/1791] 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 0978/1791] 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 0979/1791] 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 0980/1791] 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 0981/1791] 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 0982/1791] 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 0983/1791] 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 0984/1791] 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 0985/1791] 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 0986/1791] 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 0987/1791] 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 0988/1791] 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 0989/1791] 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 0990/1791] 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 0991/1791] 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 0992/1791] 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 0993/1791] 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 71a361337da8ee2ddac2feeb8af3e9644248c3d7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Mar 2021 21:57:48 +0900 Subject: [PATCH 0994/1791] Add comment regarding usage of `Reverse()` Co-authored-by: Dan Balasescu --- .../Compose/Components/Timeline/TimelineBlueprintContainer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index 4522418e87..3526f264a7 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -138,6 +138,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Stack currentConcurrentObjects = new Stack(); + // Reversing is done to enumerate in order of increasing StartTime. foreach (var b in SelectionBlueprints.Reverse()) { while (currentConcurrentObjects.TryPeek(out double stackEndTime)) 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 0995/1791] 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 0996/1791] 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 0997/1791] 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 0998/1791] 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 0999/1791] 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 1000/1791] 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 1001/1791] 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 a294f328fb4f1471273b5ebe2e49b8e7e2003f21 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 21 Mar 2021 06:30:17 +0100 Subject: [PATCH 1002/1791] Add linear circular arc test --- .../TestSceneSliderPlacementBlueprint.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs index 67a2e5a47c..f9b6fb924e 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs @@ -276,6 +276,24 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertControlPointType(0, PathType.Linear); } + [Test] + public void TestPlacePerfectCurveSegmentAlmostLinearly() + { + addMovementStep(new Vector2(0)); + addClickStep(MouseButton.Left); + + addMovementStep(new Vector2(61.382935f, 6.423767f)); + addClickStep(MouseButton.Left); + + addMovementStep(new Vector2(217.69522f, 22.84021f)); + addClickStep(MouseButton.Right); + + assertPlaced(true); + assertControlPointCount(3); + // Will be > 10000 if not falling back to Bezier path calc. + assertLength(218.8901f); + } + private void addMovementStep(Vector2 position) => AddStep($"move mouse to {position}", () => InputManager.MoveMouseTo(InputManager.ToScreenSpace(position))); private void addClickStep(MouseButton button) From fcd1f4930f9118445a0976a785db75282bf9763b Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 21 Mar 2021 06:34:55 +0100 Subject: [PATCH 1003/1791] Fix freeze due to large circular arc radius Seems to stem from the osu!framework's PathApproximator not catching a few edge cases wherein the radius approaches infinity. --- osu.Game/Rulesets/Objects/SliderPath.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index 3083fcfccb..94815fe0ea 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -219,8 +219,10 @@ namespace osu.Game.Rulesets.Objects List subpath = PathApproximator.ApproximateCircularArc(subControlPoints); - // If for some reason a circular arc could not be fit to the 3 given points, fall back to a numerically stable bezier approximation. - if (subpath.Count == 0) + // If for some reason a circular arc could not be fit to the 3 given points, or the + // resulting radius is too large (e.g. points arranged almost linearly), fall back + // to a numerically stable bezier approximation. + if (subpath.Count == 0 || subpath.Count > 400) break; return subpath; 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 1004/1791] 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 1005/1791] 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 1006/1791] 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 1007/1791] 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 1008/1791] 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 1009/1791] 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 1010/1791] 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 1011/1791] 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 1012/1791] 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 1013/1791] 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 1014/1791] 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 1015/1791] 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 1016/1791] 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 bee2f55d007bad3e83fd2d010f709ff720242784 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Mon, 22 Mar 2021 15:54:33 +0100 Subject: [PATCH 1017/1791] Undo subpath limiting --- osu.Game/Rulesets/Objects/SliderPath.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index 94815fe0ea..3083fcfccb 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -219,10 +219,8 @@ namespace osu.Game.Rulesets.Objects List subpath = PathApproximator.ApproximateCircularArc(subControlPoints); - // If for some reason a circular arc could not be fit to the 3 given points, or the - // resulting radius is too large (e.g. points arranged almost linearly), fall back - // to a numerically stable bezier approximation. - if (subpath.Count == 0 || subpath.Count > 400) + // If for some reason a circular arc could not be fit to the 3 given points, fall back to a numerically stable bezier approximation. + if (subpath.Count == 0) break; return subpath; From 7a2cb526e4bd9e04f5e0720336cf278551d4efac Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Mon, 22 Mar 2021 15:55:30 +0100 Subject: [PATCH 1018/1791] Add PointsInSegment method --- osu.Game/Rulesets/Objects/SliderPath.cs | 32 +++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index 3083fcfccb..e76699ac04 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -156,6 +156,38 @@ namespace osu.Game.Rulesets.Objects return interpolateVertices(indexOfDistance(d), d); } + /// + /// Returns the control points belonging to the same segment as the one given. + /// The first point has a PathType which all other points inherit. + /// + /// One of the control points in the segment. + /// + public List PointsInSegment(PathControlPoint controlPoint) + { + bool found = false; + List pointsInCurrentSegment = new List(); + foreach (PathControlPoint point in ControlPoints) + { + if (point.Type.Value is PathType) + { + if (!found) + pointsInCurrentSegment.Clear(); + else + { + pointsInCurrentSegment.Add(point); + break; + } + } + + pointsInCurrentSegment.Add(point); + + if (point == controlPoint) + found = true; + } + + return pointsInCurrentSegment; + } + private void invalidate() { pathCache.Invalidate(); From c82218627f914ca51875422de48811dacb136c14 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Mon, 22 Mar 2021 15:57:57 +0100 Subject: [PATCH 1019/1791] Add path type update logic Only attempts to change points to bezier if points in the slider are modified. --- osu.Game/Rulesets/Objects/SliderPath.cs | 53 +++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index e76699ac04..42f1a33dfc 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -54,13 +54,19 @@ namespace osu.Game.Rulesets.Objects { case NotifyCollectionChangedAction.Add: foreach (var c in args.NewItems.Cast()) + { c.Changed += invalidate; + c.Changed += updatePathTypes; + } break; case NotifyCollectionChangedAction.Reset: case NotifyCollectionChangedAction.Remove: foreach (var c in args.OldItems.Cast()) + { c.Changed -= invalidate; + c.Changed -= updatePathTypes; + } break; } @@ -188,6 +194,53 @@ namespace osu.Game.Rulesets.Objects return pointsInCurrentSegment; } + private void updatePathTypes() + { + // Updates each segment of the slider once + foreach (PathControlPoint controlPoint in ControlPoints.Where(p => p.Type.Value is PathType)) + updatePathType(controlPoint); + } + + private void updatePathType(PathControlPoint controlPoint) + { + List pointsInSegment = PointsInSegment(controlPoint); + PathType? pathType = pointsInSegment[0].Type.Value; + + // An almost linear arrangement of points results in radius approaching infinity, + // we should prevent that to avoid memory exhaustion when drawing / approximating + if (pathType == PathType.PerfectCurve) + { + Vector2[] points = pointsInSegment.Select(point => point.Position.Value).ToArray(); + if (points.Length < 3) + return; + + Vector2 a = points[0]; + Vector2 b = points[1]; + Vector2 c = points[2]; + + float maxLength = points.Max(p => p.Length); + + Vector2 normA = new Vector2(a.X / maxLength, a.Y / maxLength); + Vector2 normB = new Vector2(b.X / maxLength, b.Y / maxLength); + Vector2 normC = new Vector2(c.X / maxLength, c.Y / maxLength); + + float det = (normA.X - normB.X) * (normB.Y - normC.Y) - (normB.X - normC.X) * (normA.Y - normB.Y); + + float acSq = (a - c).LengthSquared; + float abSq = (a - b).LengthSquared; + float bcSq = (b - c).LengthSquared; + + // Exterior = curve wraps around the long way between end-points + // Exterior bottleneck is drawing-related, interior bottleneck is approximation-related, + // where the latter is much faster, hence differing thresholds + bool exterior = abSq > acSq || bcSq > acSq; + float threshold = exterior ? 0.05f : 0.001f; + + if (Math.Abs(det) < threshold) + pointsInSegment[0].Type.Value = PathType.Bezier; + } + } + private void invalidate() { pathCache.Invalidate(); From 067178e53779be114ffd067a28144bb6dee94dd4 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Mon, 22 Mar 2021 15:59:06 +0100 Subject: [PATCH 1020/1791] Maintain path type when dragging/placing --- .../Sliders/Components/PathControlPointPiece.cs | 10 ++++++++++ .../Blueprints/Sliders/SliderPlacementBlueprint.cs | 3 +++ 2 files changed, 13 insertions(+) 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 1390675a1a..42a7d246ea 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; @@ -31,6 +32,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components public readonly BindableBool IsSelected = new BindableBool(); public readonly PathControlPoint ControlPoint; + public readonly List PointsInSegment; private readonly Slider slider; private readonly Container marker; @@ -53,6 +55,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { this.slider = slider; ControlPoint = controlPoint; + PointsInSegment = slider.Path.PointsInSegment(controlPoint); controlPoint.Type.BindValueChanged(_ => updateMarkerDisplay()); @@ -150,6 +153,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components protected override bool OnClick(ClickEvent e) => RequestSelection != null; private Vector2 dragStartPosition; + private PathType? dragPathType; protected override bool OnDragStart(DragStartEvent e) { @@ -159,6 +163,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components if (e.Button == MouseButton.Left) { dragStartPosition = ControlPoint.Position.Value; + dragPathType = PointsInSegment[0].Type.Value; + changeHandler?.BeginChange(); return true; } @@ -184,6 +190,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components } else ControlPoint.Position.Value = dragStartPosition + (e.MousePosition - e.MouseDownPosition); + + // Maintain the path type in case it got defaulted to bezier at some point during the drag. + if (PointsInSegment[0].Type.Value != dragPathType) + PointsInSegment[0].Type.Value = dragPathType; } protected override void OnDragEnd(DragEndEvent e) => changeHandler?.EndChange(); diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index b71e1914f7..16e2a52279 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -142,6 +142,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { base.Update(); updateSlider(); + + // Maintain the path type in case it got defaulted to bezier at some point during the drag. + updatePathType(); } private void updatePathType() From 3bddc4a75d2f312541aad38e6408d956c32f9d81 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Mon, 22 Mar 2021 15:59:45 +0100 Subject: [PATCH 1021/1791] Add path type test --- .../Editor/TestSceneSliderPlacementBlueprint.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs index f9b6fb924e..4f716a31b7 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs @@ -290,6 +290,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertPlaced(true); assertControlPointCount(3); + assertControlPointType(0, PathType.Bezier); // Will be > 10000 if not falling back to Bezier path calc. assertLength(218.8901f); } From 15af57de95ec260afef3a59bedc736202c06f297 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Mon, 22 Mar 2021 15:59:59 +0100 Subject: [PATCH 1022/1791] Add path type recovery test --- .../TestSceneSliderPlacementBlueprint.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs index 4f716a31b7..db7b3aa973 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs @@ -295,6 +295,25 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertLength(218.8901f); } + [Test] + public void TestPlacePerfectCurveSegmentAlmostLinearlyRecovery() + { + addMovementStep(new Vector2(0)); + addClickStep(MouseButton.Left); + + addMovementStep(new Vector2(61.382935f, 6.423767f)); + addClickStep(MouseButton.Left); + + addMovementStep(new Vector2(217.69522f, 22.84021f)); // Should convert to bezier + addMovementStep(new Vector2(210.0f, 30.0f)); // Should convert back to perfect + addClickStep(MouseButton.Right); + + assertPlaced(true); + assertControlPointCount(3); + assertControlPointType(0, PathType.PerfectCurve); + assertLength(212.2276f); + } + private void addMovementStep(Vector2 position) => AddStep($"move mouse to {position}", () => InputManager.MoveMouseTo(InputManager.ToScreenSpace(position))); private void addClickStep(MouseButton button) From 323b875cea686e080c3b58fc04de91e73d24b437 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Mon, 22 Mar 2021 17:32:40 +0100 Subject: [PATCH 1023/1791] Fix newlines/spaces --- .../Editor/TestSceneSliderPlacementBlueprint.cs | 4 ++-- osu.Game/Rulesets/Objects/SliderPath.cs | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs index db7b3aa973..e7ea12e538 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs @@ -304,8 +304,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor addMovementStep(new Vector2(61.382935f, 6.423767f)); addClickStep(MouseButton.Left); - addMovementStep(new Vector2(217.69522f, 22.84021f)); // Should convert to bezier - addMovementStep(new Vector2(210.0f, 30.0f)); // Should convert back to perfect + addMovementStep(new Vector2(217.69522f, 22.84021f)); // Should convert to bezier + addMovementStep(new Vector2(210.0f, 30.0f)); // Should convert back to perfect addClickStep(MouseButton.Right); assertPlaced(true); diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index 42f1a33dfc..639cd4c72e 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -58,6 +58,7 @@ namespace osu.Game.Rulesets.Objects c.Changed += invalidate; c.Changed += updatePathTypes; } + break; case NotifyCollectionChangedAction.Reset: @@ -67,6 +68,7 @@ namespace osu.Game.Rulesets.Objects c.Changed -= invalidate; c.Changed -= updatePathTypes; } + break; } From a7076c329c66752955ee7501b09a1a17b7554b30 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Mon, 22 Mar 2021 17:32:55 +0100 Subject: [PATCH 1024/1791] Fix null checks --- osu.Game/Rulesets/Objects/SliderPath.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index 639cd4c72e..47cc2c6044 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -176,7 +176,7 @@ namespace osu.Game.Rulesets.Objects List pointsInCurrentSegment = new List(); foreach (PathControlPoint point in ControlPoints) { - if (point.Type.Value is PathType) + if (point.Type.Value != null) { if (!found) pointsInCurrentSegment.Clear(); @@ -199,7 +199,7 @@ namespace osu.Game.Rulesets.Objects private void updatePathTypes() { // Updates each segment of the slider once - foreach (PathControlPoint controlPoint in ControlPoints.Where(p => p.Type.Value is PathType)) + foreach (PathControlPoint controlPoint in ControlPoints.Where(p => p.Type.Value != null)) updatePathType(controlPoint); } From 6911a1b41546c04252b4d9e92bcbbf511befbf2a Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Mon, 22 Mar 2021 19:03:55 +0100 Subject: [PATCH 1025/1791] Fix missing newline --- osu.Game/Rulesets/Objects/SliderPath.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index 47cc2c6044..d8fbb46b76 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -174,6 +174,7 @@ namespace osu.Game.Rulesets.Objects { bool found = false; List pointsInCurrentSegment = new List(); + foreach (PathControlPoint point in ControlPoints) { if (point.Type.Value != null) @@ -241,6 +242,12 @@ namespace osu.Game.Rulesets.Objects if (Math.Abs(det) < threshold) pointsInSegment[0].Type.Value = PathType.Bezier; } + // where the latter is much faster, hence differing thresholds + bool exterior = abSq > acSq || bcSq > acSq; + float threshold = exterior ? 0.05f : 0.001f; + + if (Math.Abs(det) < threshold) + pointsInSegment[0].Type.Value = PathType.Bezier; } private void invalidate() From 80e7c3aba7e804ae1f1696219ee8c9a48871abb0 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Mon, 22 Mar 2021 19:09:28 +0100 Subject: [PATCH 1026/1791] Invert if statement --- osu.Game/Rulesets/Objects/SliderPath.cs | 42 +++++++++++-------------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index d8fbb46b76..767f7909d5 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -211,37 +211,31 @@ namespace osu.Game.Rulesets.Objects // An almost linear arrangement of points results in radius approaching infinity, // we should prevent that to avoid memory exhaustion when drawing / approximating - if (pathType == PathType.PerfectCurve) - { - Vector2[] points = pointsInSegment.Select(point => point.Position.Value).ToArray(); - if (points.Length < 3) - return; + if (pathType != PathType.PerfectCurve) + return; - Vector2 a = points[0]; - Vector2 b = points[1]; - Vector2 c = points[2]; + Vector2[] points = pointsInSegment.Select(point => point.Position.Value).ToArray(); + if (points.Length < 3) + return; - float maxLength = points.Max(p => p.Length); + Vector2 a = points[0]; + Vector2 b = points[1]; + Vector2 c = points[2]; - Vector2 normA = new Vector2(a.X / maxLength, a.Y / maxLength); - Vector2 normB = new Vector2(b.X / maxLength, b.Y / maxLength); - Vector2 normC = new Vector2(c.X / maxLength, c.Y / maxLength); + float maxLength = points.Max(p => p.Length); - float det = (normA.X - normB.X) * (normB.Y - normC.Y) - (normB.X - normC.X) * (normA.Y - normB.Y); + Vector2 normA = new Vector2(a.X / maxLength, a.Y / maxLength); + Vector2 normB = new Vector2(b.X / maxLength, b.Y / maxLength); + Vector2 normC = new Vector2(c.X / maxLength, c.Y / maxLength); - float acSq = (a - c).LengthSquared; - float abSq = (a - b).LengthSquared; - float bcSq = (b - c).LengthSquared; + float det = (normA.X - normB.X) * (normB.Y - normC.Y) - (normB.X - normC.X) * (normA.Y - normB.Y); - // Exterior = curve wraps around the long way between end-points - // Exterior bottleneck is drawing-related, interior bottleneck is approximation-related, - // where the latter is much faster, hence differing thresholds - bool exterior = abSq > acSq || bcSq > acSq; - float threshold = exterior ? 0.05f : 0.001f; + float acSq = (a - c).LengthSquared; + float abSq = (a - b).LengthSquared; + float bcSq = (b - c).LengthSquared; - if (Math.Abs(det) < threshold) - pointsInSegment[0].Type.Value = PathType.Bezier; - } + // Exterior = curve wraps around the long way between end-points + // Exterior bottleneck is drawing-related, interior bottleneck is approximation-related, // where the latter is much faster, hence differing thresholds bool exterior = abSq > acSq || bcSq > acSq; float threshold = exterior ? 0.05f : 0.001f; From 92f713a30e57d86b9c6408004be911f8c40755ca Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Mon, 22 Mar 2021 19:10:56 +0100 Subject: [PATCH 1027/1791] Improve fallback conditions It's possible to create a `PerfectCurve` type path with more than 3 points currently, so this accounts for that. --- osu.Game/Rulesets/Objects/SliderPath.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index 767f7909d5..23511be5e4 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -211,12 +211,10 @@ namespace osu.Game.Rulesets.Objects // An almost linear arrangement of points results in radius approaching infinity, // we should prevent that to avoid memory exhaustion when drawing / approximating - if (pathType != PathType.PerfectCurve) + if (pathType != PathType.PerfectCurve || pointsInSegment.Count != 3) return; Vector2[] points = pointsInSegment.Select(point => point.Position.Value).ToArray(); - if (points.Length < 3) - return; Vector2 a = points[0]; Vector2 b = points[1]; From b11fd7972aafea70905a1f0f73addffe82442d91 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Mon, 22 Mar 2021 19:41:48 +0100 Subject: [PATCH 1028/1791] Separate condition logic from math logic --- osu.Game/Rulesets/Objects/SliderPath.cs | 27 ++++++++++--------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index 23511be5e4..ae08660404 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -199,23 +199,19 @@ namespace osu.Game.Rulesets.Objects private void updatePathTypes() { - // Updates each segment of the slider once - foreach (PathControlPoint controlPoint in ControlPoints.Where(p => p.Type.Value != null)) - updatePathType(controlPoint); + foreach (PathControlPoint segmentStartPoint in ControlPoints.Where(p => p.Type.Value != null)) + { + if (segmentStartPoint.Type.Value != PathType.PerfectCurve) + continue; + + Vector2[] points = PointsInSegment(segmentStartPoint).Select(p => p.Position.Value).ToArray(); + if (points.Length == 3 && !validCircularArcSegment(points)) + segmentStartPoint.Type.Value = PathType.Bezier; + } } - private void updatePathType(PathControlPoint controlPoint) + private bool validCircularArcSegment(IReadOnlyList points) { - List pointsInSegment = PointsInSegment(controlPoint); - PathType? pathType = pointsInSegment[0].Type.Value; - - // An almost linear arrangement of points results in radius approaching infinity, - // we should prevent that to avoid memory exhaustion when drawing / approximating - if (pathType != PathType.PerfectCurve || pointsInSegment.Count != 3) - return; - - Vector2[] points = pointsInSegment.Select(point => point.Position.Value).ToArray(); - Vector2 a = points[0]; Vector2 b = points[1]; Vector2 c = points[2]; @@ -238,8 +234,7 @@ namespace osu.Game.Rulesets.Objects bool exterior = abSq > acSq || bcSq > acSq; float threshold = exterior ? 0.05f : 0.001f; - if (Math.Abs(det) < threshold) - pointsInSegment[0].Type.Value = PathType.Bezier; + return Math.Abs(det) < threshold; } private void invalidate() From 3fa5852e00cbb6d43e0383e5044dcd487ab43b2a Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Mon, 22 Mar 2021 19:42:27 +0100 Subject: [PATCH 1029/1791] Add method documentation --- osu.Game/Rulesets/Objects/SliderPath.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index ae08660404..b048bf2a84 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -197,6 +197,9 @@ namespace osu.Game.Rulesets.Objects return pointsInCurrentSegment; } + /// + /// Handles correction of invalid path types. + /// private void updatePathTypes() { foreach (PathControlPoint segmentStartPoint in ControlPoints.Where(p => p.Type.Value != null)) @@ -210,6 +213,13 @@ namespace osu.Game.Rulesets.Objects } } + /// + /// Returns whether the given points are arranged in a valid way. Invalid if points + /// are almost entirely linear - as this causes the radius to approach infinity, + /// which would exhaust memory when drawing / approximating. + /// + /// The three points that make up this circular arc segment. + /// private bool validCircularArcSegment(IReadOnlyList points) { Vector2 a = points[0]; From e922e67c9847b8dfea62bed7c85d673acd6278bd Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Mon, 22 Mar 2021 19:48:21 +0100 Subject: [PATCH 1030/1791] Fix inverted return statement Forgot to run tests, all passing now. --- osu.Game/Rulesets/Objects/SliderPath.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index b048bf2a84..c7931b440b 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -244,7 +244,7 @@ namespace osu.Game.Rulesets.Objects bool exterior = abSq > acSq || bcSq > acSq; float threshold = exterior ? 0.05f : 0.001f; - return Math.Abs(det) < threshold; + return Math.Abs(det) >= threshold; } private void invalidate() 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 1031/1791] 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 1032/1791] 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 1033/1791] 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 1034/1791] 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 1035/1791] 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 1036/1791] 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 1037/1791] 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 1038/1791] 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 1039/1791] 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 1040/1791] 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 1041/1791] 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 1042/1791] 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 1043/1791] 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 1044/1791] 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 1045/1791] 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 1046/1791] 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 1047/1791] 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 1048/1791] 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 1049/1791] 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 1050/1791] 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 1051/1791] 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 1052/1791] 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 1053/1791] 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 1054/1791] 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 1055/1791] 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 1056/1791] 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 1057/1791] 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 1058/1791] 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 1059/1791] 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 1060/1791] 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 1061/1791] 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 1062/1791] 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 5ee280f941b5738adf98211646b8f12cc3719702 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Wed, 24 Mar 2021 02:56:32 +0100 Subject: [PATCH 1063/1791] Update PointsInSegment when adding/removing points There was a bug where if you created a slider, moved the last point, and then added a point such that it became a PerfectCurve, it would fail to recover after becoming a Bezier. This fixes that. --- .../Blueprints/Sliders/Components/PathControlPointPiece.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) 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 42a7d246ea..f34403b377 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -29,10 +29,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components public class PathControlPointPiece : BlueprintPiece, IHasTooltip { public Action RequestSelection; + public List PointsInSegment; public readonly BindableBool IsSelected = new BindableBool(); public readonly PathControlPoint ControlPoint; - public readonly List PointsInSegment; private readonly Slider slider; private readonly Container marker; @@ -55,7 +55,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { this.slider = slider; ControlPoint = controlPoint; - PointsInSegment = slider.Path.PointsInSegment(controlPoint); + slider.Path.ControlPoints.BindCollectionChanged((_, args) => + { + PointsInSegment = slider.Path.PointsInSegment(controlPoint); + }, runOnceImmediately: true); controlPoint.Type.BindValueChanged(_ => updateMarkerDisplay()); From 0bcd38e6613fff3009a7e59f55c422751cf9e6e5 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Wed, 24 Mar 2021 02:57:47 +0100 Subject: [PATCH 1064/1791] Simplify path type maintenance when dragging --- .../Blueprints/Sliders/Components/PathControlPointPiece.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 f34403b377..4459308ea5 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -195,8 +195,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components ControlPoint.Position.Value = dragStartPosition + (e.MousePosition - e.MouseDownPosition); // Maintain the path type in case it got defaulted to bezier at some point during the drag. - if (PointsInSegment[0].Type.Value != dragPathType) - PointsInSegment[0].Type.Value = dragPathType; + PointsInSegment[0].Type.Value = dragPathType; } protected override void OnDragEnd(DragEndEvent e) => changeHandler?.EndChange(); From 4ae3eaaac6265b54c649fc64789c84ebd87f4400 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Wed, 24 Mar 2021 03:02:19 +0100 Subject: [PATCH 1065/1791] Move path type correction This is better because `PathControlPointVisualizer` is local to the editor, meaning there is no chance that this could affect gameplay. --- .../Components/PathControlPointVisualiser.cs | 54 +++++++++++++++++ osu.Game/Rulesets/Objects/PathControlPoint.cs | 2 +- osu.Game/Rulesets/Objects/SliderPath.cs | 58 ------------------- 3 files changed, 55 insertions(+), 59 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index ce5dc4855e..0c9163ae41 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -91,6 +91,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components })); Connections.Add(new PathControlPointConnectionPiece(slider, e.NewStartingIndex + i)); + + point.Changed += updatePathTypes; } break; @@ -100,6 +102,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { Pieces.RemoveAll(p => p.ControlPoint == point); Connections.RemoveAll(c => c.ControlPoint == point); + + point.Changed -= updatePathTypes; } // If removing before the end of the path, @@ -142,6 +146,56 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { } + /// + /// Handles correction of invalid path types. + /// + private void updatePathTypes() + { + foreach (PathControlPoint segmentStartPoint in slider.Path.ControlPoints.Where(p => p.Type.Value != null)) + { + if (segmentStartPoint.Type.Value != PathType.PerfectCurve) + continue; + + Vector2[] points = slider.Path.PointsInSegment(segmentStartPoint).Select(p => p.Position.Value).ToArray(); + if (points.Length == 3 && !validCircularArcSegment(points)) + segmentStartPoint.Type.Value = PathType.Bezier; + } + } + + /// + /// Returns whether the given points are arranged in a valid way. Invalid if points + /// are almost entirely linear - as this causes the radius to approach infinity, + /// which would exhaust memory when drawing / approximating. + /// + /// The three points that make up this circular arc segment. + /// + private bool validCircularArcSegment(IReadOnlyList points) + { + Vector2 a = points[0]; + Vector2 b = points[1]; + Vector2 c = points[2]; + + float maxLength = points.Max(p => p.Length); + + Vector2 normA = new Vector2(a.X / maxLength, a.Y / maxLength); + Vector2 normB = new Vector2(b.X / maxLength, b.Y / maxLength); + Vector2 normC = new Vector2(c.X / maxLength, c.Y / maxLength); + + float det = (normA.X - normB.X) * (normB.Y - normC.Y) - (normB.X - normC.X) * (normA.Y - normB.Y); + + float acSq = (a - c).LengthSquared; + float abSq = (a - b).LengthSquared; + float bcSq = (b - c).LengthSquared; + + // Exterior = curve wraps around the long way between end-points + // Exterior bottleneck is drawing-related, interior bottleneck is approximation-related, + // where the latter is much faster, hence differing thresholds + bool exterior = abSq > acSq || bcSq > acSq; + float threshold = exterior ? 0.05f : 0.001f; + + return Math.Abs(det) >= threshold; + } + private void selectPiece(PathControlPointPiece piece, MouseButtonEvent e) { if (e.Button == MouseButton.Left && inputManager.CurrentState.Keyboard.ControlPressed) diff --git a/osu.Game/Rulesets/Objects/PathControlPoint.cs b/osu.Game/Rulesets/Objects/PathControlPoint.cs index f11917f4f4..2e4100ee0b 100644 --- a/osu.Game/Rulesets/Objects/PathControlPoint.cs +++ b/osu.Game/Rulesets/Objects/PathControlPoint.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Objects /// /// Invoked when any property of this is changed. /// - internal event Action Changed; + public event Action Changed; /// /// Creates a new . diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index c7931b440b..61f5f94142 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -54,21 +54,13 @@ namespace osu.Game.Rulesets.Objects { case NotifyCollectionChangedAction.Add: foreach (var c in args.NewItems.Cast()) - { c.Changed += invalidate; - c.Changed += updatePathTypes; - } - break; case NotifyCollectionChangedAction.Reset: case NotifyCollectionChangedAction.Remove: foreach (var c in args.OldItems.Cast()) - { c.Changed -= invalidate; - c.Changed -= updatePathTypes; - } - break; } @@ -197,56 +189,6 @@ namespace osu.Game.Rulesets.Objects return pointsInCurrentSegment; } - /// - /// Handles correction of invalid path types. - /// - private void updatePathTypes() - { - foreach (PathControlPoint segmentStartPoint in ControlPoints.Where(p => p.Type.Value != null)) - { - if (segmentStartPoint.Type.Value != PathType.PerfectCurve) - continue; - - Vector2[] points = PointsInSegment(segmentStartPoint).Select(p => p.Position.Value).ToArray(); - if (points.Length == 3 && !validCircularArcSegment(points)) - segmentStartPoint.Type.Value = PathType.Bezier; - } - } - - /// - /// Returns whether the given points are arranged in a valid way. Invalid if points - /// are almost entirely linear - as this causes the radius to approach infinity, - /// which would exhaust memory when drawing / approximating. - /// - /// The three points that make up this circular arc segment. - /// - private bool validCircularArcSegment(IReadOnlyList points) - { - Vector2 a = points[0]; - Vector2 b = points[1]; - Vector2 c = points[2]; - - float maxLength = points.Max(p => p.Length); - - Vector2 normA = new Vector2(a.X / maxLength, a.Y / maxLength); - Vector2 normB = new Vector2(b.X / maxLength, b.Y / maxLength); - Vector2 normC = new Vector2(c.X / maxLength, c.Y / maxLength); - - float det = (normA.X - normB.X) * (normB.Y - normC.Y) - (normB.X - normC.X) * (normA.Y - normB.Y); - - float acSq = (a - c).LengthSquared; - float abSq = (a - b).LengthSquared; - float bcSq = (b - c).LengthSquared; - - // Exterior = curve wraps around the long way between end-points - // Exterior bottleneck is drawing-related, interior bottleneck is approximation-related, - // where the latter is much faster, hence differing thresholds - bool exterior = abSq > acSq || bcSq > acSq; - float threshold = exterior ? 0.05f : 0.001f; - - return Math.Abs(det) >= threshold; - } - private void invalidate() { pathCache.Invalidate(); From a1c35677efb5e68f5691896b8ca6dfb3763b12e3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Mar 2021 13:02:17 +0900 Subject: [PATCH 1066/1791] 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 1067/1791] 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 7bae4ff43d1d90c30dd874e54183e60934dd7fd3 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Wed, 24 Mar 2021 05:06:04 +0100 Subject: [PATCH 1068/1791] Add control point dragging tests --- .../TestSceneSliderControlPointPiece.cs | 170 ++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs new file mode 100644 index 0000000000..c4f103e40c --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs @@ -0,0 +1,170 @@ +// 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.Utils; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; +using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders; +using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Tests.Visual; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Rulesets.Osu.Tests.Editor +{ + public class TestSceneSliderControlPointPiece : SelectionBlueprintTestScene + { + private Slider slider; + private DrawableSlider drawableObject; + private TestSliderBlueprint blueprint; + + [SetUp] + public void Setup() => Schedule(() => + { + Clear(); + + slider = new Slider + { + Position = new Vector2(256, 192), + Path = new SliderPath(new[] + { + new PathControlPoint(Vector2.Zero, PathType.PerfectCurve), + new PathControlPoint(new Vector2(150, 150)), + new PathControlPoint(new Vector2(300, 0), PathType.PerfectCurve), + new PathControlPoint(new Vector2(400, 0)), + new PathControlPoint(new Vector2(400, 150)) + }) + }; + + slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 }); + + Add(drawableObject = new DrawableSlider(slider)); + AddBlueprint(blueprint = new TestSliderBlueprint(drawableObject)); + }); + + [Test] + public void TestDragControlPoint() + { + moveMouseToControlPoint(1); + AddStep("hold", () => InputManager.PressButton(MouseButton.Left)); + + addMovementStep(new Vector2(150, 50)); + AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left)); + + assertControlPointPosition(1, new Vector2(150, 50)); + assertControlPointType(0, PathType.PerfectCurve); + } + + [Test] + public void TestDragControlPointAlmostLinearly() + { + moveMouseToControlPoint(1); + AddStep("hold", () => InputManager.PressButton(MouseButton.Left)); + + addMovementStep(new Vector2(150, 0.01f)); + AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left)); + + assertControlPointPosition(1, new Vector2(150, 0.01f)); + assertControlPointType(0, PathType.Bezier); + } + + [Test] + public void TestDragControlPointAlmostLinearlyExterior() + { + moveMouseToControlPoint(1); + AddStep("hold", () => InputManager.PressButton(MouseButton.Left)); + + addMovementStep(new Vector2(400, 0.01f)); + AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left)); + + assertControlPointPosition(1, new Vector2(400, 0.01f)); + assertControlPointType(0, PathType.Bezier); + } + + [Test] + public void TestDragControlPointPathRecovery() + { + moveMouseToControlPoint(1); + AddStep("hold", () => InputManager.PressButton(MouseButton.Left)); + + addMovementStep(new Vector2(150, 0.01f)); + assertControlPointType(0, PathType.Bezier); + + addMovementStep(new Vector2(150, 50)); + AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left)); + + assertControlPointPosition(1, new Vector2(150, 50)); + assertControlPointType(0, PathType.PerfectCurve); + } + + [Test] + public void TestDragControlPointPathRecoveryOtherSegment() + { + moveMouseToControlPoint(4); + AddStep("hold", () => InputManager.PressButton(MouseButton.Left)); + + addMovementStep(new Vector2(350, 0)); + assertControlPointType(2, PathType.Bezier); + + addMovementStep(new Vector2(150, 150)); + AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left)); + + assertControlPointPosition(1, new Vector2(150, 150)); + assertControlPointType(2, PathType.PerfectCurve); + } + + private void addMovementStep(Vector2 relativePosition) + { + AddStep($"move mouse to {relativePosition}", () => + { + Vector2 position = slider.Position + relativePosition; + InputManager.MoveMouseTo(drawableObject.Parent.ToScreenSpace(position)); + }); + } + + private void moveMouseToControlPoint(int index) + { + AddStep($"move mouse to control point {index}", () => + { + Vector2 position = slider.Position + slider.Path.ControlPoints[index].Position.Value; + InputManager.MoveMouseTo(drawableObject.Parent.ToScreenSpace(position)); + }); + } + + private void assertControlPointType(int index, PathType type) => AddAssert($"control point {index} is {type}", () => slider.Path.ControlPoints[index].Type.Value == type); + + private void assertControlPointPosition(int index, Vector2 position) => + AddAssert($"control point {index} at {position}", () => Precision.AlmostEquals(position, slider.Path.ControlPoints[index].Position.Value, 1)); + + private class TestSliderBlueprint : SliderSelectionBlueprint + { + public new SliderBodyPiece BodyPiece => base.BodyPiece; + public new TestSliderCircleBlueprint HeadBlueprint => (TestSliderCircleBlueprint)base.HeadBlueprint; + public new TestSliderCircleBlueprint TailBlueprint => (TestSliderCircleBlueprint)base.TailBlueprint; + public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser; + + public TestSliderBlueprint(DrawableSlider slider) + : base(slider) + { + } + + protected override SliderCircleSelectionBlueprint CreateCircleSelectionBlueprint(DrawableSlider slider, SliderPosition position) => new TestSliderCircleBlueprint(slider, position); + } + + private class TestSliderCircleBlueprint : SliderCircleSelectionBlueprint + { + public new HitCirclePiece CirclePiece => base.CirclePiece; + + public TestSliderCircleBlueprint(DrawableSlider slider, SliderPosition position) + : base(slider, position) + { + } + } + } +} From e372e355efba030da6866f61fad35abca462cd6d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Mar 2021 13:12:51 +0900 Subject: [PATCH 1069/1791] 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 847d44c7d9998b9d2429fcc9bcef967004d314d4 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Wed, 24 Mar 2021 05:13:37 +0100 Subject: [PATCH 1070/1791] Remove unnecessary length asserts We don't actually care about the length (as this isn't what we're testing), just the type of the slider. --- .../Editor/TestSceneSliderPlacementBlueprint.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs index e7ea12e538..e47d86428b 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs @@ -291,8 +291,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertPlaced(true); assertControlPointCount(3); assertControlPointType(0, PathType.Bezier); - // Will be > 10000 if not falling back to Bezier path calc. - assertLength(218.8901f); } [Test] @@ -311,7 +309,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertPlaced(true); assertControlPointCount(3); assertControlPointType(0, PathType.PerfectCurve); - assertLength(212.2276f); } private void addMovementStep(Vector2 position) => AddStep($"move mouse to {position}", () => InputManager.MoveMouseTo(InputManager.ToScreenSpace(position))); From 6fbe5300163bea139a466c1be2bb09a63e2bd830 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Wed, 24 Mar 2021 05:14:35 +0100 Subject: [PATCH 1071/1791] Fix coordinates --- .../TestSceneSliderPlacementBlueprint.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs index e47d86428b..6eec5edfbe 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs @@ -279,13 +279,15 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor [Test] public void TestPlacePerfectCurveSegmentAlmostLinearly() { - addMovementStep(new Vector2(0)); + Vector2 startPosition = new Vector2(200); + + addMovementStep(startPosition); addClickStep(MouseButton.Left); - addMovementStep(new Vector2(61.382935f, 6.423767f)); + addMovementStep(startPosition + new Vector2(300, 0)); addClickStep(MouseButton.Left); - addMovementStep(new Vector2(217.69522f, 22.84021f)); + addMovementStep(startPosition + new Vector2(150, 0.1f)); addClickStep(MouseButton.Right); assertPlaced(true); @@ -296,14 +298,16 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor [Test] public void TestPlacePerfectCurveSegmentAlmostLinearlyRecovery() { - addMovementStep(new Vector2(0)); + Vector2 startPosition = new Vector2(200); + + addMovementStep(startPosition); addClickStep(MouseButton.Left); - addMovementStep(new Vector2(61.382935f, 6.423767f)); + addMovementStep(startPosition + new Vector2(61.382935f, 6.423767f)); addClickStep(MouseButton.Left); - addMovementStep(new Vector2(217.69522f, 22.84021f)); // Should convert to bezier - addMovementStep(new Vector2(210.0f, 30.0f)); // Should convert back to perfect + addMovementStep(startPosition + new Vector2(217.69522f, 22.84021f)); // Should convert to bezier + addMovementStep(startPosition + new Vector2(210.0f, 0.0f)); // Should convert back to perfect addClickStep(MouseButton.Right); assertPlaced(true); From 23a4d1c1350ef03a05b3c53b2c73be37ef18a4a7 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Wed, 24 Mar 2021 05:15:28 +0100 Subject: [PATCH 1072/1791] Shorten recovery test name --- .../Editor/TestSceneSliderPlacementBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs index 6eec5edfbe..e42ceaa4ac 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs @@ -296,7 +296,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor } [Test] - public void TestPlacePerfectCurveSegmentAlmostLinearlyRecovery() + public void TestPlacePerfectCurveSegmentRecovery() { Vector2 startPosition = new Vector2(200); From 7b395ed783e2811e9e29f73e287965b9bafe8b57 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Wed, 24 Mar 2021 05:15:50 +0100 Subject: [PATCH 1073/1791] Add exterior arc test --- .../TestSceneSliderPlacementBlueprint.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs index e42ceaa4ac..0433acb25c 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs @@ -284,6 +284,25 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor addMovementStep(startPosition); addClickStep(MouseButton.Left); + addMovementStep(startPosition + new Vector2(61.382935f, 6.423767f)); + addClickStep(MouseButton.Left); + + addMovementStep(startPosition + new Vector2(217.69522f, 22.84021f)); + addClickStep(MouseButton.Right); + + assertPlaced(true); + assertControlPointCount(3); + assertControlPointType(0, PathType.Bezier); + } + + [Test] + public void TestPlacePerfectCurveSegmentAlmostLinearlyExterior() + { + Vector2 startPosition = new Vector2(200); + + addMovementStep(startPosition); + addClickStep(MouseButton.Left); + addMovementStep(startPosition + new Vector2(300, 0)); addClickStep(MouseButton.Left); From a0c6c4da35dfe5cf2a28efa20da64acab28f25ab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Mar 2021 13:17:13 +0900 Subject: [PATCH 1074/1791] 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 1075/1791] 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 1076/1791] 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 1077/1791] 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 f80b3ada25f1fe688262c95df3dc65f7862479ce Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Wed, 24 Mar 2021 05:54:48 +0100 Subject: [PATCH 1078/1791] Add circular arc size tests --- .../TestSceneSliderPlacementBlueprint.cs | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs index 0433acb25c..20df46cd2c 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs @@ -334,6 +334,40 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertControlPointType(0, PathType.PerfectCurve); } + [Test] + public void TestPlacePerfectCurveSegmentLarge() + { + addMovementStep(new Vector2(200)); + addClickStep(MouseButton.Left); + + addMovementStep(new Vector2(200, 800)); + addClickStep(MouseButton.Left); + + addMovementStep(new Vector2(600, 200)); + addClickStep(MouseButton.Right); + + assertPlaced(true); + assertControlPointCount(3); + assertControlPointType(0, PathType.PerfectCurve); + } + + [Test] + public void TestPlacePerfectCurveSegmentTooLarge() + { + addMovementStep(new Vector2(200)); + addClickStep(MouseButton.Left); + + addMovementStep(new Vector2(200, 800)); + addClickStep(MouseButton.Left); + + addMovementStep(new Vector2(400, 200)); + addClickStep(MouseButton.Right); + + assertPlaced(true); + assertControlPointCount(3); + assertControlPointType(0, PathType.Bezier); + } + private void addMovementStep(Vector2 position) => AddStep($"move mouse to {position}", () => InputManager.MoveMouseTo(InputManager.ToScreenSpace(position))); private void addClickStep(MouseButton button) From e0240ab9d93db5e4d2467c4002e9a2784ff0e1a1 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Wed, 24 Mar 2021 05:55:34 +0100 Subject: [PATCH 1079/1791] Increase exterior threshold --- .../Blueprints/Sliders/Components/PathControlPointVisualiser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 0c9163ae41..c4a16f9c2a 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -191,7 +191,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components // Exterior bottleneck is drawing-related, interior bottleneck is approximation-related, // where the latter is much faster, hence differing thresholds bool exterior = abSq > acSq || bcSq > acSq; - float threshold = exterior ? 0.05f : 0.001f; + float threshold = exterior ? 0.35f : 0.001f; return Math.Abs(det) >= threshold; } From 415797aadd3c77de155ebdefc53d1c22cb83a723 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Wed, 24 Mar 2021 06:01:12 +0100 Subject: [PATCH 1080/1791] Fix broken control point drag test Broken for 2 reasons: - Assert checks the wrong control point. - The exterior arc is now too big. This fixes both. --- .../Editor/TestSceneSliderControlPointPiece.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs index c4f103e40c..c3492fc843 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs @@ -112,10 +112,10 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor addMovementStep(new Vector2(350, 0)); assertControlPointType(2, PathType.Bezier); - addMovementStep(new Vector2(150, 150)); + addMovementStep(new Vector2(400, 50)); AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left)); - assertControlPointPosition(1, new Vector2(150, 150)); + assertControlPointPosition(4, new Vector2(400, 50)); assertControlPointType(2, PathType.PerfectCurve); } From 5ad8dc316fa1aeed2f2dc194c2f9520b7f93ba09 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Mar 2021 14:09:15 +0900 Subject: [PATCH 1081/1791] 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 def0e5c42e90e5e8eb984c794a31eee0567caee4 Mon Sep 17 00:00:00 2001 From: Leon Gebler Date: Tue, 23 Mar 2021 17:21:42 +0100 Subject: [PATCH 1082/1791] Fix off-by-one error in isQuadInBounds --- osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 28e6347f4f..0418aa1925 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -242,8 +242,8 @@ namespace osu.Game.Rulesets.Osu.Edit private (bool X, bool Y) isQuadInBounds(Quad quad) { - bool xInBounds = (quad.TopLeft.X >= 0) && (quad.BottomRight.X < DrawWidth); - bool yInBounds = (quad.TopLeft.Y >= 0) && (quad.BottomRight.Y < DrawHeight); + bool xInBounds = (quad.TopLeft.X >= 0) && (quad.BottomRight.X <= DrawWidth); + bool yInBounds = (quad.TopLeft.Y >= 0) && (quad.BottomRight.Y <= DrawHeight); return (xInBounds, yInBounds); } From 3d471d239f2007c05e7af3bf417bae1c89b1d910 Mon Sep 17 00:00:00 2001 From: Leon Gebler Date: Tue, 23 Mar 2021 12:41:43 +0100 Subject: [PATCH 1083/1791] Clamp multi-object scale instead of cancelling it --- .../Edit/OsuSelectionHandler.cs | 45 +++++++++++++++++-- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 0418aa1925..595357ee65 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -215,23 +215,23 @@ namespace osu.Game.Rulesets.Osu.Edit private bool scaleHitObjects(OsuHitObject[] hitObjects, Anchor reference, Vector2 scale) { + scale = getClampedScale(hitObjects, reference, scale); + // move the selection before scaling if dragging from top or left anchors. float xOffset = ((reference & Anchor.x0) > 0) ? -scale.X : 0; float yOffset = ((reference & Anchor.y0) > 0) ? -scale.Y : 0; Quad selectionQuad = getSurroundingQuad(hitObjects); - Quad scaledQuad = new Quad(selectionQuad.TopLeft.X + xOffset, selectionQuad.TopLeft.Y + yOffset, selectionQuad.Width + scale.X, selectionQuad.Height + scale.Y); - (bool xInBounds, bool yInBounds) = isQuadInBounds(scaledQuad); foreach (var h in hitObjects) { var newPosition = h.Position; // guard against no-ops and NaN. - if (scale.X != 0 && selectionQuad.Width > 0 && xInBounds) + if (scale.X != 0 && selectionQuad.Width > 0) newPosition.X = selectionQuad.TopLeft.X + xOffset + (h.X - selectionQuad.TopLeft.X) / selectionQuad.Width * (selectionQuad.Width + scale.X); - if (scale.Y != 0 && selectionQuad.Height > 0 && yInBounds) + if (scale.Y != 0 && selectionQuad.Height > 0) newPosition.Y = selectionQuad.TopLeft.Y + yOffset + (h.Y - selectionQuad.TopLeft.Y) / selectionQuad.Height * (selectionQuad.Height + scale.Y); h.Position = newPosition; @@ -269,6 +269,43 @@ namespace osu.Game.Rulesets.Osu.Edit h.Position += delta; } + /// + /// Clamp scale where selection does not exceed playfield bounds or flip. + /// + /// The hitobjects to be scaled + /// The anchor from which the scale operation is performed + /// The scale to be clamped + /// The clamped scale vector + private Vector2 getClampedScale(OsuHitObject[] hitObjects, Anchor reference, Vector2 scale) + { + float xOffset = ((reference & Anchor.x0) > 0) ? -scale.X : 0; + float yOffset = ((reference & Anchor.y0) > 0) ? -scale.Y : 0; + + Quad selectionQuad = getSurroundingQuad(hitObjects); + + //todo: this is not always correct for selections involving sliders + Quad scaledQuad = new Quad(selectionQuad.TopLeft.X + xOffset, selectionQuad.TopLeft.Y + yOffset, selectionQuad.Width + scale.X, selectionQuad.Height + scale.Y); + + //max Size -> playfield bounds + if (scaledQuad.TopLeft.X < 0) + scale.X += scaledQuad.TopLeft.X; + if (scaledQuad.TopLeft.Y < 0) + scale.Y += scaledQuad.TopLeft.Y; + + if (scaledQuad.BottomRight.X > DrawWidth) + scale.X -= scaledQuad.BottomRight.X - DrawWidth; + if (scaledQuad.BottomRight.Y > DrawHeight) + scale.Y -= scaledQuad.BottomRight.Y - DrawHeight; + + //min Size -> almost 0. Less than 0 causes the quad to flip, exactly 0 causes scaling to get stuck at minimum scale. + Vector2 scaledSize = selectionQuad.Size + scale; + Vector2 minSize = new Vector2(Precision.FLOAT_EPSILON); + + scale = Vector2.ComponentMax(minSize, scaledSize) - selectionQuad.Size; + + return scale; + } + /// /// Returns a gamefield-space quad surrounding the provided hit objects. /// From e67ab3cca7cb55383a82769499686a37e09f82e6 Mon Sep 17 00:00:00 2001 From: Leon Gebler Date: Tue, 23 Mar 2021 16:09:44 +0100 Subject: [PATCH 1084/1791] Change single slider scaling to a method that works --- .../Edit/OsuSelectionHandler.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 595357ee65..ae92b12fd2 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -197,19 +197,19 @@ namespace osu.Game.Rulesets.Osu.Edit Quad sliderQuad = getSurroundingQuad(slider.Path.ControlPoints.Select(p => p.Position.Value)); Vector2 pathRelativeDeltaScale = new Vector2(1 + scale.X / sliderQuad.Width, 1 + scale.Y / sliderQuad.Height); - Quad selectionQuad = getSurroundingQuad(new OsuHitObject[] { slider }); - Quad scaledQuad = new Quad(selectionQuad.TopLeft.X, selectionQuad.TopLeft.Y, selectionQuad.Width + scale.X, selectionQuad.Height + scale.Y); - (bool xInBounds, bool yInBounds) = isQuadInBounds(scaledQuad); - - if (!xInBounds) - pathRelativeDeltaScale.X = 1; - - if (!yInBounds) - pathRelativeDeltaScale.Y = 1; - foreach (var point in slider.Path.ControlPoints) point.Position.Value *= pathRelativeDeltaScale; + //if sliderhead or sliderend end up outside playfield, revert scaling. + Quad scaledQuad = getSurroundingQuad(new OsuHitObject[] { slider }); + (bool xInBounds, bool yInBounds) = isQuadInBounds(scaledQuad); + + if (xInBounds && yInBounds) + return true; + + foreach (var point in slider.Path.ControlPoints) + point.Position.Value *= new Vector2(1 / pathRelativeDeltaScale.X, 1 / pathRelativeDeltaScale.Y); + return true; } From b4dc35f66ba3c8c6761fdb8dc381057bb13ce73a Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Wed, 24 Mar 2021 17:24:05 +0100 Subject: [PATCH 1085/1791] Update large arc tests Should now be more robust and readable. --- .../TestSceneSliderControlPointPiece.cs | 4 ++-- .../TestSceneSliderPlacementBlueprint.cs | 20 +++++++++++++------ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs index c3492fc843..7ca86335ce 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs @@ -112,10 +112,10 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor addMovementStep(new Vector2(350, 0)); assertControlPointType(2, PathType.Bezier); - addMovementStep(new Vector2(400, 50)); + addMovementStep(new Vector2(150, 150)); AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left)); - assertControlPointPosition(4, new Vector2(400, 50)); + assertControlPointPosition(4, new Vector2(150, 150)); assertControlPointType(2, PathType.PerfectCurve); } diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs index 20df46cd2c..46fe7b7576 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs @@ -337,13 +337,17 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor [Test] public void TestPlacePerfectCurveSegmentLarge() { - addMovementStep(new Vector2(200)); + Vector2 startPosition = new Vector2(400); + + addMovementStep(startPosition); addClickStep(MouseButton.Left); - addMovementStep(new Vector2(200, 800)); + addMovementStep(startPosition + new Vector2(240, 240)); addClickStep(MouseButton.Left); - addMovementStep(new Vector2(600, 200)); + // Playfield dimensions are 640 x 480. + // So a 480 * 480 bounding box area should be ok. + addMovementStep(startPosition + new Vector2(-240, 240)); addClickStep(MouseButton.Right); assertPlaced(true); @@ -354,13 +358,17 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor [Test] public void TestPlacePerfectCurveSegmentTooLarge() { - addMovementStep(new Vector2(200)); + Vector2 startPosition = new Vector2(480, 200); + + addMovementStep(startPosition); addClickStep(MouseButton.Left); - addMovementStep(new Vector2(200, 800)); + addMovementStep(startPosition + new Vector2(400, 400)); addClickStep(MouseButton.Left); - addMovementStep(new Vector2(400, 200)); + // Playfield dimensions are 640 x 480. + // So an 800 * 800 bounding box area should not be ok. + addMovementStep(startPosition + new Vector2(-400, 400)); addClickStep(MouseButton.Right); assertPlaced(true); From 0f4314c1d8e6741037df4407326d186fb8c3210b Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Wed, 24 Mar 2021 17:24:33 +0100 Subject: [PATCH 1086/1791] Add complete arc test Ensures we can still make smaller circles properly. --- .../Editor/TestSceneSliderPlacementBlueprint.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs index 46fe7b7576..937034fc23 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs @@ -376,6 +376,23 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertControlPointType(0, PathType.Bezier); } + [Test] + public void TestPlacePerfectCurveSegmentCompleteArc() + { + addMovementStep(new Vector2(400)); + addClickStep(MouseButton.Left); + + addMovementStep(new Vector2(600, 400)); + addClickStep(MouseButton.Left); + + addMovementStep(new Vector2(400, 410)); + addClickStep(MouseButton.Right); + + assertPlaced(true); + assertControlPointCount(3); + assertControlPointType(0, PathType.PerfectCurve); + } + private void addMovementStep(Vector2 position) => AddStep($"move mouse to {position}", () => InputManager.MoveMouseTo(InputManager.ToScreenSpace(position))); private void addClickStep(MouseButton button) From 9df059b01decf63403094a3c071a6f54bea7fe7b Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Wed, 24 Mar 2021 17:25:28 +0100 Subject: [PATCH 1087/1791] Add bounding box limit --- .../Components/PathControlPointVisualiser.cs | 101 ++++++++++++++++-- 1 file changed, 90 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index c4a16f9c2a..0270485f31 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -11,6 +11,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input; using osu.Framework.Input.Bindings; @@ -165,11 +166,26 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components /// /// Returns whether the given points are arranged in a valid way. Invalid if points /// are almost entirely linear - as this causes the radius to approach infinity, - /// which would exhaust memory when drawing / approximating. + /// or if the bounding box of the arc is too large. /// /// The three points that make up this circular arc segment. /// private bool validCircularArcSegment(IReadOnlyList points) + { + float det = circularArcDeterminant(points); + RectangleF boundingBox = circularArcBoundingBox(points); + + // Determinant limit prevents memory exhaustion as a result of approximating the subpath. + // Bounding box area limit prevents memory exhaustion as a result of drawing the texture. + return Math.Abs(det) > 0.001f && boundingBox.Area < 640 * 480; + } + + /// + /// Computes the determinant of the circular arc segment defined by the given points. + /// + /// The three points defining the circular arc. + /// + private float circularArcDeterminant(IReadOnlyList points) { Vector2 a = points[0]; Vector2 b = points[1]; @@ -181,19 +197,82 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components Vector2 normB = new Vector2(b.X / maxLength, b.Y / maxLength); Vector2 normC = new Vector2(c.X / maxLength, c.Y / maxLength); - float det = (normA.X - normB.X) * (normB.Y - normC.Y) - (normB.X - normC.X) * (normA.Y - normB.Y); + return (normA.X - normB.X) * (normB.Y - normC.Y) - (normB.X - normC.X) * (normA.Y - normB.Y); + } - float acSq = (a - c).LengthSquared; - float abSq = (a - b).LengthSquared; - float bcSq = (b - c).LengthSquared; + /// + /// Computes the bounding box of the circular arc segment defined by the given points. + /// + /// The three points defining the circular arc. + /// + private RectangleF circularArcBoundingBox(IReadOnlyList points) + { + Vector2 a = points[0]; + Vector2 b = points[1]; + Vector2 c = points[2]; - // Exterior = curve wraps around the long way between end-points - // Exterior bottleneck is drawing-related, interior bottleneck is approximation-related, - // where the latter is much faster, hence differing thresholds - bool exterior = abSq > acSq || bcSq > acSq; - float threshold = exterior ? 0.35f : 0.001f; + // See: https://en.wikipedia.org/wiki/Circumscribed_circle#Cartesian_coordinates_2 + float d = 2 * (a.X * (b - c).Y + b.X * (c - a).Y + c.X * (a - b).Y); + float aSq = a.LengthSquared; + float bSq = b.LengthSquared; + float cSq = c.LengthSquared; - return Math.Abs(det) >= threshold; + Vector2 center = new Vector2( + aSq * (b - c).Y + bSq * (c - a).Y + cSq * (a - b).Y, + aSq * (c - b).X + bSq * (a - c).X + cSq * (b - a).X) / d; + + Vector2 dA = a - center; + Vector2 dC = c - center; + + float r = dA.Length; + + double thetaStart = Math.Atan2(dA.Y, dA.X); + double thetaEnd = Math.Atan2(dC.Y, dC.X); + + while (thetaEnd < thetaStart) + thetaEnd += 2 * Math.PI; + + // Decide in which direction to draw the circle, depending on which side of + // AC B lies. + Vector2 orthoAtoC = c - a; + orthoAtoC = new Vector2(orthoAtoC.Y, -orthoAtoC.X); + bool clockwise = Vector2.Dot(orthoAtoC, b - a) >= 0; + + if (clockwise) + { + if (thetaEnd < thetaStart) + thetaEnd += Math.PI * 2; + } + else + { + if (thetaStart < thetaEnd) + thetaStart += Math.PI * 2; + } + + List boundingBoxPoints = new List(points); + + bool includes0Degrees = 0 > thetaStart && 0 < thetaEnd; + bool includes90Degrees = Math.PI / 2 > thetaStart && Math.PI / 2 < thetaEnd; + bool includes180Degrees = Math.PI > thetaStart && Math.PI < thetaEnd; + bool includes270Degrees = Math.PI * 1.5f > thetaStart && Math.PI * 1.5f < thetaEnd; + + if (!clockwise) + { + includes0Degrees = 0 < thetaStart && 0 > thetaEnd; + includes90Degrees = Math.PI / 2 < thetaStart && Math.PI / 2 > thetaEnd; + includes180Degrees = Math.PI < thetaStart && Math.PI > thetaEnd; + includes270Degrees = Math.PI * 1.5f < thetaStart && Math.PI * 1.5f > thetaEnd; + } + + if (includes0Degrees) boundingBoxPoints.Add(center + new Vector2(1, 0) * r); + if (includes90Degrees) boundingBoxPoints.Add(center + new Vector2(0, 1) * r); + if (includes180Degrees) boundingBoxPoints.Add(center + new Vector2(-1, 0) * r); + if (includes270Degrees) boundingBoxPoints.Add(center + new Vector2(0, -1) * r); + + float width = Math.Abs(boundingBoxPoints.Max(p => p.X) - boundingBoxPoints.Min(p => p.X)); + float height = Math.Abs(boundingBoxPoints.Max(p => p.Y) - boundingBoxPoints.Min(p => p.Y)); + + return new RectangleF(slider.Position, new Vector2(width, height)); } private void selectPiece(PathControlPointPiece piece, MouseButtonEvent e) From fc5719e445750c10ddad28ead84239a9cb0f519d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 24 Mar 2021 21:31:53 +0300 Subject: [PATCH 1088/1791] 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 1089/1791] 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 1090/1791] 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 1091/1791] 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:** + @@ -93,7 +93,7 @@ - + From d84c9251e6d4d6569e5f895a7cf6943c2c722467 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Mar 2021 22:17:24 +0900 Subject: [PATCH 1146/1791] Update nuget packages --- osu.Game/osu.Game.csproj | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 35b0827715..6d571218fc 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -18,20 +18,20 @@ - + - - - + + + - + - + From 6c5a10a7449a24fdd6932c6cb8ba99c7d98affd6 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 29 Mar 2021 15:27:25 +0200 Subject: [PATCH 1147/1791] Add missing license header --- osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs b/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs index 29586dfdec..086ed54435 100644 --- a/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs @@ -1,3 +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; using System.IO; using System.Threading; From 70d5b616f28bb8916bb9dda0a9e1810b942d8afb Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Mon, 29 Mar 2021 15:49:49 +0200 Subject: [PATCH 1148/1791] Add scaling path type recovery --- osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 871339ae7b..da484b9782 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -33,6 +33,7 @@ namespace osu.Game.Rulesets.Osu.Edit { base.OnOperationEnded(); referenceOrigin = null; + referencePathTypes = null; } public override bool HandleMovement(MoveSelectionEvent moveEvent) => @@ -43,6 +44,12 @@ namespace osu.Game.Rulesets.Osu.Edit /// private Vector2? referenceOrigin; + /// + /// During a transform, the initial path types of a single selected slider are stored so they + /// can be maintained throughout the operation. + /// + private List referencePathTypes; + public override bool HandleReverse() { var hitObjects = EditorBeatmap.SelectedHitObjects; @@ -141,11 +148,17 @@ namespace osu.Game.Rulesets.Osu.Edit // is not looking to change the duration of the slider but expand the whole pattern. if (hitObjects.Length == 1 && hitObjects.First() is Slider slider) { + referencePathTypes ??= slider.Path.ControlPoints.Select(p => p.Type.Value).ToList(); + Quad quad = getSurroundingQuad(slider.Path.ControlPoints.Select(p => p.Position.Value)); Vector2 pathRelativeDeltaScale = new Vector2(1 + scale.X / quad.Width, 1 + scale.Y / quad.Height); foreach (var point in slider.Path.ControlPoints) point.Position.Value *= pathRelativeDeltaScale; + + // Maintain the path types in case they were defaulted to bezier at some point during scaling + for (int i = 0; i < slider.Path.ControlPoints.Count; ++i) + slider.Path.ControlPoints[i].Type.Value = referencePathTypes[i]; } else { From 6f01070408fb5fad37d33765a22997d1189d9cc8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Mar 2021 23:06:29 +0900 Subject: [PATCH 1149/1791] Add weird android package requirements --- osu.Android/osu.Android.csproj | 5 +++++ .../osu.Game.Rulesets.Catch.Tests.Android.csproj | 5 +++++ .../osu.Game.Rulesets.Mania.Tests.Android.csproj | 5 +++++ .../osu.Game.Rulesets.Osu.Tests.Android.csproj | 5 +++++ .../osu.Game.Rulesets.Taiko.Tests.Android.csproj | 5 +++++ osu.Game.Tests.Android/osu.Game.Tests.Android.csproj | 3 +++ 6 files changed, 28 insertions(+) diff --git a/osu.Android/osu.Android.csproj b/osu.Android/osu.Android.csproj index a2638e95c8..2051beae21 100644 --- a/osu.Android/osu.Android.csproj +++ b/osu.Android/osu.Android.csproj @@ -53,5 +53,10 @@ + + + 5.0.0 + + \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch.Tests.Android/osu.Game.Rulesets.Catch.Tests.Android.csproj b/osu.Game.Rulesets.Catch.Tests.Android/osu.Game.Rulesets.Catch.Tests.Android.csproj index 88b420ffad..2e6c10a02e 100644 --- a/osu.Game.Rulesets.Catch.Tests.Android/osu.Game.Rulesets.Catch.Tests.Android.csproj +++ b/osu.Game.Rulesets.Catch.Tests.Android/osu.Game.Rulesets.Catch.Tests.Android.csproj @@ -35,5 +35,10 @@ osu.Game + + + 5.0.0 + + \ No newline at end of file diff --git a/osu.Game.Rulesets.Mania.Tests.Android/osu.Game.Rulesets.Mania.Tests.Android.csproj b/osu.Game.Rulesets.Mania.Tests.Android/osu.Game.Rulesets.Mania.Tests.Android.csproj index 0e557cb260..8c134c7114 100644 --- a/osu.Game.Rulesets.Mania.Tests.Android/osu.Game.Rulesets.Mania.Tests.Android.csproj +++ b/osu.Game.Rulesets.Mania.Tests.Android/osu.Game.Rulesets.Mania.Tests.Android.csproj @@ -35,5 +35,10 @@ osu.Game + + + 5.0.0 + + \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu.Tests.Android/osu.Game.Rulesets.Osu.Tests.Android.csproj b/osu.Game.Rulesets.Osu.Tests.Android/osu.Game.Rulesets.Osu.Tests.Android.csproj index dcf1573522..22fa605176 100644 --- a/osu.Game.Rulesets.Osu.Tests.Android/osu.Game.Rulesets.Osu.Tests.Android.csproj +++ b/osu.Game.Rulesets.Osu.Tests.Android/osu.Game.Rulesets.Osu.Tests.Android.csproj @@ -35,5 +35,10 @@ osu.Game + + + 5.0.0 + + \ No newline at end of file diff --git a/osu.Game.Rulesets.Taiko.Tests.Android/osu.Game.Rulesets.Taiko.Tests.Android.csproj b/osu.Game.Rulesets.Taiko.Tests.Android/osu.Game.Rulesets.Taiko.Tests.Android.csproj index 392442b713..a48110b354 100644 --- a/osu.Game.Rulesets.Taiko.Tests.Android/osu.Game.Rulesets.Taiko.Tests.Android.csproj +++ b/osu.Game.Rulesets.Taiko.Tests.Android/osu.Game.Rulesets.Taiko.Tests.Android.csproj @@ -35,5 +35,10 @@ osu.Game + + + 5.0.0 + + \ No newline at end of file diff --git a/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj b/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj index c3d9cb5875..bf256f486c 100644 --- a/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj +++ b/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj @@ -75,6 +75,9 @@ + + 5.0.0 + From 2d344ae6ffd9a5dee8e47096060a1c4f949b677e Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 29 Mar 2021 16:16:50 +0200 Subject: [PATCH 1150/1791] wait for IPC to be populated in the test Did not see this when locally running test until after a couple of subsequent runs. --- osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs b/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs index 086ed54435..4791da93c6 100644 --- a/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs @@ -38,6 +38,8 @@ namespace osu.Game.Tournament.Tests.NonVisual TournamentStorage storage = (TournamentStorage)osu.Dependencies.Get(); FileBasedIPC ipc = (FileBasedIPC)osu.Dependencies.Get(); + waitForOrAssert(() => ipc != null, @"ipc could not be populated in a reasonable amount of time"); + Assert.True(ipc.SetIPCLocation(testCeDir)); Assert.True(storage.AllTournaments.Exists("stable.json")); } From 804ffe9f48954378ef688bd9d3d58120d0ccbf21 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 30 Mar 2021 09:00:09 +0900 Subject: [PATCH 1151/1791] Fix inspections --- .../Online/TestAPIModJsonSerialization.cs | 22 +++++++++---------- .../Screens/Editors/TeamEditorScreen.cs | 8 +++++-- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs b/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs index ab24a72a12..77f910c144 100644 --- a/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs +++ b/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs @@ -25,7 +25,7 @@ namespace osu.Game.Tests.Online var deserialized = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(apiMod)); - Assert.That(deserialized.Acronym, Is.EqualTo(apiMod.Acronym)); + Assert.That(deserialized?.Acronym, Is.EqualTo(apiMod.Acronym)); } [Test] @@ -35,7 +35,7 @@ namespace osu.Game.Tests.Online var deserialized = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(apiMod)); - Assert.That(deserialized.Settings, Contains.Key("test_setting").With.ContainValue(2.0)); + Assert.That(deserialized?.Settings, Contains.Key("test_setting").With.ContainValue(2.0)); } [Test] @@ -44,9 +44,9 @@ namespace osu.Game.Tests.Online var apiMod = new APIMod(new TestMod { TestSetting = { Value = 2 } }); var deserialized = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(apiMod)); - var converted = (TestMod)deserialized.ToMod(new TestRuleset()); + var converted = (TestMod)deserialized?.ToMod(new TestRuleset()); - Assert.That(converted.TestSetting.Value, Is.EqualTo(2)); + Assert.That(converted?.TestSetting.Value, Is.EqualTo(2)); } [Test] @@ -61,11 +61,11 @@ namespace osu.Game.Tests.Online }); var deserialised = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(apiMod)); - var converted = (TestModTimeRamp)deserialised.ToMod(new TestRuleset()); + var converted = (TestModTimeRamp)deserialised?.ToMod(new TestRuleset()); - Assert.That(converted.AdjustPitch.Value, Is.EqualTo(false)); - Assert.That(converted.InitialRate.Value, Is.EqualTo(1.25)); - Assert.That(converted.FinalRate.Value, Is.EqualTo(0.25)); + Assert.That(converted?.AdjustPitch.Value, Is.EqualTo(false)); + Assert.That(converted?.InitialRate.Value, Is.EqualTo(1.25)); + Assert.That(converted?.FinalRate.Value, Is.EqualTo(0.25)); } [Test] @@ -78,10 +78,10 @@ namespace osu.Game.Tests.Online }); var deserialised = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(apiMod)); - var converted = (TestModDifficultyAdjust)deserialised.ToMod(new TestRuleset()); + var converted = (TestModDifficultyAdjust)deserialised?.ToMod(new TestRuleset()); - Assert.That(converted.ExtendedLimits.Value, Is.True); - Assert.That(converted.OverallDifficulty.Value, Is.EqualTo(11)); + Assert.That(converted?.ExtendedLimits.Value, Is.True); + Assert.That(converted?.OverallDifficulty.Value, Is.EqualTo(11)); } private class TestRuleset : Ruleset diff --git a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs index 582f72429b..f051823541 100644 --- a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs @@ -43,12 +43,16 @@ namespace osu.Game.Tournament.Screens.Editors private void addAllCountries() { List countries; + using (Stream stream = game.Resources.GetStream("Resources/countries.json")) using (var sr = new StreamReader(stream)) countries = JsonConvert.DeserializeObject>(sr.ReadToEnd()); - foreach (var c in countries) - Storage.Add(c); + if (countries != null) + { + foreach (var c in countries) + Storage.Add(c); + } } public class TeamRow : CompositeDrawable, IModelBacked From 69db0a55938176ca9ad4b67cb2922c00d896f934 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 30 Mar 2021 09:03:32 +0900 Subject: [PATCH 1152/1791] Countries should not be null (internal game resource) --- .../Screens/Editors/TeamEditorScreen.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs index f051823541..aa1be143ea 100644 --- a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using Newtonsoft.Json; @@ -48,11 +49,10 @@ namespace osu.Game.Tournament.Screens.Editors using (var sr = new StreamReader(stream)) countries = JsonConvert.DeserializeObject>(sr.ReadToEnd()); - if (countries != null) - { - foreach (var c in countries) - Storage.Add(c); - } + Debug.Assert(countries != null); + + foreach (var c in countries) + Storage.Add(c); } public class TeamRow : CompositeDrawable, IModelBacked From 0bf84e473d9b83a0cce1b6ba15831901baedcbc5 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 26 Mar 2021 13:09:44 +0300 Subject: [PATCH 1153/1791] Refactor spinner SPM counter for skinning purposes --- .../Mods/TestSceneOsuModAutoplay.cs | 10 +-- .../Mods/TestSceneOsuModSpunOut.cs | 4 +- .../TestSceneSpinnerRotation.cs | 10 +-- .../Objects/Drawables/DrawableSpinner.cs | 48 +++++------- .../Skinning/Default/DefaultSpinner.cs | 64 ++++++++++++++++ ...rSpmCounter.cs => SpinnerSpmCalculator.cs} | 74 +++++-------------- 6 files changed, 114 insertions(+), 96 deletions(-) rename osu.Game.Rulesets.Osu/Skinning/Default/{SpinnerSpmCounter.cs => SpinnerSpmCalculator.cs} (61%) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutoplay.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutoplay.cs index 856b6554b9..0ba775e5c7 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutoplay.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutoplay.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods private void runSpmTest(Mod mod) { - SpinnerSpmCounter spmCounter = null; + SpinnerSpmCalculator spmCalculator = null; CreateModTest(new ModTestData { @@ -53,13 +53,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods PassCondition = () => Player.ScoreProcessor.JudgedHits >= 1 }); - AddUntilStep("fetch SPM counter", () => + AddUntilStep("fetch SPM calculator", () => { - spmCounter = this.ChildrenOfType().SingleOrDefault(); - return spmCounter != null; + spmCalculator = this.ChildrenOfType().SingleOrDefault(); + return spmCalculator != null; }); - AddUntilStep("SPM is correct", () => Precision.AlmostEquals(spmCounter.SpinsPerMinute, 477, 5)); + AddUntilStep("SPM is correct", () => Precision.AlmostEquals(spmCalculator.Result.Value, 477, 5)); } } } diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs index 7df5ca0f7c..24e69703a6 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs @@ -47,8 +47,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Beatmap = singleSpinnerBeatmap, PassCondition = () => { - var counter = Player.ChildrenOfType().SingleOrDefault(); - return counter != null && Precision.AlmostEquals(counter.SpinsPerMinute, 286, 1); + var counter = Player.ChildrenOfType().SingleOrDefault(); + return counter != null && Precision.AlmostEquals(counter.Result.Value, 286, 1); } }); } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs index ac8d5c81bc..14c709cae1 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs @@ -168,13 +168,13 @@ namespace osu.Game.Rulesets.Osu.Tests double estimatedSpm = 0; addSeekStep(1000); - AddStep("retrieve spm", () => estimatedSpm = drawableSpinner.SpmCounter.SpinsPerMinute); + AddStep("retrieve spm", () => estimatedSpm = drawableSpinner.SpinsPerMinute.Value); addSeekStep(2000); - AddAssert("spm still valid", () => Precision.AlmostEquals(drawableSpinner.SpmCounter.SpinsPerMinute, estimatedSpm, 1.0)); + AddAssert("spm still valid", () => Precision.AlmostEquals(drawableSpinner.SpinsPerMinute.Value, estimatedSpm, 1.0)); addSeekStep(1000); - AddAssert("spm still valid", () => Precision.AlmostEquals(drawableSpinner.SpmCounter.SpinsPerMinute, estimatedSpm, 1.0)); + AddAssert("spm still valid", () => Precision.AlmostEquals(drawableSpinner.SpinsPerMinute.Value, estimatedSpm, 1.0)); } [TestCase(0.5)] @@ -188,7 +188,7 @@ namespace osu.Game.Rulesets.Osu.Tests AddStep("retrieve spinner state", () => { expectedProgress = drawableSpinner.Progress; - expectedSpm = drawableSpinner.SpmCounter.SpinsPerMinute; + expectedSpm = drawableSpinner.SpinsPerMinute.Value; }); addSeekStep(0); @@ -197,7 +197,7 @@ namespace osu.Game.Rulesets.Osu.Tests addSeekStep(1000); AddAssert("progress almost same", () => Precision.AlmostEquals(expectedProgress, drawableSpinner.Progress, 0.05)); - AddAssert("spm almost same", () => Precision.AlmostEquals(expectedSpm, drawableSpinner.SpmCounter.SpinsPerMinute, 2.0)); + AddAssert("spm almost same", () => Precision.AlmostEquals(expectedSpm, drawableSpinner.SpinsPerMinute.Value, 2.0)); } private Replay applyRateAdjustment(Replay scoreReplay, double rate) => new Replay diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 39e78a14aa..1a89fc308e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -30,7 +30,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public new OsuSpinnerJudgementResult Result => (OsuSpinnerJudgementResult)base.Result; public SpinnerRotationTracker RotationTracker { get; private set; } - public SpinnerSpmCounter SpmCounter { get; private set; } + + private SpinnerSpmCalculator spmCalculator; private Container ticks; private PausableSkinnableSound spinningSample; @@ -43,7 +44,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables /// public IBindable GainedBonus => gainedBonus; - private readonly Bindable gainedBonus = new Bindable(); + private readonly Bindable gainedBonus = new BindableDouble(); + + /// + /// The number of spins per minute this spinner is spinning at, for display purposes. + /// + public readonly IBindable SpinsPerMinute = new BindableDouble(); private const double fade_out_duration = 160; @@ -63,7 +69,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Origin = Anchor.Centre; RelativeSizeAxes = Axes.Both; - InternalChildren = new Drawable[] + AddInternal(spmCalculator = new SpinnerSpmCalculator + { + Result = { BindTarget = SpinsPerMinute }, + }); + + AddRangeInternal(new Drawable[] { ticks = new Container(), new AspectContainer @@ -77,20 +88,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables RotationTracker = new SpinnerRotationTracker(this) } }, - SpmCounter = new SpinnerSpmCounter - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Y = 120, - Alpha = 0 - }, spinningSample = new PausableSkinnableSound { Volume = { Value = 0 }, Looping = true, Frequency = { Value = spinning_sample_initial_frequency } } - }; + }); PositionBindable.BindValueChanged(pos => Position = pos.NewValue); } @@ -161,17 +165,6 @@ 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); @@ -282,22 +275,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.UpdateAfterChildren(); - if (!SpmCounter.IsPresent && RotationTracker.Tracking) - { - Result.TimeStarted ??= Time.Current; - fadeInCounter(); - } + if (Result.TimeStarted == null && RotationTracker.Tracking) + Result.TimeStarted = Time.Current; // 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); + spmCalculator.SetRotation(Result.RateAdjustedRotation); updateBonusScore(); } - private void fadeInCounter() => SpmCounter.FadeIn(HitObject.TimeFadeIn); - private static readonly int score_per_tick = new SpinnerBonusTick.OsuSpinnerBonusTickJudgement().MaxNumericResult; private int wholeSpins; diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSpinner.cs index 891821fe2f..ae8c03dad1 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; using System.Globalization; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -19,6 +20,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default private OsuSpriteText bonusCounter; + private Container spmContainer; + private OsuSpriteText spmCounter; + public DefaultSpinner() { RelativeSizeAxes = Axes.Both; @@ -46,11 +50,37 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default Origin = Anchor.Centre, Font = OsuFont.Numeric.With(size: 24), Y = -120, + }, + spmContainer = new Container + { + Alpha = 0f, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Y = 120, + Children = new[] + { + spmCounter = new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = @"0", + Font = OsuFont.Numeric.With(size: 24) + }, + new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = @"SPINS PER MINUTE", + Font = OsuFont.Numeric.With(size: 12), + Y = 30 + } + } } }); } private IBindable gainedBonus; + private IBindable spinsPerMinute; protected override void LoadComplete() { @@ -63,6 +93,40 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default bonusCounter.FadeOutFromOne(1500); bonusCounter.ScaleTo(1.5f).Then().ScaleTo(1f, 1000, Easing.OutQuint); }); + + spinsPerMinute = drawableSpinner.SpinsPerMinute.GetBoundCopy(); + spinsPerMinute.BindValueChanged(spm => + { + spmCounter.Text = Math.Truncate(spm.NewValue).ToString(@"#0"); + }, true); + + drawableSpinner.ApplyCustomUpdateState += updateStateTransforms; + updateStateTransforms(drawableSpinner, drawableSpinner.State.Value); + } + + protected override void Update() + { + base.Update(); + + if (!spmContainer.IsPresent && drawableSpinner.Result?.TimeStarted != null) + fadeCounterOnTimeStart(); + } + + private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state) + { + if (!(drawableHitObject is DrawableSpinner)) + return; + + fadeCounterOnTimeStart(); + } + + private void fadeCounterOnTimeStart() + { + if (drawableSpinner.Result?.TimeStarted is double startTime) + { + using (BeginAbsoluteSequence(startTime)) + spmContainer.FadeIn(drawableSpinner.HitObject.TimeFadeIn); + } } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerSpmCounter.cs b/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerSpmCalculator.cs similarity index 61% rename from osu.Game.Rulesets.Osu/Skinning/Default/SpinnerSpmCounter.cs rename to osu.Game.Rulesets.Osu/Skinning/Default/SpinnerSpmCalculator.cs index 69355f624b..a5205bbb8c 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerSpmCounter.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerSpmCalculator.cs @@ -1,77 +1,37 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Utils; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.Osu.Skinning.Default { - public class SpinnerSpmCounter : Container + public class SpinnerSpmCalculator : Component { + private readonly Queue records = new Queue(); + private const double spm_count_duration = 595; // not using hundreds to avoid frame rounding issues + + /// + /// The resultant spins per minute value, which is updated via . + /// + public IBindable Result => result; + + private readonly Bindable result = new BindableDouble(); + [Resolved] private DrawableHitObject drawableSpinner { get; set; } - private readonly OsuSpriteText spmText; - - public SpinnerSpmCounter() - { - Children = new Drawable[] - { - spmText = new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Text = @"0", - Font = OsuFont.Numeric.With(size: 24) - }, - new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Text = @"SPINS PER MINUTE", - Font = OsuFont.Numeric.With(size: 12), - Y = 30 - } - }; - } - protected override void LoadComplete() { base.LoadComplete(); drawableSpinner.HitObjectApplied += resetState; } - private double spm; - - public double SpinsPerMinute - { - get => spm; - private set - { - if (value == spm) return; - - spm = value; - spmText.Text = Math.Truncate(value).ToString(@"#0"); - } - } - - private struct RotationRecord - { - public float Rotation; - public double Time; - } - - private readonly Queue records = new Queue(); - private const double spm_count_duration = 595; // not using hundreds to avoid frame rounding issues - public void SetRotation(float currentRotation) { // Never calculate SPM by same time of record to avoid 0 / 0 = NaN or X / 0 = Infinity result. @@ -88,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default while (records.Count > 0 && Time.Current - records.Peek().Time > spm_count_duration) record = records.Dequeue(); - SpinsPerMinute = (currentRotation - record.Rotation) / (Time.Current - record.Time) * 1000 * 60 / 360; + result.Value = (currentRotation - record.Rotation) / (Time.Current - record.Time) * 1000 * 60 / 360; } records.Enqueue(new RotationRecord { Rotation = currentRotation, Time = Time.Current }); @@ -96,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default private void resetState(DrawableHitObject hitObject) { - SpinsPerMinute = 0; + result.Value = 0; records.Clear(); } @@ -107,5 +67,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default if (drawableSpinner != null) drawableSpinner.HitObjectApplied -= resetState; } + + private struct RotationRecord + { + public float Rotation; + public double Time; + } } } From f848ef534747babe4c020e3b1a759b2fc42d259d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 26 Mar 2021 13:10:04 +0300 Subject: [PATCH 1154/1791] Add legacy spinner SPM counter support --- .../Skinning/Legacy/LegacySpinner.cs | 39 +++++++++++++++++-- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs index 064b7a4680..7eb6898abc 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs @@ -28,6 +28,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy protected const float SPRITE_SCALE = 0.625f; + private const float spm_hide_offset = 50f; + protected DrawableSpinner DrawableSpinner { get; private set; } private Sprite spin; @@ -35,6 +37,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private LegacySpriteText bonusCounter; + private Sprite spmBackground; + private LegacySpriteText spmCounter; + [BackgroundDependencyLoader] private void load(DrawableHitObject drawableHitObject, ISkinSource source) { @@ -79,11 +84,27 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy Scale = new Vector2(SPRITE_SCALE), Y = SPINNER_TOP_OFFSET + 299, }.With(s => s.Font = s.Font.With(fixedWidth: false)), + spmBackground = new Sprite + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopLeft, + Texture = source.GetTexture("spinner-rpm"), + Scale = new Vector2(SPRITE_SCALE), + Position = new Vector2(-87, 445 + spm_hide_offset), + }, + spmCounter = new LegacySpriteText(source, LegacyFont.Score) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopRight, + Scale = new Vector2(SPRITE_SCALE * 0.9f), + Position = new Vector2(80, 448 + spm_hide_offset), + }.With(s => s.Font = s.Font.With(fixedWidth: false)), } }); } private IBindable gainedBonus; + private IBindable spinsPerMinute; private readonly Bindable completed = new Bindable(); @@ -99,6 +120,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy bonusCounter.ScaleTo(SPRITE_SCALE * 2f).Then().ScaleTo(SPRITE_SCALE * 1.28f, 800, Easing.Out); }); + spinsPerMinute = DrawableSpinner.SpinsPerMinute.GetBoundCopy(); + spinsPerMinute.BindValueChanged(spm => + { + spmCounter.Text = Math.Truncate(spm.NewValue).ToString(@"#0"); + }, true); + completed.BindValueChanged(onCompletedChanged, true); DrawableSpinner.ApplyCustomUpdateState += UpdateStateTransforms; @@ -142,10 +169,16 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy switch (drawableHitObject) { case DrawableSpinner d: - double fadeOutLength = Math.Min(400, d.HitObject.Duration); + using (BeginAbsoluteSequence(d.HitObject.StartTime - d.HitObject.TimeFadeIn)) + { + spmBackground.MoveToOffset(new Vector2(0, -spm_hide_offset), d.HitObject.TimeFadeIn, Easing.Out); + spmCounter.MoveToOffset(new Vector2(0, -spm_hide_offset), d.HitObject.TimeFadeIn, Easing.Out); + } - using (BeginAbsoluteSequence(drawableHitObject.HitStateUpdateTime - fadeOutLength, true)) - spin.FadeOutFromOne(fadeOutLength); + double spinFadeOutLength = Math.Min(400, d.HitObject.Duration); + + using (BeginAbsoluteSequence(drawableHitObject.HitStateUpdateTime - spinFadeOutLength, true)) + spin.FadeOutFromOne(spinFadeOutLength); break; case DrawableSpinnerTick d: From 8a0fcf20ede3a63a074072899b33cb02cd83fdec Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Mar 2021 12:32:42 +0900 Subject: [PATCH 1155/1791] Move offset settings up for more logical ordering --- .../Settings/Sections/Input/TabletSettings.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index bd0f7ddc4c..571d79baf8 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -110,12 +110,6 @@ namespace osu.Game.Overlays.Settings.Sections.Input } }, new SettingsSlider - { - TransferValueOnCommit = true, - LabelText = "Aspect Ratio", - Current = aspectRatio - }, - new SettingsSlider { TransferValueOnCommit = true, LabelText = "X Offset", @@ -127,6 +121,12 @@ namespace osu.Game.Overlays.Settings.Sections.Input LabelText = "Y Offset", Current = offsetY }, + new SettingsSlider + { + TransferValueOnCommit = true, + LabelText = "Aspect Ratio", + Current = aspectRatio + }, new SettingsCheckbox { LabelText = "Lock aspect ratio", From 8dfff999f9ced7f2e550df44fcf3c76e871055fb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Mar 2021 12:40:50 +0900 Subject: [PATCH 1156/1791] Add rotation slider --- .../Visual/Settings/TestSceneTabletSettings.cs | 2 ++ .../Settings/Sections/Input/TabletAreaSelection.cs | 9 +++++++++ .../Overlays/Settings/Sections/Input/TabletSettings.cs | 10 ++++++++++ 3 files changed, 21 insertions(+) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs index a7f6c8c0d3..a62980addf 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs @@ -45,6 +45,8 @@ namespace osu.Game.Tests.Visual.Settings public Bindable AreaOffset { get; } = new Bindable(); public Bindable AreaSize { get; } = new Bindable(); + public Bindable Rotation { get; } = new Bindable(); + public IBindable Tablet => tablet; private readonly Bindable tablet = new Bindable(); diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs index ecb8acce54..f61742093c 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs @@ -25,6 +25,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input private readonly Bindable areaOffset = new Bindable(); private readonly Bindable areaSize = new Bindable(); + private readonly BindableNumber rotation = new BindableNumber(); + private readonly IBindable tablet = new Bindable(); private OsuSpriteText tabletName; @@ -124,6 +126,13 @@ namespace osu.Game.Overlays.Settings.Sections.Input usableAreaText.Text = $"{(float)x / commonDivider}:{(float)y / commonDivider}"; }, true); + rotation.BindTo(handler.Rotation); + rotation.BindValueChanged(val => + { + usableAreaContainer.RotateTo(val.NewValue, 100, Easing.OutQuint) + .OnComplete(_ => checkBounds()); // required as we are using SSDQ. + }); + tablet.BindTo(handler.Tablet); tablet.BindValueChanged(_ => Scheduler.AddOnce(updateTabletDetails)); diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index 571d79baf8..9d128f8db3 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -27,6 +27,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input private readonly BindableNumber sizeX = new BindableNumber { MinValue = 10 }; private readonly BindableNumber sizeY = new BindableNumber { MinValue = 10 }; + private readonly BindableNumber rotation = new BindableNumber { MinValue = 0, MaxValue = 360 }; + [Resolved] private GameHost host { get; set; } @@ -122,6 +124,12 @@ namespace osu.Game.Overlays.Settings.Sections.Input Current = offsetY }, new SettingsSlider + { + TransferValueOnCommit = true, + LabelText = "Rotation", + Current = rotation + }, + new SettingsSlider { TransferValueOnCommit = true, LabelText = "Aspect Ratio", @@ -153,6 +161,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input { base.LoadComplete(); + rotation.BindTo(tabletHandler.Rotation); + areaOffset.BindTo(tabletHandler.AreaOffset); areaOffset.BindValueChanged(val => { From 1dfd08eded521c40576cc746216f3b487e239e9d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Mar 2021 13:01:48 +0900 Subject: [PATCH 1157/1791] Add tablet rotation configuration --- .../Sections/Input/RotationPresetButtons.cs | 109 ++++++++++++++++++ .../Settings/Sections/Input/TabletSettings.cs | 1 + 2 files changed, 110 insertions(+) create mode 100644 osu.Game/Overlays/Settings/Sections/Input/RotationPresetButtons.cs diff --git a/osu.Game/Overlays/Settings/Sections/Input/RotationPresetButtons.cs b/osu.Game/Overlays/Settings/Sections/Input/RotationPresetButtons.cs new file mode 100644 index 0000000000..3e8da9f7d0 --- /dev/null +++ b/osu.Game/Overlays/Settings/Sections/Input/RotationPresetButtons.cs @@ -0,0 +1,109 @@ +// 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 osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Handlers.Tablet; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Overlays.Settings.Sections.Input +{ + internal class RotationPresetButtons : FillFlowContainer + { + private readonly ITabletHandler tabletHandler; + + private Bindable rotation; + + private const int height = 50; + + public RotationPresetButtons(ITabletHandler tabletHandler) + { + this.tabletHandler = tabletHandler; + + RelativeSizeAxes = Axes.X; + Height = height; + + for (int i = 0; i < 360; i += 90) + { + var presetRotation = i; + + Add(new RotationButton(i) + { + RelativeSizeAxes = Axes.X, + Height = height, + Width = 0.25f, + Text = $"{presetRotation}º", + Action = () => tabletHandler.Rotation.Value = presetRotation, + }); + } + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + rotation = tabletHandler.Rotation.GetBoundCopy(); + rotation.BindValueChanged(val => + { + foreach (var b in Children.OfType()) + b.IsSelected = b.Preset == val.NewValue; + }, true); + } + + public class RotationButton : TriangleButton + { + [Resolved] + private OsuColour colours { get; set; } + + public readonly int Preset; + + public RotationButton(int preset) + { + Preset = preset; + } + + private bool isSelected; + + public bool IsSelected + { + get => isSelected; + set + { + if (value == isSelected) + return; + + isSelected = value; + + if (IsLoaded) + updateColour(); + } + } + + protected override void LoadComplete() + { + base.LoadComplete(); + updateColour(); + } + + private void updateColour() + { + if (isSelected) + { + BackgroundColour = colours.BlueDark; + Triangles.ColourDark = colours.BlueDarker; + Triangles.ColourLight = colours.Blue; + } + else + { + BackgroundColour = colours.Gray4; + Triangles.ColourDark = colours.Gray5; + Triangles.ColourLight = colours.Gray6; + } + } + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index 9d128f8db3..d770c18878 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -129,6 +129,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input LabelText = "Rotation", Current = rotation }, + new RotationPresetButtons(tabletHandler), new SettingsSlider { TransferValueOnCommit = true, From 9504fe3f3c1fb78b8b4f6510ec988933f26bd71d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Mar 2021 13:43:05 +0900 Subject: [PATCH 1158/1791] Inline add of spm calculation (no need for it to be a separate call) --- .../Objects/Drawables/DrawableSpinner.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 1a89fc308e..3a4753761a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -69,13 +69,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Origin = Anchor.Centre; RelativeSizeAxes = Axes.Both; - AddInternal(spmCalculator = new SpinnerSpmCalculator - { - Result = { BindTarget = SpinsPerMinute }, - }); - AddRangeInternal(new Drawable[] { + spmCalculator = new SpinnerSpmCalculator + { + Result = { BindTarget = SpinsPerMinute }, + }, ticks = new Container(), new AspectContainer { From b82247aabec2ee10c12105701d1c0e073a775e6a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Mar 2021 14:13:16 +0900 Subject: [PATCH 1159/1791] Add inline comments and use Vector2.Zero --- osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 07643bd75e..82301bd37f 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -39,9 +39,11 @@ namespace osu.Game.Rulesets.Osu.Edit { var hitObjects = selectedMovableObjects; + // this will potentially move the selection out of bounds... foreach (var h in hitObjects) h.Position += moveEvent.InstantDelta; + // but this will be corrected. moveSelectionInBounds(); return true; } @@ -153,7 +155,6 @@ namespace osu.Game.Rulesets.Osu.Edit scaleHitObjects(hitObjects, reference, scale); moveSelectionInBounds(); - return true; } @@ -257,7 +258,8 @@ namespace osu.Game.Rulesets.Osu.Edit var hitObjects = selectedMovableObjects; Quad quad = getSurroundingQuad(hitObjects); - Vector2 delta = new Vector2(0); + + Vector2 delta = Vector2.Zero; if (quad.TopLeft.X < 0) delta.X -= quad.TopLeft.X; From 88035f73e00229ca5587adb916dc36716da29fb4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Mar 2021 14:23:46 +0900 Subject: [PATCH 1160/1791] Fix incorrect wait logic in IPC location test Not really willing to put more effort into fixing this one. Should do the job. --- .../NonVisual/IPCLocationTest.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs b/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs index 4791da93c6..4c5f5a7a1a 100644 --- a/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs @@ -26,26 +26,26 @@ namespace osu.Game.Tournament.Tests.NonVisual string basePath = Path.Combine(RuntimeInfo.StartupDirectory, "headless", nameof(CheckIPCLocation)); // Set up a fake IPC client for the IPC Storage to switch to. - string testCeDir = Path.Combine(basePath, "stable-ce"); - Directory.CreateDirectory(testCeDir); + string testStableInstallDirectory = Path.Combine(basePath, "stable-ce"); + Directory.CreateDirectory(testStableInstallDirectory); - string ipcFile = Path.Combine(testCeDir, "ipc.txt"); + string ipcFile = Path.Combine(testStableInstallDirectory, "ipc.txt"); File.WriteAllText(ipcFile, string.Empty); try { var osu = loadOsu(host); TournamentStorage storage = (TournamentStorage)osu.Dependencies.Get(); - FileBasedIPC ipc = (FileBasedIPC)osu.Dependencies.Get(); + FileBasedIPC ipc = null; - waitForOrAssert(() => ipc != null, @"ipc could not be populated in a reasonable amount of time"); + waitForOrAssert(() => (ipc = osu.Dependencies.Get() as FileBasedIPC) != null, @"ipc could not be populated in a reasonable amount of time"); - Assert.True(ipc.SetIPCLocation(testCeDir)); + Assert.True(ipc.SetIPCLocation(testStableInstallDirectory)); Assert.True(storage.AllTournaments.Exists("stable.json")); } finally { - host.Storage.DeleteDirectory(testCeDir); + host.Storage.DeleteDirectory(testStableInstallDirectory); host.Storage.DeleteDirectory("tournaments"); host.Exit(); } From 89bea2868a9ca68a8c09a9c93db6f504e9022a96 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Mar 2021 14:33:55 +0900 Subject: [PATCH 1161/1791] Move bool one level down --- osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs | 2 +- osu.Game.Rulesets.Catch/UI/Catcher.cs | 9 +++++++-- osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 7 +------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs index 5c5e41234d..bba42dea97 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Catch.Mods var drawableCatchRuleset = (DrawableCatchRuleset)drawableRuleset; var catchPlayfield = (CatchPlayfield)drawableCatchRuleset.Playfield; - catchPlayfield.CatcherArea.CatchFruitOnPlate = false; + catchPlayfield.CatcherArea.MovableCatcher.CatchFruitOnPlate = false; } protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 42be745c2e..5d57e84b75 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -43,6 +43,11 @@ namespace osu.Game.Rulesets.Catch.UI /// public bool HyperDashing => hyperDashModifier != 1; + /// + /// Whether fruit should appear on the plate. + /// + public bool CatchFruitOnPlate { get; set; } = true; + /// /// The relative space to cover in 1 millisecond. based on 1 game pixel per millisecond as in osu-stable. /// @@ -223,7 +228,7 @@ namespace osu.Game.Rulesets.Catch.UI catchObjectPosition <= catcherPosition + halfCatchWidth; } - public void OnNewResult(DrawableCatchHitObject drawableObject, JudgementResult result, bool placeOnPlate) + public void OnNewResult(DrawableCatchHitObject drawableObject, JudgementResult result) { var catchResult = (CatchJudgementResult)result; catchResult.CatcherAnimationState = CurrentState; @@ -237,7 +242,7 @@ namespace osu.Game.Rulesets.Catch.UI { var positionInStack = computePositionInStack(new Vector2(palpableObject.X - X, 0), palpableObject.DisplaySize.X / 2); - if (placeOnPlate) + if (CatchFruitOnPlate) placeCaughtObject(palpableObject, positionInStack); if (hitLighting.Value) diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index 29c95ec61c..44adbd5512 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -21,11 +21,6 @@ namespace osu.Game.Rulesets.Catch.UI public readonly Catcher MovableCatcher; private readonly CatchComboDisplay comboDisplay; - /// - /// Whether fruit should appear on the plate. - /// - public bool CatchFruitOnPlate { get; set; } = true; - public CatcherArea(Container droppedObjectContainer, BeatmapDifficulty difficulty = null) { Size = new Vector2(CatchPlayfield.WIDTH, CATCHER_SIZE); @@ -46,7 +41,7 @@ namespace osu.Game.Rulesets.Catch.UI public void OnNewResult(DrawableCatchHitObject hitObject, JudgementResult result) { - MovableCatcher.OnNewResult(hitObject, result, CatchFruitOnPlate); + MovableCatcher.OnNewResult(hitObject, result); if (!result.Type.IsScorable()) return; From f12353a99e5b38c57d1578460b542b27d07a1cec Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Mar 2021 14:57:06 +0900 Subject: [PATCH 1162/1791] Update forgotten test scene usage --- osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs index ddb6194899..48efd73222 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs @@ -242,7 +242,7 @@ namespace osu.Game.Rulesets.Catch.Tests Add(drawableObject); drawableObject.OnLoadComplete += _ => { - catcher.OnNewResult(drawableObject, result, true); + catcher.OnNewResult(drawableObject, result); drawableObject.Expire(); }; } From 90c75a64cf9e30e213821706e8d54de262d2b629 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Mar 2021 15:24:11 +0900 Subject: [PATCH 1163/1791] Fix legacy control point precision having an adverse effect on the editor --- osu.Game/Screens/Edit/Timing/DifficultySection.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/DifficultySection.cs b/osu.Game/Screens/Edit/Timing/DifficultySection.cs index 9d80ca0b14..97d110c502 100644 --- a/osu.Game/Screens/Edit/Timing/DifficultySection.cs +++ b/osu.Game/Screens/Edit/Timing/DifficultySection.cs @@ -28,7 +28,16 @@ namespace osu.Game.Screens.Edit.Timing { if (point.NewValue != null) { - multiplierSlider.Current = point.NewValue.SpeedMultiplierBindable; + var selectedPointBindable = point.NewValue.SpeedMultiplierBindable; + + // there may be legacy control points, which contain infinite precision for compatibility reasons (see LegacyDifficultyControlPoint). + // generally that level of precision could only be set by externally editing the .osu file, so at the point + // a user is looking to update this within the editor it should be safe to obliterate this additional precision. + double expectedPrecision = new DifficultyControlPoint().SpeedMultiplierBindable.Precision; + if (selectedPointBindable.Precision < expectedPrecision) + selectedPointBindable.Precision = expectedPrecision; + + multiplierSlider.Current = selectedPointBindable; multiplierSlider.Current.BindValueChanged(_ => ChangeHandler?.SaveState()); } } From 1d968009c2c329a1abd107409c9e37c302ae36c7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Mar 2021 16:07:04 +0900 Subject: [PATCH 1164/1791] Add osu!mania key filtering using "keys=4" at song select --- .../Beatmaps/ManiaBeatmapConverter.cs | 8 ++++- .../ManiaFilterCriteria.cs | 33 +++++++++++++++++++ osu.Game.Rulesets.Mania/ManiaRuleset.cs | 6 ++++ 3 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 7a0e3b2b76..756207a201 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps if (IsForCurrentRuleset) { - TargetColumns = (int)Math.Max(1, roundedCircleSize); + TargetColumns = GetColumnCountForNonConvert(beatmap.BeatmapInfo); if (TargetColumns > ManiaRuleset.MAX_STAGE_KEYS) { @@ -71,6 +71,12 @@ namespace osu.Game.Rulesets.Mania.Beatmaps originalTargetColumns = TargetColumns; } + internal static int GetColumnCountForNonConvert(BeatmapInfo beatmap) + { + var roundedCircleSize = Math.Round(beatmap.BaseDifficulty.CircleSize); + return (int)Math.Max(1, roundedCircleSize); + } + public override bool CanConvert() => Beatmap.HitObjects.All(h => h is IHasXPosition); protected override Beatmap ConvertBeatmap(IBeatmap original, CancellationToken cancellationToken) diff --git a/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs b/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs new file mode 100644 index 0000000000..c9a1ae84d4 --- /dev/null +++ b/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs @@ -0,0 +1,33 @@ +// 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.Filter; +using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Screens.Select; +using osu.Game.Screens.Select.Filter; + +namespace osu.Game.Rulesets.Mania +{ + public class ManiaFilterCriteria : IRulesetFilterCriteria + { + private FilterCriteria.OptionalRange keys; + + public bool Matches(BeatmapInfo beatmap) + { + return !keys.HasFilter || (beatmap.RulesetID == new ManiaRuleset().LegacyID) && keys.IsInRange(ManiaBeatmapConverter.GetColumnCountForNonConvert(beatmap)); + } + + public bool TryParseCustomKeywordCriteria(string key, Operator op, string value) + { + switch (key) + { + case "key": + case "keys": + return FilterQueryParser.TryUpdateCriteriaRange(ref keys, op, value); + } + + return false; + } + } +} diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index d624e094ad..88b63606b9 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -22,6 +22,7 @@ using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Filter; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Difficulty; @@ -382,6 +383,11 @@ namespace osu.Game.Rulesets.Mania } } }; + + public override IRulesetFilterCriteria CreateRulesetFilterCriteria() + { + return new ManiaFilterCriteria(); + } } public enum PlayfieldType From e769ef45be36d1bf700969bf76bcab5c0dac2f99 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Mar 2021 16:55:39 +0900 Subject: [PATCH 1165/1791] Fix misplaced parenthesis --- osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs b/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs index c9a1ae84d4..d9a278ef29 100644 --- a/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs +++ b/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Mania public bool Matches(BeatmapInfo beatmap) { - return !keys.HasFilter || (beatmap.RulesetID == new ManiaRuleset().LegacyID) && keys.IsInRange(ManiaBeatmapConverter.GetColumnCountForNonConvert(beatmap)); + return !keys.HasFilter || (beatmap.RulesetID == new ManiaRuleset().LegacyID && keys.IsInRange(ManiaBeatmapConverter.GetColumnCountForNonConvert(beatmap))); } public bool TryParseCustomKeywordCriteria(string key, Operator op, string value) From 56428a027e53e9e89dcb321a5f58eb32c899d52d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Mar 2021 16:56:19 +0900 Subject: [PATCH 1166/1791] Change static method to public --- osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 756207a201..26393c8edb 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps originalTargetColumns = TargetColumns; } - internal static int GetColumnCountForNonConvert(BeatmapInfo beatmap) + public static int GetColumnCountForNonConvert(BeatmapInfo beatmap) { var roundedCircleSize = Math.Round(beatmap.BaseDifficulty.CircleSize); return (int)Math.Max(1, roundedCircleSize); From 05961e98d5e3ac0b36f936d0f09ed62896b19093 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Mar 2021 19:03:15 +0900 Subject: [PATCH 1167/1791] Ensure GlobalActions are handled before anything else game-wide --- .../Input/Bindings/GlobalActionContainer.cs | 9 +++-- osu.Game/Input/Bindings/GlobalInputManager.cs | 33 +++++++++++++++++++ osu.Game/OsuGameBase.cs | 8 ++--- 3 files changed, 44 insertions(+), 6 deletions(-) create mode 100644 osu.Game/Input/Bindings/GlobalInputManager.cs diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 8ccdb9249e..6d038c43cf 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.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.Collections.Generic; using System.ComponentModel; using System.Linq; @@ -14,11 +15,13 @@ namespace osu.Game.Input.Bindings { private readonly Drawable handler; - public GlobalActionContainer(OsuGameBase game) + public GlobalActionContainer(OsuGameBase game, bool nested = false) : base(matchingMode: KeyCombinationMatchingMode.Modifiers) { if (game is IKeyBindingHandler) handler = game; + + GetInputQueue = () => base.KeyBindingInputQueue.ToArray(); } public override IEnumerable DefaultKeyBindings => GlobalKeyBindings.Concat(InGameKeyBindings).Concat(AudioControlKeyBindings).Concat(EditorKeyBindings); @@ -91,8 +94,10 @@ namespace osu.Game.Input.Bindings new KeyBinding(InputKey.F3, GlobalAction.MusicPlay) }; + public Func GetInputQueue { get; set; } + protected override IEnumerable KeyBindingInputQueue => - handler == null ? base.KeyBindingInputQueue : base.KeyBindingInputQueue.Prepend(handler); + handler == null ? GetInputQueue() : GetInputQueue().Prepend(handler); } public enum GlobalAction diff --git a/osu.Game/Input/Bindings/GlobalInputManager.cs b/osu.Game/Input/Bindings/GlobalInputManager.cs new file mode 100644 index 0000000000..475397408a --- /dev/null +++ b/osu.Game/Input/Bindings/GlobalInputManager.cs @@ -0,0 +1,33 @@ +// 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 osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input; + +namespace osu.Game.Input.Bindings +{ + public class GlobalInputManager : PassThroughInputManager + { + public readonly GlobalActionContainer GlobalBindings; + + protected override Container Content { get; } + + public GlobalInputManager(OsuGameBase game) + { + InternalChildren = new Drawable[] + { + Content = new Container + { + RelativeSizeAxes = Axes.Both, + }, + // to avoid positional input being blocked by children, ensure the GlobalActionContainer is above everything. + GlobalBindings = new GlobalActionContainer(game, true) + { + GetInputQueue = () => NonPositionalInputQueue.ToArray() + }, + }; + } + } +} diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index e1c7b67a8c..bff3e15bfb 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -310,9 +310,9 @@ namespace osu.Game MenuCursorContainer = new MenuCursorContainer { RelativeSizeAxes = Axes.Both }; - GlobalActionContainer globalBindings; + GlobalInputManager globalInput; - MenuCursorContainer.Child = globalBindings = new GlobalActionContainer(this) + MenuCursorContainer.Child = globalInput = new GlobalInputManager(this) { RelativeSizeAxes = Axes.Both, Child = content = new OsuTooltipContainer(MenuCursorContainer.Cursor) { RelativeSizeAxes = Axes.Both } @@ -320,8 +320,8 @@ namespace osu.Game base.Content.Add(CreateScalingContainer().WithChild(MenuCursorContainer)); - KeyBindingStore.Register(globalBindings); - dependencies.Cache(globalBindings); + KeyBindingStore.Register(globalInput.GlobalBindings); + dependencies.Cache(globalInput.GlobalBindings); PreviewTrackManager previewTrackManager; dependencies.Cache(previewTrackManager = new PreviewTrackManager()); From a2f50af4243dfde95ec556859666b65e34f3007b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Mar 2021 19:42:40 +0900 Subject: [PATCH 1168/1791] Fix fall-through scroll redirection --- osu.Game/OsuGame.cs | 7 ------- osu.Game/Overlays/Volume/VolumeControlReceptor.cs | 8 ++++++++ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index dd1fa32ad9..5fa315a464 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -879,13 +879,6 @@ namespace osu.Game return component; } - protected override bool OnScroll(ScrollEvent e) - { - // forward any unhandled mouse scroll events to the volume control. - volume.Adjust(GlobalAction.IncreaseVolume, e.ScrollDelta.Y, e.IsPrecise); - return true; - } - public bool OnPressed(GlobalAction action) { if (introScreen == null) return false; diff --git a/osu.Game/Overlays/Volume/VolumeControlReceptor.cs b/osu.Game/Overlays/Volume/VolumeControlReceptor.cs index 3478f18a40..3b39b74e00 100644 --- a/osu.Game/Overlays/Volume/VolumeControlReceptor.cs +++ b/osu.Game/Overlays/Volume/VolumeControlReceptor.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Graphics.Containers; using osu.Framework.Input; using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Game.Input.Bindings; namespace osu.Game.Overlays.Volume @@ -17,6 +18,13 @@ namespace osu.Game.Overlays.Volume public bool OnPressed(GlobalAction action) => ActionRequested?.Invoke(action) ?? false; + protected override bool OnScroll(ScrollEvent e) + { + // forward any unhandled mouse scroll events to the volume control. + ScrollActionRequested?.Invoke(GlobalAction.IncreaseVolume, e.ScrollDelta.Y, e.IsPrecise); + return true; + } + public bool OnScroll(GlobalAction action, float amount, bool isPrecise) => ScrollActionRequested?.Invoke(action, amount, isPrecise) ?? false; From 633e6130bf434c628d7be98eeba0f0fff9d30cf8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Mar 2021 19:45:22 +0900 Subject: [PATCH 1169/1791] 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 3682a44b9f..5b65670869 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 6d571218fc..6a7f7e7026 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 ceb46eae87..4aa3ad1c61 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From fb0079fb9f641c7a7c769a3144684cb90c0320a2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Mar 2021 22:42:31 +0900 Subject: [PATCH 1170/1791] Fix accuracy displaying incorrectly in online contexts Closes #12221. --- osu.Game/Users/UserStatistics.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Users/UserStatistics.cs b/osu.Game/Users/UserStatistics.cs index 04a358436e..5ddcd86d28 100644 --- a/osu.Game/Users/UserStatistics.cs +++ b/osu.Game/Users/UserStatistics.cs @@ -45,7 +45,7 @@ namespace osu.Game.Users public double Accuracy; [JsonIgnore] - public string DisplayAccuracy => Accuracy.FormatAccuracy(); + public string DisplayAccuracy => (Accuracy / 100).FormatAccuracy(); [JsonProperty(@"play_count")] public int PlayCount; From ded91b32a4facc2e55d909428999401ffec0ac80 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 31 Mar 2021 12:11:43 +0900 Subject: [PATCH 1171/1791] Add failing test --- .../TestSceneHoldNoteInput.cs | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs index 7ae69bf7d7..2357778948 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs @@ -288,6 +288,42 @@ namespace osu.Game.Rulesets.Mania.Tests .All(j => j.Type.IsHit())); } + [Test] + public void TestHitTailBeforeLastTick() + { + const int tick_rate = 8; + const double tick_spacing = TimingControlPoint.DEFAULT_BEAT_LENGTH / tick_rate; + const double time_last_tick = time_head + tick_spacing * (int)((time_tail - time_head) / tick_spacing - 1); + + var beatmap = new Beatmap + { + HitObjects = + { + new HoldNote + { + StartTime = time_head, + Duration = time_tail - time_head, + Column = 0, + } + }, + BeatmapInfo = + { + BaseDifficulty = new BeatmapDifficulty { SliderTickRate = tick_rate }, + Ruleset = new ManiaRuleset().RulesetInfo + }, + }; + + performTest(new List + { + new ManiaReplayFrame(time_head, ManiaAction.Key1), + new ManiaReplayFrame(time_last_tick - 5) + }, beatmap); + + assertHeadJudgement(HitResult.Perfect); + AddAssert("one tick missed", () => judgementResults.Where(j => j.HitObject is HoldNoteTick).Count(j => j.Type == HitResult.LargeTickMiss) == 1); + assertTailJudgement(HitResult.Ok); + } + private void assertHeadJudgement(HitResult result) => AddAssert($"head judged as {result}", () => judgementResults[0].Type == result); From f78d628878a0244c1de1b0efa26d9e206d593771 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 31 Mar 2021 12:21:07 +0900 Subject: [PATCH 1172/1791] Improve assertions --- .../TestSceneHoldNoteInput.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs index 2357778948..42ea12214f 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs @@ -320,21 +320,24 @@ namespace osu.Game.Rulesets.Mania.Tests }, beatmap); assertHeadJudgement(HitResult.Perfect); - AddAssert("one tick missed", () => judgementResults.Where(j => j.HitObject is HoldNoteTick).Count(j => j.Type == HitResult.LargeTickMiss) == 1); + assertLastTickJudgement(HitResult.LargeTickMiss); assertTailJudgement(HitResult.Ok); } private void assertHeadJudgement(HitResult result) - => AddAssert($"head judged as {result}", () => judgementResults[0].Type == result); + => AddAssert($"head judged as {result}", () => judgementResults.First(j => j.HitObject is Note).Type == result); private void assertTailJudgement(HitResult result) - => AddAssert($"tail judged as {result}", () => judgementResults[^2].Type == result); + => AddAssert($"tail judged as {result}", () => judgementResults.Single(j => j.HitObject is TailNote).Type == result); private void assertNoteJudgement(HitResult result) - => AddAssert($"hold note judged as {result}", () => judgementResults[^1].Type == result); + => AddAssert($"hold note judged as {result}", () => judgementResults.Single(j => j.HitObject is HoldNote).Type == result); private void assertTickJudgement(HitResult result) - => AddAssert($"tick judged as {result}", () => judgementResults[6].Type == result); // arbitrary tick + => AddAssert($"any tick judged as {result}", () => judgementResults.Where(j => j.HitObject is HoldNoteTick).Any(j => j.Type == result)); + + private void assertLastTickJudgement(HitResult result) + => AddAssert($"last tick judged as {result}", () => judgementResults.Last(j => j.HitObject is HoldNoteTick).Type == result); private ScoreAccessibleReplayPlayer currentPlayer; From 43e48406caa2253ccdc580c53ad22d0b39ad7ebf Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 31 Mar 2021 12:21:14 +0900 Subject: [PATCH 1173/1791] Miss all ticks when hold note is hit --- .../Objects/Drawables/DrawableHoldNote.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 4f062753a6..828ee7b03e 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -233,6 +233,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { if (Tail.AllJudged) { + foreach (var tick in tickContainer) + { + if (!tick.Judged) + tick.MissForcefully(); + } + ApplyResult(r => r.Type = r.Judgement.MaxResult); endHold(); } From e0c61f4dc556189de3e841242568df93269aafdd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 31 Mar 2021 13:57:57 +0900 Subject: [PATCH 1174/1791] Fix retry count not updating correctly Regressed with changes to player reference retention logic. Could add a test but the logic is so local now it seems quite redundant. --- osu.Game/Screens/Play/PlayerLoader.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 7d906cdc5b..2bbc4a0469 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -309,10 +309,8 @@ namespace osu.Game.Screens.Play if (!this.IsCurrentScreen()) return; - var restartCount = player?.RestartCount + 1 ?? 0; - player = createPlayer(); - player.RestartCount = restartCount; + player.RestartCount = ++restartCount; player.RestartRequested = restartRequested; LoadTask = LoadComponentAsync(player, _ => MetadataInfo.Loading = false); @@ -428,6 +426,8 @@ namespace osu.Game.Screens.Play private Bindable muteWarningShownOnce; + private int restartCount; + private void showMuteWarningIfNeeded() { if (!muteWarningShownOnce.Value) From 0c53b4eb938a2770c5b2420dcf646cce0bd16c23 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 31 Mar 2021 14:09:38 +0900 Subject: [PATCH 1175/1791] Fix wrong counting and add test --- .../Navigation/TestSceneScreenNavigation.cs | 23 +++++++++++++++++++ osu.Game/Screens/Play/PlayerLoader.cs | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index f2bb518b2e..3e25e22b5f 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -43,6 +43,29 @@ namespace osu.Game.Tests.Visual.Navigation exitViaEscapeAndConfirm(); } + [Test] + public void TestRetryCountIncrements() + { + Player player = null; + + PushAndConfirm(() => new TestSongSelect()); + + AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait()); + + AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); + + AddStep("press enter", () => InputManager.Key(Key.Enter)); + + AddUntilStep("wait for player", () => (player = Game.ScreenStack.CurrentScreen as Player) != null); + AddAssert("retry count is 0", () => player.RestartCount == 0); + + AddStep("attempt to retry", () => player.ChildrenOfType().First().Action()); + AddUntilStep("wait for old player gone", () => Game.ScreenStack.CurrentScreen != player); + + AddUntilStep("get new player", () => (player = Game.ScreenStack.CurrentScreen as Player) != null); + AddAssert("retry count is 1", () => player.RestartCount == 1); + } + [Test] public void TestRetryFromResults() { diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 2bbc4a0469..679b3c7313 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -310,7 +310,7 @@ namespace osu.Game.Screens.Play return; player = createPlayer(); - player.RestartCount = ++restartCount; + player.RestartCount = restartCount++; player.RestartRequested = restartRequested; LoadTask = LoadComponentAsync(player, _ => MetadataInfo.Loading = false); From 30cae46cbdf44021e850f63122d90d803052ca9d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 31 Mar 2021 14:57:28 +0900 Subject: [PATCH 1176/1791] Group large drag drop imports into a single operation --- osu.Desktop/OsuGameDesktop.cs | 34 +++++++++++++++++++++++++++++++--- osu.Game/OsuGameBase.cs | 3 +++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index b2487568ce..0c21c75290 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; @@ -18,6 +19,7 @@ using osu.Framework.Screens; using osu.Game.Screens.Menu; using osu.Game.Updater; using osu.Desktop.Windows; +using osu.Framework.Threading; using osu.Game.IO; namespace osu.Desktop @@ -144,13 +146,39 @@ namespace osu.Desktop desktopWindow.DragDrop += f => fileDrop(new[] { f }); } + private readonly List importableFiles = new List(); + private ScheduledDelegate importSchedule; + private void fileDrop(string[] filePaths) { - var firstExtension = Path.GetExtension(filePaths.First()); + lock (importableFiles) + { + var firstExtension = Path.GetExtension(filePaths.First()); - if (filePaths.Any(f => Path.GetExtension(f) != firstExtension)) return; + if (filePaths.Any(f => Path.GetExtension(f) != firstExtension)) return; - Task.Factory.StartNew(() => Import(filePaths), TaskCreationOptions.LongRunning); + importableFiles.AddRange(filePaths); + + Logger.Log($"Adding {filePaths.Length} files for import"); + + // File drag drop operations can potentially trigger hundreds or thousands of these calls on some platforms. + // In order to avoid spawning multiple import tasks for a single drop operation, debounce a touch. + importSchedule?.Cancel(); + importSchedule = Scheduler.AddDelayed(handlePendingImports, 100); + } + } + + private void handlePendingImports() + { + lock (importableFiles) + { + Logger.Log($"Handling batch import of {importableFiles.Count} files"); + + var paths = importableFiles.ToArray(); + importableFiles.Clear(); + + Task.Factory.StartNew(() => Import(paths), TaskCreationOptions.LongRunning); + } } } } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index e1c7b67a8c..7eef0f7158 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -429,6 +429,9 @@ namespace osu.Game public async Task Import(params string[] paths) { + if (paths.Length == 0) + return; + var extension = Path.GetExtension(paths.First())?.ToLowerInvariant(); foreach (var importer in fileImporters) From 1718084dbc55761c9f0a2cc49bccde6d88ccb3f3 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Wed, 31 Mar 2021 20:08:39 +0200 Subject: [PATCH 1177/1791] Update/remove determinant tests We now only change the path type based on the bounding box. If the control points are too linear, the framework now handles the fallback to Bezier. --- .../TestSceneSliderControlPointPiece.cs | 17 ++----------- .../TestSceneSliderPlacementBlueprint.cs | 25 +++---------------- 2 files changed, 5 insertions(+), 37 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs index 7ca86335ce..b4eba7070b 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs @@ -61,19 +61,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertControlPointType(0, PathType.PerfectCurve); } - [Test] - public void TestDragControlPointAlmostLinearly() - { - moveMouseToControlPoint(1); - AddStep("hold", () => InputManager.PressButton(MouseButton.Left)); - - addMovementStep(new Vector2(150, 0.01f)); - AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left)); - - assertControlPointPosition(1, new Vector2(150, 0.01f)); - assertControlPointType(0, PathType.Bezier); - } - [Test] public void TestDragControlPointAlmostLinearlyExterior() { @@ -93,7 +80,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor moveMouseToControlPoint(1); AddStep("hold", () => InputManager.PressButton(MouseButton.Left)); - addMovementStep(new Vector2(150, 0.01f)); + addMovementStep(new Vector2(400, 0.01f)); assertControlPointType(0, PathType.Bezier); addMovementStep(new Vector2(150, 50)); @@ -109,7 +96,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor moveMouseToControlPoint(4); AddStep("hold", () => InputManager.PressButton(MouseButton.Left)); - addMovementStep(new Vector2(350, 0)); + addMovementStep(new Vector2(350, 0.01f)); assertControlPointType(2, PathType.Bezier); addMovementStep(new Vector2(150, 150)); diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs index 937034fc23..24382d2c65 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs @@ -276,25 +276,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertControlPointType(0, PathType.Linear); } - [Test] - public void TestPlacePerfectCurveSegmentAlmostLinearly() - { - Vector2 startPosition = new Vector2(200); - - addMovementStep(startPosition); - addClickStep(MouseButton.Left); - - addMovementStep(startPosition + new Vector2(61.382935f, 6.423767f)); - addClickStep(MouseButton.Left); - - addMovementStep(startPosition + new Vector2(217.69522f, 22.84021f)); - addClickStep(MouseButton.Right); - - assertPlaced(true); - assertControlPointCount(3); - assertControlPointType(0, PathType.Bezier); - } - [Test] public void TestPlacePerfectCurveSegmentAlmostLinearlyExterior() { @@ -322,10 +303,10 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor addMovementStep(startPosition); addClickStep(MouseButton.Left); - addMovementStep(startPosition + new Vector2(61.382935f, 6.423767f)); + addMovementStep(startPosition + new Vector2(300, 0)); addClickStep(MouseButton.Left); - addMovementStep(startPosition + new Vector2(217.69522f, 22.84021f)); // Should convert to bezier + addMovementStep(startPosition + new Vector2(150, 0.1f)); // Should convert to bezier addMovementStep(startPosition + new Vector2(210.0f, 0.0f)); // Should convert back to perfect addClickStep(MouseButton.Right); @@ -346,7 +327,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor addClickStep(MouseButton.Left); // Playfield dimensions are 640 x 480. - // So a 480 * 480 bounding box area should be ok. + // So a 480 x 480 bounding box should be ok. addMovementStep(startPosition + new Vector2(-240, 240)); addClickStep(MouseButton.Right); From 75b8f2535ff3c024b1cc4ab219989658c7003f1e Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Wed, 31 Mar 2021 20:09:56 +0200 Subject: [PATCH 1178/1791] Move updatePathTypes to PathControlPointPiece Here we produce a local bound copy of the path version, and bind it to update the path type. This way, if the path version updates (i.e. any control point changes type or position), we check that all control points have a well-defined path. Additionally, if the control point piece is disposed of, the GB should also swoop up the subscription because of the local bound copy. --- .../Components/PathControlPointPiece.cs | 24 +++++++++++++++++++ .../Components/PathControlPointVisualiser.cs | 23 ------------------ 2 files changed, 24 insertions(+), 23 deletions(-) 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 4459308ea5..8059fc910c 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -3,14 +3,17 @@ using System; using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; 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.Primitives; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; @@ -47,6 +50,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components [Resolved] private OsuColour colours { get; set; } + private IBindable sliderVersion; private IBindable sliderPosition; private IBindable sliderScale; private IBindable controlPointPosition; @@ -105,6 +109,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { base.LoadComplete(); + sliderVersion = slider.Path.Version.GetBoundCopy(); + sliderVersion.BindValueChanged(_ => updatePathType()); + sliderPosition = slider.PositionBindable.GetBoundCopy(); sliderPosition.BindValueChanged(_ => updateMarkerDisplay()); @@ -200,6 +207,23 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components protected override void OnDragEnd(DragEndEvent e) => changeHandler?.EndChange(); + /// + /// Handles correction of invalid path types. + /// + private void updatePathType() + { + if (PointsInSegment[0].Type.Value != PathType.PerfectCurve) + return; + + ReadOnlySpan points = PointsInSegment.Select(p => p.Position.Value).ToArray(); + if (points.Length != 3) + return; + + RectangleF boundingBox = PathApproximator.CircularArcBoundingBox(points); + if (boundingBox.Width >= 640 || boundingBox.Height >= 480) + PointsInSegment[0].Type.Value = PathType.Bezier; + } + /// /// Updates the state of the circular control point marker. /// diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index dd39014bb6..ce5dc4855e 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -11,12 +11,10 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; -using osu.Framework.Utils; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; @@ -93,8 +91,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components })); Connections.Add(new PathControlPointConnectionPiece(slider, e.NewStartingIndex + i)); - - point.Changed += updatePathTypes; } break; @@ -104,8 +100,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { Pieces.RemoveAll(p => p.ControlPoint == point); Connections.RemoveAll(c => c.ControlPoint == point); - - point.Changed -= updatePathTypes; } // If removing before the end of the path, @@ -148,23 +142,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { } - /// - /// Handles correction of invalid path types. - /// - private void updatePathTypes() - { - foreach (PathControlPoint segmentStartPoint in slider.Path.ControlPoints.Where(p => p.Type.Value != null)) - { - if (segmentStartPoint.Type.Value != PathType.PerfectCurve) - continue; - - ReadOnlySpan points = slider.Path.PointsInSegment(segmentStartPoint).Select(p => p.Position.Value).ToArray(); - RectangleF boundingBox = PathApproximator.CircularArcBoundingBox(points); - if (points.Length == 3 && boundingBox.Area >= 640 * 480) - segmentStartPoint.Type.Value = PathType.Bezier; - } - } - private void selectPiece(PathControlPointPiece piece, MouseButtonEvent e) { if (e.Button == MouseButton.Left && inputManager.CurrentState.Keyboard.ControlPressed) From 5022a78e80b1a07c54ab55176570a7e4be2debaa Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Wed, 31 Mar 2021 20:25:46 +0200 Subject: [PATCH 1179/1791] Check current point instead of start point Since each control point will call this when the path updates, the previous would correct the start segment 3 times instead of just once. This fixes that. --- .../Blueprints/Sliders/Components/PathControlPointPiece.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 8059fc910c..394d2b039d 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -212,7 +212,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components /// private void updatePathType() { - if (PointsInSegment[0].Type.Value != PathType.PerfectCurve) + if (ControlPoint.Type.Value != PathType.PerfectCurve) return; ReadOnlySpan points = PointsInSegment.Select(p => p.Position.Value).ToArray(); @@ -221,7 +221,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components RectangleF boundingBox = PathApproximator.CircularArcBoundingBox(points); if (boundingBox.Width >= 640 || boundingBox.Height >= 480) - PointsInSegment[0].Type.Value = PathType.Bezier; + ControlPoint.Type.Value = PathType.Bezier; } /// From 7b684339ed64703c169dfccc27c3abfec521e586 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Wed, 31 Mar 2021 20:32:49 +0200 Subject: [PATCH 1180/1791] Undo public -> internal for `PathControlPoint.Changed` No longer used. --- osu.Game/Rulesets/Objects/PathControlPoint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/PathControlPoint.cs b/osu.Game/Rulesets/Objects/PathControlPoint.cs index 2e4100ee0b..f11917f4f4 100644 --- a/osu.Game/Rulesets/Objects/PathControlPoint.cs +++ b/osu.Game/Rulesets/Objects/PathControlPoint.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Objects /// /// Invoked when any property of this is changed. /// - public event Action Changed; + internal event Action Changed; /// /// Creates a new . From 25afae5671514f88966f06b59eda1ef82b824ecf Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Wed, 31 Mar 2021 20:48:17 +0200 Subject: [PATCH 1181/1791] Fix broken test case Seems this technically works, but only because of the edge case of being entirely linear, which the framework catches. This fixes that. --- .../Editor/TestSceneSliderPlacementBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs index 24382d2c65..462abf2edb 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs @@ -307,7 +307,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor addClickStep(MouseButton.Left); addMovementStep(startPosition + new Vector2(150, 0.1f)); // Should convert to bezier - addMovementStep(startPosition + new Vector2(210.0f, 0.0f)); // Should convert back to perfect + addMovementStep(startPosition + new Vector2(400.0f, 50.0f)); // Should convert back to perfect addClickStep(MouseButton.Right); assertPlaced(true); From af478fb2eb6052ddfcc0b99137d9605d2ff06681 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Apr 2021 22:02:32 +0900 Subject: [PATCH 1182/1791] Add abstract spectator screen class --- osu.Game/Screens/Spectate/GameplayState.cs | 37 +++ osu.Game/Screens/Spectate/SpectatorScreen.cs | 237 +++++++++++++++++++ 2 files changed, 274 insertions(+) create mode 100644 osu.Game/Screens/Spectate/GameplayState.cs create mode 100644 osu.Game/Screens/Spectate/SpectatorScreen.cs diff --git a/osu.Game/Screens/Spectate/GameplayState.cs b/osu.Game/Screens/Spectate/GameplayState.cs new file mode 100644 index 0000000000..4579b9c07c --- /dev/null +++ b/osu.Game/Screens/Spectate/GameplayState.cs @@ -0,0 +1,37 @@ +// 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; +using osu.Game.Scoring; + +namespace osu.Game.Screens.Spectate +{ + /// + /// The gameplay state of a spectated user. This class is immutable. + /// + public class GameplayState + { + /// + /// The score which the user is playing. + /// + public readonly Score Score; + + /// + /// The ruleset which the user is playing. + /// + public readonly Ruleset Ruleset; + + /// + /// The beatmap which the user is playing. + /// + public readonly WorkingBeatmap Beatmap; + + public GameplayState(Score score, Ruleset ruleset, WorkingBeatmap beatmap) + { + Score = score; + Ruleset = ruleset; + Beatmap = beatmap; + } + } +} diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs new file mode 100644 index 0000000000..a534581807 --- /dev/null +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -0,0 +1,237 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using JetBrains.Annotations; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.Online.Spectator; +using osu.Game.Replays; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.Replays.Types; +using osu.Game.Scoring; +using osu.Game.Users; + +namespace osu.Game.Screens.Spectate +{ + /// + /// A which spectates one or more users. + /// + public abstract class SpectatorScreen : OsuScreen + { + private readonly int[] userIds; + + [Resolved] + private BeatmapManager beatmaps { get; set; } + + [Resolved] + private RulesetStore rulesets { get; set; } + + [Resolved] + private SpectatorStreamingClient spectatorClient { get; set; } + + [Resolved] + private UserLookupCache userLookupCache { get; set; } + + private readonly object stateLock = new object(); + + private readonly Dictionary userMap = new Dictionary(); + private readonly Dictionary spectatorStates = new Dictionary(); + private readonly Dictionary gameplayStates = new Dictionary(); + + private IBindable> managerUpdated; + + /// + /// Creates a new . + /// + /// The users to spectate. + protected SpectatorScreen(params int[] userIds) + { + this.userIds = userIds; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + spectatorClient.OnUserBeganPlaying += userBeganPlaying; + spectatorClient.OnUserFinishedPlaying += userFinishedPlaying; + spectatorClient.OnNewFrames += userSentFrames; + + foreach (var id in userIds) + { + userLookupCache.GetUserAsync(id).ContinueWith(u => Schedule(() => + { + if (u.Result == null) + return; + + lock (stateLock) + userMap[id] = u.Result; + + spectatorClient.WatchUser(id); + }), TaskContinuationOptions.OnlyOnRanToCompletion); + } + + managerUpdated = beatmaps.ItemUpdated.GetBoundCopy(); + managerUpdated.BindValueChanged(beatmapUpdated); + } + + private void beatmapUpdated(ValueChangedEvent> beatmap) + { + if (!beatmap.NewValue.TryGetTarget(out var beatmapSet)) + return; + + lock (stateLock) + { + foreach (var (userId, state) in spectatorStates) + { + if (beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID == state.BeatmapID)) + updateGameplayState(userId); + } + } + } + + private void userBeganPlaying(int userId, SpectatorState state) + { + if (state.RulesetID == null || state.BeatmapID == null) + return; + + lock (stateLock) + { + if (!userMap.ContainsKey(userId)) + return; + + spectatorStates[userId] = state; + OnUserStateChanged(userId, state); + + updateGameplayState(userId); + } + } + + private void updateGameplayState(int userId) + { + lock (stateLock) + { + Debug.Assert(userMap.ContainsKey(userId)); + + var spectatorState = spectatorStates[userId]; + var user = userMap[userId]; + + var resolvedRuleset = rulesets.AvailableRulesets.FirstOrDefault(r => r.ID == spectatorState.RulesetID)?.CreateInstance(); + if (resolvedRuleset == null) + return; + + var resolvedBeatmap = beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == spectatorState.BeatmapID); + if (resolvedBeatmap == null) + return; + + var score = new Score + { + ScoreInfo = new ScoreInfo + { + Beatmap = resolvedBeatmap, + User = user, + Mods = spectatorState.Mods.Select(m => m.ToMod(resolvedRuleset)).ToArray(), + Ruleset = resolvedRuleset.RulesetInfo, + }, + Replay = new Replay { HasReceivedAllFrames = false }, + }; + + var gameplayState = new GameplayState(score, resolvedRuleset, beatmaps.GetWorkingBeatmap(resolvedBeatmap)); + + gameplayStates[userId] = gameplayState; + StartGameplay(userId, gameplayState); + } + } + + private void userSentFrames(int userId, FrameDataBundle bundle) + { + lock (stateLock) + { + if (!userMap.ContainsKey(userId)) + return; + + if (!gameplayStates.TryGetValue(userId, out var gameplayState)) + return; + + // The ruleset instance should be guaranteed to be in sync with the score via ScoreLock. + Debug.Assert(gameplayState.Ruleset != null && gameplayState.Ruleset.RulesetInfo.Equals(gameplayState.Score.ScoreInfo.Ruleset)); + + foreach (var frame in bundle.Frames) + { + IConvertibleReplayFrame convertibleFrame = gameplayState.Ruleset.CreateConvertibleReplayFrame(); + convertibleFrame.FromLegacy(frame, gameplayState.Beatmap.Beatmap); + + var convertedFrame = (ReplayFrame)convertibleFrame; + convertedFrame.Time = frame.Time; + + gameplayState.Score.Replay.Frames.Add(convertedFrame); + } + } + } + + private void userFinishedPlaying(int userId, SpectatorState state) + { + lock (stateLock) + { + if (!userMap.ContainsKey(userId)) + return; + + if (!gameplayStates.TryGetValue(userId, out var gameplayState)) + return; + + gameplayState.Score.Replay.HasReceivedAllFrames = true; + + gameplayStates.Remove(userId); + EndGameplay(userId); + } + } + + /// + /// Invoked when a spectated user's state has changed. + /// + /// The user whose state has changed. + /// The new state. + protected abstract void OnUserStateChanged(int userId, [NotNull] SpectatorState spectatorState); + + /// + /// Starts gameplay for a user. + /// + /// The user to start gameplay for. + /// The gameplay state. + protected abstract void StartGameplay(int userId, [NotNull] GameplayState gameplayState); + + /// + /// Ends gameplay for a user. + /// + /// The user to end gameplay for. + protected abstract void EndGameplay(int userId); + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (spectatorClient != null) + { + spectatorClient.OnUserBeganPlaying -= userBeganPlaying; + spectatorClient.OnUserFinishedPlaying -= userFinishedPlaying; + spectatorClient.OnNewFrames -= userSentFrames; + + lock (stateLock) + { + foreach (var (userId, _) in userMap) + spectatorClient.StopWatchingUser(userId); + } + } + + managerUpdated?.UnbindAll(); + } + } +} From 9e95441aa63ef92065204aa349cbd7abe2f44f62 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Apr 2021 22:08:52 +0900 Subject: [PATCH 1183/1791] Rename Spectator -> SoloSpectator --- osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs | 8 ++++---- osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs | 2 +- osu.Game/Screens/Play/{Spectator.cs => SoloSpectator.cs} | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) rename osu.Game/Screens/Play/{Spectator.cs => SoloSpectator.cs} (99%) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 4a0e1282c4..b59f6a4eb7 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.Gameplay // used just to show beatmap card for the time being. protected override bool UseOnlineAPI => true; - private Spectator spectatorScreen; + private SoloSpectator spectatorScreen; [Resolved] private OsuGameBase game { get; set; } @@ -69,7 +69,7 @@ namespace osu.Game.Tests.Visual.Gameplay { loadSpectatingScreen(); - AddAssert("screen hasn't changed", () => Stack.CurrentScreen is Spectator); + AddAssert("screen hasn't changed", () => Stack.CurrentScreen is SoloSpectator); start(); sendFrames(); @@ -195,7 +195,7 @@ namespace osu.Game.Tests.Visual.Gameplay start(-1234); sendFrames(); - AddAssert("screen didn't change", () => Stack.CurrentScreen is Spectator); + AddAssert("screen didn't change", () => Stack.CurrentScreen is SoloSpectator); } private OsuFramedReplayInputHandler replayHandler => @@ -226,7 +226,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void loadSpectatingScreen() { - AddStep("load screen", () => LoadScreen(spectatorScreen = new Spectator(testSpectatorStreamingClient.StreamingUser))); + AddStep("load screen", () => LoadScreen(spectatorScreen = new SoloSpectator(testSpectatorStreamingClient.StreamingUser))); AddUntilStep("wait for screen load", () => spectatorScreen.LoadState == LoadState.Loaded); } diff --git a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs index c89699f2ee..336430fd9b 100644 --- a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs +++ b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs @@ -137,7 +137,7 @@ namespace osu.Game.Overlays.Dashboard Text = "Watch", Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Action = () => game?.PerformFromScreen(s => s.Push(new Spectator(User))), + Action = () => game?.PerformFromScreen(s => s.Push(new SoloSpectator(User))), Enabled = { Value = User.Id != api.LocalUser.Value.Id } } } diff --git a/osu.Game/Screens/Play/Spectator.cs b/osu.Game/Screens/Play/SoloSpectator.cs similarity index 99% rename from osu.Game/Screens/Play/Spectator.cs rename to osu.Game/Screens/Play/SoloSpectator.cs index 28311f5113..9be42b52b3 100644 --- a/osu.Game/Screens/Play/Spectator.cs +++ b/osu.Game/Screens/Play/SoloSpectator.cs @@ -37,7 +37,7 @@ using osuTK; namespace osu.Game.Screens.Play { [Cached(typeof(IPreviewTrackOwner))] - public class Spectator : OsuScreen, IPreviewTrackOwner + public class SoloSpectator : OsuScreen, IPreviewTrackOwner { private readonly User targetUser; @@ -88,7 +88,7 @@ namespace osu.Game.Screens.Play /// private bool newStatePending; - public Spectator([NotNull] User targetUser) + public SoloSpectator([NotNull] User targetUser) { this.targetUser = targetUser ?? throw new ArgumentNullException(nameof(targetUser)); } From 9bc2a486e0c40baf2db334f6ef4cf1fdc5977a0a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Apr 2021 22:09:51 +0900 Subject: [PATCH 1184/1791] Make SoloSpectator use the new SpectatorScreen class --- osu.Game/Screens/Play/SoloSpectator.cs | 233 ++++++------------------- 1 file changed, 53 insertions(+), 180 deletions(-) diff --git a/osu.Game/Screens/Play/SoloSpectator.cs b/osu.Game/Screens/Play/SoloSpectator.cs index 9be42b52b3..f8ed7b585f 100644 --- a/osu.Game/Screens/Play/SoloSpectator.cs +++ b/osu.Game/Screens/Play/SoloSpectator.cs @@ -1,13 +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; -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; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -24,73 +20,54 @@ using osu.Game.Online.API.Requests; using osu.Game.Online.Spectator; using osu.Game.Overlays.BeatmapListing.Panels; using osu.Game.Overlays.Settings; -using osu.Game.Replays; using osu.Game.Rulesets; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Replays; -using osu.Game.Rulesets.Replays.Types; -using osu.Game.Scoring; using osu.Game.Screens.OnlinePlay.Match.Components; +using osu.Game.Screens.Spectate; using osu.Game.Users; using osuTK; namespace osu.Game.Screens.Play { [Cached(typeof(IPreviewTrackOwner))] - public class SoloSpectator : OsuScreen, IPreviewTrackOwner + public class SoloSpectator : SpectatorScreen, IPreviewTrackOwner { + [NotNull] private readonly User targetUser; - [Resolved] - private Bindable beatmap { get; set; } - - [Resolved] - private Bindable ruleset { get; set; } - - private Ruleset rulesetInstance; - - [Resolved] - private Bindable> mods { get; set; } - [Resolved] private IAPIProvider api { get; set; } [Resolved] - private SpectatorStreamingClient spectatorStreaming { get; set; } - - [Resolved] - private BeatmapManager beatmaps { get; set; } + private PreviewTrackManager previewTrackManager { get; set; } [Resolved] private RulesetStore rulesets { get; set; } [Resolved] - private PreviewTrackManager previewTrackManager { get; set; } - - private Score score; - - private readonly object scoreLock = new object(); + private BeatmapManager beatmaps { get; set; } private Container beatmapPanelContainer; - - private SpectatorState state; - - private IBindable> managerUpdated; - private TriangleButton watchButton; - private SettingsCheckbox automaticDownload; - private BeatmapSetInfo onlineBeatmap; /// - /// Becomes true if a new state is waiting to be loaded (while this screen was not active). + /// The player's immediate online gameplay state. + /// This doesn't reflect the gameplay state being watched by the user if is non-null. /// - private bool newStatePending; + private GameplayState immediateGameplayState; + + /// + /// The gameplay state that is pending to be watched, upon this screen becoming current. + /// + private GameplayState pendingGameplayState; + + private GetBeatmapSetRequest onlineBeatmapRequest; public SoloSpectator([NotNull] User targetUser) + : base(targetUser.Id) { - this.targetUser = targetUser ?? throw new ArgumentNullException(nameof(targetUser)); + this.targetUser = targetUser; } [BackgroundDependencyLoader] @@ -173,7 +150,7 @@ namespace osu.Game.Screens.Play Width = 250, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Action = attemptStart, + Action = () => attemptStart(immediateGameplayState), Enabled = { Value = false } } } @@ -185,169 +162,81 @@ namespace osu.Game.Screens.Play protected override void LoadComplete() { base.LoadComplete(); - - spectatorStreaming.OnUserBeganPlaying += userBeganPlaying; - spectatorStreaming.OnUserFinishedPlaying += userFinishedPlaying; - spectatorStreaming.OnNewFrames += userSentFrames; - - spectatorStreaming.WatchUser(targetUser.Id); - - managerUpdated = beatmaps.ItemUpdated.GetBoundCopy(); - managerUpdated.BindValueChanged(beatmapUpdated); - automaticDownload.Current.BindValueChanged(_ => checkForAutomaticDownload()); } - private void beatmapUpdated(ValueChangedEvent> beatmap) + protected override void OnUserStateChanged(int userId, SpectatorState spectatorState) => Schedule(() => { - if (beatmap.NewValue.TryGetTarget(out var beatmapSet) && beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID == state.BeatmapID)) - Schedule(attemptStart); - } + clearDisplay(); + showBeatmapPanel(spectatorState); + }); - private void userSentFrames(int userId, FrameDataBundle data) + protected override void StartGameplay(int userId, GameplayState gameplayState) => Schedule(() => { - // this is not scheduled as it handles propagation of frames even when in a child screen (at which point we are not alive). - // probably not the safest way to handle this. - - if (userId != targetUser.Id) - return; - - lock (scoreLock) - { - // this should never happen as the server sends the user's state on watching, - // but is here as a safety measure. - if (score == null) - return; - - // rulesetInstance should be guaranteed to be in sync with the score via scoreLock. - Debug.Assert(rulesetInstance != null && rulesetInstance.RulesetInfo.Equals(score.ScoreInfo.Ruleset)); - - foreach (var frame in data.Frames) - { - IConvertibleReplayFrame convertibleFrame = rulesetInstance.CreateConvertibleReplayFrame(); - convertibleFrame.FromLegacy(frame, beatmap.Value.Beatmap); - - var convertedFrame = (ReplayFrame)convertibleFrame; - convertedFrame.Time = frame.Time; - - score.Replay.Frames.Add(convertedFrame); - } - } - } - - private void userBeganPlaying(int userId, SpectatorState state) - { - if (userId != targetUser.Id) - return; - - this.state = state; + pendingGameplayState = null; + immediateGameplayState = gameplayState; if (this.IsCurrentScreen()) - Schedule(attemptStart); + attemptStart(gameplayState); else - newStatePending = true; - } + pendingGameplayState = gameplayState; + + watchButton.Enabled.Value = true; + }); + + protected override void EndGameplay(int userId) => Schedule(() => + { + pendingGameplayState = null; + immediateGameplayState = null; + + Schedule(clearDisplay); + + watchButton.Enabled.Value = false; + }); public override void OnResuming(IScreen last) { base.OnResuming(last); - if (newStatePending) + if (pendingGameplayState != null) { - attemptStart(); - newStatePending = false; + attemptStart(pendingGameplayState); + pendingGameplayState = null; } } - private void userFinishedPlaying(int userId, SpectatorState state) - { - if (userId != targetUser.Id) - return; - - lock (scoreLock) - { - if (score != null) - { - score.Replay.HasReceivedAllFrames = true; - score = null; - } - } - - Schedule(clearDisplay); - } - private void clearDisplay() { watchButton.Enabled.Value = false; + onlineBeatmapRequest?.Cancel(); beatmapPanelContainer.Clear(); previewTrackManager.StopAnyPlaying(this); } - private void attemptStart() + private void attemptStart(GameplayState gameplayState) { - clearDisplay(); - showBeatmapPanel(state); - - var resolvedRuleset = rulesets.AvailableRulesets.FirstOrDefault(r => r.ID == state.RulesetID)?.CreateInstance(); - - // ruleset not available - if (resolvedRuleset == null) + if (gameplayState == null) return; - if (state.BeatmapID == null) - return; + Beatmap.Value = gameplayState.Beatmap; + Ruleset.Value = gameplayState.Ruleset.RulesetInfo; - var resolvedBeatmap = beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == state.BeatmapID); - - if (resolvedBeatmap == null) - { - return; - } - - lock (scoreLock) - { - score = new Score - { - ScoreInfo = new ScoreInfo - { - Beatmap = resolvedBeatmap, - User = targetUser, - Mods = state.Mods.Select(m => m.ToMod(resolvedRuleset)).ToArray(), - Ruleset = resolvedRuleset.RulesetInfo, - }, - Replay = new Replay { HasReceivedAllFrames = false }, - }; - - ruleset.Value = resolvedRuleset.RulesetInfo; - rulesetInstance = resolvedRuleset; - - beatmap.Value = beatmaps.GetWorkingBeatmap(resolvedBeatmap); - watchButton.Enabled.Value = true; - - this.Push(new SpectatorPlayerLoader(score)); - } + this.Push(new SpectatorPlayerLoader(gameplayState.Score)); } private void showBeatmapPanel(SpectatorState state) { - if (state?.BeatmapID == null) - { - onlineBeatmap = null; - return; - } + Debug.Assert(state.BeatmapID != null); - var req = new GetBeatmapSetRequest(state.BeatmapID.Value, BeatmapSetLookupType.BeatmapId); - req.Success += res => Schedule(() => + onlineBeatmapRequest = new GetBeatmapSetRequest(state.BeatmapID.Value, BeatmapSetLookupType.BeatmapId); + onlineBeatmapRequest.Success += res => Schedule(() => { - if (state != this.state) - return; - onlineBeatmap = res.ToBeatmapSet(rulesets); beatmapPanelContainer.Child = new GridBeatmapPanel(onlineBeatmap); checkForAutomaticDownload(); }); - api.Queue(req); + api.Queue(onlineBeatmapRequest); } private void checkForAutomaticDownload() @@ -369,21 +258,5 @@ namespace osu.Game.Screens.Play previewTrackManager.StopAnyPlaying(this); return base.OnExiting(next); } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - if (spectatorStreaming != null) - { - spectatorStreaming.OnUserBeganPlaying -= userBeganPlaying; - spectatorStreaming.OnUserFinishedPlaying -= userFinishedPlaying; - spectatorStreaming.OnNewFrames -= userSentFrames; - - spectatorStreaming.StopWatchingUser(targetUser.Id); - } - - managerUpdated?.UnbindAll(); - } } } From c3c7c18549e281503d3225db9bc0d3fbc85c1511 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Apr 2021 23:48:26 +0900 Subject: [PATCH 1185/1791] Fix tests --- .../Visual/Gameplay/TestSceneSpectator.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index b59f6a4eb7..9d85a9995d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -11,6 +13,7 @@ using osu.Framework.Screens; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Online; using osu.Game.Online.Spectator; using osu.Game.Replays.Legacy; @@ -29,6 +32,9 @@ namespace osu.Game.Tests.Visual.Gameplay [Cached(typeof(SpectatorStreamingClient))] private TestSpectatorStreamingClient testSpectatorStreamingClient = new TestSpectatorStreamingClient(); + [Cached(typeof(UserLookupCache))] + private UserLookupCache lookupCache = new TestUserLookupCache(); + // used just to show beatmap card for the time being. protected override bool UseOnlineAPI => true; @@ -301,5 +307,14 @@ namespace osu.Game.Tests.Visual.Gameplay }); } } + + internal class TestUserLookupCache : UserLookupCache + { + protected override Task ComputeValueAsync(int lookup, CancellationToken token = default) => Task.FromResult(new User + { + Id = lookup, + Username = $"User {lookup}" + }); + } } } From b8479a979f1ae1453e4488ec555a3cdc0e49f082 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Thu, 1 Apr 2021 18:06:12 +0200 Subject: [PATCH 1186/1791] Remove unused blueprint variable --- .../Editor/TestSceneSliderControlPointPiece.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs index b4eba7070b..9b67d18db6 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs @@ -22,7 +22,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { private Slider slider; private DrawableSlider drawableObject; - private TestSliderBlueprint blueprint; [SetUp] public void Setup() => Schedule(() => @@ -45,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 }); Add(drawableObject = new DrawableSlider(slider)); - AddBlueprint(blueprint = new TestSliderBlueprint(drawableObject)); + AddBlueprint(new TestSliderBlueprint(drawableObject)); }); [Test] From 6a286c5e2128c3103bd36548434f29260aec806d Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 1 Apr 2021 17:16:02 +0000 Subject: [PATCH 1187/1791] Bump Microsoft.NET.Test.Sdk from 16.9.1 to 16.9.4 Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 16.9.1 to 16.9.4. - [Release notes](https://github.com/microsoft/vstest/releases) - [Commits](https://github.com/microsoft/vstest/compare/v16.9.1...v16.9.4) 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 728af5124e..c2d9a923d9 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 af16f39563..64e934efd2 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 3d2d1f3fec..f743d65db3 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 fa00922706..eab144592f 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 e36b3cdc74..0e1f6f6b0c 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 b20583dd7e..a4e52f8cd4 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 8621a6b4fe8b84e50e2a1d56b25e3bed9824be6f Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Thu, 1 Apr 2021 20:34:04 +0200 Subject: [PATCH 1188/1791] Add margin to large segment test Test ran fine on my end, but apparently not on the CI. This should make results a bit more consistent, hopefully. --- .../Editor/TestSceneSliderPlacementBlueprint.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs index 462abf2edb..d2c37061f0 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs @@ -323,12 +323,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor addMovementStep(startPosition); addClickStep(MouseButton.Left); - addMovementStep(startPosition + new Vector2(240, 240)); + addMovementStep(startPosition + new Vector2(220, 220)); addClickStep(MouseButton.Left); // Playfield dimensions are 640 x 480. - // So a 480 x 480 bounding box should be ok. - addMovementStep(startPosition + new Vector2(-240, 240)); + // So a 440 x 440 bounding box should be ok. + addMovementStep(startPosition + new Vector2(-220, 220)); addClickStep(MouseButton.Right); assertPlaced(true); From fcd56dba44a84e3e752e97f0eb3ed6409407f5ac Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 2 Apr 2021 01:38:10 +0300 Subject: [PATCH 1189/1791] Guard against same ruleset file with loaded assembly filenames instead --- osu.Game/Rulesets/RulesetStore.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index deabea57ef..eb5271aa17 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; @@ -173,7 +174,7 @@ namespace osu.Game.Rulesets { var filename = Path.GetFileNameWithoutExtension(file); - if (loadedAssemblies.Values.Any(t => t.Namespace == filename)) + if (loadedAssemblies.Values.Any(t => Path.GetFileNameWithoutExtension(t.Assembly.Location) == filename)) return; try From 5b1dc7d2b426e505da4b67cf8876404078a7f72f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 2 Apr 2021 02:45:26 +0300 Subject: [PATCH 1190/1791] Remove unused using directive --- osu.Game/Rulesets/RulesetStore.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index eb5271aa17..4261ee3d47 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; From e1aa9278272eabe7f6a975f3c78f4c03fca9122c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 2 Apr 2021 13:20:15 +0900 Subject: [PATCH 1191/1791] Add dropdown option to export score --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 5608002513..da1bbd18c7 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -61,6 +61,9 @@ namespace osu.Game.Online.Leaderboards [Resolved(CanBeNull = true)] private SongSelect songSelect { get; set; } + [Resolved] + private ScoreManager scoreManager { get; set; } + public LeaderboardScore(ScoreInfo score, int? rank, bool allowHighlight = true) { this.score = score; @@ -388,6 +391,9 @@ namespace osu.Game.Online.Leaderboards if (score.Mods.Length > 0 && modsContainer.Any(s => s.IsHovered) && songSelect != null) items.Add(new OsuMenuItem("Use these mods", MenuItemType.Highlighted, () => songSelect.Mods.Value = score.Mods)); + if (score.Files.Count > 0) + items.Add(new OsuMenuItem("Export", MenuItemType.Standard, () => scoreManager.Export(score))); + if (score.ID != 0) items.Add(new OsuMenuItem("Delete", MenuItemType.Destructive, () => dialogOverlay?.Push(new LocalScoreDeleteDialog(score)))); From 6d4d574a659c5148a7400003c6896990b2b689aa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Apr 2021 14:10:25 +0900 Subject: [PATCH 1192/1791] Fix exported replay filenames not having full metadata --- osu.Game/Beatmaps/BeatmapInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index a898e10e4f..bf7906bd5c 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -147,7 +147,7 @@ namespace osu.Game.Beatmaps { string version = string.IsNullOrEmpty(Version) ? string.Empty : $"[{Version}]"; - return $"{Metadata} {version}".Trim(); + return $"{Metadata ?? BeatmapSet?.Metadata} {version}".Trim(); } public bool Equals(BeatmapInfo other) From 5063cd957f8013c060aff0d5bc14b6a4ec58a9e1 Mon Sep 17 00:00:00 2001 From: Amber Date: Fri, 2 Apr 2021 02:54:35 -0500 Subject: [PATCH 1193/1791] Force hit sample to play when Classic mod is enabled --- osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs | 4 ++++ osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 2 +- .../Objects/Drawables/DrawableSliderTail.cs | 6 ++++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs index 5470d0fcb4..21fde7cdc7 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs @@ -79,6 +79,10 @@ namespace osu.Game.Rulesets.Osu.Mods case DrawableSliderHead head: head.TrackFollowCircle = !NoSliderHeadMovement.Value; break; + + case DrawableSliderTail tail: + tail.AlwaysPlaySample = true; + break; } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 9122f347d0..4288f00e3a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -280,7 +280,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { // rather than doing it this way, we should probably attach the sample to the tail circle. // this can only be done after we stop using LegacyLastTick. - if (TailCircle.IsHit) + if (TailCircle.IsHit || TailCircle.AlwaysPlaySample) base.PlaySamples(); } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs index 6a8e02e886..ad6b98cba8 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs @@ -26,6 +26,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables /// public override bool DisplayResult => false; + /// + /// Whether the hit sample should always be played, regardless of whether the tail was actually hit. + /// + public bool AlwaysPlaySample { get; set; } + public bool Tracking { get; set; } private SkinnableDrawable circlePiece; @@ -44,6 +49,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables [BackgroundDependencyLoader] private void load() { + AlwaysPlaySample = false; Origin = Anchor.Centre; Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); From 45d16fb9164014de9990b6342f2a2d04b3805ba8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Apr 2021 16:56:47 +0900 Subject: [PATCH 1194/1791] Rename event parameter for clarity --- osu.Game/Screens/Spectate/SpectatorScreen.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index a534581807..25dd482112 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -83,9 +83,9 @@ namespace osu.Game.Screens.Spectate managerUpdated.BindValueChanged(beatmapUpdated); } - private void beatmapUpdated(ValueChangedEvent> beatmap) + private void beatmapUpdated(ValueChangedEvent> e) { - if (!beatmap.NewValue.TryGetTarget(out var beatmapSet)) + if (!e.NewValue.TryGetTarget(out var beatmapSet)) return; lock (stateLock) From 48e9985782cb5537b1053d6beb05342409a7a921 Mon Sep 17 00:00:00 2001 From: Amber Date: Fri, 2 Apr 2021 03:10:28 -0500 Subject: [PATCH 1195/1791] Make "AlwaysPlayTailSample" a mod setting rather than a hardcoded constant. --- osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs index 21fde7cdc7..ec74225774 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs @@ -44,6 +44,9 @@ namespace osu.Game.Rulesets.Osu.Mods [SettingSource("Use fixed slider follow circle hit area", "Makes the slider follow circle track its final size at all times.")] public Bindable FixedFollowCircleHitArea { get; } = new BindableBool(true); + [SettingSource("Always play a slider's tail sample", "Always plays a slider's tail sample regardless of whether it was hit or not.")] + public Bindable AlwaysPlayTailSample { get; } = new BindableBool(true); + public void ApplyToHitObject(HitObject hitObject) { switch (hitObject) @@ -81,7 +84,7 @@ namespace osu.Game.Rulesets.Osu.Mods break; case DrawableSliderTail tail: - tail.AlwaysPlaySample = true; + tail.AlwaysPlaySample = AlwaysPlayTailSample.Value; break; } } From 5ac36a24625eb80c5f897ed7929788e08bb988c4 Mon Sep 17 00:00:00 2001 From: Amber Date: Fri, 2 Apr 2021 03:56:23 -0500 Subject: [PATCH 1196/1791] Switch AlwaysPlaySample to SamplePlaysOnlyOnHit in DrawableSliderTail for conformity --- osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs index ec74225774..882f848190 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs @@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Osu.Mods break; case DrawableSliderTail tail: - tail.AlwaysPlaySample = AlwaysPlayTailSample.Value; + tail.SamplePlaysOnlyOnHit = !AlwaysPlayTailSample.Value; break; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 4288f00e3a..04708a5ece 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -280,7 +280,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { // rather than doing it this way, we should probably attach the sample to the tail circle. // this can only be done after we stop using LegacyLastTick. - if (TailCircle.IsHit || TailCircle.AlwaysPlaySample) + if (!TailCircle.SamplePlaysOnlyOnHit || TailCircle.IsHit) base.PlaySamples(); } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs index ad6b98cba8..4b9c10ab67 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables /// /// Whether the hit sample should always be played, regardless of whether the tail was actually hit. /// - public bool AlwaysPlaySample { get; set; } + public bool SamplePlaysOnlyOnHit { get; set; } = true; public bool Tracking { get; set; } @@ -49,7 +49,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables [BackgroundDependencyLoader] private void load() { - AlwaysPlaySample = false; Origin = Anchor.Centre; Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); From 9c3d15171c6ba033970550e6e158a3fce750c3f6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 2 Apr 2021 18:00:28 +0900 Subject: [PATCH 1197/1791] Reword xmldoc slightly --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs index 4b9c10ab67..87f098dd29 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs @@ -27,7 +27,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public override bool DisplayResult => false; /// - /// Whether the hit sample should always be played, regardless of whether the tail was actually hit. + /// Whether the hit samples only play on successful hits. + /// If false, the hit samples will also play on misses. /// public bool SamplePlaysOnlyOnHit { get; set; } = true; From d2950105fb391fffe4463248ca3d172286b54d09 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 2 Apr 2021 20:31:34 +0900 Subject: [PATCH 1198/1791] Add comment explaining use of lock --- osu.Game/Screens/Spectate/SpectatorScreen.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index 25dd482112..4d285d519e 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -40,6 +40,7 @@ namespace osu.Game.Screens.Spectate [Resolved] private UserLookupCache userLookupCache { get; set; } + // A lock is used to synchronise access to spectator/gameplay states, since this class is a screen which may become non-current and stop receiving updates at any point. private readonly object stateLock = new object(); private readonly Dictionary userMap = new Dictionary(); From 1ff77754fd9f1e40709b3a547ca090fcef07ef76 Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Fri, 2 Apr 2021 20:14:31 +0800 Subject: [PATCH 1199/1791] Use OnlineViewContainer --- .../Visual/SongSelect/TestSceneBeatmapDetails.cs | 5 +++++ osu.Game/Screens/Select/BeatmapDetails.cs | 14 +++++++------- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs index acf037198f..06572f66bf 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs @@ -5,6 +5,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Beatmaps; +using osu.Game.Online.API; using osu.Game.Screens.Select; namespace osu.Game.Tests.Visual.SongSelect @@ -14,6 +15,8 @@ namespace osu.Game.Tests.Visual.SongSelect { private BeatmapDetails details; + private DummyAPIAccess api => (DummyAPIAccess)API; + [SetUp] public void Setup() => Schedule(() => { @@ -173,6 +176,8 @@ namespace osu.Game.Tests.Visual.SongSelect { OnlineBeatmapID = 162, }); + AddStep("set online", () => api.SetState(APIState.Online)); + AddStep("set offline", () => api.SetState(APIState.Offline)); } } } diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index 8a1c291fca..55616b1640 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -19,6 +19,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API.Requests; using osu.Game.Rulesets; +using osu.Game.Online; namespace osu.Game.Screens.Select { @@ -136,7 +137,7 @@ namespace osu.Game.Screens.Select }, }, }, - failRetryContainer = new Container + failRetryContainer = new OnlineViewContainer("Sign in to view more details") { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, @@ -153,11 +154,11 @@ namespace osu.Game.Screens.Select RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Top = 14 + spacing / 2 }, }, + loading = new LoadingLayer(true) }, }, }, }, - loading = new LoadingLayer(true), }; } @@ -222,11 +223,9 @@ namespace osu.Game.Screens.Select if (beatmap != requestedBeatmap) // the beatmap has been changed since we started the lookup. return; - updateMetrics(); }); }; - api.Queue(lookup); loading.Show(); } @@ -236,7 +235,9 @@ namespace osu.Game.Screens.Select var hasRatings = beatmap?.BeatmapSet?.Metrics?.Ratings?.Any() ?? false; var hasRetriesFails = (beatmap?.Metrics?.Retries?.Any() ?? false) || (beatmap?.Metrics?.Fails?.Any() ?? false); - if (hasRatings) + bool isOnline = api.State.Value == APIState.Online; + + if (hasRatings && isOnline) { ratings.Metrics = beatmap.BeatmapSet.Metrics; ratingsContainer.FadeIn(transition_duration); @@ -244,7 +245,7 @@ namespace osu.Game.Screens.Select else { ratings.Metrics = new BeatmapSetMetrics { Ratings = new int[10] }; - ratingsContainer.FadeTo(0.25f, transition_duration); + ratingsContainer.FadeTo(isOnline ? 0.25f : 0, transition_duration); } if (hasRetriesFails) @@ -259,7 +260,6 @@ namespace osu.Game.Screens.Select Fails = new int[100], Retries = new int[100], }; - failRetryContainer.FadeOut(transition_duration); } loading.Hide(); From cd53074941d864e0e1eaa33d8008f1730e8339a6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 2 Apr 2021 21:27:20 +0900 Subject: [PATCH 1200/1791] Schedule spectator callbacks --- osu.Game/Screens/Play/SoloSpectator.cs | 69 +++++++++----------- osu.Game/Screens/Spectate/SpectatorScreen.cs | 6 +- 2 files changed, 33 insertions(+), 42 deletions(-) diff --git a/osu.Game/Screens/Play/SoloSpectator.cs b/osu.Game/Screens/Play/SoloSpectator.cs index f8ed7b585f..820d776e63 100644 --- a/osu.Game/Screens/Play/SoloSpectator.cs +++ b/osu.Game/Screens/Play/SoloSpectator.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Screens; +using osu.Framework.Threading; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Configuration; @@ -53,15 +54,10 @@ namespace osu.Game.Screens.Play /// /// The player's immediate online gameplay state. - /// This doesn't reflect the gameplay state being watched by the user if is non-null. + /// This doesn't always reflect the gameplay state being watched. /// private GameplayState immediateGameplayState; - /// - /// The gameplay state that is pending to be watched, upon this screen becoming current. - /// - private GameplayState pendingGameplayState; - private GetBeatmapSetRequest onlineBeatmapRequest; public SoloSpectator([NotNull] User targetUser) @@ -150,7 +146,7 @@ namespace osu.Game.Screens.Play Width = 250, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Action = () => attemptStart(immediateGameplayState), + Action = () => scheduleStart(immediateGameplayState), Enabled = { Value = false } } } @@ -165,44 +161,27 @@ namespace osu.Game.Screens.Play automaticDownload.Current.BindValueChanged(_ => checkForAutomaticDownload()); } - protected override void OnUserStateChanged(int userId, SpectatorState spectatorState) => Schedule(() => + protected override void OnUserStateChanged(int userId, SpectatorState spectatorState) { clearDisplay(); showBeatmapPanel(spectatorState); - }); + } - protected override void StartGameplay(int userId, GameplayState gameplayState) => Schedule(() => + protected override void StartGameplay(int userId, GameplayState gameplayState) { - pendingGameplayState = null; immediateGameplayState = gameplayState; - - if (this.IsCurrentScreen()) - attemptStart(gameplayState); - else - pendingGameplayState = gameplayState; - watchButton.Enabled.Value = true; - }); - protected override void EndGameplay(int userId) => Schedule(() => + scheduleStart(gameplayState); + } + + protected override void EndGameplay(int userId) { - pendingGameplayState = null; + scheduledStart?.Cancel(); immediateGameplayState = null; - - Schedule(clearDisplay); - watchButton.Enabled.Value = false; - }); - public override void OnResuming(IScreen last) - { - base.OnResuming(last); - - if (pendingGameplayState != null) - { - attemptStart(pendingGameplayState); - pendingGameplayState = null; - } + clearDisplay(); } private void clearDisplay() @@ -213,15 +192,27 @@ namespace osu.Game.Screens.Play previewTrackManager.StopAnyPlaying(this); } - private void attemptStart(GameplayState gameplayState) + private ScheduledDelegate scheduledStart; + + private void scheduleStart(GameplayState gameplayState) { - if (gameplayState == null) - return; + // This function may be called multiple times in quick succession once the screen becomes current again. + scheduledStart?.Cancel(); + scheduledStart = Schedule(() => + { + if (this.IsCurrentScreen()) + start(); + else + scheduleStart(gameplayState); + }); - Beatmap.Value = gameplayState.Beatmap; - Ruleset.Value = gameplayState.Ruleset.RulesetInfo; + void start() + { + Beatmap.Value = gameplayState.Beatmap; + Ruleset.Value = gameplayState.Ruleset.RulesetInfo; - this.Push(new SpectatorPlayerLoader(gameplayState.Score)); + this.Push(new SpectatorPlayerLoader(gameplayState.Score)); + } } private void showBeatmapPanel(SpectatorState state) diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index 4d285d519e..6dd3144fc8 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -110,7 +110,7 @@ namespace osu.Game.Screens.Spectate return; spectatorStates[userId] = state; - OnUserStateChanged(userId, state); + Schedule(() => OnUserStateChanged(userId, state)); updateGameplayState(userId); } @@ -148,7 +148,7 @@ namespace osu.Game.Screens.Spectate var gameplayState = new GameplayState(score, resolvedRuleset, beatmaps.GetWorkingBeatmap(resolvedBeatmap)); gameplayStates[userId] = gameplayState; - StartGameplay(userId, gameplayState); + Schedule(() => StartGameplay(userId, gameplayState)); } } @@ -191,7 +191,7 @@ namespace osu.Game.Screens.Spectate gameplayState.Score.Replay.HasReceivedAllFrames = true; gameplayStates.Remove(userId); - EndGameplay(userId); + Schedule(() => EndGameplay(userId)); } } From a5a19319cc6682e8380b8bee725657b64c5ee66e Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Fri, 2 Apr 2021 21:15:28 +0800 Subject: [PATCH 1201/1791] Fix code style --- osu.Game/Screens/Select/BeatmapDetails.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index 55616b1640..40029cc19a 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -223,9 +223,11 @@ namespace osu.Game.Screens.Select if (beatmap != requestedBeatmap) // the beatmap has been changed since we started the lookup. return; + updateMetrics(); }); }; + api.Queue(lookup); loading.Show(); } From 438f3e63499528226d13e21dac77880dc118a67a Mon Sep 17 00:00:00 2001 From: hbnrmx Date: Fri, 2 Apr 2021 17:57:21 +0200 Subject: [PATCH 1202/1791] move fallback text to PlaceholderText --- osu.Game/Screens/Edit/Setup/ResourcesSection.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 1b841775e2..e00eaf9aa2 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -73,7 +73,8 @@ namespace osu.Game.Screens.Edit.Setup audioTrackTextBox = new FileChooserLabelledTextBox { Label = "Audio Track", - Current = { Value = working.Value.Metadata.AudioFile ?? "Click to select a track" }, + PlaceholderText = "Click to select a track", + Current = { Value = working.Value.Metadata.AudioFile }, Target = audioTrackFileChooserContainer, TabbableContentContainer = this }, From 824fb9f3987817585365e45ed01642b21573f387 Mon Sep 17 00:00:00 2001 From: hbnrmx Date: Fri, 2 Apr 2021 18:01:26 +0200 Subject: [PATCH 1203/1791] reopen FileSelector in the directory of the previous selection --- osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs b/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs index 6e2737256a..70876bf26c 100644 --- a/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs +++ b/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs @@ -54,7 +54,7 @@ namespace osu.Game.Screens.Edit.Setup { FileSelector fileSelector; - Target.Child = fileSelector = new FileSelector(validFileExtensions: ResourcesSection.AudioExtensions) + Target.Child = fileSelector = new FileSelector(currentFile.Value?.DirectoryName, ResourcesSection.AudioExtensions) { RelativeSizeAxes = Axes.X, Height = 400, From 0dce4b8894a55d9d6295673476557fd8041b6b6e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 3 Apr 2021 13:01:08 +0900 Subject: [PATCH 1204/1791] 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 5b65670869..73ee1d9d10 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 6a7f7e7026..931b55222a 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 4aa3ad1c61..64e9a01a92 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From bd7da9eb39703e033c0ffa5b30563659f505efc2 Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Sat, 3 Apr 2021 12:43:17 +0800 Subject: [PATCH 1205/1791] Make beatmap title use unicode --- osu.Game/Beatmaps/BeatmapMetadata.cs | 2 ++ osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index 367f612dc8..eea9bdd976 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -19,8 +19,10 @@ namespace osu.Game.Beatmaps public int ID { get; set; } public string Title { get; set; } + [JsonProperty("title_unicode")] public string TitleUnicode { get; set; } public string Artist { get; set; } + [JsonProperty("artist_unicode")] public string ArtistUnicode { get; set; } [JsonIgnore] diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs index 153aa41582..a61640a02e 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -228,8 +229,8 @@ namespace osu.Game.Overlays.BeatmapSet loading.Hide(); - title.Text = setInfo.NewValue.Metadata.Title ?? string.Empty; - artist.Text = setInfo.NewValue.Metadata.Artist ?? string.Empty; + title.Text = new RomanisableString(setInfo.NewValue.Metadata.TitleUnicode, setInfo.NewValue.Metadata.Title); + artist.Text = new RomanisableString(setInfo.NewValue.Metadata.ArtistUnicode, setInfo.NewValue.Metadata.Artist); explicitContentPill.Alpha = setInfo.NewValue.OnlineInfo.HasExplicitContent ? 1 : 0; From dde255980b9aa24bfe95764e20f811fca6b757f7 Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Sat, 3 Apr 2021 12:44:51 +0800 Subject: [PATCH 1206/1791] Fix formatting --- osu.Game/Beatmaps/BeatmapMetadata.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index eea9bdd976..858da8e602 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -19,9 +19,12 @@ namespace osu.Game.Beatmaps public int ID { get; set; } public string Title { get; set; } + [JsonProperty("title_unicode")] public string TitleUnicode { get; set; } + public string Artist { get; set; } + [JsonProperty("artist_unicode")] public string ArtistUnicode { get; set; } From fe66b84bede18b7a2148c5c4ae1fdea948cad717 Mon Sep 17 00:00:00 2001 From: Samuel Cattini-Schultz Date: Sat, 6 Feb 2021 15:48:14 +1100 Subject: [PATCH 1207/1791] Implement dynamic previous hitobject retention for Skill class There is no reason we should be limiting skills to knowing only the previous 2 objects. This originally existed as an angle implementation detail of the original pp+ codebase which made its way here, but didn't get used in the same way. --- .../NonVisual/LimitedCapacityStackTest.cs | 115 -------------- osu.Game.Tests/NonVisual/ReverseQueueTest.cs | 143 ++++++++++++++++++ osu.Game/Rulesets/Difficulty/Skills/Skill.cs | 33 +++- .../Difficulty/Utils/LimitedCapacityStack.cs | 92 ----------- .../Rulesets/Difficulty/Utils/ReverseQueue.cs | 110 ++++++++++++++ 5 files changed, 284 insertions(+), 209 deletions(-) delete mode 100644 osu.Game.Tests/NonVisual/LimitedCapacityStackTest.cs create mode 100644 osu.Game.Tests/NonVisual/ReverseQueueTest.cs delete mode 100644 osu.Game/Rulesets/Difficulty/Utils/LimitedCapacityStack.cs create mode 100644 osu.Game/Rulesets/Difficulty/Utils/ReverseQueue.cs diff --git a/osu.Game.Tests/NonVisual/LimitedCapacityStackTest.cs b/osu.Game.Tests/NonVisual/LimitedCapacityStackTest.cs deleted file mode 100644 index d5ac38008e..0000000000 --- a/osu.Game.Tests/NonVisual/LimitedCapacityStackTest.cs +++ /dev/null @@ -1,115 +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 NUnit.Framework; -using osu.Game.Rulesets.Difficulty.Utils; - -namespace osu.Game.Tests.NonVisual -{ - [TestFixture] - public class LimitedCapacityStackTest - { - private const int capacity = 3; - - private LimitedCapacityStack stack; - - [SetUp] - public void Setup() - { - stack = new LimitedCapacityStack(capacity); - } - - [Test] - public void TestEmptyStack() - { - Assert.AreEqual(0, stack.Count); - - Assert.Throws(() => - { - int unused = stack[0]; - }); - - int count = 0; - foreach (var unused in stack) - count++; - - Assert.AreEqual(0, count); - } - - [TestCase(1)] - [TestCase(2)] - [TestCase(3)] - public void TestInRangeElements(int count) - { - // e.g. 0 -> 1 -> 2 - for (int i = 0; i < count; i++) - stack.Push(i); - - Assert.AreEqual(count, stack.Count); - - // e.g. 2 -> 1 -> 0 (reverse order) - for (int i = 0; i < stack.Count; i++) - Assert.AreEqual(count - 1 - i, stack[i]); - - // e.g. indices 3, 4, 5, 6 (out of range) - for (int i = stack.Count; i < stack.Count + capacity; i++) - { - Assert.Throws(() => - { - int unused = stack[i]; - }); - } - } - - [TestCase(4)] - [TestCase(5)] - [TestCase(6)] - public void TestOverflowElements(int count) - { - // e.g. 0 -> 1 -> 2 -> 3 - for (int i = 0; i < count; i++) - stack.Push(i); - - Assert.AreEqual(capacity, stack.Count); - - // e.g. 3 -> 2 -> 1 (reverse order) - for (int i = 0; i < stack.Count; i++) - Assert.AreEqual(count - 1 - i, stack[i]); - - // e.g. indices 3, 4, 5, 6 (out of range) - for (int i = stack.Count; i < stack.Count + capacity; i++) - { - Assert.Throws(() => - { - int unused = stack[i]; - }); - } - } - - [TestCase(1)] - [TestCase(2)] - [TestCase(3)] - [TestCase(4)] - [TestCase(5)] - [TestCase(6)] - public void TestEnumerator(int count) - { - // e.g. 0 -> 1 -> 2 -> 3 - for (int i = 0; i < count; i++) - stack.Push(i); - - int enumeratorCount = 0; - int expectedValue = count - 1; - - foreach (var item in stack) - { - Assert.AreEqual(expectedValue, item); - enumeratorCount++; - expectedValue--; - } - - Assert.AreEqual(stack.Count, enumeratorCount); - } - } -} diff --git a/osu.Game.Tests/NonVisual/ReverseQueueTest.cs b/osu.Game.Tests/NonVisual/ReverseQueueTest.cs new file mode 100644 index 0000000000..93cd9403ce --- /dev/null +++ b/osu.Game.Tests/NonVisual/ReverseQueueTest.cs @@ -0,0 +1,143 @@ +// 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.Game.Rulesets.Difficulty.Utils; + +namespace osu.Game.Tests.NonVisual +{ + [TestFixture] + public class ReverseQueueTest + { + private ReverseQueue queue; + + [SetUp] + public void Setup() + { + queue = new ReverseQueue(4); + } + + [Test] + public void TestEmptyQueue() + { + Assert.AreEqual(0, queue.Count); + + Assert.Throws(() => + { + char unused = queue[0]; + }); + + int count = 0; + foreach (var unused in queue) + count++; + + Assert.AreEqual(0, count); + } + + [Test] + public void TestEnqueue() + { + // Assert correct values and reverse index after enqueueing + queue.Enqueue('a'); + queue.Enqueue('b'); + queue.Enqueue('c'); + + Assert.AreEqual('c', queue[0]); + Assert.AreEqual('b', queue[1]); + Assert.AreEqual('a', queue[2]); + + // Assert correct values and reverse index after enqueueing beyond initial capacity of 4 + queue.Enqueue('d'); + queue.Enqueue('e'); + queue.Enqueue('f'); + + Assert.AreEqual('f', queue[0]); + Assert.AreEqual('e', queue[1]); + Assert.AreEqual('d', queue[2]); + Assert.AreEqual('c', queue[3]); + Assert.AreEqual('b', queue[4]); + Assert.AreEqual('a', queue[5]); + } + + [Test] + public void TestDequeue() + { + queue.Enqueue('a'); + queue.Enqueue('b'); + queue.Enqueue('c'); + queue.Enqueue('d'); + queue.Enqueue('e'); + queue.Enqueue('f'); + + // Assert correct item return and no longer in queue after dequeueing + Assert.AreEqual('a', queue[5]); + var dequeuedItem = queue.Dequeue(); + + Assert.AreEqual('a', dequeuedItem); + Assert.AreEqual(5, queue.Count); + Assert.AreEqual('f', queue[0]); + Assert.AreEqual('b', queue[4]); + Assert.Throws(() => + { + char unused = queue[5]; + }); + + // Assert correct state after enough enqueues and dequeues to wrap around array (queue.start = 0 again) + queue.Enqueue('g'); + queue.Enqueue('h'); + queue.Enqueue('i'); + queue.Dequeue(); + queue.Dequeue(); + queue.Dequeue(); + queue.Dequeue(); + queue.Dequeue(); + queue.Dequeue(); + queue.Dequeue(); + + Assert.AreEqual(1, queue.Count); + Assert.AreEqual('i', queue[0]); + } + + [Test] + public void TestClear() + { + queue.Enqueue('a'); + queue.Enqueue('b'); + queue.Enqueue('c'); + queue.Enqueue('d'); + queue.Enqueue('e'); + queue.Enqueue('f'); + + // Assert queue is empty after clearing + queue.Clear(); + + Assert.AreEqual(0, queue.Count); + Assert.Throws(() => + { + char unused = queue[0]; + }); + } + + [Test] + public void TestEnumerator() + { + queue.Enqueue('a'); + queue.Enqueue('b'); + queue.Enqueue('c'); + queue.Enqueue('d'); + queue.Enqueue('e'); + queue.Enqueue('f'); + + char[] expectedValues = { 'f', 'e', 'd', 'c', 'b', 'a' }; + int expectedValueIndex = 0; + + // Assert items are enumerated in correct order + foreach (var item in queue) + { + Assert.AreEqual(expectedValues[expectedValueIndex], item); + expectedValueIndex++; + } + } + } +} diff --git a/osu.Game/Rulesets/Difficulty/Skills/Skill.cs b/osu.Game/Rulesets/Difficulty/Skills/Skill.cs index 126e30ed73..caa94a5355 100644 --- a/osu.Game/Rulesets/Difficulty/Skills/Skill.cs +++ b/osu.Game/Rulesets/Difficulty/Skills/Skill.cs @@ -40,7 +40,14 @@ namespace osu.Game.Rulesets.Difficulty.Skills /// /// s that were processed previously. They can affect the strain values of the following objects. /// - protected readonly LimitedCapacityStack Previous = new LimitedCapacityStack(2); // Contained objects not used yet + protected readonly ReverseQueue Previous; + + /// + /// Soft capacity of the queue. + /// will automatically resize if it exceeds capacity, but will do so at a very slight performance impact. + /// The actual capacity will be set to this value + 1 to allow for storage of the current object before the next can be processed. + /// + protected virtual int PreviousCollectionSoftCapacity => 1; /// /// The current strain level. @@ -61,6 +68,7 @@ namespace osu.Game.Rulesets.Difficulty.Skills protected Skill(Mod[] mods) { this.mods = mods; + Previous = new ReverseQueue(PreviousCollectionSoftCapacity + 1); } /// @@ -68,12 +76,33 @@ namespace osu.Game.Rulesets.Difficulty.Skills /// public void Process(DifficultyHitObject current) { + RemoveExtraneousHistory(current); + CurrentStrain *= strainDecay(current.DeltaTime); CurrentStrain += StrainValueOf(current) * SkillMultiplier; currentSectionPeak = Math.Max(CurrentStrain, currentSectionPeak); - Previous.Push(current); + AddToHistory(current); + } + + /// + /// Remove objects from that are no longer needed for calculations from the current object onwards. + /// + /// The to be processed. + protected virtual void RemoveExtraneousHistory(DifficultyHitObject current) + { + while (Previous.Count > 1) + Previous.Dequeue(); + } + + /// + /// Add the current to the queue (if required). + /// + /// The that was just processed. + protected virtual void AddToHistory(DifficultyHitObject current) + { + Previous.Enqueue(current); } /// diff --git a/osu.Game/Rulesets/Difficulty/Utils/LimitedCapacityStack.cs b/osu.Game/Rulesets/Difficulty/Utils/LimitedCapacityStack.cs deleted file mode 100644 index 1fc5abce90..0000000000 --- a/osu.Game/Rulesets/Difficulty/Utils/LimitedCapacityStack.cs +++ /dev/null @@ -1,92 +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; -using System.Collections.Generic; - -namespace osu.Game.Rulesets.Difficulty.Utils -{ - /// - /// An indexed stack with limited depth. Indexing starts at the top of the stack. - /// - public class LimitedCapacityStack : IEnumerable - { - /// - /// The number of elements in the stack. - /// - public int Count { get; private set; } - - private readonly T[] array; - private readonly int capacity; - private int marker; // Marks the position of the most recently added item. - - /// - /// Constructs a new . - /// - /// The number of items the stack can hold. - public LimitedCapacityStack(int capacity) - { - if (capacity < 0) - throw new ArgumentOutOfRangeException(nameof(capacity)); - - this.capacity = capacity; - array = new T[capacity]; - marker = capacity; // Set marker to the end of the array, outside of the indexed range by one. - } - - /// - /// Retrieves the item at an index in the stack. - /// - /// The index of the item to retrieve. The top of the stack is returned at index 0. - public T this[int i] - { - get - { - if (i < 0 || i > Count - 1) - throw new ArgumentOutOfRangeException(nameof(i)); - - i += marker; - if (i > capacity - 1) - i -= capacity; - - return array[i]; - } - } - - /// - /// Pushes an item to this . - /// - /// The item to push. - public void Push(T item) - { - // Overwrite the oldest item instead of shifting every item by one with every addition. - if (marker == 0) - marker = capacity - 1; - else - --marker; - - array[marker] = item; - - if (Count < capacity) - ++Count; - } - - /// - /// Returns an enumerator which enumerates items in the history starting from the most recently added one. - /// - public IEnumerator GetEnumerator() - { - for (int i = marker; i < capacity; ++i) - yield return array[i]; - - if (Count == capacity) - { - for (int i = 0; i < marker; ++i) - yield return array[i]; - } - } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - } -} diff --git a/osu.Game/Rulesets/Difficulty/Utils/ReverseQueue.cs b/osu.Game/Rulesets/Difficulty/Utils/ReverseQueue.cs new file mode 100644 index 0000000000..08b2936876 --- /dev/null +++ b/osu.Game/Rulesets/Difficulty/Utils/ReverseQueue.cs @@ -0,0 +1,110 @@ +// 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; +using System.Collections.Generic; + +namespace osu.Game.Rulesets.Difficulty.Utils +{ + /// + /// An indexed queue where items are indexed beginning from the end instead of the start. + /// + public class ReverseQueue : IEnumerable + { + /// + /// The number of elements in the . + /// + public int Count { get; private set; } + + private T[] items; + private int capacity; + private int start; + + public ReverseQueue(int initialCapacity) + { + if (initialCapacity <= 0) + throw new ArgumentOutOfRangeException(nameof(initialCapacity)); + + items = new T[initialCapacity]; + capacity = initialCapacity; + start = 0; + Count = 0; + } + + /// + /// Retrieves the item at an index in the . + /// + /// The index of the item to retrieve. The most recently enqueued item is at index 0. + public T this[int index] + { + get + { + if (index < 0 || index > Count - 1) + throw new ArgumentOutOfRangeException(nameof(index)); + + int reverseIndex = Count - 1 - index; + return items[(start + reverseIndex) % capacity]; + } + } + + /// + /// Enqueues an item to this . + /// + /// The item to enqueue. + public void Enqueue(T item) + { + if (Count == capacity) + { + // Double the buffer size + var buffer = new T[capacity * 2]; + + // Copy items to new queue + for (int i = 0; i < Count; i++) + { + buffer[i] = items[(start + i) % capacity]; + } + + // Replace array with new buffer + items = buffer; + capacity *= 2; + start = 0; + } + + items[(start + Count) % capacity] = item; + Count++; + } + + /// + /// Dequeues an item from the and returns it. + /// + /// The item dequeued from the . + public T Dequeue() + { + var item = items[start]; + start = (start + 1) % capacity; + Count--; + return item; + } + + /// + /// Clears the of all items. + /// + public void Clear() + { + start = 0; + Count = 0; + } + + /// + /// Returns an enumerator which enumerates items in the starting from the most recently enqueued one. + /// + public IEnumerator GetEnumerator() + { + for (int i = Count - 1; i >= 0; i--) + yield return items[(start + i) % capacity]; + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} From 5b2dcea8a85b9655fc24f5419c5c19c7b93eb61c Mon Sep 17 00:00:00 2001 From: Samuel Cattini-Schultz Date: Sat, 3 Apr 2021 20:47:39 +1100 Subject: [PATCH 1208/1791] Refactor to encapsulate strain logic into Skill class As strains are an implementation detail of the current Skill calculations, it makes sense that strain related logic should be encapsulated within the Skill class. --- .../Difficulty/CatchDifficultyCalculator.cs | 2 - .../Difficulty/Skills/Movement.cs | 2 + .../Difficulty/Skills/Strain.cs | 5 +- .../Difficulty/TaikoDifficultyCalculator.cs | 13 +++-- .../Difficulty/DifficultyCalculator.cs | 29 ++---------- .../Preprocessing/DifficultyHitObject.cs | 2 +- osu.Game/Rulesets/Difficulty/Skills/Skill.cs | 47 +++++++++++++------ 7 files changed, 49 insertions(+), 51 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs index 10aae70722..f5cce47186 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -21,8 +21,6 @@ namespace osu.Game.Rulesets.Catch.Difficulty { private const double star_scaling_factor = 0.153; - protected override int SectionLength => 750; - private float halfCatcherWidth; public CatchDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) diff --git a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs index 9ad719be1a..7222166535 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs @@ -20,6 +20,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills protected override double DecayWeight => 0.94; + protected override int SectionLength => 750; + protected readonly float HalfCatcherWidth; private float? lastPlayerPosition; diff --git a/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs b/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs index 830b6004a6..0761724e83 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs @@ -7,7 +7,6 @@ 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 { @@ -36,7 +35,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills protected override double StrainValueOf(DifficultyHitObject current) { var maniaCurrent = (ManiaDifficultyHitObject)current; - var endTime = maniaCurrent.BaseObject.GetEndTime(); + var endTime = maniaCurrent.EndTime; var column = maniaCurrent.BaseObject.Column; double holdFactor = 1.0; // Factor to all additional strains in case something else is held @@ -46,7 +45,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills for (int i = 0; i < holdEndTimes.Length; ++i) { // If there is at least one other overlapping end or note, then we get an addition, buuuuuut... - if (Precision.DefinitelyBigger(holdEndTimes[i], maniaCurrent.BaseObject.StartTime, 1) && Precision.DefinitelyBigger(endTime, holdEndTimes[i], 1)) + if (Precision.DefinitelyBigger(holdEndTimes[i], maniaCurrent.StartTime, 1) && Precision.DefinitelyBigger(endTime, holdEndTimes[i], 1)) holdAddition = 1.0; // ... this addition only is valid if there is _no_ other note with the same ending. Releasing multiple notes at the same time is just as easy as releasing 1 diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index fc198d2493..6b3e31c5d5 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -133,11 +133,16 @@ namespace osu.Game.Rulesets.Taiko.Difficulty { List peaks = new List(); - for (int i = 0; i < colour.StrainPeaks.Count; i++) + var colourPeaks = colour.GetCurrentStrainPeaks().ToList(); + var rhythmPeaks = rhythm.GetCurrentStrainPeaks().ToList(); + var staminaRightPeaks = staminaRight.GetCurrentStrainPeaks().ToList(); + var staminaLeftPeaks = staminaLeft.GetCurrentStrainPeaks().ToList(); + + for (int i = 0; i < colourPeaks.Count; i++) { - double colourPeak = colour.StrainPeaks[i] * colour_skill_multiplier; - double rhythmPeak = rhythm.StrainPeaks[i] * rhythm_skill_multiplier; - double staminaPeak = (staminaRight.StrainPeaks[i] + staminaLeft.StrainPeaks[i]) * stamina_skill_multiplier * staminaPenalty; + double colourPeak = colourPeaks[i] * colour_skill_multiplier; + double rhythmPeak = rhythmPeaks[i] * rhythm_skill_multiplier; + double staminaPeak = (staminaRightPeaks[i] + staminaLeftPeaks[i]) * stamina_skill_multiplier * staminaPenalty; peaks.Add(norm(2, colourPeak, rhythmPeak, staminaPeak)); } diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 9d06f960b7..14ada8ca09 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -16,11 +16,6 @@ namespace osu.Game.Rulesets.Difficulty { public abstract class DifficultyCalculator { - /// - /// The length of each strain section. - /// - protected virtual int SectionLength => 400; - private readonly Ruleset ruleset; private readonly WorkingBeatmap beatmap; @@ -71,32 +66,14 @@ namespace osu.Game.Rulesets.Difficulty var difficultyHitObjects = SortObjects(CreateDifficultyHitObjects(beatmap, clockRate)).ToList(); - double sectionLength = SectionLength * clockRate; - - // The first object doesn't generate a strain, so we begin with an incremented section end - double currentSectionEnd = Math.Ceiling(beatmap.HitObjects.First().StartTime / sectionLength) * sectionLength; - - foreach (DifficultyHitObject h in difficultyHitObjects) + foreach (var hitObject in difficultyHitObjects) { - while (h.BaseObject.StartTime > currentSectionEnd) + foreach (var skill in skills) { - foreach (Skill s in skills) - { - s.SaveCurrentPeak(); - s.StartNewSectionFrom(currentSectionEnd / clockRate); - } - - currentSectionEnd += sectionLength; + skill.Process(hitObject); } - - foreach (Skill s in skills) - s.Process(h); } - // The peak strain will not be saved for the last section in the above loop - foreach (Skill s in skills) - s.SaveCurrentPeak(); - return CreateDifficultyAttributes(beatmap, mods, skills, clockRate); } diff --git a/osu.Game/Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs b/osu.Game/Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs index 576fbb2af0..5edfb2207b 100644 --- a/osu.Game/Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs +++ b/osu.Game/Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Difficulty.Preprocessing public readonly HitObject LastObject; /// - /// Amount of time elapsed between and . + /// Amount of time elapsed between and , adjusted by clockrate. /// public readonly double DeltaTime; diff --git a/osu.Game/Rulesets/Difficulty/Skills/Skill.cs b/osu.Game/Rulesets/Difficulty/Skills/Skill.cs index 126e30ed73..aa187c6afd 100644 --- a/osu.Game/Rulesets/Difficulty/Skills/Skill.cs +++ b/osu.Game/Rulesets/Difficulty/Skills/Skill.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; @@ -16,11 +16,6 @@ namespace osu.Game.Rulesets.Difficulty.Skills /// public abstract class Skill { - /// - /// The peak strain for each section of the beatmap. - /// - public IReadOnlyList StrainPeaks => strainPeaks; - /// /// Strain values are multiplied by this number for the given skill. Used to balance the value of different skills between each other. /// @@ -47,6 +42,11 @@ namespace osu.Game.Rulesets.Difficulty.Skills /// protected double CurrentStrain { get; private set; } = 1; + /// + /// The length of each strain section. + /// + protected virtual int SectionLength => 400; + /// /// Mods for use in skill calculations. /// @@ -54,6 +54,8 @@ namespace osu.Game.Rulesets.Difficulty.Skills private double currentSectionPeak = 1; // We also keep track of the peak strain level in the current section. + private double currentSectionEnd; + private readonly List strainPeaks = new List(); private readonly Mod[] mods; @@ -68,6 +70,17 @@ namespace osu.Game.Rulesets.Difficulty.Skills /// public void Process(DifficultyHitObject current) { + // The first object doesn't generate a strain, so we begin with an incremented section end + if (Previous.Count == 0) + currentSectionEnd = Math.Ceiling(current.StartTime / SectionLength) * SectionLength; + + while (current.StartTime > currentSectionEnd) + { + saveCurrentPeak(); + startNewSectionFrom(currentSectionEnd); + currentSectionEnd += SectionLength; + } + CurrentStrain *= strainDecay(current.DeltaTime); CurrentStrain += StrainValueOf(current) * SkillMultiplier; @@ -79,22 +92,20 @@ namespace osu.Game.Rulesets.Difficulty.Skills /// /// Saves the current peak strain level to the list of strain peaks, which will be used to calculate an overall difficulty. /// - public void SaveCurrentPeak() + private void saveCurrentPeak() { - if (Previous.Count > 0) - strainPeaks.Add(currentSectionPeak); + strainPeaks.Add(currentSectionPeak); } /// /// Sets the initial strain level for a new section. /// - /// The beginning of the new section in milliseconds, adjusted by clockrate. - public void StartNewSectionFrom(double time) + /// The beginning of the new section in milliseconds. + private void startNewSectionFrom(double time) { // The maximum strain of the new section is not zero by default, strain decays as usual regardless of section boundaries. // This means we need to capture the strain level at the beginning of the new section, and use that as the initial peak level. - if (Previous.Count > 0) - currentSectionPeak = GetPeakStrain(time); + currentSectionPeak = GetPeakStrain(time); } /// @@ -105,7 +116,13 @@ namespace osu.Game.Rulesets.Difficulty.Skills protected virtual double GetPeakStrain(double time) => CurrentStrain * strainDecay(time - Previous[0].StartTime); /// - /// Returns the calculated difficulty value representing all processed s. + /// Returns a live enumerable of the peak strains for each section of the beatmap, + /// including the peak of the current section. + /// + public IEnumerable GetCurrentStrainPeaks() => strainPeaks.Append(currentSectionPeak); + + /// + /// Returns the calculated difficulty value representing all s that have been processed up to this point. /// public double DifficultyValue() { @@ -114,7 +131,7 @@ namespace osu.Game.Rulesets.Difficulty.Skills // Difficulty is the weighted sum of the highest strains from every section. // We're sorting from highest to lowest strain. - foreach (double strain in strainPeaks.OrderByDescending(d => d)) + foreach (double strain in GetCurrentStrainPeaks().OrderByDescending(d => d)) { difficulty += strain * weight; weight *= DecayWeight; From 85d2b1232a22f900187731033a7b753ea1429225 Mon Sep 17 00:00:00 2001 From: Samuel Cattini-Schultz Date: Sat, 3 Apr 2021 20:52:36 +1100 Subject: [PATCH 1209/1791] Refactor to abstract out strain logic into StrainSkill class While it is the case for the existing official Skills, Skill implementations shouldn't be required to conform to a strain based approach. There are other valid approaches to calculating skill difficulty that can be supported by abstracting the strain logic into its own StrainSkill class. --- .../Difficulty/Skills/Movement.cs | 2 +- .../Difficulty/Skills/Strain.cs | 2 +- .../Difficulty/Skills/Aim.cs | 2 +- .../Difficulty/Skills/Speed.cs | 2 +- .../Difficulty/Skills/Colour.cs | 2 +- .../Difficulty/Skills/Rhythm.cs | 2 +- .../Difficulty/Skills/Stamina.cs | 2 +- osu.Game/Rulesets/Difficulty/Skills/Skill.cs | 115 +-------------- .../Rulesets/Difficulty/Skills/StrainSkill.cs | 137 ++++++++++++++++++ 9 files changed, 150 insertions(+), 116 deletions(-) create mode 100644 osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs diff --git a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs index 7222166535..75e17f6c48 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs @@ -9,7 +9,7 @@ using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Catch.Difficulty.Skills { - public class Movement : Skill + public class Movement : StrainSkill { private const float absolute_player_positioning_error = 16f; private const float normalized_hitobject_radius = 41.0f; diff --git a/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs b/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs index 0761724e83..2ba2ee6b4a 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Mania.Difficulty.Skills { - public class Strain : Skill + public class Strain : StrainSkill { private const double individual_decay_base = 0.125; private const double overall_decay_base = 0.30; diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index 90cba13c7c..cb819ec090 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills /// /// Represents the skill required to correctly aim at every object in the map with a uniform CircleSize and normalized distances. /// - public class Aim : Skill + public class Aim : StrainSkill { private const double angle_bonus_begin = Math.PI / 3; private const double timing_threshold = 107; diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 200bc7997d..fbac080fc6 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills /// /// Represents the skill required to press keys with regards to keeping up with the speed at which objects need to be hit. /// - public class Speed : Skill + public class Speed : StrainSkill { private const double single_spacing_threshold = 125; diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs index cc0738e252..769d021362 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Colour.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills /// /// Calculates the colour coefficient of taiko difficulty. /// - public class Colour : Skill + public class Colour : StrainSkill { protected override double SkillMultiplier => 1; protected override double StrainDecayBase => 0.4; diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs index f2b8309ac5..a32f6ebe0d 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills /// /// Calculates the rhythm coefficient of taiko difficulty. /// - public class Rhythm : Skill + public class Rhythm : StrainSkill { protected override double SkillMultiplier => 10; protected override double StrainDecayBase => 0; diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs index c34cce0cd6..4cceadb23f 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills /// /// The reference play style chosen uses two hands, with full alternating (the hand changes after every hit). /// - public class Stamina : Skill + public class Stamina : StrainSkill { protected override double SkillMultiplier => 1; protected override double StrainDecayBase => 0.4; diff --git a/osu.Game/Rulesets/Difficulty/Skills/Skill.cs b/osu.Game/Rulesets/Difficulty/Skills/Skill.cs index aa187c6afd..b3d7ce3c40 100644 --- a/osu.Game/Rulesets/Difficulty/Skills/Skill.cs +++ b/osu.Game/Rulesets/Difficulty/Skills/Skill.cs @@ -1,9 +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.Collections.Generic; -using System.Linq; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Utils; using osu.Game.Rulesets.Mods; @@ -11,140 +9,39 @@ using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Difficulty.Skills { /// - /// Used to processes strain values of s, keep track of strain levels caused by the processed objects - /// and to calculate a final difficulty value representing the difficulty of hitting all the processed objects. + /// A bare minimal abstract skill for fully custom skill implementations. /// public abstract class Skill { - /// - /// Strain values are multiplied by this number for the given skill. Used to balance the value of different skills between each other. - /// - protected abstract double SkillMultiplier { get; } - - /// - /// Determines how quickly strain decays for the given skill. - /// For example a value of 0.15 indicates that strain decays to 15% of its original value in one second. - /// - protected abstract double StrainDecayBase { get; } - - /// - /// The weight by which each strain value decays. - /// - protected virtual double DecayWeight => 0.9; - /// /// s that were processed previously. They can affect the strain values of the following objects. /// protected readonly LimitedCapacityStack Previous = new LimitedCapacityStack(2); // Contained objects not used yet - /// - /// The current strain level. - /// - protected double CurrentStrain { get; private set; } = 1; - - /// - /// The length of each strain section. - /// - protected virtual int SectionLength => 400; - + /// /// 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 double currentSectionEnd; - - 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. + /// Process a . /// - public void Process(DifficultyHitObject current) + /// The to process. + public virtual void Process(DifficultyHitObject current) { - // The first object doesn't generate a strain, so we begin with an incremented section end - if (Previous.Count == 0) - currentSectionEnd = Math.Ceiling(current.StartTime / SectionLength) * SectionLength; - - while (current.StartTime > currentSectionEnd) - { - saveCurrentPeak(); - startNewSectionFrom(currentSectionEnd); - currentSectionEnd += SectionLength; - } - - CurrentStrain *= strainDecay(current.DeltaTime); - CurrentStrain += StrainValueOf(current) * SkillMultiplier; - - currentSectionPeak = Math.Max(CurrentStrain, currentSectionPeak); - Previous.Push(current); } - /// - /// Saves the current peak strain level to the list of strain peaks, which will be used to calculate an overall difficulty. - /// - private void saveCurrentPeak() - { - strainPeaks.Add(currentSectionPeak); - } - - /// - /// Sets the initial strain level for a new section. - /// - /// The beginning of the new section in milliseconds. - private void startNewSectionFrom(double time) - { - // The maximum strain of the new section is not zero by default, strain decays as usual regardless of section boundaries. - // This means we need to capture the strain level at the beginning of the new section, and use that as the initial peak level. - currentSectionPeak = GetPeakStrain(time); - } - - /// - /// Retrieves the peak strain at a point in time. - /// - /// The time to retrieve the peak strain at, adjusted by clockrate. - /// The peak strain. - protected virtual double GetPeakStrain(double time) => CurrentStrain * strainDecay(time - Previous[0].StartTime); - - /// - /// Returns a live enumerable of the peak strains for each section of the beatmap, - /// including the peak of the current section. - /// - public IEnumerable GetCurrentStrainPeaks() => strainPeaks.Append(currentSectionPeak); - /// /// Returns the calculated difficulty value representing all s that have been processed up to this point. /// - public double DifficultyValue() - { - double difficulty = 0; - double weight = 1; - - // Difficulty is the weighted sum of the highest strains from every section. - // We're sorting from highest to lowest strain. - foreach (double strain in GetCurrentStrainPeaks().OrderByDescending(d => d)) - { - difficulty += strain * weight; - weight *= DecayWeight; - } - - return difficulty; - } - - /// - /// Calculates the strain value of a . This value is affected by previously processed objects. - /// - protected abstract double StrainValueOf(DifficultyHitObject current); - - private double strainDecay(double ms) => Math.Pow(StrainDecayBase, ms / 1000); + public abstract double DifficultyValue(); } } diff --git a/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs b/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs new file mode 100644 index 0000000000..c324f8e414 --- /dev/null +++ b/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs @@ -0,0 +1,137 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Difficulty.Skills +{ + /// + /// Used to processes strain values of s, keep track of strain levels caused by the processed objects + /// and to calculate a final difficulty value representing the difficulty of hitting all the processed objects. + /// + public abstract class StrainSkill : Skill + { + /// + /// Strain values are multiplied by this number for the given skill. Used to balance the value of different skills between each other. + /// + protected abstract double SkillMultiplier { get; } + + /// + /// Determines how quickly strain decays for the given skill. + /// For example a value of 0.15 indicates that strain decays to 15% of its original value in one second. + /// + protected abstract double StrainDecayBase { get; } + + /// + /// The weight by which each strain value decays. + /// + protected virtual double DecayWeight => 0.9; + + /// + /// The current strain level. + /// + protected double CurrentStrain { get; private set; } = 1; + + /// + /// The length of each strain section. + /// + protected virtual int SectionLength => 400; + + private double currentSectionPeak = 1; // We also keep track of the peak strain level in the current section. + + private double currentSectionEnd; + + private readonly List strainPeaks = new List(); + + protected StrainSkill(Mod[] mods) + : base(mods) + { + } + + /// + /// Process a and update current strain values accordingly. + /// + public sealed override void Process(DifficultyHitObject current) + { + // The first object doesn't generate a strain, so we begin with an incremented section end + if (Previous.Count == 0) + currentSectionEnd = Math.Ceiling(current.StartTime / SectionLength) * SectionLength; + + while (current.StartTime > currentSectionEnd) + { + saveCurrentPeak(); + startNewSectionFrom(currentSectionEnd); + currentSectionEnd += SectionLength; + } + + CurrentStrain *= strainDecay(current.DeltaTime); + CurrentStrain += StrainValueOf(current) * SkillMultiplier; + + currentSectionPeak = Math.Max(CurrentStrain, currentSectionPeak); + + base.Process(current); + } + + /// + /// Saves the current peak strain level to the list of strain peaks, which will be used to calculate an overall difficulty. + /// + private void saveCurrentPeak() + { + strainPeaks.Add(currentSectionPeak); + } + + /// + /// Sets the initial strain level for a new section. + /// + /// The beginning of the new section in milliseconds. + private void startNewSectionFrom(double time) + { + // The maximum strain of the new section is not zero by default, strain decays as usual regardless of section boundaries. + // This means we need to capture the strain level at the beginning of the new section, and use that as the initial peak level. + currentSectionPeak = GetPeakStrain(time); + } + + /// + /// Retrieves the peak strain at a point in time. + /// + /// The time to retrieve the peak strain at. + /// The peak strain. + protected virtual double GetPeakStrain(double time) => CurrentStrain * strainDecay(time - Previous[0].StartTime); + + /// + /// Returns a live enumerable of the peak strains for each section of the beatmap, + /// including the peak of the current section. + /// + public IEnumerable GetCurrentStrainPeaks() => strainPeaks.Append(currentSectionPeak); + + /// + /// Returns the calculated difficulty value representing all s that have been processed up to this point. + /// + public sealed override double DifficultyValue() + { + double difficulty = 0; + double weight = 1; + + // Difficulty is the weighted sum of the highest strains from every section. + // We're sorting from highest to lowest strain. + foreach (double strain in GetCurrentStrainPeaks().OrderByDescending(d => d)) + { + difficulty += strain * weight; + weight *= DecayWeight; + } + + return difficulty; + } + + /// + /// Calculates the strain value of a . This value is affected by previously processed objects. + /// + protected abstract double StrainValueOf(DifficultyHitObject current); + + private double strainDecay(double ms) => Math.Pow(StrainDecayBase, ms / 1000); + } +} From 7d4b0e3f0afde6f3046ce3d0eecdddfaf90561e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 3 Apr 2021 12:34:48 +0200 Subject: [PATCH 1210/1791] Fix editor clock scene not re-enabling beatmap Could interfere with other tests due to causing crashes on attempts to change `Beatmap.Value`. --- osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs index 390198be04..0b1617b6a6 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs @@ -77,5 +77,11 @@ namespace osu.Game.Tests.Visual.Editing AddStep("start clock again", Clock.Start); AddAssert("clock looped to start", () => Clock.IsRunning && Clock.CurrentTime < 500); } + + protected override void Dispose(bool isDisposing) + { + Beatmap.Disabled = false; + base.Dispose(isDisposing); + } } } From b66ba43bc56f27a096912255c147d31453dba659 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 3 Apr 2021 14:02:15 +0200 Subject: [PATCH 1211/1791] Add failing test scene --- .../Visual/Editing/TestSceneEditorSeeking.cs | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 osu.Game.Tests/Visual/Editing/TestSceneEditorSeeking.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSeeking.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSeeking.cs new file mode 100644 index 0000000000..96ce418851 --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSeeking.cs @@ -0,0 +1,79 @@ +// 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.Utils; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Editing +{ + public class TestSceneEditorSeeking : EditorTestScene + { + protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) + { + var beatmap = base.CreateBeatmap(ruleset); + + beatmap.BeatmapInfo.BeatDivisor = 1; + + beatmap.ControlPointInfo = new ControlPointInfo(); + beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 }); + beatmap.ControlPointInfo.Add(2000, new TimingControlPoint { BeatLength = 500 }); + + return beatmap; + } + + [Test] + public void TestSnappedSeeking() + { + AddStep("seek to 0", () => EditorClock.Seek(0)); + AddAssert("time is 0", () => EditorClock.CurrentTime == 0); + + pressAndCheckTime(Key.Right, 1000); + pressAndCheckTime(Key.Right, 2000); + pressAndCheckTime(Key.Right, 2500); + pressAndCheckTime(Key.Right, 3000); + + pressAndCheckTime(Key.Left, 2500); + pressAndCheckTime(Key.Left, 2000); + pressAndCheckTime(Key.Left, 1000); + } + + [Test] + public void TestSnappedSeekingAfterControlPointChange() + { + AddStep("seek to 0", () => EditorClock.Seek(0)); + AddAssert("time is 0", () => EditorClock.CurrentTime == 0); + + pressAndCheckTime(Key.Right, 1000); + pressAndCheckTime(Key.Right, 2000); + pressAndCheckTime(Key.Right, 2500); + pressAndCheckTime(Key.Right, 3000); + + AddStep("remove 2nd timing point", () => + { + EditorBeatmap.BeginChange(); + var group = EditorBeatmap.ControlPointInfo.GroupAt(2000); + EditorBeatmap.ControlPointInfo.RemoveGroup(group); + EditorBeatmap.EndChange(); + }); + + pressAndCheckTime(Key.Left, 2000); + pressAndCheckTime(Key.Left, 1000); + + pressAndCheckTime(Key.Right, 2000); + pressAndCheckTime(Key.Right, 3000); + } + + private void pressAndCheckTime(Key key, double expectedTime) + { + AddStep($"press {key}", () => InputManager.Key(key)); + AddUntilStep($"time is {expectedTime}", () => Precision.AlmostEquals(expectedTime, EditorClock.CurrentTime, 1)); + } + } +} From 4df7ff21c787fc3ebad631fb8d93331903b9c908 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 3 Apr 2021 11:43:47 +0200 Subject: [PATCH 1212/1791] Fix editor arrow seek snapping not updating after control point changes The editor clock, which is responsible for performing the seek, was not aware of changes in control points due to reading from the wrong beatmap. `loadableBeatmap` is not actually changed by any of the editor components; `playableBeatmap` and `editorBeatmap` are. For now this is changed to use `playableBeatmap`. A better follow-up would be to use `editorBeatmap`, but it would probably be best to move the beat snap bindable into `EditorBeatmap` first. --- osu.Game/Screens/Edit/Editor.cs | 32 +++++++++---------- osu.Game/Screens/Edit/EditorClock.cs | 8 ++--- osu.Game/Tests/Visual/EditorClockTestScene.cs | 2 +- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 389eb79797..0759e21382 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -122,22 +122,6 @@ namespace osu.Game.Screens.Edit return; } - 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(loadableBeatmap, beatDivisor) { IsCoupled = false }; - - UpdateClockSource(); - - dependencies.CacheAs(clock); - AddInternal(clock); - - clock.SeekingOrStopped.BindValueChanged(_ => updateSampleDisabledState()); - - // todo: remove caching of this and consume via editorBeatmap? - dependencies.Cache(beatDivisor); - try { playableBeatmap = loadableBeatmap.GetPlayableBeatmap(loadableBeatmap.BeatmapInfo.Ruleset); @@ -154,6 +138,22 @@ namespace osu.Game.Screens.Edit return; } + beatDivisor.Value = playableBeatmap.BeatmapInfo.BeatDivisor; + beatDivisor.BindValueChanged(divisor => playableBeatmap.BeatmapInfo.BeatDivisor = divisor.NewValue); + + // Todo: should probably be done at a DrawableRuleset level to share logic with Player. + clock = new EditorClock(playableBeatmap, beatDivisor) { IsCoupled = false }; + + UpdateClockSource(); + + dependencies.CacheAs(clock); + AddInternal(clock); + + clock.SeekingOrStopped.BindValueChanged(_ => updateSampleDisabledState()); + + // todo: remove caching of this and consume via editorBeatmap? + dependencies.Cache(beatDivisor); + AddInternal(editorBeatmap = new EditorBeatmap(playableBeatmap, loadableBeatmap.Skin)); dependencies.CacheAs(editorBeatmap); changeHandler = new EditorChangeHandler(editorBeatmap); diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index d0197ce1ec..772f6ea192 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -42,12 +42,12 @@ namespace osu.Game.Screens.Edit /// public bool IsSeeking { get; private set; } - public EditorClock(WorkingBeatmap beatmap, BindableBeatDivisor beatDivisor) - : this(beatmap.Beatmap.ControlPointInfo, beatmap.Track.Length, beatDivisor) + public EditorClock(IBeatmap beatmap, BindableBeatDivisor beatDivisor) + : this(beatmap.ControlPointInfo, beatDivisor) { } - public EditorClock(ControlPointInfo controlPointInfo, double trackLength, BindableBeatDivisor beatDivisor) + public EditorClock(ControlPointInfo controlPointInfo, BindableBeatDivisor beatDivisor) { this.beatDivisor = beatDivisor; @@ -57,7 +57,7 @@ namespace osu.Game.Screens.Edit } public EditorClock() - : this(new ControlPointInfo(), 1000, new BindableBeatDivisor()) + : this(new ControlPointInfo(), new BindableBeatDivisor()) { } diff --git a/osu.Game/Tests/Visual/EditorClockTestScene.cs b/osu.Game/Tests/Visual/EditorClockTestScene.cs index 693c9cb792..79cfee8518 100644 --- a/osu.Game/Tests/Visual/EditorClockTestScene.cs +++ b/osu.Game/Tests/Visual/EditorClockTestScene.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Visual protected EditorClockTestScene() { - Clock = new EditorClock(new ControlPointInfo(), 5000, BeatDivisor) { IsCoupled = false }; + Clock = new EditorClock(new ControlPointInfo(), BeatDivisor) { IsCoupled = false }; } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) From bdd1072dcebf7fd88af0198645bcaa98c4218416 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 3 Apr 2021 18:52:50 +0200 Subject: [PATCH 1213/1791] Adjust colours and spacing to be closer to design --- osu.Game/Screens/Edit/Setup/SetupScreen.cs | 4 ++-- osu.Game/Screens/Edit/Setup/SetupSection.cs | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index 1c3cbb7206..de09347d0a 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -22,7 +22,7 @@ namespace osu.Game.Screens.Edit.Setup public SetupScreen() : base(EditorScreenMode.SongSetup) { - ColourProvider = new OverlayColourProvider(OverlayColourScheme.Green); + ColourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); } [BackgroundDependencyLoader] @@ -41,7 +41,7 @@ namespace osu.Game.Screens.Edit.Setup { new Box { - Colour = colours.GreySeafoamDark, + Colour = ColourProvider.Dark4, RelativeSizeAxes = Axes.Both, }, new SectionsContainer diff --git a/osu.Game/Screens/Edit/Setup/SetupSection.cs b/osu.Game/Screens/Edit/Setup/SetupSection.cs index 88521a8fb0..8347ca1157 100644 --- a/osu.Game/Screens/Edit/Setup/SetupSection.cs +++ b/osu.Game/Screens/Edit/Setup/SetupSection.cs @@ -33,6 +33,10 @@ namespace osu.Game.Screens.Edit.Setup RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Spacing = new Vector2(20), + Padding = new MarginPadding + { + Horizontal = 90 + }, Direction = FillDirection.Vertical, }; } From 95d7e6c74b579c4049b0eec9c34b9ad451835a6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 3 Apr 2021 19:02:33 +0200 Subject: [PATCH 1214/1791] Explicitly associate setup sections with titles --- .../Screens/Edit/Setup/DifficultySection.cs | 8 ++--- .../Screens/Edit/Setup/MetadataSection.cs | 8 ++--- .../Screens/Edit/Setup/ResourcesSection.cs | 8 ++--- osu.Game/Screens/Edit/Setup/SetupSection.cs | 35 ++++++++++++++----- 4 files changed, 36 insertions(+), 23 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/DifficultySection.cs b/osu.Game/Screens/Edit/Setup/DifficultySection.cs index 36fb0191b0..493d3ed20c 100644 --- a/osu.Game/Screens/Edit/Setup/DifficultySection.cs +++ b/osu.Game/Screens/Edit/Setup/DifficultySection.cs @@ -5,8 +5,8 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Localisation; using osu.Game.Beatmaps; -using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterfaceV2; namespace osu.Game.Screens.Edit.Setup @@ -18,15 +18,13 @@ namespace osu.Game.Screens.Edit.Setup private LabelledSliderBar approachRateSlider; private LabelledSliderBar overallDifficultySlider; + public override LocalisableString Title => "Difficulty"; + [BackgroundDependencyLoader] private void load() { Children = new Drawable[] { - new OsuSpriteText - { - Text = "Difficulty settings" - }, circleSizeSlider = new LabelledSliderBar { Label = "Object Size", diff --git a/osu.Game/Screens/Edit/Setup/MetadataSection.cs b/osu.Game/Screens/Edit/Setup/MetadataSection.cs index f429164ece..889a5eab5e 100644 --- a/osu.Game/Screens/Edit/Setup/MetadataSection.cs +++ b/osu.Game/Screens/Edit/Setup/MetadataSection.cs @@ -5,7 +5,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.UserInterface; -using osu.Game.Graphics.Sprites; +using osu.Framework.Localisation; using osu.Game.Graphics.UserInterfaceV2; namespace osu.Game.Screens.Edit.Setup @@ -17,15 +17,13 @@ namespace osu.Game.Screens.Edit.Setup private LabelledTextBox creatorTextBox; private LabelledTextBox difficultyTextBox; + public override LocalisableString Title => "Metadata"; + [BackgroundDependencyLoader] private void load() { Children = new Drawable[] { - new OsuSpriteText - { - Text = "Beatmap metadata" - }, artistTextBox = new LabelledTextBox { Label = "Artist", diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index e00eaf9aa2..be8efa18f4 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -11,12 +11,12 @@ using osu.Framework.Bindables; 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.Beatmaps.Drawables; using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays; @@ -27,6 +27,8 @@ namespace osu.Game.Screens.Edit.Setup private LabelledTextBox audioTrackTextBox; private Container backgroundSpriteContainer; + public override LocalisableString Title => "Resources"; + public IEnumerable HandledExtensions => ImageExtensions.Concat(AudioExtensions); public static string[] ImageExtensions { get; } = { ".jpg", ".jpeg", ".png" }; @@ -66,10 +68,6 @@ namespace osu.Game.Screens.Edit.Setup Masking = true, CornerRadius = 10, }, - new OsuSpriteText - { - Text = "Resources" - }, audioTrackTextBox = new FileChooserLabelledTextBox { Label = "Audio Track", diff --git a/osu.Game/Screens/Edit/Setup/SetupSection.cs b/osu.Game/Screens/Edit/Setup/SetupSection.cs index 8347ca1157..fb697eacc6 100644 --- a/osu.Game/Screens/Edit/Setup/SetupSection.cs +++ b/osu.Game/Screens/Edit/Setup/SetupSection.cs @@ -4,12 +4,14 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Localisation; using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; using osuTK; namespace osu.Game.Screens.Edit.Setup { - internal class SetupSection : Container + internal abstract class SetupSection : Container { private readonly FillFlowContainer flow; @@ -21,23 +23,40 @@ namespace osu.Game.Screens.Edit.Setup protected override Container Content => flow; - public SetupSection() + public abstract LocalisableString Title { get; } + + protected SetupSection() { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - Padding = new MarginPadding(10); + Padding = new MarginPadding + { + Vertical = 10, + Horizontal = 90 + }; - InternalChild = flow = new FillFlowContainer + InternalChild = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Spacing = new Vector2(20), - Padding = new MarginPadding - { - Horizontal = 90 - }, Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new OsuSpriteText + { + Font = OsuFont.GetFont(weight: FontWeight.Bold), + Text = Title + }, + flow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(20), + Direction = FillDirection.Vertical, + } + } }; } } From 3572178bdcfbe917ca1ea15dc9b238a340d0734d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 3 Apr 2021 20:02:26 +0200 Subject: [PATCH 1215/1791] Add tab control to setup screen header --- osu.Game/Screens/Edit/Setup/SetupScreen.cs | 22 +--- .../Screens/Edit/Setup/SetupScreenHeader.cs | 106 ++++++++++++++++++ osu.Game/Screens/Edit/Setup/SetupSection.cs | 2 +- 3 files changed, 113 insertions(+), 17 deletions(-) create mode 100644 osu.Game/Screens/Edit/Setup/SetupScreenHeader.cs diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index de09347d0a..dccc69039c 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -13,12 +13,17 @@ namespace osu.Game.Screens.Edit.Setup { public class SetupScreen : EditorScreen { + public const int HORIZONTAL_PADDING = 100; + [Resolved] private OsuColour colours { get; set; } [Cached] protected readonly OverlayColourProvider ColourProvider; + [Cached] + private SectionsContainer sections = new SectionsContainer(); + public SetupScreen() : base(EditorScreenMode.SongSetup) { @@ -44,7 +49,7 @@ namespace osu.Game.Screens.Edit.Setup Colour = ColourProvider.Dark4, RelativeSizeAxes = Axes.Both, }, - new SectionsContainer + sections = new SectionsContainer { FixedHeader = new SetupScreenHeader(), RelativeSizeAxes = Axes.Both, @@ -60,19 +65,4 @@ namespace osu.Game.Screens.Edit.Setup }; } } - - internal class SetupScreenHeader : OverlayHeader - { - protected override OverlayTitle CreateTitle() => new SetupScreenTitle(); - - private class SetupScreenTitle : OverlayTitle - { - public SetupScreenTitle() - { - Title = "beatmap setup"; - Description = "change general settings of your beatmap"; - IconTexture = "Icons/Hexacons/social"; - } - } - } } diff --git a/osu.Game/Screens/Edit/Setup/SetupScreenHeader.cs b/osu.Game/Screens/Edit/Setup/SetupScreenHeader.cs new file mode 100644 index 0000000000..1be833cb9d --- /dev/null +++ b/osu.Game/Screens/Edit/Setup/SetupScreenHeader.cs @@ -0,0 +1,106 @@ +// 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.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics.Containers; +using osu.Game.Overlays; +using osuTK.Graphics; + +namespace osu.Game.Screens.Edit.Setup +{ + internal class SetupScreenHeader : OverlayHeader + { + [Resolved] + private SectionsContainer sections { get; set; } + + private SetupScreenTabControl tabControl; + + protected override OverlayTitle CreateTitle() => new SetupScreenTitle(); + + protected override Drawable CreateContent() => new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + tabControl = new SetupScreenTabControl + { + RelativeSizeAxes = Axes.X, + Height = 30 + } + } + }; + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + tabControl.AccentColour = colourProvider.Highlight1; + tabControl.BackgroundColour = colourProvider.Dark5; + + foreach (var section in sections) + tabControl.AddItem(section); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + sections.SelectedSection.BindValueChanged(section => tabControl.Current.Value = section.NewValue); + tabControl.Current.BindValueChanged(section => + { + if (section.NewValue != sections.SelectedSection.Value) + sections.ScrollTo(section.NewValue); + }); + } + + private class SetupScreenTitle : OverlayTitle + { + public SetupScreenTitle() + { + Title = "beatmap setup"; + Description = "change general settings of your beatmap"; + IconTexture = "Icons/Hexacons/social"; + } + } + + internal class SetupScreenTabControl : OverlayTabControl + { + private readonly Box background; + + public Color4 BackgroundColour + { + get => background.Colour; + set => background.Colour = value; + } + + public SetupScreenTabControl() + { + TabContainer.Margin = new MarginPadding { Horizontal = SetupScreen.HORIZONTAL_PADDING }; + + AddInternal(background = new Box + { + RelativeSizeAxes = Axes.Both, + Depth = 1 + }); + } + + protected override TabItem CreateTabItem(SetupSection value) => new SetupScreenTabItem(value) + { + AccentColour = AccentColour + }; + + private class SetupScreenTabItem : OverlayTabItem + { + public SetupScreenTabItem(SetupSection value) + : base(value) + { + Text.Text = value.Title; + } + } + } + } +} diff --git a/osu.Game/Screens/Edit/Setup/SetupSection.cs b/osu.Game/Screens/Edit/Setup/SetupSection.cs index fb697eacc6..de62c3a468 100644 --- a/osu.Game/Screens/Edit/Setup/SetupSection.cs +++ b/osu.Game/Screens/Edit/Setup/SetupSection.cs @@ -33,7 +33,7 @@ namespace osu.Game.Screens.Edit.Setup Padding = new MarginPadding { Vertical = 10, - Horizontal = 90 + Horizontal = SetupScreen.HORIZONTAL_PADDING }; InternalChild = new FillFlowContainer From 61f9eb51c474e2573bc49f55d6a435f75d598792 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 3 Apr 2021 18:20:40 +0200 Subject: [PATCH 1216/1791] Split background chooser to own component --- .../Screens/Edit/Setup/BackgroundChooser.cs | 145 ++++++++++++++++++ .../Screens/Edit/Setup/ResourcesSection.cs | 86 +---------- 2 files changed, 148 insertions(+), 83 deletions(-) create mode 100644 osu.Game/Screens/Edit/Setup/BackgroundChooser.cs diff --git a/osu.Game/Screens/Edit/Setup/BackgroundChooser.cs b/osu.Game/Screens/Edit/Setup/BackgroundChooser.cs new file mode 100644 index 0000000000..7bdd962ec8 --- /dev/null +++ b/osu.Game/Screens/Edit/Setup/BackgroundChooser.cs @@ -0,0 +1,145 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables; +using osu.Game.Database; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; + +namespace osu.Game.Screens.Edit.Setup +{ + public class BackgroundChooser : CompositeDrawable, ICanAcceptFiles + { + public IEnumerable HandledExtensions => ImageExtensions; + + public static string[] ImageExtensions { get; } = { ".jpg", ".jpeg", ".png" }; + + [Resolved] + private OsuGameBase game { get; set; } + + [Resolved] + private OsuColour colours { get; set; } + + [Resolved] + private BeatmapManager beatmaps { get; set; } + + [Resolved] + private IBindable working { get; set; } + + private readonly Container content; + + public BackgroundChooser() + { + InternalChild = content = new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 10, + }; + } + + [BackgroundDependencyLoader] + private void load() + { + updateBackgroundSprite(); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + game.RegisterImportHandler(this); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + game?.UnregisterImportHandler(this); + } + + Task ICanAcceptFiles.Import(params string[] paths) + { + Schedule(() => + { + var firstFile = new FileInfo(paths.First()); + + ChangeBackgroundImage(firstFile.FullName); + }); + return Task.CompletedTask; + } + + Task ICanAcceptFiles.Import(params ImportTask[] tasks) => throw new NotImplementedException(); + + public bool ChangeBackgroundImage(string path) + { + var info = new FileInfo(path); + + if (!info.Exists) + return false; + + var set = working.Value.BeatmapSetInfo; + + // remove the previous background for now. + // in the future we probably want to check if this is being used elsewhere (other difficulties?) + var oldFile = set.Files.FirstOrDefault(f => f.Filename == working.Value.Metadata.BackgroundFile); + + using (var stream = info.OpenRead()) + { + if (oldFile != null) + beatmaps.ReplaceFile(set, oldFile, stream, info.Name); + else + beatmaps.AddFile(set, stream, info.Name); + } + + working.Value.Metadata.BackgroundFile = info.Name; + updateBackgroundSprite(); + + return true; + } + + private void updateBackgroundSprite() + { + LoadComponentAsync(new BeatmapBackgroundSprite(working.Value) + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + FillMode = FillMode.Fill, + }, background => + { + if (background.Texture != null) + content.Child = background; + else + { + content.Children = new Drawable[] + { + new Box + { + Colour = colours.GreySeafoamDarker, + RelativeSizeAxes = Axes.Both, + }, + new OsuTextFlowContainer(t => t.Font = OsuFont.Default.With(size: 24)) + { + Text = "Drag image here to set beatmap background!", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.X, + } + }; + } + + background.FadeInFromZero(500); + }); + } + } +} diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index be8efa18f4..523ded7aec 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -10,13 +10,9 @@ 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.Localisation; using osu.Game.Beatmaps; -using osu.Game.Beatmaps.Drawables; using osu.Game.Database; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays; @@ -25,13 +21,10 @@ namespace osu.Game.Screens.Edit.Setup internal class ResourcesSection : SetupSection, ICanAcceptFiles { private LabelledTextBox audioTrackTextBox; - private Container backgroundSpriteContainer; public override LocalisableString Title => "Resources"; - public IEnumerable HandledExtensions => ImageExtensions.Concat(AudioExtensions); - - public static string[] ImageExtensions { get; } = { ".jpg", ".jpeg", ".png" }; + public IEnumerable HandledExtensions => AudioExtensions; public static string[] AudioExtensions { get; } = { ".mp3", ".ogg" }; @@ -61,12 +54,10 @@ namespace osu.Game.Screens.Edit.Setup Children = new Drawable[] { - backgroundSpriteContainer = new Container + new BackgroundChooser { RelativeSizeAxes = Axes.X, Height = 250, - Masking = true, - CornerRadius = 10, }, audioTrackTextBox = new FileChooserLabelledTextBox { @@ -79,8 +70,6 @@ namespace osu.Game.Screens.Edit.Setup audioTrackFileChooserContainer, }; - updateBackgroundSprite(); - audioTrackTextBox.Current.BindValueChanged(audioTrackChanged); } @@ -90,14 +79,7 @@ namespace osu.Game.Screens.Edit.Setup { var firstFile = new FileInfo(paths.First()); - if (ImageExtensions.Contains(firstFile.Extension)) - { - ChangeBackgroundImage(firstFile.FullName); - } - else if (AudioExtensions.Contains(firstFile.Extension)) - { - audioTrackTextBox.Text = firstFile.FullName; - } + audioTrackTextBox.Text = firstFile.FullName; }); return Task.CompletedTask; } @@ -110,33 +92,6 @@ namespace osu.Game.Screens.Edit.Setup game.RegisterImportHandler(this); } - public bool ChangeBackgroundImage(string path) - { - var info = new FileInfo(path); - - if (!info.Exists) - return false; - - var set = working.Value.BeatmapSetInfo; - - // remove the previous background for now. - // in the future we probably want to check if this is being used elsewhere (other difficulties?) - var oldFile = set.Files.FirstOrDefault(f => f.Filename == working.Value.Metadata.BackgroundFile); - - using (var stream = info.OpenRead()) - { - if (oldFile != null) - beatmaps.ReplaceFile(set, oldFile, stream, info.Name); - else - beatmaps.AddFile(set, stream, info.Name); - } - - working.Value.Metadata.BackgroundFile = info.Name; - updateBackgroundSprite(); - - return true; - } - protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); @@ -177,40 +132,5 @@ namespace osu.Game.Screens.Edit.Setup if (!ChangeAudioTrack(filePath.NewValue)) audioTrackTextBox.Current.Value = filePath.OldValue; } - - private void updateBackgroundSprite() - { - LoadComponentAsync(new BeatmapBackgroundSprite(working.Value) - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - FillMode = FillMode.Fill, - }, background => - { - if (background.Texture != null) - backgroundSpriteContainer.Child = background; - else - { - backgroundSpriteContainer.Children = new Drawable[] - { - new Box - { - Colour = Colours.GreySeafoamDarker, - RelativeSizeAxes = Axes.Both, - }, - new OsuTextFlowContainer(t => t.Font = OsuFont.Default.With(size: 24)) - { - Text = "Drag image here to set beatmap background!", - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.X, - } - }; - } - - background.FadeInFromZero(500); - }); - } } } From 294d9114260af37de1aced94d4120096144188c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 3 Apr 2021 20:21:36 +0200 Subject: [PATCH 1217/1791] Move background chooser to header --- .../Screens/Edit/Setup/BackgroundChooser.cs | 5 ++--- .../Screens/Edit/Setup/ResourcesSection.cs | 5 ----- .../Screens/Edit/Setup/SetupScreenHeader.cs | 20 +++++++++++++++---- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/BackgroundChooser.cs b/osu.Game/Screens/Edit/Setup/BackgroundChooser.cs index 7bdd962ec8..9137eca334 100644 --- a/osu.Game/Screens/Edit/Setup/BackgroundChooser.cs +++ b/osu.Game/Screens/Edit/Setup/BackgroundChooser.cs @@ -44,8 +44,7 @@ namespace osu.Game.Screens.Edit.Setup InternalChild = content = new Container { RelativeSizeAxes = Axes.Both, - Masking = true, - CornerRadius = 10, + Masking = true }; } @@ -133,7 +132,7 @@ namespace osu.Game.Screens.Edit.Setup Text = "Drag image here to set beatmap background!", Anchor = Anchor.Centre, Origin = Anchor.Centre, - AutoSizeAxes = Axes.X, + AutoSizeAxes = Axes.Both } }; } diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 523ded7aec..3058f99fce 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -54,11 +54,6 @@ namespace osu.Game.Screens.Edit.Setup Children = new Drawable[] { - new BackgroundChooser - { - RelativeSizeAxes = Axes.X, - Height = 250, - }, audioTrackTextBox = new FileChooserLabelledTextBox { Label = "Audio Track", diff --git a/osu.Game/Screens/Edit/Setup/SetupScreenHeader.cs b/osu.Game/Screens/Edit/Setup/SetupScreenHeader.cs index 1be833cb9d..b0533e6f00 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreenHeader.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreenHeader.cs @@ -25,12 +25,24 @@ namespace osu.Game.Screens.Edit.Setup { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Children = new Drawable[] + // reverse flow is used to ensure that the tab control's expandable bars extend over the background chooser. + Child = new ReverseChildIDFillFlowContainer { - tabControl = new SetupScreenTabControl + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] { - RelativeSizeAxes = Axes.X, - Height = 30 + tabControl = new SetupScreenTabControl + { + RelativeSizeAxes = Axes.X, + Height = 30 + }, + new BackgroundChooser + { + RelativeSizeAxes = Axes.X, + Height = 120 + } } } }; From eb26f6f4273c638fac4be82a7a8535bcdf42a820 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 3 Apr 2021 21:45:11 +0200 Subject: [PATCH 1218/1791] Add failing test case --- .../Screens/TestSceneGameplayScreen.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneGameplayScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneGameplayScreen.cs index c1159dc000..522567584d 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneGameplayScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneGameplayScreen.cs @@ -1,9 +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.Linq; +using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Testing; using osu.Game.Tournament.Components; using osu.Game.Tournament.Screens.Gameplay; +using osu.Game.Tournament.Screens.Gameplay.Components; namespace osu.Game.Tournament.Tests.Screens { @@ -18,5 +22,24 @@ namespace osu.Game.Tournament.Tests.Screens Add(new GameplayScreen()); Add(chat); } + + [Test] + public void TestWarmup() + { + checkScoreVisibility(false); + + toggleWarmup(); + checkScoreVisibility(true); + + toggleWarmup(); + checkScoreVisibility(false); + } + + private void checkScoreVisibility(bool visible) + => AddUntilStep($"scores {(visible ? "shown" : "hidden")}", + () => this.ChildrenOfType().All(score => score.Alpha == (visible ? 1 : 0))); + + private void toggleWarmup() + => AddStep("toggle warmup", () => this.ChildrenOfType().First().Click()); } } From 0d9793797ff07ceb9d9046eaa184e9863010967e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 3 Apr 2021 21:46:34 +0200 Subject: [PATCH 1219/1791] Fix scores being initially visible incorrectly in gameplay screen --- .../Screens/Gameplay/Components/MatchHeader.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/MatchHeader.cs b/osu.Game.Tournament/Screens/Gameplay/Components/MatchHeader.cs index d790f4b754..8048425ce1 100644 --- a/osu.Game.Tournament/Screens/Gameplay/Components/MatchHeader.cs +++ b/osu.Game.Tournament/Screens/Gameplay/Components/MatchHeader.cs @@ -95,7 +95,11 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components Origin = Anchor.TopRight, }, }; + } + protected override void LoadComplete() + { + base.LoadComplete(); updateDisplay(); } From 0febefd8eb187dc1f9e79d210ffd89dd17bac569 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 3 Apr 2021 22:24:55 +0200 Subject: [PATCH 1220/1791] Fix scores fading out on entering gameplay screen --- .../Gameplay/Components/TeamDisplay.cs | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs b/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs index 4ba86dcefc..59132bfd2d 100644 --- a/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs +++ b/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs @@ -14,9 +14,21 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components { private readonly TeamScore score; + private bool showScore; + public bool ShowScore { - set => score.FadeTo(value ? 1 : 0, 200); + get => showScore; + set + { + if (showScore == value) + return; + + showScore = value; + + if (IsLoaded) + score.FadeTo(value ? 1 : 0, 200); + } } public TeamDisplay(TournamentTeam team, TeamColour colour, Bindable currentTeamScore, int pointsToWin) @@ -92,5 +104,11 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components } }; } + + protected override void LoadComplete() + { + base.LoadComplete(); + score.Alpha = ShowScore ? 1 : 0; + } } } From d4724f4494e8184c4ad3d9ae6e262fe6f8da69b1 Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Sun, 4 Apr 2021 09:44:45 +0800 Subject: [PATCH 1221/1791] Fix crash --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index da1bbd18c7..795540b65d 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -391,7 +391,7 @@ namespace osu.Game.Online.Leaderboards if (score.Mods.Length > 0 && modsContainer.Any(s => s.IsHovered) && songSelect != null) items.Add(new OsuMenuItem("Use these mods", MenuItemType.Highlighted, () => songSelect.Mods.Value = score.Mods)); - if (score.Files.Count > 0) + if (score.Files?.Count > 0) items.Add(new OsuMenuItem("Export", MenuItemType.Standard, () => scoreManager.Export(score))); if (score.ID != 0) From 5df27ce3d4e1131bcb8567626582bb256ee9b096 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 4 Apr 2021 11:41:40 +0200 Subject: [PATCH 1222/1791] Split out score transform logic to method --- .../Screens/Gameplay/Components/TeamDisplay.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs b/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs index 59132bfd2d..33658115cc 100644 --- a/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs +++ b/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs @@ -27,7 +27,7 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components showScore = value; if (IsLoaded) - score.FadeTo(value ? 1 : 0, 200); + updateDisplay(); } } @@ -108,7 +108,14 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components protected override void LoadComplete() { base.LoadComplete(); - score.Alpha = ShowScore ? 1 : 0; + + updateDisplay(); + FinishTransforms(true); + } + + private void updateDisplay() + { + score.FadeTo(ShowScore ? 1 : 0, 200); } } } From 9394af32f5150ecbc911ee2191cf571a824033e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 4 Apr 2021 12:34:52 +0200 Subject: [PATCH 1223/1791] Move drag & drop support logic to chooser component --- .../Edit/Setup/FileChooserLabelledTextBox.cs | 63 ++++++++++++++----- .../Screens/Edit/Setup/ResourcesSection.cs | 40 +----------- 2 files changed, 51 insertions(+), 52 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs b/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs index 70876bf26c..a33a70af65 100644 --- a/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs +++ b/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs @@ -2,12 +2,16 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.IO; +using System.Linq; +using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; +using osu.Game.Database; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; @@ -17,27 +21,27 @@ namespace osu.Game.Screens.Edit.Setup /// /// A labelled textbox which reveals an inline file chooser when clicked. /// - internal class FileChooserLabelledTextBox : LabelledTextBox + internal class FileChooserLabelledTextBox : LabelledTextBox, ICanAcceptFiles { + private readonly string[] handledExtensions; + public IEnumerable HandledExtensions => handledExtensions; + + /// + /// The target container to display the file chooser in. + /// public Container Target; - private readonly IBindable currentFile = new Bindable(); + private readonly Bindable currentFile = new Bindable(); + + [Resolved] + private OsuGameBase game { get; set; } [Resolved] private SectionsContainer sectionsContainer { get; set; } - public FileChooserLabelledTextBox() + public FileChooserLabelledTextBox(params string[] handledExtensions) { - currentFile.BindValueChanged(onFileSelected); - } - - private void onFileSelected(ValueChangedEvent file) - { - if (file.NewValue == null) - return; - - Target.Clear(); - Current.Value = file.NewValue.FullName; + this.handledExtensions = handledExtensions; } protected override OsuTextBox CreateTextBox() => @@ -54,7 +58,7 @@ namespace osu.Game.Screens.Edit.Setup { FileSelector fileSelector; - Target.Child = fileSelector = new FileSelector(currentFile.Value?.DirectoryName, ResourcesSection.AudioExtensions) + Target.Child = fileSelector = new FileSelector(currentFile.Value?.DirectoryName, handledExtensions) { RelativeSizeAxes = Axes.X, Height = 400, @@ -64,6 +68,37 @@ namespace osu.Game.Screens.Edit.Setup sectionsContainer.ScrollTo(fileSelector); } + protected override void LoadComplete() + { + base.LoadComplete(); + + game.RegisterImportHandler(this); + currentFile.BindValueChanged(onFileSelected); + } + + private void onFileSelected(ValueChangedEvent file) + { + if (file.NewValue == null) + return; + + Target.Clear(); + Current.Value = file.NewValue.FullName; + } + + Task ICanAcceptFiles.Import(params string[] paths) + { + Schedule(() => currentFile.Value = new FileInfo(paths.First())); + return Task.CompletedTask; + } + + Task ICanAcceptFiles.Import(params ImportTask[] tasks) => throw new NotImplementedException(); + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + game.UnregisterImportHandler(this); + } + internal class FileChooserOsuTextBox : OsuTextBox { public Action OnFocused; diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 3058f99fce..b4c7c865e2 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -1,36 +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 System.Collections.Generic; using System.IO; using System.Linq; -using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Localisation; using osu.Game.Beatmaps; -using osu.Game.Database; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays; namespace osu.Game.Screens.Edit.Setup { - internal class ResourcesSection : SetupSection, ICanAcceptFiles + internal class ResourcesSection : SetupSection { private LabelledTextBox audioTrackTextBox; public override LocalisableString Title => "Resources"; - public IEnumerable HandledExtensions => AudioExtensions; - - public static string[] AudioExtensions { get; } = { ".mp3", ".ogg" }; - - [Resolved] - private OsuGameBase game { get; set; } - [Resolved] private MusicController music { get; set; } @@ -54,7 +43,7 @@ namespace osu.Game.Screens.Edit.Setup Children = new Drawable[] { - audioTrackTextBox = new FileChooserLabelledTextBox + audioTrackTextBox = new FileChooserLabelledTextBox(".mp3", ".ogg") { Label = "Audio Track", PlaceholderText = "Click to select a track", @@ -68,31 +57,6 @@ namespace osu.Game.Screens.Edit.Setup audioTrackTextBox.Current.BindValueChanged(audioTrackChanged); } - Task ICanAcceptFiles.Import(params string[] paths) - { - Schedule(() => - { - var firstFile = new FileInfo(paths.First()); - - audioTrackTextBox.Text = firstFile.FullName; - }); - return Task.CompletedTask; - } - - Task ICanAcceptFiles.Import(params ImportTask[] tasks) => throw new NotImplementedException(); - - protected override void LoadComplete() - { - base.LoadComplete(); - game.RegisterImportHandler(this); - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - game?.UnregisterImportHandler(this); - } - public bool ChangeAudioTrack(string path) { var info = new FileInfo(path); From f2d4ca7676b5f3524a241d07b3c6c661dc553b91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 4 Apr 2021 12:50:50 +0200 Subject: [PATCH 1224/1791] Add background chooser text box --- .../Screens/Edit/Setup/BackgroundChooser.cs | 144 ------------------ .../Screens/Edit/Setup/ResourcesSection.cs | 61 +++++++- osu.Game/Screens/Edit/Setup/SetupScreen.cs | 5 +- .../Screens/Edit/Setup/SetupScreenHeader.cs | 4 +- .../Edit/Setup/SetupScreenHeaderBackground.cs | 76 +++++++++ 5 files changed, 139 insertions(+), 151 deletions(-) delete mode 100644 osu.Game/Screens/Edit/Setup/BackgroundChooser.cs create mode 100644 osu.Game/Screens/Edit/Setup/SetupScreenHeaderBackground.cs diff --git a/osu.Game/Screens/Edit/Setup/BackgroundChooser.cs b/osu.Game/Screens/Edit/Setup/BackgroundChooser.cs deleted file mode 100644 index 9137eca334..0000000000 --- a/osu.Game/Screens/Edit/Setup/BackgroundChooser.cs +++ /dev/null @@ -1,144 +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 System.IO; -using System.Linq; -using System.Threading.Tasks; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.Drawables; -using osu.Game.Database; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; - -namespace osu.Game.Screens.Edit.Setup -{ - public class BackgroundChooser : CompositeDrawable, ICanAcceptFiles - { - public IEnumerable HandledExtensions => ImageExtensions; - - public static string[] ImageExtensions { get; } = { ".jpg", ".jpeg", ".png" }; - - [Resolved] - private OsuGameBase game { get; set; } - - [Resolved] - private OsuColour colours { get; set; } - - [Resolved] - private BeatmapManager beatmaps { get; set; } - - [Resolved] - private IBindable working { get; set; } - - private readonly Container content; - - public BackgroundChooser() - { - InternalChild = content = new Container - { - RelativeSizeAxes = Axes.Both, - Masking = true - }; - } - - [BackgroundDependencyLoader] - private void load() - { - updateBackgroundSprite(); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - game.RegisterImportHandler(this); - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - game?.UnregisterImportHandler(this); - } - - Task ICanAcceptFiles.Import(params string[] paths) - { - Schedule(() => - { - var firstFile = new FileInfo(paths.First()); - - ChangeBackgroundImage(firstFile.FullName); - }); - return Task.CompletedTask; - } - - Task ICanAcceptFiles.Import(params ImportTask[] tasks) => throw new NotImplementedException(); - - public bool ChangeBackgroundImage(string path) - { - var info = new FileInfo(path); - - if (!info.Exists) - return false; - - var set = working.Value.BeatmapSetInfo; - - // remove the previous background for now. - // in the future we probably want to check if this is being used elsewhere (other difficulties?) - var oldFile = set.Files.FirstOrDefault(f => f.Filename == working.Value.Metadata.BackgroundFile); - - using (var stream = info.OpenRead()) - { - if (oldFile != null) - beatmaps.ReplaceFile(set, oldFile, stream, info.Name); - else - beatmaps.AddFile(set, stream, info.Name); - } - - working.Value.Metadata.BackgroundFile = info.Name; - updateBackgroundSprite(); - - return true; - } - - private void updateBackgroundSprite() - { - LoadComponentAsync(new BeatmapBackgroundSprite(working.Value) - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - FillMode = FillMode.Fill, - }, background => - { - if (background.Texture != null) - content.Child = background; - else - { - content.Children = new Drawable[] - { - new Box - { - Colour = colours.GreySeafoamDarker, - RelativeSizeAxes = Axes.Both, - }, - new OsuTextFlowContainer(t => t.Font = OsuFont.Default.With(size: 24)) - { - Text = "Drag image here to set beatmap background!", - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both - } - }; - } - - background.FadeInFromZero(500); - }); - } - } -} diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index b4c7c865e2..dda6d79919 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -17,6 +17,7 @@ namespace osu.Game.Screens.Edit.Setup internal class ResourcesSection : SetupSection { private LabelledTextBox audioTrackTextBox; + private LabelledTextBox backgroundTextBox; public override LocalisableString Title => "Resources"; @@ -32,17 +33,26 @@ namespace osu.Game.Screens.Edit.Setup [Resolved(canBeNull: true)] private Editor editor { get; set; } + [Resolved] + private SetupScreenHeader header { get; set; } + [BackgroundDependencyLoader] private void load() { - Container audioTrackFileChooserContainer = new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - }; + Container audioTrackFileChooserContainer = createFileChooserContainer(); + Container backgroundFileChooserContainer = createFileChooserContainer(); Children = new Drawable[] { + backgroundTextBox = new FileChooserLabelledTextBox(".jpg", ".jpeg", ".png") + { + Label = "Background", + PlaceholderText = "Click to select a background image", + Current = { Value = working.Value.Metadata.BackgroundFile }, + Target = backgroundFileChooserContainer, + TabbableContentContainer = this + }, + backgroundFileChooserContainer, audioTrackTextBox = new FileChooserLabelledTextBox(".mp3", ".ogg") { Label = "Audio Track", @@ -54,9 +64,17 @@ namespace osu.Game.Screens.Edit.Setup audioTrackFileChooserContainer, }; + backgroundTextBox.Current.BindValueChanged(backgroundChanged); audioTrackTextBox.Current.BindValueChanged(audioTrackChanged); } + private static Container createFileChooserContainer() => + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }; + public bool ChangeAudioTrack(string path) { var info = new FileInfo(path); @@ -86,6 +104,39 @@ namespace osu.Game.Screens.Edit.Setup return true; } + public bool ChangeBackgroundImage(string path) + { + var info = new FileInfo(path); + + if (!info.Exists) + return false; + + var set = working.Value.BeatmapSetInfo; + + // remove the previous background for now. + // in the future we probably want to check if this is being used elsewhere (other difficulties?) + var oldFile = set.Files.FirstOrDefault(f => f.Filename == working.Value.Metadata.BackgroundFile); + + using (var stream = info.OpenRead()) + { + if (oldFile != null) + beatmaps.ReplaceFile(set, oldFile, stream, info.Name); + else + beatmaps.AddFile(set, stream, info.Name); + } + + working.Value.Metadata.BackgroundFile = info.Name; + header.Background.UpdateBackground(); + + return true; + } + + private void backgroundChanged(ValueChangedEvent filePath) + { + if (!ChangeBackgroundImage(filePath.NewValue)) + backgroundTextBox.Current.Value = filePath.OldValue; + } + private void audioTrackChanged(ValueChangedEvent filePath) { if (!ChangeAudioTrack(filePath.NewValue)) diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index dccc69039c..70671b487c 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -24,6 +24,9 @@ namespace osu.Game.Screens.Edit.Setup [Cached] private SectionsContainer sections = new SectionsContainer(); + [Cached] + private SetupScreenHeader header = new SetupScreenHeader(); + public SetupScreen() : base(EditorScreenMode.SongSetup) { @@ -51,7 +54,7 @@ namespace osu.Game.Screens.Edit.Setup }, sections = new SectionsContainer { - FixedHeader = new SetupScreenHeader(), + FixedHeader = header, RelativeSizeAxes = Axes.Both, Children = new SetupSection[] { diff --git a/osu.Game/Screens/Edit/Setup/SetupScreenHeader.cs b/osu.Game/Screens/Edit/Setup/SetupScreenHeader.cs index b0533e6f00..06aad69afa 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreenHeader.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreenHeader.cs @@ -14,6 +14,8 @@ namespace osu.Game.Screens.Edit.Setup { internal class SetupScreenHeader : OverlayHeader { + public SetupScreenHeaderBackground Background { get; private set; } + [Resolved] private SectionsContainer sections { get; set; } @@ -38,7 +40,7 @@ namespace osu.Game.Screens.Edit.Setup RelativeSizeAxes = Axes.X, Height = 30 }, - new BackgroundChooser + Background = new SetupScreenHeaderBackground { RelativeSizeAxes = Axes.X, Height = 120 diff --git a/osu.Game/Screens/Edit/Setup/SetupScreenHeaderBackground.cs b/osu.Game/Screens/Edit/Setup/SetupScreenHeaderBackground.cs new file mode 100644 index 0000000000..7304323004 --- /dev/null +++ b/osu.Game/Screens/Edit/Setup/SetupScreenHeaderBackground.cs @@ -0,0 +1,76 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; + +namespace osu.Game.Screens.Edit.Setup +{ + public class SetupScreenHeaderBackground : CompositeDrawable + { + [Resolved] + private OsuColour colours { get; set; } + + [Resolved] + private IBindable working { get; set; } + + private readonly Container content; + + public SetupScreenHeaderBackground() + { + InternalChild = content = new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true + }; + } + + [BackgroundDependencyLoader] + private void load() + { + UpdateBackground(); + } + + public void UpdateBackground() + { + LoadComponentAsync(new BeatmapBackgroundSprite(working.Value) + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + FillMode = FillMode.Fill, + }, background => + { + if (background.Texture != null) + content.Child = background; + else + { + content.Children = new Drawable[] + { + new Box + { + Colour = colours.GreySeafoamDarker, + RelativeSizeAxes = Axes.Both, + }, + new OsuTextFlowContainer(t => t.Font = OsuFont.Default.With(size: 24)) + { + Text = "Drag image here to set beatmap background!", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both + } + }; + } + + background.FadeInFromZero(500); + }); + } + } +} From a0f0ae7979727baa1eae01895e7e3606c526eb31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 4 Apr 2021 12:53:51 +0200 Subject: [PATCH 1225/1791] Adjust spacings in resources section --- .../Screens/Edit/Setup/ResourcesSection.cs | 48 +++++++++++++------ 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index dda6d79919..74002b3b1c 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -44,24 +44,42 @@ namespace osu.Game.Screens.Edit.Setup Children = new Drawable[] { - backgroundTextBox = new FileChooserLabelledTextBox(".jpg", ".jpeg", ".png") + new FillFlowContainer { - Label = "Background", - PlaceholderText = "Click to select a background image", - Current = { Value = working.Value.Metadata.BackgroundFile }, - Target = backgroundFileChooserContainer, - TabbableContentContainer = this + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + backgroundTextBox = new FileChooserLabelledTextBox(".jpg", ".jpeg", ".png") + { + Label = "Background", + PlaceholderText = "Click to select a background image", + Current = { Value = working.Value.Metadata.BackgroundFile }, + Target = backgroundFileChooserContainer, + TabbableContentContainer = this + }, + backgroundFileChooserContainer, + } }, - backgroundFileChooserContainer, - audioTrackTextBox = new FileChooserLabelledTextBox(".mp3", ".ogg") + new FillFlowContainer { - Label = "Audio Track", - PlaceholderText = "Click to select a track", - Current = { Value = working.Value.Metadata.AudioFile }, - Target = audioTrackFileChooserContainer, - TabbableContentContainer = this - }, - audioTrackFileChooserContainer, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + audioTrackTextBox = new FileChooserLabelledTextBox(".mp3", ".ogg") + { + Label = "Audio Track", + PlaceholderText = "Click to select a track", + Current = { Value = working.Value.Metadata.AudioFile }, + Target = audioTrackFileChooserContainer, + TabbableContentContainer = this + }, + audioTrackFileChooserContainer, + } + } }; backgroundTextBox.Current.BindValueChanged(backgroundChanged); From 0a1417bc6731bdbddbeb6791b7dfcaf9d2d1c1d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 4 Apr 2021 13:10:12 +0200 Subject: [PATCH 1226/1791] Swap order of background/audio track changing methods Mostly for quality of reviewing (restores previous order) and more consistency overall. --- .../Screens/Edit/Setup/ResourcesSection.cs | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 74002b3b1c..12270f2aa4 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -93,6 +93,33 @@ namespace osu.Game.Screens.Edit.Setup AutoSizeAxes = Axes.Y, }; + public bool ChangeBackgroundImage(string path) + { + var info = new FileInfo(path); + + if (!info.Exists) + return false; + + var set = working.Value.BeatmapSetInfo; + + // remove the previous background for now. + // in the future we probably want to check if this is being used elsewhere (other difficulties?) + var oldFile = set.Files.FirstOrDefault(f => f.Filename == working.Value.Metadata.BackgroundFile); + + using (var stream = info.OpenRead()) + { + if (oldFile != null) + beatmaps.ReplaceFile(set, oldFile, stream, info.Name); + else + beatmaps.AddFile(set, stream, info.Name); + } + + working.Value.Metadata.BackgroundFile = info.Name; + header.Background.UpdateBackground(); + + return true; + } + public bool ChangeAudioTrack(string path) { var info = new FileInfo(path); @@ -122,33 +149,6 @@ namespace osu.Game.Screens.Edit.Setup return true; } - public bool ChangeBackgroundImage(string path) - { - var info = new FileInfo(path); - - if (!info.Exists) - return false; - - var set = working.Value.BeatmapSetInfo; - - // remove the previous background for now. - // in the future we probably want to check if this is being used elsewhere (other difficulties?) - var oldFile = set.Files.FirstOrDefault(f => f.Filename == working.Value.Metadata.BackgroundFile); - - using (var stream = info.OpenRead()) - { - if (oldFile != null) - beatmaps.ReplaceFile(set, oldFile, stream, info.Name); - else - beatmaps.AddFile(set, stream, info.Name); - } - - working.Value.Metadata.BackgroundFile = info.Name; - header.Background.UpdateBackground(); - - return true; - } - private void backgroundChanged(ValueChangedEvent filePath) { if (!ChangeBackgroundImage(filePath.NewValue)) From 5f1f8ec0ef9d666fafa109196644691be877e713 Mon Sep 17 00:00:00 2001 From: Shivam Date: Sun, 4 Apr 2021 14:10:07 +0200 Subject: [PATCH 1227/1791] Fix IPC Source getting read from the incorrect location --- osu.Game.Tournament/Models/StableInfo.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/Models/StableInfo.cs b/osu.Game.Tournament/Models/StableInfo.cs index d390f88d59..2dc47db26d 100644 --- a/osu.Game.Tournament/Models/StableInfo.cs +++ b/osu.Game.Tournament/Models/StableInfo.cs @@ -34,10 +34,10 @@ namespace osu.Game.Tournament.Models TournamentStorage tStorage = (TournamentStorage)storage; this.storage = tStorage.AllTournaments; - if (!storage.Exists(config_path)) + if (!this.storage.Exists(config_path)) return; - using (Stream stream = storage.GetStream(config_path, FileAccess.Read, FileMode.Open)) + using (Stream stream = this.storage.GetStream(config_path, FileAccess.Read, FileMode.Open)) using (var sr = new StreamReader(stream)) { JsonConvert.PopulateObject(sr.ReadToEnd(), this); From 4ee8224f8b624320b52743c2e68986b795002388 Mon Sep 17 00:00:00 2001 From: Shivam Date: Sun, 4 Apr 2021 14:31:08 +0200 Subject: [PATCH 1228/1791] change naming to be less confusing --- osu.Game.Tournament/Models/StableInfo.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tournament/Models/StableInfo.cs b/osu.Game.Tournament/Models/StableInfo.cs index 2dc47db26d..c0538ca587 100644 --- a/osu.Game.Tournament/Models/StableInfo.cs +++ b/osu.Game.Tournament/Models/StableInfo.cs @@ -27,17 +27,17 @@ namespace osu.Game.Tournament.Models private const string config_path = "stable.json"; - private readonly Storage storage; + private readonly Storage configStorage; public StableInfo(Storage storage) { TournamentStorage tStorage = (TournamentStorage)storage; - this.storage = tStorage.AllTournaments; + configStorage = tStorage.AllTournaments; - if (!this.storage.Exists(config_path)) + if (!configStorage.Exists(config_path)) return; - using (Stream stream = this.storage.GetStream(config_path, FileAccess.Read, FileMode.Open)) + using (Stream stream = configStorage.GetStream(config_path, FileAccess.Read, FileMode.Open)) using (var sr = new StreamReader(stream)) { JsonConvert.PopulateObject(sr.ReadToEnd(), this); @@ -46,7 +46,7 @@ namespace osu.Game.Tournament.Models public void SaveChanges() { - using (var stream = storage.GetStream(config_path, FileAccess.Write, FileMode.Create)) + using (var stream = configStorage.GetStream(config_path, FileAccess.Write, FileMode.Create)) using (var sw = new StreamWriter(stream)) { sw.Write(JsonConvert.SerializeObject(this, From 879b1ab046eefd501e82a8d4d4ce4baa124872ac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 4 Apr 2021 21:58:25 +0900 Subject: [PATCH 1229/1791] Avoid unnecessary casts --- osu.Game.Tournament.Tests/TournamentTestScene.cs | 3 ++- osu.Game.Tournament/Models/StableInfo.cs | 5 ++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tournament.Tests/TournamentTestScene.cs b/osu.Game.Tournament.Tests/TournamentTestScene.cs index cdfd19c157..1fa0ffc8e9 100644 --- a/osu.Game.Tournament.Tests/TournamentTestScene.cs +++ b/osu.Game.Tournament.Tests/TournamentTestScene.cs @@ -10,6 +10,7 @@ using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Tests.Visual; +using osu.Game.Tournament.IO; using osu.Game.Tournament.IPC; using osu.Game.Tournament.Models; using osu.Game.Users; @@ -28,7 +29,7 @@ namespace osu.Game.Tournament.Tests protected MatchIPCInfo IPCInfo { get; private set; } = new MatchIPCInfo(); [BackgroundDependencyLoader] - private void load(Storage storage) + private void load(TournamentStorage storage) { Ladder.Ruleset.Value ??= rulesetStore.AvailableRulesets.First(); diff --git a/osu.Game.Tournament/Models/StableInfo.cs b/osu.Game.Tournament/Models/StableInfo.cs index c0538ca587..1ebc81c773 100644 --- a/osu.Game.Tournament/Models/StableInfo.cs +++ b/osu.Game.Tournament/Models/StableInfo.cs @@ -29,10 +29,9 @@ namespace osu.Game.Tournament.Models private readonly Storage configStorage; - public StableInfo(Storage storage) + public StableInfo(TournamentStorage storage) { - TournamentStorage tStorage = (TournamentStorage)storage; - configStorage = tStorage.AllTournaments; + configStorage = storage.AllTournaments; if (!configStorage.Exists(config_path)) return; From 37f8b6220067a03615cb7d487e8429eb9822dbd4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 5 Apr 2021 11:41:40 +0900 Subject: [PATCH 1230/1791] Add ruleset templates structure --- Templates/LICENSE | 21 + Templates/README.md | 21 + .../Rulesets/ruleset-empty/.editorconfig | 27 + Templates/Rulesets/ruleset-empty/.gitignore | 288 ++++++ .../.template.config/template.json | 16 + .../.vscode/launch.json | 31 + .../.vscode/tasks.json | 47 + .../TestSceneOsuGame.cs | 32 + .../TestSceneOsuPlayer.cs | 14 + .../VisualTestRunner.cs | 23 + ...u.Game.Rulesets.EmptyFreeform.Tests.csproj | 26 + .../osu.Game.Rulesets.EmptyFreeform.sln | 96 ++ ...ame.Rulesets.EmptyFreeform.sln.DotSettings | 877 ++++++++++++++++++ .../Beatmaps/EmptyFreeformBeatmapConverter.cs | 35 + .../EmptyFreeformDifficultyCalculator.cs | 30 + .../EmptyFreeformInputManager.cs | 26 + .../EmptyFreeformRuleset.cs | 80 ++ .../Mods/EmptyFreeformModAutoplay.cs | 25 + .../DrawableEmptyFreeformHitObject.cs | 49 + .../Objects/EmptyFreeformHitObject.cs | 20 + .../Replays/EmptyFreeformAutoGenerator.cs | 42 + .../EmptyFreeformFramedReplayInputHandler.cs | 51 + .../Replays/EmptyFreeformReplayFrame.cs | 21 + .../UI/DrawableEmptyFreeformRuleset.cs | 35 + .../UI/EmptyFreeformPlayfield.cs | 22 + .../osu.Game.Rulesets.EmptyFreeform.csproj | 15 + .../Rulesets/ruleset-example/.editorconfig | 27 + Templates/Rulesets/ruleset-example/.gitignore | 288 ++++++ .../.template.config/template.json | 17 + .../.vscode/launch.json | 31 + .../.vscode/tasks.json | 47 + .../TestSceneOsuGame.cs | 32 + .../TestSceneOsuPlayer.cs | 14 + .../VisualTestRunner.cs | 23 + .../osu.Game.Rulesets.Pippidon.Tests.csproj | 26 + .../osu.Game.Rulesets.Pippidon.sln | 96 ++ ...osu.Game.Rulesets.Pippidon.sln.DotSettings | 877 ++++++++++++++++++ .../Beatmaps/PippidonBeatmapConverter.cs | 34 + .../Mods/PippidonModAutoplay.cs | 25 + .../Drawables/DrawablePippidonHitObject.cs | 78 ++ .../Objects/PippidonHitObject.cs | 20 + .../PippidonDifficultyCalculator.cs | 30 + .../PippidonInputManager.cs | 26 + .../PippidonRuleset.cs | 57 ++ .../Replays/PippidonAutoGenerator.cs | 41 + .../PippidonFramedReplayInputHandler.cs | 46 + .../Replays/PippidonReplayFrame.cs | 13 + .../Samples/Gameplay/normal-hitnormal.mp3 | Bin 0 -> 8022 bytes .../Resources/Textures/character.png | Bin 0 -> 78937 bytes .../Resources/Textures/coin.png | Bin 0 -> 3141 bytes .../Scoring/PippidonScoreProcessor.cs | 11 + .../UI/DrawablePippidonRuleset.cs | 37 + .../UI/PippidonCursorContainer.cs | 34 + .../UI/PippidonPlayfield.cs | 24 + .../PippidonPlayfieldAdjustmentContainer.cs | 20 + .../osu.Game.Rulesets.Pippidon.csproj | 15 + .../ruleset-scrolling-empty/.editorconfig | 27 + .../ruleset-scrolling-empty/.gitignore | 288 ++++++ .../.template.config/template.json | 16 + .../.vscode/launch.json | 31 + .../.vscode/tasks.json | 47 + .../TestSceneOsuGame.cs | 32 + .../TestSceneOsuPlayer.cs | 14 + .../VisualTestRunner.cs | 23 + ....Game.Rulesets.EmptyScrolling.Tests.csproj | 26 + .../osu.Game.Rulesets.EmptyScrolling.sln | 96 ++ ...me.Rulesets.EmptyScrolling.sln.DotSettings | 877 ++++++++++++++++++ .../EmptyScrollingBeatmapConverter.cs | 32 + .../EmptyScrollingDifficultyCalculator.cs | 30 + .../EmptyScrollingInputManager.cs | 26 + .../EmptyScrollingRuleset.cs | 57 ++ .../Mods/EmptyScrollingModAutoplay.cs | 25 + .../DrawableEmptyScrollingHitObject.cs | 48 + .../Objects/EmptyScrollingHitObject.cs | 13 + .../Replays/EmptyScrollingAutoGenerator.cs | 41 + .../EmptyScrollingFramedReplayInputHandler.cs | 29 + .../Replays/EmptyScrollingReplayFrame.cs | 19 + .../UI/DrawableEmptyScrollingRuleset.cs | 38 + .../UI/EmptyScrollingPlayfield.cs | 22 + .../osu.Game.Rulesets.EmptyScrolling.csproj | 15 + .../ruleset-scrolling-example/.editorconfig | 27 + .../ruleset-scrolling-example/.gitignore | 288 ++++++ .../.template.config/template.json | 16 + .../.vscode/launch.json | 31 + .../.vscode/tasks.json | 47 + .../TestSceneOsuGame.cs | 32 + .../TestSceneOsuPlayer.cs | 14 + .../VisualTestRunner.cs | 23 + .../osu.Game.Rulesets.Pippidon.Tests.csproj | 26 + .../osu.Game.Rulesets.Pippidon.sln | 96 ++ ...osu.Game.Rulesets.Pippidon.sln.DotSettings | 877 ++++++++++++++++++ .../Beatmaps/PippidonBeatmapConverter.cs | 45 + .../Mods/PippidonModAutoplay.cs | 25 + .../Drawables/DrawablePippidonHitObject.cs | 75 ++ .../Objects/PippidonHitObject.cs | 18 + .../PippidonDifficultyCalculator.cs | 30 + .../PippidonInputManager.cs | 26 + .../PippidonRuleset.cs | 55 ++ .../Replays/PippidonAutoGenerator.cs | 68 ++ .../PippidonFramedReplayInputHandler.cs | 29 + .../Replays/PippidonReplayFrame.cs | 19 + .../Samples/Gameplay/normal-hitnormal.mp3 | Bin 0 -> 8022 bytes .../Resources/Textures/character.png | Bin 0 -> 78937 bytes .../Resources/Textures/coin.png | Bin 0 -> 3141 bytes .../UI/DrawablePippidonRuleset.cs | 38 + .../UI/PippidonCharacter.cs | 87 ++ .../UI/PippidonPlayfield.cs | 128 +++ .../osu.Game.Rulesets.Pippidon.csproj | 15 + Templates/osu.Game.Templates.csproj | 24 + 109 files changed, 7990 insertions(+) create mode 100644 Templates/LICENSE create mode 100644 Templates/README.md create mode 100644 Templates/Rulesets/ruleset-empty/.editorconfig create mode 100644 Templates/Rulesets/ruleset-empty/.gitignore create mode 100644 Templates/Rulesets/ruleset-empty/.template.config/template.json create mode 100644 Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/.vscode/launch.json create mode 100644 Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/.vscode/tasks.json create mode 100644 Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/TestSceneOsuGame.cs create mode 100644 Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/TestSceneOsuPlayer.cs create mode 100644 Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/VisualTestRunner.cs create mode 100644 Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj create mode 100644 Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.sln create mode 100644 Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.sln.DotSettings create mode 100644 Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Beatmaps/EmptyFreeformBeatmapConverter.cs create mode 100644 Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/EmptyFreeformDifficultyCalculator.cs create mode 100644 Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/EmptyFreeformInputManager.cs create mode 100644 Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/EmptyFreeformRuleset.cs create mode 100644 Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Mods/EmptyFreeformModAutoplay.cs create mode 100644 Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Objects/Drawables/DrawableEmptyFreeformHitObject.cs create mode 100644 Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Objects/EmptyFreeformHitObject.cs create mode 100644 Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformAutoGenerator.cs create mode 100644 Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformFramedReplayInputHandler.cs create mode 100644 Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformReplayFrame.cs create mode 100644 Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/UI/DrawableEmptyFreeformRuleset.cs create mode 100644 Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/UI/EmptyFreeformPlayfield.cs create mode 100644 Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/osu.Game.Rulesets.EmptyFreeform.csproj create mode 100644 Templates/Rulesets/ruleset-example/.editorconfig create mode 100644 Templates/Rulesets/ruleset-example/.gitignore create mode 100644 Templates/Rulesets/ruleset-example/.template.config/template.json create mode 100644 Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/.vscode/launch.json create mode 100644 Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/.vscode/tasks.json create mode 100644 Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs create mode 100644 Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuPlayer.cs create mode 100644 Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs create mode 100644 Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj create mode 100644 Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.sln create mode 100644 Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.sln.DotSettings create mode 100644 Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Beatmaps/PippidonBeatmapConverter.cs create mode 100644 Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Mods/PippidonModAutoplay.cs create mode 100644 Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Objects/Drawables/DrawablePippidonHitObject.cs create mode 100644 Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Objects/PippidonHitObject.cs create mode 100644 Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/PippidonDifficultyCalculator.cs create mode 100644 Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/PippidonInputManager.cs create mode 100644 Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/PippidonRuleset.cs create mode 100644 Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonAutoGenerator.cs create mode 100644 Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs create mode 100644 Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonReplayFrame.cs create mode 100644 Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Resources/Samples/Gameplay/normal-hitnormal.mp3 create mode 100644 Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Resources/Textures/character.png create mode 100644 Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Resources/Textures/coin.png create mode 100644 Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Scoring/PippidonScoreProcessor.cs create mode 100644 Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/UI/DrawablePippidonRuleset.cs create mode 100644 Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/UI/PippidonCursorContainer.cs create mode 100644 Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/UI/PippidonPlayfield.cs create mode 100644 Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/UI/PippidonPlayfieldAdjustmentContainer.cs create mode 100644 Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj create mode 100644 Templates/Rulesets/ruleset-scrolling-empty/.editorconfig create mode 100644 Templates/Rulesets/ruleset-scrolling-empty/.gitignore create mode 100644 Templates/Rulesets/ruleset-scrolling-empty/.template.config/template.json create mode 100644 Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/.vscode/launch.json create mode 100644 Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/.vscode/tasks.json create mode 100644 Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/TestSceneOsuGame.cs create mode 100644 Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/TestSceneOsuPlayer.cs create mode 100644 Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/VisualTestRunner.cs create mode 100644 Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj create mode 100644 Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.sln create mode 100644 Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.sln.DotSettings create mode 100644 Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Beatmaps/EmptyScrollingBeatmapConverter.cs create mode 100644 Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/EmptyScrollingDifficultyCalculator.cs create mode 100644 Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/EmptyScrollingInputManager.cs create mode 100644 Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/EmptyScrollingRuleset.cs create mode 100644 Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Mods/EmptyScrollingModAutoplay.cs create mode 100644 Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Objects/Drawables/DrawableEmptyScrollingHitObject.cs create mode 100644 Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Objects/EmptyScrollingHitObject.cs create mode 100644 Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Replays/EmptyScrollingAutoGenerator.cs create mode 100644 Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Replays/EmptyScrollingFramedReplayInputHandler.cs create mode 100644 Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Replays/EmptyScrollingReplayFrame.cs create mode 100644 Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/UI/DrawableEmptyScrollingRuleset.cs create mode 100644 Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/UI/EmptyScrollingPlayfield.cs create mode 100644 Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/osu.Game.Rulesets.EmptyScrolling.csproj create mode 100644 Templates/Rulesets/ruleset-scrolling-example/.editorconfig create mode 100644 Templates/Rulesets/ruleset-scrolling-example/.gitignore create mode 100644 Templates/Rulesets/ruleset-scrolling-example/.template.config/template.json create mode 100644 Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/.vscode/launch.json create mode 100644 Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/.vscode/tasks.json create mode 100644 Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs create mode 100644 Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuPlayer.cs create mode 100644 Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs create mode 100644 Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj create mode 100644 Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.sln create mode 100644 Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.sln.DotSettings create mode 100644 Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Beatmaps/PippidonBeatmapConverter.cs create mode 100644 Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Mods/PippidonModAutoplay.cs create mode 100644 Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Objects/Drawables/DrawablePippidonHitObject.cs create mode 100644 Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Objects/PippidonHitObject.cs create mode 100644 Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/PippidonDifficultyCalculator.cs create mode 100644 Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/PippidonInputManager.cs create mode 100644 Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/PippidonRuleset.cs create mode 100644 Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Replays/PippidonAutoGenerator.cs create mode 100644 Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs create mode 100644 Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Replays/PippidonReplayFrame.cs create mode 100644 Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Resources/Samples/Gameplay/normal-hitnormal.mp3 create mode 100644 Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Resources/Textures/character.png create mode 100644 Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Resources/Textures/coin.png create mode 100644 Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/UI/DrawablePippidonRuleset.cs create mode 100644 Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/UI/PippidonCharacter.cs create mode 100644 Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/UI/PippidonPlayfield.cs create mode 100644 Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj create mode 100644 Templates/osu.Game.Templates.csproj diff --git a/Templates/LICENSE b/Templates/LICENSE new file mode 100644 index 0000000000..3abffc40a7 --- /dev/null +++ b/Templates/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 ppy Pty Ltd . + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/Templates/README.md b/Templates/README.md new file mode 100644 index 0000000000..75ee76ddba --- /dev/null +++ b/Templates/README.md @@ -0,0 +1,21 @@ +# osu-templates + +Templates for use when creating osu! dependent projects. Create a fully-testable (and ready for git) custom ruleset in just two lines. + +## Usage + +```bash +# install (or update) templates package. +# this only needs to be done once +dotnet new -i ppy.osu.Game.Templates + +# create an empty freeform ruleset +dotnet new ruleset -n MyCoolRuleset +# create an empty scrolling ruleset (which provides the basics for a scrolling ←↑→↓ ruleset) +dotnet new ruleset-scrolling -n MyCoolRuleset + +# ..or start with a working sample freeform game +dotnet new ruleset-example -n MyCoolWorkingRuleset +# ..or a working sample scrolling game +dotnet new ruleset-scrolling-example -n MyCoolWorkingRuleset +``` diff --git a/Templates/Rulesets/ruleset-empty/.editorconfig b/Templates/Rulesets/ruleset-empty/.editorconfig new file mode 100644 index 0000000000..24825b7c42 --- /dev/null +++ b/Templates/Rulesets/ruleset-empty/.editorconfig @@ -0,0 +1,27 @@ +# EditorConfig is awesome: http://editorconfig.org +root = true + +[*.cs] +end_of_line = crlf +insert_final_newline = true +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true + +#Roslyn naming styles + +#PascalCase for public and protected members +dotnet_naming_style.pascalcase.capitalization = pascal_case +dotnet_naming_symbols.public_members.applicable_accessibilities = public,internal,protected,protected_internal +dotnet_naming_symbols.public_members.applicable_kinds = property,method,field,event,delegate +dotnet_naming_rule.public_members_pascalcase.severity = suggestion +dotnet_naming_rule.public_members_pascalcase.symbols = public_members +dotnet_naming_rule.public_members_pascalcase.style = pascalcase + +#camelCase for private members +dotnet_naming_style.camelcase.capitalization = camel_case +dotnet_naming_symbols.private_members.applicable_accessibilities = private +dotnet_naming_symbols.private_members.applicable_kinds = property,method,field,event,delegate +dotnet_naming_rule.private_members_camelcase.severity = suggestion +dotnet_naming_rule.private_members_camelcase.symbols = private_members +dotnet_naming_rule.private_members_camelcase.style = camelcase \ No newline at end of file diff --git a/Templates/Rulesets/ruleset-empty/.gitignore b/Templates/Rulesets/ruleset-empty/.gitignore new file mode 100644 index 0000000000..940794e60f --- /dev/null +++ b/Templates/Rulesets/ruleset-empty/.gitignore @@ -0,0 +1,288 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ +**/Properties/launchSettings.json + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Typescript v1 declaration files +typings/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs diff --git a/Templates/Rulesets/ruleset-empty/.template.config/template.json b/Templates/Rulesets/ruleset-empty/.template.config/template.json new file mode 100644 index 0000000000..6bfe2e19dc --- /dev/null +++ b/Templates/Rulesets/ruleset-empty/.template.config/template.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json.schemastore.org/template", + "author": "ppy Pty Ltd", + "classifications": [ + "Console" + ], + "name": "osu! ruleset", + "identity": "ppy.osu.Game.Templates.Rulesets", + "shortName": "ruleset", + "tags": { + "language": "C#", + "type": "project" + }, + "sourceName": "EmptyFreeform", + "preferNameDirectory": true +} diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/.vscode/launch.json b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/.vscode/launch.json new file mode 100644 index 0000000000..fd03878699 --- /dev/null +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/.vscode/launch.json @@ -0,0 +1,31 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "VisualTests (Debug)", + "type": "coreclr", + "request": "launch", + "program": "dotnet", + "args": [ + "${workspaceRoot}/bin/Debug/net5.0/osu.Game.Rulesets.EmptyFreeformRuleset.Tests.dll" + ], + "cwd": "${workspaceRoot}", + "preLaunchTask": "Build (Debug)", + "env": {}, + "console": "internalConsole" + }, + { + "name": "VisualTests (Release)", + "type": "coreclr", + "request": "launch", + "program": "dotnet", + "args": [ + "${workspaceRoot}/bin/Release/net5.0/osu.Game.Rulesets.EmptyFreeformRuleset.Tests.dll" + ], + "cwd": "${workspaceRoot}", + "preLaunchTask": "Build (Release)", + "env": {}, + "console": "internalConsole" + } + ] +} \ No newline at end of file diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/.vscode/tasks.json b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/.vscode/tasks.json new file mode 100644 index 0000000000..509df6a510 --- /dev/null +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/.vscode/tasks.json @@ -0,0 +1,47 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "Build (Debug)", + "type": "shell", + "command": "dotnet", + "args": [ + "build", + "--no-restore", + "osu.Game.Rulesets.EmptyFreeformRuleset.Tests.csproj", + "-p:GenerateFullPaths=true", + "-m", + "-verbosity:m" + ], + "group": "build", + "problemMatcher": "$msCompile" + }, + { + "label": "Build (Release)", + "type": "shell", + "command": "dotnet", + "args": [ + "build", + "--no-restore", + "osu.Game.Rulesets.EmptyFreeformRuleset.Tests.csproj", + "-p:Configuration=Release", + "-p:GenerateFullPaths=true", + "-m", + "-verbosity:m" + ], + "group": "build", + "problemMatcher": "$msCompile" + }, + { + "label": "Restore", + "type": "shell", + "command": "dotnet", + "args": [ + "restore" + ], + "problemMatcher": [] + } + ] +} \ No newline at end of file diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/TestSceneOsuGame.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/TestSceneOsuGame.cs new file mode 100644 index 0000000000..9c512a01ea --- /dev/null +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/TestSceneOsuGame.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.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Platform; +using osu.Game.Tests.Visual; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.EmptyFreeform.Tests +{ + public class TestSceneOsuGame : OsuTestScene + { + [BackgroundDependencyLoader] + private void load(GameHost host, OsuGameBase gameBase) + { + OsuGame game = new OsuGame(); + game.SetHost(host); + + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + }, + game + }; + } + } +} diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/TestSceneOsuPlayer.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/TestSceneOsuPlayer.cs new file mode 100644 index 0000000000..0f2ddf82a5 --- /dev/null +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/TestSceneOsuPlayer.cs @@ -0,0 +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 NUnit.Framework; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.EmptyFreeform.Tests +{ + [TestFixture] + public class TestSceneOsuPlayer : PlayerTestScene + { + protected override Ruleset CreatePlayerRuleset() => new EmptyFreeformRuleset(); + } +} diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/VisualTestRunner.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/VisualTestRunner.cs new file mode 100644 index 0000000000..4f810ce17f --- /dev/null +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/VisualTestRunner.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 System; +using osu.Framework; +using osu.Framework.Platform; +using osu.Game.Tests; + +namespace osu.Game.Rulesets.EmptyFreeform.Tests +{ + public static class VisualTestRunner + { + [STAThread] + public static int Main(string[] args) + { + using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true)) + { + host.Run(new OsuTestBrowser()); + return 0; + } + } + } +} diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj new file mode 100644 index 0000000000..98a32f9b3a --- /dev/null +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj @@ -0,0 +1,26 @@ + + + osu.Game.Rulesets.EmptyFreeform.Tests.VisualTestRunner + + + + + + false + + + + + + + + + + + + + WinExe + net5.0 + osu.Game.Rulesets.EmptyFreeform.Tests + + \ No newline at end of file diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.sln b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.sln new file mode 100644 index 0000000000..706df08472 --- /dev/null +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.sln @@ -0,0 +1,96 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29123.88 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Rulesets.EmptyFreeform", "osu.Game.Rulesets.EmptyFreeform\osu.Game.Rulesets.EmptyFreeform.csproj", "{5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.EmptyFreeform.Tests", "osu.Game.Rulesets.EmptyFreeform.Tests\osu.Game.Rulesets.EmptyFreeform.Tests.csproj", "{B4577C85-CB83-462A-BCE3-22FFEB16311D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + VisualTests|Any CPU = VisualTests|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.Release|Any CPU.Build.0 = Release|Any CPU + {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.VisualTests|Any CPU.ActiveCfg = Release|Any CPU + {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.VisualTests|Any CPU.Build.0 = Release|Any CPU + {B4577C85-CB83-462A-BCE3-22FFEB16311D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B4577C85-CB83-462A-BCE3-22FFEB16311D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B4577C85-CB83-462A-BCE3-22FFEB16311D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B4577C85-CB83-462A-BCE3-22FFEB16311D}.Release|Any CPU.Build.0 = Release|Any CPU + {B4577C85-CB83-462A-BCE3-22FFEB16311D}.VisualTests|Any CPU.ActiveCfg = Debug|Any CPU + {B4577C85-CB83-462A-BCE3-22FFEB16311D}.VisualTests|Any CPU.Build.0 = Debug|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {671B0BEC-2403-45B0-9357-2C97CC517668} + EndGlobalSection + GlobalSection(MonoDevelopProperties) = preSolution + Policies = $0 + $0.TextStylePolicy = $1 + $1.EolMarker = Windows + $1.inheritsSet = VisualStudio + $1.inheritsScope = text/plain + $1.scope = text/x-csharp + $0.CSharpFormattingPolicy = $2 + $2.IndentSwitchSection = True + $2.NewLinesForBracesInProperties = True + $2.NewLinesForBracesInAccessors = True + $2.NewLinesForBracesInAnonymousMethods = True + $2.NewLinesForBracesInControlBlocks = True + $2.NewLinesForBracesInAnonymousTypes = True + $2.NewLinesForBracesInObjectCollectionArrayInitializers = True + $2.NewLinesForBracesInLambdaExpressionBody = True + $2.NewLineForElse = True + $2.NewLineForCatch = True + $2.NewLineForFinally = True + $2.NewLineForMembersInObjectInit = True + $2.NewLineForMembersInAnonymousTypes = True + $2.NewLineForClausesInQuery = True + $2.SpacingAfterMethodDeclarationName = False + $2.SpaceAfterMethodCallName = False + $2.SpaceBeforeOpenSquareBracket = False + $2.inheritsSet = Mono + $2.inheritsScope = text/x-csharp + $2.scope = text/x-csharp + EndGlobalSection + GlobalSection(MonoDevelopProperties) = preSolution + Policies = $0 + $0.TextStylePolicy = $1 + $1.EolMarker = Windows + $1.inheritsSet = VisualStudio + $1.inheritsScope = text/plain + $1.scope = text/x-csharp + $0.CSharpFormattingPolicy = $2 + $2.IndentSwitchSection = True + $2.NewLinesForBracesInProperties = True + $2.NewLinesForBracesInAccessors = True + $2.NewLinesForBracesInAnonymousMethods = True + $2.NewLinesForBracesInControlBlocks = True + $2.NewLinesForBracesInAnonymousTypes = True + $2.NewLinesForBracesInObjectCollectionArrayInitializers = True + $2.NewLinesForBracesInLambdaExpressionBody = True + $2.NewLineForElse = True + $2.NewLineForCatch = True + $2.NewLineForFinally = True + $2.NewLineForMembersInObjectInit = True + $2.NewLineForMembersInAnonymousTypes = True + $2.NewLineForClausesInQuery = True + $2.SpacingAfterMethodDeclarationName = False + $2.SpaceAfterMethodCallName = False + $2.SpaceBeforeOpenSquareBracket = False + $2.inheritsSet = Mono + $2.inheritsScope = text/x-csharp + $2.scope = text/x-csharp + EndGlobalSection +EndGlobal diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.sln.DotSettings b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.sln.DotSettings new file mode 100644 index 0000000000..1cbe36794a --- /dev/null +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.sln.DotSettings @@ -0,0 +1,877 @@ + + True + True + True + True + ExplicitlyExcluded + ExplicitlyExcluded + SOLUTION + WARNING + WARNING + WARNING + HINT + HINT + WARNING + + True + WARNING + WARNING + HINT + HINT + HINT + HINT + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + HINT + WARNING + HINT + SUGGESTION + HINT + HINT + HINT + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + HINT + WARNING + WARNING + HINT + WARNING + WARNING + DO_NOT_SHOW + HINT + WARNING + DO_NOT_SHOW + WARNING + HINT + HINT + HINT + ERROR + WARNING + HINT + HINT + HINT + WARNING + WARNING + HINT + DO_NOT_SHOW + HINT + HINT + HINT + HINT + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + HINT + WARNING + HINT + HINT + HINT + HINT + HINT + WARNING + WARNING + HINT + WARNING + WARNING + WARNING + WARNING + HINT + DO_NOT_SHOW + DO_NOT_SHOW + DO_NOT_SHOW + WARNING + + WARNING + WARNING + WARNING + WARNING + WARNING + ERROR + WARNING + WARNING + HINT + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + HINT + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + HINT + DO_NOT_SHOW + DO_NOT_SHOW + DO_NOT_SHOW + WARNING + WARNING + WARNING + WARNING + WARNING + HINT + WARNING + HINT + HINT + HINT + HINT + HINT + HINT + HINT + + HINT + WARNING + WARNING + WARNING + WARNING + + True + WARNING + WARNING + WARNING + WARNING + WARNING + HINT + HINT + WARNING + WARNING + <?xml version="1.0" encoding="utf-16"?><Profile name="Code Cleanup (peppy)"><CSArrangeThisQualifier>True</CSArrangeThisQualifier><CSUseVar><BehavourStyle>CAN_CHANGE_TO_EXPLICIT</BehavourStyle><LocalVariableStyle>ALWAYS_EXPLICIT</LocalVariableStyle><ForeachVariableStyle>ALWAYS_EXPLICIT</ForeachVariableStyle></CSUseVar><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSUpdateFileHeader>True</CSUpdateFileHeader><CSCodeStyleAttributes ArrangeTypeAccessModifier="False" ArrangeTypeMemberAccessModifier="False" SortModifiers="True" RemoveRedundantParentheses="True" AddMissingParentheses="False" ArrangeBraces="False" ArrangeAttributes="False" ArrangeArgumentsStyle="False" /><XAMLCollapseEmptyFreeformScrollingTags>False</XAMLCollapseEmptyFreeformScrollingTags><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CSArrangeQualifiers>True</CSArrangeQualifiers></Profile> + Code Cleanup (peppy) + ExpressionBody + ExpressionBody + True + True + True + True + True + True + True + True + True + NEXT_LINE + 1 + 1 + NEXT_LINE + 1 + 1 + True + NEVER + NEVER + False + NEVER + False + True + False + False + True + True + False + CHOP_IF_LONG + True + 200 + CHOP_IF_LONG + False + False + AABB + API + BPM + GC + GL + GLSL + HID + HUD + ID + IL + IP + IPC + JIT + LTRB + MD5 + NS + OS + PM + RGB + RNG + SHA + SRGB + TK + SS + PP + GMT + QAT + BNG + UI + False + HINT + <?xml version="1.0" encoding="utf-16"?> +<Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns"> + <TypePattern DisplayName="COM interfaces or structs"> + <TypePattern.Match> + <Or> + <And> + <Kind Is="Interface" /> + <Or> + <HasAttribute Name="System.Runtime.InteropServices.InterfaceTypeAttribute" /> + <HasAttribute Name="System.Runtime.InteropServices.ComImport" /> + </Or> + </And> + <Kind Is="Struct" /> + </Or> + </TypePattern.Match> + </TypePattern> + <TypePattern DisplayName="NUnit Test Fixtures" RemoveRegions="All"> + <TypePattern.Match> + <And> + <Kind Is="Class" /> + <HasAttribute Name="NUnit.Framework.TestFixtureAttribute" Inherited="True" /> + </And> + </TypePattern.Match> + <Entry DisplayName="Setup/Teardown Methods"> + <Entry.Match> + <And> + <Kind Is="Method" /> + <Or> + <HasAttribute Name="NUnit.Framework.SetUpAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.TearDownAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.FixtureSetUpAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.FixtureTearDownAttribute" Inherited="True" /> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="All other members" /> + <Entry Priority="100" DisplayName="Test Methods"> + <Entry.Match> + <And> + <Kind Is="Method" /> + <HasAttribute Name="NUnit.Framework.TestAttribute" /> + </And> + </Entry.Match> + <Entry.SortBy> + <Name /> + </Entry.SortBy> + </Entry> + </TypePattern> + <TypePattern DisplayName="Default Pattern"> + <Group DisplayName="Fields/Properties"> + <Group DisplayName="Public Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Public Properties"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + <Group DisplayName="Internal Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Internal Properties"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + <Group DisplayName="Protected Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Protected Properties"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + <Group DisplayName="Private Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Private Properties"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Constructor/Destructor"> + <Entry DisplayName="Ctor"> + <Entry.Match> + <Kind Is="Constructor" /> + </Entry.Match> + </Entry> + <Region Name="Disposal"> + <Entry DisplayName="Dtor"> + <Entry.Match> + <Kind Is="Destructor" /> + </Entry.Match> + </Entry> + <Entry DisplayName="Dispose()"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Kind Is="Method" /> + <Name Is="Dispose" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Dispose(true)"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Or> + <Virtual /> + <Override /> + </Or> + <Kind Is="Method" /> + <Name Is="Dispose" /> + </And> + </Entry.Match> + </Entry> + </Region> + </Group> + <Group DisplayName="Methods"> + <Group DisplayName="Public"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Internal"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Protected"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Private"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + </Group> + </TypePattern> +</Patterns> + Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. +See the LICENCE file in the repository root for full licence text. + + <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /> + <Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb"><ExtraRule Prefix="_" Suffix="" Style="aaBb" /></Policy> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Private" Description="private methods"><ElementKinds><Kind Name="ASYNC_METHOD" /><Kind Name="METHOD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> + <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public" Description="internal/protected/public methods"><ElementKinds><Kind Name="ASYNC_METHOD" /><Kind Name="METHOD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> + <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Private" Description="private properties"><ElementKinds><Kind Name="PROPERTY" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> + <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public" Description="internal/protected/public properties"><ElementKinds><Kind Name="PROPERTY" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="I" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + True + True + True + True + True + True + True + True + True + True + True + True + o!f – Object Initializer: Anchor&Origin + True + constant("Centre") + 0 + True + True + 2.0 + InCSharpFile + ofao + True + Anchor = Anchor.$anchor$, +Origin = Anchor.$anchor$, + True + True + o!f – InternalChildren = [] + True + True + 2.0 + InCSharpFile + ofic + True + InternalChildren = new Drawable[] +{ + $END$ +}; + True + True + o!f – new GridContainer { .. } + True + True + 2.0 + InCSharpFile + ofgc + True + new GridContainer +{ + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] { $END$ }, + new Drawable[] { } + } +}; + True + True + o!f – new FillFlowContainer { .. } + True + True + 2.0 + InCSharpFile + offf + True + new FillFlowContainer +{ + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + $END$ + } +}, + True + True + o!f – new Container { .. } + True + True + 2.0 + InCSharpFile + ofcont + True + new Container +{ + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + $END$ + } +}, + True + True + o!f – BackgroundDependencyLoader load() + True + True + 2.0 + InCSharpFile + ofbdl + True + [BackgroundDependencyLoader] +private void load() +{ + $END$ +} + True + True + o!f – new Box { .. } + True + True + 2.0 + InCSharpFile + ofbox + True + new Box +{ + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, +}, + True + True + o!f – Children = [] + True + True + 2.0 + InCSharpFile + ofc + True + Children = new Drawable[] +{ + $END$ +}; + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Beatmaps/EmptyFreeformBeatmapConverter.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Beatmaps/EmptyFreeformBeatmapConverter.cs new file mode 100644 index 0000000000..a441438d80 --- /dev/null +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Beatmaps/EmptyFreeformBeatmapConverter.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.Collections.Generic; +using System.Threading; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.EmptyFreeform.Objects; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osuTK; + +namespace osu.Game.Rulesets.EmptyFreeform.Beatmaps +{ + public class EmptyFreeformBeatmapConverter : BeatmapConverter + { + public EmptyFreeformBeatmapConverter(IBeatmap beatmap, Ruleset ruleset) + : base(beatmap, ruleset) + { + } + + // todo: Check for conversion types that should be supported (ie. Beatmap.HitObjects.Any(h => h is IHasXPosition)) + // https://github.com/ppy/osu/tree/master/osu.Game/Rulesets/Objects/Types + public override bool CanConvert() => true; + + protected override IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap, CancellationToken cancellationToken) + { + yield return new EmptyFreeformHitObject + { + Samples = original.Samples, + StartTime = original.StartTime, + Position = (original as IHasPosition)?.Position ?? Vector2.Zero, + }; + } + } +} diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/EmptyFreeformDifficultyCalculator.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/EmptyFreeformDifficultyCalculator.cs new file mode 100644 index 0000000000..59a68245a6 --- /dev/null +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/EmptyFreeformDifficultyCalculator.cs @@ -0,0 +1,30 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Difficulty.Skills; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.EmptyFreeform +{ + public class EmptyFreeformDifficultyCalculator : DifficultyCalculator + { + public EmptyFreeformDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) + : base(ruleset, beatmap) + { + } + + protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) + { + return new DifficultyAttributes(mods, skills, 0); + } + + protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty(); + + protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) => new Skill[0]; + } +} diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/EmptyFreeformInputManager.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/EmptyFreeformInputManager.cs new file mode 100644 index 0000000000..b292a28c0d --- /dev/null +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/EmptyFreeformInputManager.cs @@ -0,0 +1,26 @@ +// 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 osu.Framework.Input.Bindings; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.EmptyFreeform +{ + public class EmptyFreeformInputManager : RulesetInputManager + { + public EmptyFreeformInputManager(RulesetInfo ruleset) + : base(ruleset, 0, SimultaneousBindingMode.Unique) + { + } + } + + public enum EmptyFreeformAction + { + [Description("Button 1")] + Button1, + + [Description("Button 2")] + Button2, + } +} diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/EmptyFreeformRuleset.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/EmptyFreeformRuleset.cs new file mode 100644 index 0000000000..96675e3e99 --- /dev/null +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/EmptyFreeformRuleset.cs @@ -0,0 +1,80 @@ +// 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.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Bindings; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.EmptyFreeform.Beatmaps; +using osu.Game.Rulesets.EmptyFreeform.Mods; +using osu.Game.Rulesets.EmptyFreeform.UI; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.EmptyFreeform +{ + public class EmptyFreeformRuleset : Ruleset + { + public override string Description => "a very emptyfreeformruleset ruleset"; + + public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => + new DrawableEmptyFreeformRuleset(this, beatmap, mods); + + public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => + new EmptyFreeformBeatmapConverter(beatmap, this); + + public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => + new EmptyFreeformDifficultyCalculator(this, beatmap); + + public override IEnumerable GetModsFor(ModType type) + { + switch (type) + { + case ModType.Automation: + return new[] { new EmptyFreeformModAutoplay() }; + + default: + return new Mod[] { null }; + } + } + + public override string ShortName => "emptyfreeformruleset"; + + public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[] + { + new KeyBinding(InputKey.Z, EmptyFreeformAction.Button1), + new KeyBinding(InputKey.X, EmptyFreeformAction.Button2), + }; + + public override Drawable CreateIcon() => new Icon(ShortName[0]); + + public class Icon : CompositeDrawable + { + public Icon(char c) + { + InternalChildren = new Drawable[] + { + new Circle + { + Size = new Vector2(20), + Colour = Color4.White, + }, + new SpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = c.ToString(), + Font = OsuFont.Default.With(size: 18) + } + }; + } + } + } +} diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Mods/EmptyFreeformModAutoplay.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Mods/EmptyFreeformModAutoplay.cs new file mode 100644 index 0000000000..d5c1e9bd15 --- /dev/null +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Mods/EmptyFreeformModAutoplay.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.Collections.Generic; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.EmptyFreeform.Objects; +using osu.Game.Rulesets.EmptyFreeform.Replays; +using osu.Game.Rulesets.Mods; +using osu.Game.Scoring; +using osu.Game.Users; + +namespace osu.Game.Rulesets.EmptyFreeform.Mods +{ + public class EmptyFreeformModAutoplay : ModAutoplay + { + public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score + { + ScoreInfo = new ScoreInfo + { + User = new User { Username = "sample" }, + }, + Replay = new EmptyFreeformAutoGenerator(beatmap).Generate(), + }; + } +} diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Objects/Drawables/DrawableEmptyFreeformHitObject.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Objects/Drawables/DrawableEmptyFreeformHitObject.cs new file mode 100644 index 0000000000..0f38e9fdf8 --- /dev/null +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Objects/Drawables/DrawableEmptyFreeformHitObject.cs @@ -0,0 +1,49 @@ +// 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.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Scoring; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.EmptyFreeform.Objects.Drawables +{ + public class DrawableEmptyFreeformHitObject : DrawableHitObject + { + public DrawableEmptyFreeformHitObject(EmptyFreeformHitObject hitObject) + : base(hitObject) + { + Size = new Vector2(40); + Origin = Anchor.Centre; + + Position = hitObject.Position; + + // todo: add visuals. + } + + protected override void CheckForResult(bool userTriggered, double timeOffset) + { + if (timeOffset >= 0) + // todo: implement judgement logic + ApplyResult(r => r.Type = HitResult.Perfect); + } + + protected override void UpdateHitStateTransforms(ArmedState state) + { + const double duration = 1000; + + switch (state) + { + case ArmedState.Hit: + this.FadeOut(duration, Easing.OutQuint).Expire(); + break; + + case ArmedState.Miss: + this.FadeColour(Color4.Red, duration); + this.FadeOut(duration, Easing.InQuint).Expire(); + break; + } + } + } +} diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Objects/EmptyFreeformHitObject.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Objects/EmptyFreeformHitObject.cs new file mode 100644 index 0000000000..9cd18d2d9f --- /dev/null +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Objects/EmptyFreeformHitObject.cs @@ -0,0 +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.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osuTK; + +namespace osu.Game.Rulesets.EmptyFreeform.Objects +{ + public class EmptyFreeformHitObject : HitObject, IHasPosition + { + public override Judgement CreateJudgement() => new Judgement(); + + public Vector2 Position { get; set; } + + public float X => Position.X; + public float Y => Position.Y; + } +} diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformAutoGenerator.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformAutoGenerator.cs new file mode 100644 index 0000000000..6d8d4215a2 --- /dev/null +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformAutoGenerator.cs @@ -0,0 +1,42 @@ +// 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.Replays; +using osu.Game.Rulesets.EmptyFreeform.Objects; +using osu.Game.Rulesets.Replays; + +namespace osu.Game.Rulesets.EmptyFreeform.Replays +{ + public class EmptyFreeformAutoGenerator : AutoGenerator + { + protected Replay Replay; + protected List Frames => Replay.Frames; + + public new Beatmap Beatmap => (Beatmap)base.Beatmap; + + public EmptyFreeformAutoGenerator(IBeatmap beatmap) + : base(beatmap) + { + Replay = new Replay(); + } + + public override Replay Generate() + { + Frames.Add(new EmptyFreeformReplayFrame()); + + foreach (EmptyFreeformHitObject hitObject in Beatmap.HitObjects) + { + Frames.Add(new EmptyFreeformReplayFrame + { + Time = hitObject.StartTime, + Position = hitObject.Position, + // todo: add required inputs and extra frames. + }); + } + + return Replay; + } + } +} diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformFramedReplayInputHandler.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformFramedReplayInputHandler.cs new file mode 100644 index 0000000000..f25ea6ec62 --- /dev/null +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformFramedReplayInputHandler.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 System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using osu.Framework.Input.StateChanges; +using osu.Framework.Utils; +using osu.Game.Replays; +using osu.Game.Rulesets.Replays; +using osuTK; + +namespace osu.Game.Rulesets.EmptyFreeform.Replays +{ + public class EmptyFreeformFramedReplayInputHandler : FramedReplayInputHandler + { + public EmptyFreeformFramedReplayInputHandler(Replay replay) + : base(replay) + { + } + + protected override bool IsImportant(EmptyFreeformReplayFrame frame) => frame.Actions.Any(); + + protected Vector2 Position + { + get + { + var frame = CurrentFrame; + + if (frame == null) + return Vector2.Zero; + + Debug.Assert(CurrentTime != null); + + return Interpolation.ValueAt(CurrentTime.Value, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time); + } + } + + public override void CollectPendingInputs(List inputs) + { + inputs.Add(new MousePositionAbsoluteInput + { + Position = GamefieldToScreenSpace(Position), + }); + inputs.Add(new ReplayState + { + PressedActions = CurrentFrame?.Actions ?? new List(), + }); + } + } +} diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformReplayFrame.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformReplayFrame.cs new file mode 100644 index 0000000000..c84101ca70 --- /dev/null +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformReplayFrame.cs @@ -0,0 +1,21 @@ +// 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.Rulesets.Replays; +using osuTK; + +namespace osu.Game.Rulesets.EmptyFreeform.Replays +{ + public class EmptyFreeformReplayFrame : ReplayFrame + { + public List Actions = new List(); + public Vector2 Position; + + public EmptyFreeformReplayFrame(EmptyFreeformAction? button = null) + { + if (button.HasValue) + Actions.Add(button.Value); + } + } +} diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/UI/DrawableEmptyFreeformRuleset.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/UI/DrawableEmptyFreeformRuleset.cs new file mode 100644 index 0000000000..290f35f516 --- /dev/null +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/UI/DrawableEmptyFreeformRuleset.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.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Input; +using osu.Game.Beatmaps; +using osu.Game.Input.Handlers; +using osu.Game.Replays; +using osu.Game.Rulesets.EmptyFreeform.Objects; +using osu.Game.Rulesets.EmptyFreeform.Objects.Drawables; +using osu.Game.Rulesets.EmptyFreeform.Replays; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.EmptyFreeform.UI +{ + [Cached] + public class DrawableEmptyFreeformRuleset : DrawableRuleset + { + public DrawableEmptyFreeformRuleset(EmptyFreeformRuleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) + : base(ruleset, beatmap, mods) + { + } + + protected override Playfield CreatePlayfield() => new EmptyFreeformPlayfield(); + + protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new EmptyFreeformFramedReplayInputHandler(replay); + + public override DrawableHitObject CreateDrawableRepresentation(EmptyFreeformHitObject h) => new DrawableEmptyFreeformHitObject(h); + + protected override PassThroughInputManager CreateInputManager() => new EmptyFreeformInputManager(Ruleset?.RulesetInfo); + } +} diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/UI/EmptyFreeformPlayfield.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/UI/EmptyFreeformPlayfield.cs new file mode 100644 index 0000000000..9df5935c45 --- /dev/null +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/UI/EmptyFreeformPlayfield.cs @@ -0,0 +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.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.EmptyFreeform.UI +{ + [Cached] + public class EmptyFreeformPlayfield : Playfield + { + [BackgroundDependencyLoader] + private void load() + { + AddRangeInternal(new Drawable[] + { + HitObjectContainer, + }); + } + } +} diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/osu.Game.Rulesets.EmptyFreeform.csproj b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/osu.Game.Rulesets.EmptyFreeform.csproj new file mode 100644 index 0000000000..26349ed34f --- /dev/null +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/osu.Game.Rulesets.EmptyFreeform.csproj @@ -0,0 +1,15 @@ + + + netstandard2.1 + osu.Game.Rulesets.Sample + Library + AnyCPU + osu.Game.Rulesets.EmptyFreeform + + + + + + + + \ No newline at end of file diff --git a/Templates/Rulesets/ruleset-example/.editorconfig b/Templates/Rulesets/ruleset-example/.editorconfig new file mode 100644 index 0000000000..24825b7c42 --- /dev/null +++ b/Templates/Rulesets/ruleset-example/.editorconfig @@ -0,0 +1,27 @@ +# EditorConfig is awesome: http://editorconfig.org +root = true + +[*.cs] +end_of_line = crlf +insert_final_newline = true +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true + +#Roslyn naming styles + +#PascalCase for public and protected members +dotnet_naming_style.pascalcase.capitalization = pascal_case +dotnet_naming_symbols.public_members.applicable_accessibilities = public,internal,protected,protected_internal +dotnet_naming_symbols.public_members.applicable_kinds = property,method,field,event,delegate +dotnet_naming_rule.public_members_pascalcase.severity = suggestion +dotnet_naming_rule.public_members_pascalcase.symbols = public_members +dotnet_naming_rule.public_members_pascalcase.style = pascalcase + +#camelCase for private members +dotnet_naming_style.camelcase.capitalization = camel_case +dotnet_naming_symbols.private_members.applicable_accessibilities = private +dotnet_naming_symbols.private_members.applicable_kinds = property,method,field,event,delegate +dotnet_naming_rule.private_members_camelcase.severity = suggestion +dotnet_naming_rule.private_members_camelcase.symbols = private_members +dotnet_naming_rule.private_members_camelcase.style = camelcase \ No newline at end of file diff --git a/Templates/Rulesets/ruleset-example/.gitignore b/Templates/Rulesets/ruleset-example/.gitignore new file mode 100644 index 0000000000..940794e60f --- /dev/null +++ b/Templates/Rulesets/ruleset-example/.gitignore @@ -0,0 +1,288 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ +**/Properties/launchSettings.json + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Typescript v1 declaration files +typings/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs diff --git a/Templates/Rulesets/ruleset-example/.template.config/template.json b/Templates/Rulesets/ruleset-example/.template.config/template.json new file mode 100644 index 0000000000..5d2f6f1ebd --- /dev/null +++ b/Templates/Rulesets/ruleset-example/.template.config/template.json @@ -0,0 +1,17 @@ +{ + "$schema": "http://json.schemastore.org/template", + "author": "ppy Pty Ltd", + "classifications": [ + "Console" + ], + "name": "osu! ruleset (pippidon example)", + "identity": "ppy.osu.Game.Templates.Rulesets.Pippidon", + "shortName": "ruleset-example", + "tags": { + "language": "C#", + "type": "project" + }, + "sourceName": "Pippidon", + "preferNameDirectory": true +} + diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/.vscode/launch.json b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/.vscode/launch.json new file mode 100644 index 0000000000..bd9db14259 --- /dev/null +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/.vscode/launch.json @@ -0,0 +1,31 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "VisualTests (Debug)", + "type": "coreclr", + "request": "launch", + "program": "dotnet", + "args": [ + "${workspaceRoot}/bin/Debug/net5.0/osu.Game.Rulesets.Pippidon.Tests.dll" + ], + "cwd": "${workspaceRoot}", + "preLaunchTask": "Build (Debug)", + "env": {}, + "console": "internalConsole" + }, + { + "name": "VisualTests (Release)", + "type": "coreclr", + "request": "launch", + "program": "dotnet", + "args": [ + "${workspaceRoot}/bin/Release/net5.0/osu.Game.Rulesets.Pippidon.Tests.dll" + ], + "cwd": "${workspaceRoot}", + "preLaunchTask": "Build (Release)", + "env": {}, + "console": "internalConsole" + } + ] +} \ No newline at end of file diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/.vscode/tasks.json b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/.vscode/tasks.json new file mode 100644 index 0000000000..0ee07c1036 --- /dev/null +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/.vscode/tasks.json @@ -0,0 +1,47 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "Build (Debug)", + "type": "shell", + "command": "dotnet", + "args": [ + "build", + "--no-restore", + "osu.Game.Rulesets.Pippidon.Tests.csproj", + "-p:GenerateFullPaths=true", + "-m", + "-verbosity:m" + ], + "group": "build", + "problemMatcher": "$msCompile" + }, + { + "label": "Build (Release)", + "type": "shell", + "command": "dotnet", + "args": [ + "build", + "--no-restore", + "osu.Game.Rulesets.Pippidon.Tests.csproj", + "-p:Configuration=Release", + "-p:GenerateFullPaths=true", + "-m", + "-verbosity:m" + ], + "group": "build", + "problemMatcher": "$msCompile" + }, + { + "label": "Restore", + "type": "shell", + "command": "dotnet", + "args": [ + "restore" + ], + "problemMatcher": [] + } + ] +} \ No newline at end of file diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs new file mode 100644 index 0000000000..270d906b01 --- /dev/null +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.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.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Platform; +using osu.Game.Tests.Visual; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Pippidon.Tests +{ + public class TestSceneOsuGame : OsuTestScene + { + [BackgroundDependencyLoader] + private void load(GameHost host, OsuGameBase gameBase) + { + OsuGame game = new OsuGame(); + game.SetHost(host); + + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + }, + game + }; + } + } +} diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuPlayer.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuPlayer.cs new file mode 100644 index 0000000000..f00528900c --- /dev/null +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuPlayer.cs @@ -0,0 +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 NUnit.Framework; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Pippidon.Tests +{ + [TestFixture] + public class TestSceneOsuPlayer : PlayerTestScene + { + protected override Ruleset CreatePlayerRuleset() => new PippidonRuleset(); + } +} diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs new file mode 100644 index 0000000000..fd6bd9b714 --- /dev/null +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.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 System; +using osu.Framework; +using osu.Framework.Platform; +using osu.Game.Tests; + +namespace osu.Game.Rulesets.Pippidon.Tests +{ + public static class VisualTestRunner + { + [STAThread] + public static int Main(string[] args) + { + using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true)) + { + host.Run(new OsuTestBrowser()); + return 0; + } + } + } +} diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj new file mode 100644 index 0000000000..afa7b03536 --- /dev/null +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -0,0 +1,26 @@ + + + osu.Game.Rulesets.Pippidon.Tests.VisualTestRunner + + + + + + false + + + + + + + + + + + + + WinExe + net5.0 + osu.Game.Rulesets.Pippidon.Tests + + \ No newline at end of file diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.sln b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.sln new file mode 100644 index 0000000000..bccffcd7ff --- /dev/null +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.sln @@ -0,0 +1,96 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29123.88 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Rulesets.Pippidon", "osu.Game.Rulesets.Pippidon\osu.Game.Rulesets.Pippidon.csproj", "{5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Pippidon.Tests", "osu.Game.Rulesets.Pippidon.Tests\osu.Game.Rulesets.Pippidon.Tests.csproj", "{B4577C85-CB83-462A-BCE3-22FFEB16311D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + VisualTests|Any CPU = VisualTests|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.Release|Any CPU.Build.0 = Release|Any CPU + {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.VisualTests|Any CPU.ActiveCfg = Release|Any CPU + {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.VisualTests|Any CPU.Build.0 = Release|Any CPU + {B4577C85-CB83-462A-BCE3-22FFEB16311D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B4577C85-CB83-462A-BCE3-22FFEB16311D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B4577C85-CB83-462A-BCE3-22FFEB16311D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B4577C85-CB83-462A-BCE3-22FFEB16311D}.Release|Any CPU.Build.0 = Release|Any CPU + {B4577C85-CB83-462A-BCE3-22FFEB16311D}.VisualTests|Any CPU.ActiveCfg = Debug|Any CPU + {B4577C85-CB83-462A-BCE3-22FFEB16311D}.VisualTests|Any CPU.Build.0 = Debug|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {671B0BEC-2403-45B0-9357-2C97CC517668} + EndGlobalSection + GlobalSection(MonoDevelopProperties) = preSolution + Policies = $0 + $0.TextStylePolicy = $1 + $1.EolMarker = Windows + $1.inheritsSet = VisualStudio + $1.inheritsScope = text/plain + $1.scope = text/x-csharp + $0.CSharpFormattingPolicy = $2 + $2.IndentSwitchSection = True + $2.NewLinesForBracesInProperties = True + $2.NewLinesForBracesInAccessors = True + $2.NewLinesForBracesInAnonymousMethods = True + $2.NewLinesForBracesInControlBlocks = True + $2.NewLinesForBracesInAnonymousTypes = True + $2.NewLinesForBracesInObjectCollectionArrayInitializers = True + $2.NewLinesForBracesInLambdaExpressionBody = True + $2.NewLineForElse = True + $2.NewLineForCatch = True + $2.NewLineForFinally = True + $2.NewLineForMembersInObjectInit = True + $2.NewLineForMembersInAnonymousTypes = True + $2.NewLineForClausesInQuery = True + $2.SpacingAfterMethodDeclarationName = False + $2.SpaceAfterMethodCallName = False + $2.SpaceBeforeOpenSquareBracket = False + $2.inheritsSet = Mono + $2.inheritsScope = text/x-csharp + $2.scope = text/x-csharp + EndGlobalSection + GlobalSection(MonoDevelopProperties) = preSolution + Policies = $0 + $0.TextStylePolicy = $1 + $1.EolMarker = Windows + $1.inheritsSet = VisualStudio + $1.inheritsScope = text/plain + $1.scope = text/x-csharp + $0.CSharpFormattingPolicy = $2 + $2.IndentSwitchSection = True + $2.NewLinesForBracesInProperties = True + $2.NewLinesForBracesInAccessors = True + $2.NewLinesForBracesInAnonymousMethods = True + $2.NewLinesForBracesInControlBlocks = True + $2.NewLinesForBracesInAnonymousTypes = True + $2.NewLinesForBracesInObjectCollectionArrayInitializers = True + $2.NewLinesForBracesInLambdaExpressionBody = True + $2.NewLineForElse = True + $2.NewLineForCatch = True + $2.NewLineForFinally = True + $2.NewLineForMembersInObjectInit = True + $2.NewLineForMembersInAnonymousTypes = True + $2.NewLineForClausesInQuery = True + $2.SpacingAfterMethodDeclarationName = False + $2.SpaceAfterMethodCallName = False + $2.SpaceBeforeOpenSquareBracket = False + $2.inheritsSet = Mono + $2.inheritsScope = text/x-csharp + $2.scope = text/x-csharp + EndGlobalSection +EndGlobal diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.sln.DotSettings b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.sln.DotSettings new file mode 100644 index 0000000000..190a1046f5 --- /dev/null +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.sln.DotSettings @@ -0,0 +1,877 @@ + + True + True + True + True + ExplicitlyExcluded + ExplicitlyExcluded + SOLUTION + WARNING + WARNING + WARNING + HINT + HINT + WARNING + + True + WARNING + WARNING + HINT + HINT + HINT + HINT + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + HINT + WARNING + HINT + SUGGESTION + HINT + HINT + HINT + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + HINT + WARNING + WARNING + HINT + WARNING + WARNING + DO_NOT_SHOW + HINT + WARNING + DO_NOT_SHOW + WARNING + HINT + HINT + HINT + ERROR + WARNING + HINT + HINT + HINT + WARNING + WARNING + HINT + DO_NOT_SHOW + HINT + HINT + HINT + HINT + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + HINT + WARNING + HINT + HINT + HINT + HINT + HINT + WARNING + WARNING + HINT + WARNING + WARNING + WARNING + WARNING + HINT + DO_NOT_SHOW + DO_NOT_SHOW + DO_NOT_SHOW + WARNING + + WARNING + WARNING + WARNING + WARNING + WARNING + ERROR + WARNING + WARNING + HINT + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + HINT + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + HINT + DO_NOT_SHOW + DO_NOT_SHOW + DO_NOT_SHOW + WARNING + WARNING + WARNING + WARNING + WARNING + HINT + WARNING + HINT + HINT + HINT + HINT + HINT + HINT + HINT + + HINT + WARNING + WARNING + WARNING + WARNING + + True + WARNING + WARNING + WARNING + WARNING + WARNING + HINT + HINT + WARNING + WARNING + <?xml version="1.0" encoding="utf-16"?><Profile name="Code Cleanup (peppy)"><CSArrangeThisQualifier>True</CSArrangeThisQualifier><CSUseVar><BehavourStyle>CAN_CHANGE_TO_EXPLICIT</BehavourStyle><LocalVariableStyle>ALWAYS_EXPLICIT</LocalVariableStyle><ForeachVariableStyle>ALWAYS_EXPLICIT</ForeachVariableStyle></CSUseVar><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSUpdateFileHeader>True</CSUpdateFileHeader><CSCodeStyleAttributes ArrangeTypeAccessModifier="False" ArrangeTypeMemberAccessModifier="False" SortModifiers="True" RemoveRedundantParentheses="True" AddMissingParentheses="False" ArrangeBraces="False" ArrangeAttributes="False" ArrangeArgumentsStyle="False" /><XAMLCollapsePippidonScrollingTags>False</XAMLCollapsePippidonScrollingTags><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CSArrangeQualifiers>True</CSArrangeQualifiers></Profile> + Code Cleanup (peppy) + ExpressionBody + ExpressionBody + True + True + True + True + True + True + True + True + True + NEXT_LINE + 1 + 1 + NEXT_LINE + 1 + 1 + True + NEVER + NEVER + False + NEVER + False + True + False + False + True + True + False + CHOP_IF_LONG + True + 200 + CHOP_IF_LONG + False + False + AABB + API + BPM + GC + GL + GLSL + HID + HUD + ID + IL + IP + IPC + JIT + LTRB + MD5 + NS + OS + PM + RGB + RNG + SHA + SRGB + TK + SS + PP + GMT + QAT + BNG + UI + False + HINT + <?xml version="1.0" encoding="utf-16"?> +<Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns"> + <TypePattern DisplayName="COM interfaces or structs"> + <TypePattern.Match> + <Or> + <And> + <Kind Is="Interface" /> + <Or> + <HasAttribute Name="System.Runtime.InteropServices.InterfaceTypeAttribute" /> + <HasAttribute Name="System.Runtime.InteropServices.ComImport" /> + </Or> + </And> + <Kind Is="Struct" /> + </Or> + </TypePattern.Match> + </TypePattern> + <TypePattern DisplayName="NUnit Test Fixtures" RemoveRegions="All"> + <TypePattern.Match> + <And> + <Kind Is="Class" /> + <HasAttribute Name="NUnit.Framework.TestFixtureAttribute" Inherited="True" /> + </And> + </TypePattern.Match> + <Entry DisplayName="Setup/Teardown Methods"> + <Entry.Match> + <And> + <Kind Is="Method" /> + <Or> + <HasAttribute Name="NUnit.Framework.SetUpAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.TearDownAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.FixtureSetUpAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.FixtureTearDownAttribute" Inherited="True" /> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="All other members" /> + <Entry Priority="100" DisplayName="Test Methods"> + <Entry.Match> + <And> + <Kind Is="Method" /> + <HasAttribute Name="NUnit.Framework.TestAttribute" /> + </And> + </Entry.Match> + <Entry.SortBy> + <Name /> + </Entry.SortBy> + </Entry> + </TypePattern> + <TypePattern DisplayName="Default Pattern"> + <Group DisplayName="Fields/Properties"> + <Group DisplayName="Public Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Public Properties"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + <Group DisplayName="Internal Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Internal Properties"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + <Group DisplayName="Protected Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Protected Properties"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + <Group DisplayName="Private Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Private Properties"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Constructor/Destructor"> + <Entry DisplayName="Ctor"> + <Entry.Match> + <Kind Is="Constructor" /> + </Entry.Match> + </Entry> + <Region Name="Disposal"> + <Entry DisplayName="Dtor"> + <Entry.Match> + <Kind Is="Destructor" /> + </Entry.Match> + </Entry> + <Entry DisplayName="Dispose()"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Kind Is="Method" /> + <Name Is="Dispose" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Dispose(true)"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Or> + <Virtual /> + <Override /> + </Or> + <Kind Is="Method" /> + <Name Is="Dispose" /> + </And> + </Entry.Match> + </Entry> + </Region> + </Group> + <Group DisplayName="Methods"> + <Group DisplayName="Public"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Internal"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Protected"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Private"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + </Group> + </TypePattern> +</Patterns> + Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. +See the LICENCE file in the repository root for full licence text. + + <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /> + <Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb"><ExtraRule Prefix="_" Suffix="" Style="aaBb" /></Policy> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Private" Description="private methods"><ElementKinds><Kind Name="ASYNC_METHOD" /><Kind Name="METHOD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> + <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public" Description="internal/protected/public methods"><ElementKinds><Kind Name="ASYNC_METHOD" /><Kind Name="METHOD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> + <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Private" Description="private properties"><ElementKinds><Kind Name="PROPERTY" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> + <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public" Description="internal/protected/public properties"><ElementKinds><Kind Name="PROPERTY" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="I" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + True + True + True + True + True + True + True + True + True + True + True + True + o!f – Object Initializer: Anchor&Origin + True + constant("Centre") + 0 + True + True + 2.0 + InCSharpFile + ofao + True + Anchor = Anchor.$anchor$, +Origin = Anchor.$anchor$, + True + True + o!f – InternalChildren = [] + True + True + 2.0 + InCSharpFile + ofic + True + InternalChildren = new Drawable[] +{ + $END$ +}; + True + True + o!f – new GridContainer { .. } + True + True + 2.0 + InCSharpFile + ofgc + True + new GridContainer +{ + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] { $END$ }, + new Drawable[] { } + } +}; + True + True + o!f – new FillFlowContainer { .. } + True + True + 2.0 + InCSharpFile + offf + True + new FillFlowContainer +{ + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + $END$ + } +}, + True + True + o!f – new Container { .. } + True + True + 2.0 + InCSharpFile + ofcont + True + new Container +{ + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + $END$ + } +}, + True + True + o!f – BackgroundDependencyLoader load() + True + True + 2.0 + InCSharpFile + ofbdl + True + [BackgroundDependencyLoader] +private void load() +{ + $END$ +} + True + True + o!f – new Box { .. } + True + True + 2.0 + InCSharpFile + ofbox + True + new Box +{ + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, +}, + True + True + o!f – Children = [] + True + True + 2.0 + InCSharpFile + ofc + True + Children = new Drawable[] +{ + $END$ +}; + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Beatmaps/PippidonBeatmapConverter.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Beatmaps/PippidonBeatmapConverter.cs new file mode 100644 index 0000000000..a2a4784603 --- /dev/null +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Beatmaps/PippidonBeatmapConverter.cs @@ -0,0 +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.Collections.Generic; +using System.Linq; +using System.Threading; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Pippidon.Objects; +using osuTK; + +namespace osu.Game.Rulesets.Pippidon.Beatmaps +{ + public class PippidonBeatmapConverter : BeatmapConverter + { + public PippidonBeatmapConverter(IBeatmap beatmap, Ruleset ruleset) + : base(beatmap, ruleset) + { + } + + public override bool CanConvert() => Beatmap.HitObjects.All(h => h is IHasPosition); + + protected override IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap, CancellationToken cancellationToken) + { + yield return new PippidonHitObject + { + Samples = original.Samples, + StartTime = original.StartTime, + Position = (original as IHasPosition)?.Position ?? Vector2.Zero, + }; + } + } +} diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Mods/PippidonModAutoplay.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Mods/PippidonModAutoplay.cs new file mode 100644 index 0000000000..8ea334c99c --- /dev/null +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Mods/PippidonModAutoplay.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.Collections.Generic; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Pippidon.Objects; +using osu.Game.Rulesets.Pippidon.Replays; +using osu.Game.Scoring; +using osu.Game.Users; + +namespace osu.Game.Rulesets.Pippidon.Mods +{ + public class PippidonModAutoplay : ModAutoplay + { + public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score + { + ScoreInfo = new ScoreInfo + { + User = new User { Username = "sample" }, + }, + Replay = new PippidonAutoGenerator(beatmap).Generate(), + }; + } +} diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Objects/Drawables/DrawablePippidonHitObject.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Objects/Drawables/DrawablePippidonHitObject.cs new file mode 100644 index 0000000000..399d6adda2 --- /dev/null +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Objects/Drawables/DrawablePippidonHitObject.cs @@ -0,0 +1,78 @@ +// 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.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Game.Audio; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Scoring; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Pippidon.Objects.Drawables +{ + public class DrawablePippidonHitObject : DrawableHitObject + { + private const double time_preempt = 600; + private const double time_fadein = 400; + + public override bool HandlePositionalInput => true; + + public DrawablePippidonHitObject(PippidonHitObject hitObject) + : base(hitObject) + { + Size = new Vector2(80); + + Origin = Anchor.Centre; + Position = hitObject.Position; + } + + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + AddInternal(new Sprite + { + RelativeSizeAxes = Axes.Both, + Texture = textures.Get("coin"), + }); + } + + public override IEnumerable GetSamples() => new[] + { + new HitSampleInfo(HitSampleInfo.HIT_NORMAL, SampleControlPoint.DEFAULT_BANK) + }; + + protected override void CheckForResult(bool userTriggered, double timeOffset) + { + if (timeOffset >= 0) + ApplyResult(r => r.Type = IsHovered ? HitResult.Perfect : HitResult.Miss); + } + + protected override double InitialLifetimeOffset => time_preempt; + + protected override void UpdateInitialTransforms() => this.FadeInFromZero(time_fadein); + + protected override void UpdateHitStateTransforms(ArmedState state) + { + switch (state) + { + case ArmedState.Hit: + this.ScaleTo(5, 1500, Easing.OutQuint).FadeOut(1500, Easing.OutQuint).Expire(); + break; + + case ArmedState.Miss: + const double duration = 1000; + + this.ScaleTo(0.8f, duration, Easing.OutQuint); + this.MoveToOffset(new Vector2(0, 10), duration, Easing.In); + this.FadeColour(Color4.Red.Opacity(0.5f), duration / 2, Easing.OutQuint).Then().FadeOut(duration / 2, Easing.InQuint).Expire(); + break; + } + } + } +} diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Objects/PippidonHitObject.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Objects/PippidonHitObject.cs new file mode 100644 index 0000000000..0c22554e82 --- /dev/null +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Objects/PippidonHitObject.cs @@ -0,0 +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.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osuTK; + +namespace osu.Game.Rulesets.Pippidon.Objects +{ + public class PippidonHitObject : HitObject, IHasPosition + { + public override Judgement CreateJudgement() => new Judgement(); + + public Vector2 Position { get; set; } + + public float X => Position.X; + public float Y => Position.Y; + } +} diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/PippidonDifficultyCalculator.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/PippidonDifficultyCalculator.cs new file mode 100644 index 0000000000..f6340f6c25 --- /dev/null +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/PippidonDifficultyCalculator.cs @@ -0,0 +1,30 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Difficulty.Skills; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Pippidon +{ + public class PippidonDifficultyCalculator : DifficultyCalculator + { + public PippidonDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) + : base(ruleset, beatmap) + { + } + + protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) + { + return new DifficultyAttributes(mods, skills, 0); + } + + protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty(); + + protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) => new Skill[0]; + } +} diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/PippidonInputManager.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/PippidonInputManager.cs new file mode 100644 index 0000000000..aa7fa3188b --- /dev/null +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/PippidonInputManager.cs @@ -0,0 +1,26 @@ +// 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 osu.Framework.Input.Bindings; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Pippidon +{ + public class PippidonInputManager : RulesetInputManager + { + public PippidonInputManager(RulesetInfo ruleset) + : base(ruleset, 0, SimultaneousBindingMode.Unique) + { + } + } + + public enum PippidonAction + { + [Description("Button 1")] + Button1, + + [Description("Button 2")] + Button2, + } +} diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/PippidonRuleset.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/PippidonRuleset.cs new file mode 100644 index 0000000000..89fed791cd --- /dev/null +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/PippidonRuleset.cs @@ -0,0 +1,57 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Framework.Input.Bindings; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Pippidon.Beatmaps; +using osu.Game.Rulesets.Pippidon.Mods; +using osu.Game.Rulesets.Pippidon.UI; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Pippidon +{ + public class PippidonRuleset : Ruleset + { + public override string Description => "gather the osu!coins"; + + public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => + new DrawablePippidonRuleset(this, beatmap, mods); + + public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => + new PippidonBeatmapConverter(beatmap, this); + + public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => + new PippidonDifficultyCalculator(this, beatmap); + + public override IEnumerable GetModsFor(ModType type) + { + switch (type) + { + case ModType.Automation: + return new[] { new PippidonModAutoplay() }; + + default: + return new Mod[] { null }; + } + } + + public override string ShortName => "pippidon"; + + public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[] + { + new KeyBinding(InputKey.Z, PippidonAction.Button1), + new KeyBinding(InputKey.X, PippidonAction.Button2), + }; + + public override Drawable CreateIcon() => new Sprite + { + Texture = new TextureStore(new TextureLoaderStore(CreateResourceStore()), false).Get("Textures/coin"), + }; + } +} diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonAutoGenerator.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonAutoGenerator.cs new file mode 100644 index 0000000000..9c54b82e38 --- /dev/null +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonAutoGenerator.cs @@ -0,0 +1,41 @@ +// 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.Replays; +using osu.Game.Rulesets.Pippidon.Objects; +using osu.Game.Rulesets.Replays; + +namespace osu.Game.Rulesets.Pippidon.Replays +{ + public class PippidonAutoGenerator : AutoGenerator + { + protected Replay Replay; + protected List Frames => Replay.Frames; + + public new Beatmap Beatmap => (Beatmap)base.Beatmap; + + public PippidonAutoGenerator(IBeatmap beatmap) + : base(beatmap) + { + Replay = new Replay(); + } + + public override Replay Generate() + { + Frames.Add(new PippidonReplayFrame()); + + foreach (PippidonHitObject hitObject in Beatmap.HitObjects) + { + Frames.Add(new PippidonReplayFrame + { + Time = hitObject.StartTime, + Position = hitObject.Position, + }); + } + + return Replay; + } + } +} diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs new file mode 100644 index 0000000000..18efa6b885 --- /dev/null +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs @@ -0,0 +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 System.Collections.Generic; +using System.Diagnostics; +using osu.Framework.Input.StateChanges; +using osu.Framework.Utils; +using osu.Game.Replays; +using osu.Game.Rulesets.Replays; +using osuTK; + +namespace osu.Game.Rulesets.Pippidon.Replays +{ + public class PippidonFramedReplayInputHandler : FramedReplayInputHandler + { + public PippidonFramedReplayInputHandler(Replay replay) + : base(replay) + { + } + + protected override bool IsImportant(PippidonReplayFrame frame) => true; + + protected Vector2 Position + { + get + { + var frame = CurrentFrame; + + if (frame == null) + return Vector2.Zero; + + Debug.Assert(CurrentTime != null); + + return NextFrame != null ? Interpolation.ValueAt(CurrentTime.Value, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time) : frame.Position; + } + } + + public override void CollectPendingInputs(List inputs) + { + inputs.Add(new MousePositionAbsoluteInput + { + Position = GamefieldToScreenSpace(Position) + }); + } + } +} diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonReplayFrame.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonReplayFrame.cs new file mode 100644 index 0000000000..949ca160be --- /dev/null +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonReplayFrame.cs @@ -0,0 +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 osu.Game.Rulesets.Replays; +using osuTK; + +namespace osu.Game.Rulesets.Pippidon.Replays +{ + public class PippidonReplayFrame : ReplayFrame + { + public Vector2 Position; + } +} diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Resources/Samples/Gameplay/normal-hitnormal.mp3 b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Resources/Samples/Gameplay/normal-hitnormal.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..90b13d1f734a4020945bed6706f6a86228b87938 GIT binary patch literal 8022 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 literal 0 HcmV?d00001 diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Scoring/PippidonScoreProcessor.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Scoring/PippidonScoreProcessor.cs new file mode 100644 index 0000000000..1c4fe698c2 --- /dev/null +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Scoring/PippidonScoreProcessor.cs @@ -0,0 +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.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Pippidon.Scoring +{ + public class PippidonScoreProcessor : ScoreProcessor + { + } +} diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/UI/DrawablePippidonRuleset.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/UI/DrawablePippidonRuleset.cs new file mode 100644 index 0000000000..d923963bef --- /dev/null +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/UI/DrawablePippidonRuleset.cs @@ -0,0 +1,37 @@ +// 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.Framework.Allocation; +using osu.Framework.Input; +using osu.Game.Beatmaps; +using osu.Game.Input.Handlers; +using osu.Game.Replays; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Pippidon.Objects; +using osu.Game.Rulesets.Pippidon.Objects.Drawables; +using osu.Game.Rulesets.Pippidon.Replays; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Pippidon.UI +{ + [Cached] + public class DrawablePippidonRuleset : DrawableRuleset + { + public DrawablePippidonRuleset(PippidonRuleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) + : base(ruleset, beatmap, mods) + { + } + + public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new PippidonPlayfieldAdjustmentContainer(); + + protected override Playfield CreatePlayfield() => new PippidonPlayfield(); + + protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new PippidonFramedReplayInputHandler(replay); + + public override DrawableHitObject CreateDrawableRepresentation(PippidonHitObject h) => new DrawablePippidonHitObject(h); + + protected override PassThroughInputManager CreateInputManager() => new PippidonInputManager(Ruleset?.RulesetInfo); + } +} diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/UI/PippidonCursorContainer.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/UI/PippidonCursorContainer.cs new file mode 100644 index 0000000000..9de3f4ba14 --- /dev/null +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/UI/PippidonCursorContainer.cs @@ -0,0 +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 osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Game.Rulesets.UI; +using osuTK; + +namespace osu.Game.Rulesets.Pippidon.UI +{ + public class PippidonCursorContainer : GameplayCursorContainer + { + private Sprite cursorSprite; + private Texture cursorTexture; + + protected override Drawable CreateCursor() => cursorSprite = new Sprite + { + Scale = new Vector2(0.5f), + Origin = Anchor.Centre, + Texture = cursorTexture, + }; + + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + cursorTexture = textures.Get("character"); + + if (cursorSprite != null) + cursorSprite.Texture = cursorTexture; + } + } +} diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/UI/PippidonPlayfield.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/UI/PippidonPlayfield.cs new file mode 100644 index 0000000000..b5a97c5ea3 --- /dev/null +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/UI/PippidonPlayfield.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.Allocation; +using osu.Framework.Graphics; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Pippidon.UI +{ + [Cached] + public class PippidonPlayfield : Playfield + { + protected override GameplayCursorContainer CreateCursor() => new PippidonCursorContainer(); + + [BackgroundDependencyLoader] + private void load() + { + AddRangeInternal(new Drawable[] + { + HitObjectContainer, + }); + } + } +} diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/UI/PippidonPlayfieldAdjustmentContainer.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/UI/PippidonPlayfieldAdjustmentContainer.cs new file mode 100644 index 0000000000..9236683827 --- /dev/null +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/UI/PippidonPlayfieldAdjustmentContainer.cs @@ -0,0 +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; +using osu.Game.Rulesets.UI; +using osuTK; + +namespace osu.Game.Rulesets.Pippidon.UI +{ + public class PippidonPlayfieldAdjustmentContainer : PlayfieldAdjustmentContainer + { + public PippidonPlayfieldAdjustmentContainer() + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + Size = new Vector2(0.8f); + } + } +} diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj new file mode 100644 index 0000000000..e4a3d39d6d --- /dev/null +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj @@ -0,0 +1,15 @@ + + + netstandard2.1 + osu.Game.Rulesets.Sample + Library + AnyCPU + osu.Game.Rulesets.Pippidon + + + + + + + + \ No newline at end of file diff --git a/Templates/Rulesets/ruleset-scrolling-empty/.editorconfig b/Templates/Rulesets/ruleset-scrolling-empty/.editorconfig new file mode 100644 index 0000000000..24825b7c42 --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-empty/.editorconfig @@ -0,0 +1,27 @@ +# EditorConfig is awesome: http://editorconfig.org +root = true + +[*.cs] +end_of_line = crlf +insert_final_newline = true +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true + +#Roslyn naming styles + +#PascalCase for public and protected members +dotnet_naming_style.pascalcase.capitalization = pascal_case +dotnet_naming_symbols.public_members.applicable_accessibilities = public,internal,protected,protected_internal +dotnet_naming_symbols.public_members.applicable_kinds = property,method,field,event,delegate +dotnet_naming_rule.public_members_pascalcase.severity = suggestion +dotnet_naming_rule.public_members_pascalcase.symbols = public_members +dotnet_naming_rule.public_members_pascalcase.style = pascalcase + +#camelCase for private members +dotnet_naming_style.camelcase.capitalization = camel_case +dotnet_naming_symbols.private_members.applicable_accessibilities = private +dotnet_naming_symbols.private_members.applicable_kinds = property,method,field,event,delegate +dotnet_naming_rule.private_members_camelcase.severity = suggestion +dotnet_naming_rule.private_members_camelcase.symbols = private_members +dotnet_naming_rule.private_members_camelcase.style = camelcase \ No newline at end of file diff --git a/Templates/Rulesets/ruleset-scrolling-empty/.gitignore b/Templates/Rulesets/ruleset-scrolling-empty/.gitignore new file mode 100644 index 0000000000..940794e60f --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-empty/.gitignore @@ -0,0 +1,288 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ +**/Properties/launchSettings.json + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Typescript v1 declaration files +typings/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs diff --git a/Templates/Rulesets/ruleset-scrolling-empty/.template.config/template.json b/Templates/Rulesets/ruleset-scrolling-empty/.template.config/template.json new file mode 100644 index 0000000000..3eb99a1f9d --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-empty/.template.config/template.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json.schemastore.org/template", + "author": "ppy Pty Ltd", + "classifications": [ + "Console" + ], + "name": "osu! ruleset (scrolling)", + "identity": "ppy.osu.Game.Templates.Rulesets.Scrolling", + "shortName": "ruleset-scrolling", + "tags": { + "language": "C#", + "type": "project" + }, + "sourceName": "EmptyScrolling", + "preferNameDirectory": true +} \ No newline at end of file diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/.vscode/launch.json b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/.vscode/launch.json new file mode 100644 index 0000000000..24e4873ed6 --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/.vscode/launch.json @@ -0,0 +1,31 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "VisualTests (Debug)", + "type": "coreclr", + "request": "launch", + "program": "dotnet", + "args": [ + "${workspaceRoot}/bin/Debug/net5.0/osu.Game.Rulesets.EmptyScrolling.Tests.dll" + ], + "cwd": "${workspaceRoot}", + "preLaunchTask": "Build (Debug)", + "env": {}, + "console": "internalConsole" + }, + { + "name": "VisualTests (Release)", + "type": "coreclr", + "request": "launch", + "program": "dotnet", + "args": [ + "${workspaceRoot}/bin/Release/net5.0/osu.Game.Rulesets.EmptyScrolling.Tests.dll" + ], + "cwd": "${workspaceRoot}", + "preLaunchTask": "Build (Release)", + "env": {}, + "console": "internalConsole" + } + ] +} \ No newline at end of file diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/.vscode/tasks.json b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/.vscode/tasks.json new file mode 100644 index 0000000000..00d0dc7d9b --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/.vscode/tasks.json @@ -0,0 +1,47 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "Build (Debug)", + "type": "shell", + "command": "dotnet", + "args": [ + "build", + "--no-restore", + "osu.Game.Rulesets.EmptyScrolling.Tests.csproj", + "-p:GenerateFullPaths=true", + "-m", + "-verbosity:m" + ], + "group": "build", + "problemMatcher": "$msCompile" + }, + { + "label": "Build (Release)", + "type": "shell", + "command": "dotnet", + "args": [ + "build", + "--no-restore", + "osu.Game.Rulesets.EmptyScrolling.Tests.csproj", + "-p:Configuration=Release", + "-p:GenerateFullPaths=true", + "-m", + "-verbosity:m" + ], + "group": "build", + "problemMatcher": "$msCompile" + }, + { + "label": "Restore", + "type": "shell", + "command": "dotnet", + "args": [ + "restore" + ], + "problemMatcher": [] + } + ] +} \ No newline at end of file diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/TestSceneOsuGame.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/TestSceneOsuGame.cs new file mode 100644 index 0000000000..aed6abb6bf --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/TestSceneOsuGame.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.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Platform; +using osu.Game.Tests.Visual; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.EmptyScrolling.Tests +{ + public class TestSceneOsuGame : OsuTestScene + { + [BackgroundDependencyLoader] + private void load(GameHost host, OsuGameBase gameBase) + { + OsuGame game = new OsuGame(); + game.SetHost(host); + + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + }, + game + }; + } + } +} diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/TestSceneOsuPlayer.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/TestSceneOsuPlayer.cs new file mode 100644 index 0000000000..9460576196 --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/TestSceneOsuPlayer.cs @@ -0,0 +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 NUnit.Framework; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.EmptyScrolling.Tests +{ + [TestFixture] + public class TestSceneOsuPlayer : PlayerTestScene + { + protected override Ruleset CreatePlayerRuleset() => new EmptyScrollingRuleset(); + } +} diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/VisualTestRunner.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/VisualTestRunner.cs new file mode 100644 index 0000000000..65cfb2bff4 --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/VisualTestRunner.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 System; +using osu.Framework; +using osu.Framework.Platform; +using osu.Game.Tests; + +namespace osu.Game.Rulesets.EmptyScrolling.Tests +{ + public static class VisualTestRunner + { + [STAThread] + public static int Main(string[] args) + { + using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true)) + { + host.Run(new OsuTestBrowser()); + return 0; + } + } + } +} diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj new file mode 100644 index 0000000000..c9f87a8551 --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj @@ -0,0 +1,26 @@ + + + osu.Game.Rulesets.EmptyScrolling.Tests.VisualTestRunner + + + + + + false + + + + + + + + + + + + + WinExe + net5.0 + osu.Game.Rulesets.EmptyScrolling.Tests + + \ No newline at end of file diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.sln b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.sln new file mode 100644 index 0000000000..97361e1a7b --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.sln @@ -0,0 +1,96 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29123.88 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Rulesets.EmptyScrolling", "osu.Game.Rulesets.EmptyScrolling\osu.Game.Rulesets.EmptyScrolling.csproj", "{5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.EmptyScrolling.Tests", "osu.Game.Rulesets.EmptyScrolling.Tests\osu.Game.Rulesets.EmptyScrolling.Tests.csproj", "{B4577C85-CB83-462A-BCE3-22FFEB16311D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + VisualTests|Any CPU = VisualTests|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.Release|Any CPU.Build.0 = Release|Any CPU + {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.VisualTests|Any CPU.ActiveCfg = Release|Any CPU + {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.VisualTests|Any CPU.Build.0 = Release|Any CPU + {B4577C85-CB83-462A-BCE3-22FFEB16311D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B4577C85-CB83-462A-BCE3-22FFEB16311D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B4577C85-CB83-462A-BCE3-22FFEB16311D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B4577C85-CB83-462A-BCE3-22FFEB16311D}.Release|Any CPU.Build.0 = Release|Any CPU + {B4577C85-CB83-462A-BCE3-22FFEB16311D}.VisualTests|Any CPU.ActiveCfg = Debug|Any CPU + {B4577C85-CB83-462A-BCE3-22FFEB16311D}.VisualTests|Any CPU.Build.0 = Debug|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {671B0BEC-2403-45B0-9357-2C97CC517668} + EndGlobalSection + GlobalSection(MonoDevelopProperties) = preSolution + Policies = $0 + $0.TextStylePolicy = $1 + $1.EolMarker = Windows + $1.inheritsSet = VisualStudio + $1.inheritsScope = text/plain + $1.scope = text/x-csharp + $0.CSharpFormattingPolicy = $2 + $2.IndentSwitchSection = True + $2.NewLinesForBracesInProperties = True + $2.NewLinesForBracesInAccessors = True + $2.NewLinesForBracesInAnonymousMethods = True + $2.NewLinesForBracesInControlBlocks = True + $2.NewLinesForBracesInAnonymousTypes = True + $2.NewLinesForBracesInObjectCollectionArrayInitializers = True + $2.NewLinesForBracesInLambdaExpressionBody = True + $2.NewLineForElse = True + $2.NewLineForCatch = True + $2.NewLineForFinally = True + $2.NewLineForMembersInObjectInit = True + $2.NewLineForMembersInAnonymousTypes = True + $2.NewLineForClausesInQuery = True + $2.SpacingAfterMethodDeclarationName = False + $2.SpaceAfterMethodCallName = False + $2.SpaceBeforeOpenSquareBracket = False + $2.inheritsSet = Mono + $2.inheritsScope = text/x-csharp + $2.scope = text/x-csharp + EndGlobalSection + GlobalSection(MonoDevelopProperties) = preSolution + Policies = $0 + $0.TextStylePolicy = $1 + $1.EolMarker = Windows + $1.inheritsSet = VisualStudio + $1.inheritsScope = text/plain + $1.scope = text/x-csharp + $0.CSharpFormattingPolicy = $2 + $2.IndentSwitchSection = True + $2.NewLinesForBracesInProperties = True + $2.NewLinesForBracesInAccessors = True + $2.NewLinesForBracesInAnonymousMethods = True + $2.NewLinesForBracesInControlBlocks = True + $2.NewLinesForBracesInAnonymousTypes = True + $2.NewLinesForBracesInObjectCollectionArrayInitializers = True + $2.NewLinesForBracesInLambdaExpressionBody = True + $2.NewLineForElse = True + $2.NewLineForCatch = True + $2.NewLineForFinally = True + $2.NewLineForMembersInObjectInit = True + $2.NewLineForMembersInAnonymousTypes = True + $2.NewLineForClausesInQuery = True + $2.SpacingAfterMethodDeclarationName = False + $2.SpaceAfterMethodCallName = False + $2.SpaceBeforeOpenSquareBracket = False + $2.inheritsSet = Mono + $2.inheritsScope = text/x-csharp + $2.scope = text/x-csharp + EndGlobalSection +EndGlobal diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.sln.DotSettings b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.sln.DotSettings new file mode 100644 index 0000000000..1ceac22be9 --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.sln.DotSettings @@ -0,0 +1,877 @@ + + True + True + True + True + ExplicitlyExcluded + ExplicitlyExcluded + SOLUTION + WARNING + WARNING + WARNING + HINT + HINT + WARNING + + True + WARNING + WARNING + HINT + HINT + HINT + HINT + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + HINT + WARNING + HINT + SUGGESTION + HINT + HINT + HINT + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + HINT + WARNING + WARNING + HINT + WARNING + WARNING + DO_NOT_SHOW + HINT + WARNING + DO_NOT_SHOW + WARNING + HINT + HINT + HINT + ERROR + WARNING + HINT + HINT + HINT + WARNING + WARNING + HINT + DO_NOT_SHOW + HINT + HINT + HINT + HINT + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + HINT + WARNING + HINT + HINT + HINT + HINT + HINT + WARNING + WARNING + HINT + WARNING + WARNING + WARNING + WARNING + HINT + DO_NOT_SHOW + DO_NOT_SHOW + DO_NOT_SHOW + WARNING + + WARNING + WARNING + WARNING + WARNING + WARNING + ERROR + WARNING + WARNING + HINT + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + HINT + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + HINT + DO_NOT_SHOW + DO_NOT_SHOW + DO_NOT_SHOW + WARNING + WARNING + WARNING + WARNING + WARNING + HINT + WARNING + HINT + HINT + HINT + HINT + HINT + HINT + HINT + + HINT + WARNING + WARNING + WARNING + WARNING + + True + WARNING + WARNING + WARNING + WARNING + WARNING + HINT + HINT + WARNING + WARNING + <?xml version="1.0" encoding="utf-16"?><Profile name="Code Cleanup (peppy)"><CSArrangeThisQualifier>True</CSArrangeThisQualifier><CSUseVar><BehavourStyle>CAN_CHANGE_TO_EXPLICIT</BehavourStyle><LocalVariableStyle>ALWAYS_EXPLICIT</LocalVariableStyle><ForeachVariableStyle>ALWAYS_EXPLICIT</ForeachVariableStyle></CSUseVar><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSUpdateFileHeader>True</CSUpdateFileHeader><CSCodeStyleAttributes ArrangeTypeAccessModifier="False" ArrangeTypeMemberAccessModifier="False" SortModifiers="True" RemoveRedundantParentheses="True" AddMissingParentheses="False" ArrangeBraces="False" ArrangeAttributes="False" ArrangeArgumentsStyle="False" /><XAMLCollapseEmptyScrollingTags>False</XAMLCollapseEmptyScrollingTags><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CSArrangeQualifiers>True</CSArrangeQualifiers></Profile> + Code Cleanup (peppy) + ExpressionBody + ExpressionBody + True + True + True + True + True + True + True + True + True + NEXT_LINE + 1 + 1 + NEXT_LINE + 1 + 1 + True + NEVER + NEVER + False + NEVER + False + True + False + False + True + True + False + CHOP_IF_LONG + True + 200 + CHOP_IF_LONG + False + False + AABB + API + BPM + GC + GL + GLSL + HID + HUD + ID + IL + IP + IPC + JIT + LTRB + MD5 + NS + OS + PM + RGB + RNG + SHA + SRGB + TK + SS + PP + GMT + QAT + BNG + UI + False + HINT + <?xml version="1.0" encoding="utf-16"?> +<Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns"> + <TypePattern DisplayName="COM interfaces or structs"> + <TypePattern.Match> + <Or> + <And> + <Kind Is="Interface" /> + <Or> + <HasAttribute Name="System.Runtime.InteropServices.InterfaceTypeAttribute" /> + <HasAttribute Name="System.Runtime.InteropServices.ComImport" /> + </Or> + </And> + <Kind Is="Struct" /> + </Or> + </TypePattern.Match> + </TypePattern> + <TypePattern DisplayName="NUnit Test Fixtures" RemoveRegions="All"> + <TypePattern.Match> + <And> + <Kind Is="Class" /> + <HasAttribute Name="NUnit.Framework.TestFixtureAttribute" Inherited="True" /> + </And> + </TypePattern.Match> + <Entry DisplayName="Setup/Teardown Methods"> + <Entry.Match> + <And> + <Kind Is="Method" /> + <Or> + <HasAttribute Name="NUnit.Framework.SetUpAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.TearDownAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.FixtureSetUpAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.FixtureTearDownAttribute" Inherited="True" /> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="All other members" /> + <Entry Priority="100" DisplayName="Test Methods"> + <Entry.Match> + <And> + <Kind Is="Method" /> + <HasAttribute Name="NUnit.Framework.TestAttribute" /> + </And> + </Entry.Match> + <Entry.SortBy> + <Name /> + </Entry.SortBy> + </Entry> + </TypePattern> + <TypePattern DisplayName="Default Pattern"> + <Group DisplayName="Fields/Properties"> + <Group DisplayName="Public Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Public Properties"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + <Group DisplayName="Internal Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Internal Properties"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + <Group DisplayName="Protected Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Protected Properties"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + <Group DisplayName="Private Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Private Properties"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Constructor/Destructor"> + <Entry DisplayName="Ctor"> + <Entry.Match> + <Kind Is="Constructor" /> + </Entry.Match> + </Entry> + <Region Name="Disposal"> + <Entry DisplayName="Dtor"> + <Entry.Match> + <Kind Is="Destructor" /> + </Entry.Match> + </Entry> + <Entry DisplayName="Dispose()"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Kind Is="Method" /> + <Name Is="Dispose" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Dispose(true)"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Or> + <Virtual /> + <Override /> + </Or> + <Kind Is="Method" /> + <Name Is="Dispose" /> + </And> + </Entry.Match> + </Entry> + </Region> + </Group> + <Group DisplayName="Methods"> + <Group DisplayName="Public"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Internal"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Protected"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Private"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + </Group> + </TypePattern> +</Patterns> + Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. +See the LICENCE file in the repository root for full licence text. + + <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /> + <Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb"><ExtraRule Prefix="_" Suffix="" Style="aaBb" /></Policy> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Private" Description="private methods"><ElementKinds><Kind Name="ASYNC_METHOD" /><Kind Name="METHOD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> + <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public" Description="internal/protected/public methods"><ElementKinds><Kind Name="ASYNC_METHOD" /><Kind Name="METHOD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> + <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Private" Description="private properties"><ElementKinds><Kind Name="PROPERTY" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> + <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public" Description="internal/protected/public properties"><ElementKinds><Kind Name="PROPERTY" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="I" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + True + True + True + True + True + True + True + True + True + True + True + True + o!f – Object Initializer: Anchor&Origin + True + constant("Centre") + 0 + True + True + 2.0 + InCSharpFile + ofao + True + Anchor = Anchor.$anchor$, +Origin = Anchor.$anchor$, + True + True + o!f – InternalChildren = [] + True + True + 2.0 + InCSharpFile + ofic + True + InternalChildren = new Drawable[] +{ + $END$ +}; + True + True + o!f – new GridContainer { .. } + True + True + 2.0 + InCSharpFile + ofgc + True + new GridContainer +{ + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] { $END$ }, + new Drawable[] { } + } +}; + True + True + o!f – new FillFlowContainer { .. } + True + True + 2.0 + InCSharpFile + offf + True + new FillFlowContainer +{ + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + $END$ + } +}, + True + True + o!f – new Container { .. } + True + True + 2.0 + InCSharpFile + ofcont + True + new Container +{ + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + $END$ + } +}, + True + True + o!f – BackgroundDependencyLoader load() + True + True + 2.0 + InCSharpFile + ofbdl + True + [BackgroundDependencyLoader] +private void load() +{ + $END$ +} + True + True + o!f – new Box { .. } + True + True + 2.0 + InCSharpFile + ofbox + True + new Box +{ + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, +}, + True + True + o!f – Children = [] + True + True + 2.0 + InCSharpFile + ofc + True + Children = new Drawable[] +{ + $END$ +}; + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Beatmaps/EmptyScrollingBeatmapConverter.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Beatmaps/EmptyScrollingBeatmapConverter.cs new file mode 100644 index 0000000000..02fb9a9dd5 --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Beatmaps/EmptyScrollingBeatmapConverter.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.Collections.Generic; +using System.Threading; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.EmptyScrolling.Objects; + +namespace osu.Game.Rulesets.EmptyScrolling.Beatmaps +{ + public class EmptyScrollingBeatmapConverter : BeatmapConverter + { + public EmptyScrollingBeatmapConverter(IBeatmap beatmap, Ruleset ruleset) + : base(beatmap, ruleset) + { + } + + // todo: Check for conversion types that should be supported (ie. Beatmap.HitObjects.Any(h => h is IHasXPosition)) + // https://github.com/ppy/osu/tree/master/osu.Game/Rulesets/Objects/Types + public override bool CanConvert() => true; + + protected override IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap, CancellationToken cancellationToken) + { + yield return new EmptyScrollingHitObject + { + Samples = original.Samples, + StartTime = original.StartTime, + }; + } + } +} diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/EmptyScrollingDifficultyCalculator.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/EmptyScrollingDifficultyCalculator.cs new file mode 100644 index 0000000000..7f29c4e712 --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/EmptyScrollingDifficultyCalculator.cs @@ -0,0 +1,30 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Difficulty.Skills; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.EmptyScrolling +{ + public class EmptyScrollingDifficultyCalculator : DifficultyCalculator + { + public EmptyScrollingDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) + : base(ruleset, beatmap) + { + } + + protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) + { + return new DifficultyAttributes(mods, skills, 0); + } + + protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty(); + + protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) => new Skill[0]; + } +} diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/EmptyScrollingInputManager.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/EmptyScrollingInputManager.cs new file mode 100644 index 0000000000..632e04f301 --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/EmptyScrollingInputManager.cs @@ -0,0 +1,26 @@ +// 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 osu.Framework.Input.Bindings; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.EmptyScrolling +{ + public class EmptyScrollingInputManager : RulesetInputManager + { + public EmptyScrollingInputManager(RulesetInfo ruleset) + : base(ruleset, 0, SimultaneousBindingMode.Unique) + { + } + } + + public enum EmptyScrollingAction + { + [Description("Button 1")] + Button1, + + [Description("Button 2")] + Button2, + } +} diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/EmptyScrollingRuleset.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/EmptyScrollingRuleset.cs new file mode 100644 index 0000000000..c1d4de52b7 --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/EmptyScrollingRuleset.cs @@ -0,0 +1,57 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Bindings; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.EmptyScrolling.Beatmaps; +using osu.Game.Rulesets.EmptyScrolling.Mods; +using osu.Game.Rulesets.EmptyScrolling.UI; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.EmptyScrolling +{ + public class EmptyScrollingRuleset : Ruleset + { + public override string Description => "a very emptyscrolling ruleset"; + + public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableEmptyScrollingRuleset(this, beatmap, mods); + + public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new EmptyScrollingBeatmapConverter(beatmap, this); + + public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new EmptyScrollingDifficultyCalculator(this, beatmap); + + public override IEnumerable GetModsFor(ModType type) + { + switch (type) + { + case ModType.Automation: + return new[] { new EmptyScrollingModAutoplay() }; + + default: + return new Mod[] { null }; + } + } + + public override string ShortName => "emptyscrolling"; + + public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[] + { + new KeyBinding(InputKey.Z, EmptyScrollingAction.Button1), + new KeyBinding(InputKey.X, EmptyScrollingAction.Button2), + }; + + public override Drawable CreateIcon() => new SpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = ShortName[0].ToString(), + Font = OsuFont.Default.With(size: 18), + }; + } +} diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Mods/EmptyScrollingModAutoplay.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Mods/EmptyScrollingModAutoplay.cs new file mode 100644 index 0000000000..6dad1ff43b --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Mods/EmptyScrollingModAutoplay.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 osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.EmptyScrolling.Objects; +using osu.Game.Rulesets.EmptyScrolling.Replays; +using osu.Game.Scoring; +using osu.Game.Users; +using System.Collections.Generic; + +namespace osu.Game.Rulesets.EmptyScrolling.Mods +{ + public class EmptyScrollingModAutoplay : ModAutoplay + { + public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score + { + ScoreInfo = new ScoreInfo + { + User = new User { Username = "sample" }, + }, + Replay = new EmptyScrollingAutoGenerator(beatmap).Generate(), + }; + } +} diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Objects/Drawables/DrawableEmptyScrollingHitObject.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Objects/Drawables/DrawableEmptyScrollingHitObject.cs new file mode 100644 index 0000000000..b5ff0cde7c --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Objects/Drawables/DrawableEmptyScrollingHitObject.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 osu.Framework.Graphics; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Scoring; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.EmptyScrolling.Objects.Drawables +{ + public class DrawableEmptyScrollingHitObject : DrawableHitObject + { + public DrawableEmptyScrollingHitObject(EmptyScrollingHitObject hitObject) + : base(hitObject) + { + Size = new Vector2(40); + Origin = Anchor.Centre; + + // todo: add visuals. + } + + protected override void CheckForResult(bool userTriggered, double timeOffset) + { + if (timeOffset >= 0) + // todo: implement judgement logic + ApplyResult(r => r.Type = HitResult.Perfect); + } + + protected override void UpdateHitStateTransforms(ArmedState state) + { + const double duration = 1000; + + switch (state) + { + case ArmedState.Hit: + this.FadeOut(duration, Easing.OutQuint).Expire(); + break; + + case ArmedState.Miss: + + this.FadeColour(Color4.Red, duration); + this.FadeOut(duration, Easing.InQuint).Expire(); + break; + } + } + } +} diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Objects/EmptyScrollingHitObject.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Objects/EmptyScrollingHitObject.cs new file mode 100644 index 0000000000..9b469be496 --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Objects/EmptyScrollingHitObject.cs @@ -0,0 +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 osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Rulesets.EmptyScrolling.Objects +{ + public class EmptyScrollingHitObject : HitObject + { + public override Judgement CreateJudgement() => new Judgement(); + } +} diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Replays/EmptyScrollingAutoGenerator.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Replays/EmptyScrollingAutoGenerator.cs new file mode 100644 index 0000000000..7923918842 --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Replays/EmptyScrollingAutoGenerator.cs @@ -0,0 +1,41 @@ +// 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.Replays; +using osu.Game.Rulesets.EmptyScrolling.Objects; +using osu.Game.Rulesets.Replays; + +namespace osu.Game.Rulesets.EmptyScrolling.Replays +{ + public class EmptyScrollingAutoGenerator : AutoGenerator + { + protected Replay Replay; + protected List Frames => Replay.Frames; + + public new Beatmap Beatmap => (Beatmap)base.Beatmap; + + public EmptyScrollingAutoGenerator(IBeatmap beatmap) + : base(beatmap) + { + Replay = new Replay(); + } + + public override Replay Generate() + { + Frames.Add(new EmptyScrollingReplayFrame()); + + foreach (EmptyScrollingHitObject hitObject in Beatmap.HitObjects) + { + Frames.Add(new EmptyScrollingReplayFrame + { + Time = hitObject.StartTime + // todo: add required inputs and extra frames. + }); + } + + return Replay; + } + } +} diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Replays/EmptyScrollingFramedReplayInputHandler.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Replays/EmptyScrollingFramedReplayInputHandler.cs new file mode 100644 index 0000000000..4b998cfca3 --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Replays/EmptyScrollingFramedReplayInputHandler.cs @@ -0,0 +1,29 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Input.StateChanges; +using osu.Game.Replays; +using osu.Game.Rulesets.Replays; + +namespace osu.Game.Rulesets.EmptyScrolling.Replays +{ + public class EmptyScrollingFramedReplayInputHandler : FramedReplayInputHandler + { + public EmptyScrollingFramedReplayInputHandler(Replay replay) + : base(replay) + { + } + + protected override bool IsImportant(EmptyScrollingReplayFrame frame) => frame.Actions.Any(); + + public override void CollectPendingInputs(List inputs) + { + inputs.Add(new ReplayState + { + PressedActions = CurrentFrame?.Actions ?? new List(), + }); + } + } +} diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Replays/EmptyScrollingReplayFrame.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Replays/EmptyScrollingReplayFrame.cs new file mode 100644 index 0000000000..2f19cffd2a --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Replays/EmptyScrollingReplayFrame.cs @@ -0,0 +1,19 @@ +// 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.Rulesets.Replays; + +namespace osu.Game.Rulesets.EmptyScrolling.Replays +{ + public class EmptyScrollingReplayFrame : ReplayFrame + { + public List Actions = new List(); + + public EmptyScrollingReplayFrame(EmptyScrollingAction? button = null) + { + if (button.HasValue) + Actions.Add(button.Value); + } + } +} diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/UI/DrawableEmptyScrollingRuleset.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/UI/DrawableEmptyScrollingRuleset.cs new file mode 100644 index 0000000000..620a4abc51 --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/UI/DrawableEmptyScrollingRuleset.cs @@ -0,0 +1,38 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Input; +using osu.Game.Beatmaps; +using osu.Game.Input.Handlers; +using osu.Game.Replays; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.EmptyScrolling.Objects; +using osu.Game.Rulesets.EmptyScrolling.Objects.Drawables; +using osu.Game.Rulesets.EmptyScrolling.Replays; +using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.UI.Scrolling; + +namespace osu.Game.Rulesets.EmptyScrolling.UI +{ + [Cached] + public class DrawableEmptyScrollingRuleset : DrawableScrollingRuleset + { + public DrawableEmptyScrollingRuleset(EmptyScrollingRuleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) + : base(ruleset, beatmap, mods) + { + Direction.Value = ScrollingDirection.Left; + TimeRange.Value = 6000; + } + + protected override Playfield CreatePlayfield() => new EmptyScrollingPlayfield(); + + protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new EmptyScrollingFramedReplayInputHandler(replay); + + public override DrawableHitObject CreateDrawableRepresentation(EmptyScrollingHitObject h) => new DrawableEmptyScrollingHitObject(h); + + protected override PassThroughInputManager CreateInputManager() => new EmptyScrollingInputManager(Ruleset?.RulesetInfo); + } +} diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/UI/EmptyScrollingPlayfield.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/UI/EmptyScrollingPlayfield.cs new file mode 100644 index 0000000000..56620e44b3 --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/UI/EmptyScrollingPlayfield.cs @@ -0,0 +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.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Rulesets.UI.Scrolling; + +namespace osu.Game.Rulesets.EmptyScrolling.UI +{ + [Cached] + public class EmptyScrollingPlayfield : ScrollingPlayfield + { + [BackgroundDependencyLoader] + private void load() + { + AddRangeInternal(new Drawable[] + { + HitObjectContainer, + }); + } + } +} diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/osu.Game.Rulesets.EmptyScrolling.csproj b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/osu.Game.Rulesets.EmptyScrolling.csproj new file mode 100644 index 0000000000..ce0ada6b6e --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/osu.Game.Rulesets.EmptyScrolling.csproj @@ -0,0 +1,15 @@ + + + netstandard2.1 + osu.Game.Rulesets.Sample + Library + AnyCPU + osu.Game.Rulesets.EmptyScrolling + + + + + + + + \ No newline at end of file diff --git a/Templates/Rulesets/ruleset-scrolling-example/.editorconfig b/Templates/Rulesets/ruleset-scrolling-example/.editorconfig new file mode 100644 index 0000000000..24825b7c42 --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-example/.editorconfig @@ -0,0 +1,27 @@ +# EditorConfig is awesome: http://editorconfig.org +root = true + +[*.cs] +end_of_line = crlf +insert_final_newline = true +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true + +#Roslyn naming styles + +#PascalCase for public and protected members +dotnet_naming_style.pascalcase.capitalization = pascal_case +dotnet_naming_symbols.public_members.applicable_accessibilities = public,internal,protected,protected_internal +dotnet_naming_symbols.public_members.applicable_kinds = property,method,field,event,delegate +dotnet_naming_rule.public_members_pascalcase.severity = suggestion +dotnet_naming_rule.public_members_pascalcase.symbols = public_members +dotnet_naming_rule.public_members_pascalcase.style = pascalcase + +#camelCase for private members +dotnet_naming_style.camelcase.capitalization = camel_case +dotnet_naming_symbols.private_members.applicable_accessibilities = private +dotnet_naming_symbols.private_members.applicable_kinds = property,method,field,event,delegate +dotnet_naming_rule.private_members_camelcase.severity = suggestion +dotnet_naming_rule.private_members_camelcase.symbols = private_members +dotnet_naming_rule.private_members_camelcase.style = camelcase \ No newline at end of file diff --git a/Templates/Rulesets/ruleset-scrolling-example/.gitignore b/Templates/Rulesets/ruleset-scrolling-example/.gitignore new file mode 100644 index 0000000000..940794e60f --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-example/.gitignore @@ -0,0 +1,288 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ +**/Properties/launchSettings.json + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Typescript v1 declaration files +typings/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs diff --git a/Templates/Rulesets/ruleset-scrolling-example/.template.config/template.json b/Templates/Rulesets/ruleset-scrolling-example/.template.config/template.json new file mode 100644 index 0000000000..a1c097f1c8 --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-example/.template.config/template.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json.schemastore.org/template", + "author": "ppy Pty Ltd", + "classifications": [ + "Console" + ], + "name": "osu! ruleset (scrolling pippidon example)", + "identity": "ppy.osu.Game.Templates.Rulesets.Scrolling.Pippidon", + "shortName": "ruleset-scrolling-example", + "tags": { + "language": "C#", + "type": "project" + }, + "sourceName": "Pippidon", + "preferNameDirectory": true +} diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/.vscode/launch.json b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/.vscode/launch.json new file mode 100644 index 0000000000..bd9db14259 --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/.vscode/launch.json @@ -0,0 +1,31 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "VisualTests (Debug)", + "type": "coreclr", + "request": "launch", + "program": "dotnet", + "args": [ + "${workspaceRoot}/bin/Debug/net5.0/osu.Game.Rulesets.Pippidon.Tests.dll" + ], + "cwd": "${workspaceRoot}", + "preLaunchTask": "Build (Debug)", + "env": {}, + "console": "internalConsole" + }, + { + "name": "VisualTests (Release)", + "type": "coreclr", + "request": "launch", + "program": "dotnet", + "args": [ + "${workspaceRoot}/bin/Release/net5.0/osu.Game.Rulesets.Pippidon.Tests.dll" + ], + "cwd": "${workspaceRoot}", + "preLaunchTask": "Build (Release)", + "env": {}, + "console": "internalConsole" + } + ] +} \ No newline at end of file diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/.vscode/tasks.json b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/.vscode/tasks.json new file mode 100644 index 0000000000..0ee07c1036 --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/.vscode/tasks.json @@ -0,0 +1,47 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "Build (Debug)", + "type": "shell", + "command": "dotnet", + "args": [ + "build", + "--no-restore", + "osu.Game.Rulesets.Pippidon.Tests.csproj", + "-p:GenerateFullPaths=true", + "-m", + "-verbosity:m" + ], + "group": "build", + "problemMatcher": "$msCompile" + }, + { + "label": "Build (Release)", + "type": "shell", + "command": "dotnet", + "args": [ + "build", + "--no-restore", + "osu.Game.Rulesets.Pippidon.Tests.csproj", + "-p:Configuration=Release", + "-p:GenerateFullPaths=true", + "-m", + "-verbosity:m" + ], + "group": "build", + "problemMatcher": "$msCompile" + }, + { + "label": "Restore", + "type": "shell", + "command": "dotnet", + "args": [ + "restore" + ], + "problemMatcher": [] + } + ] +} \ No newline at end of file diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs new file mode 100644 index 0000000000..270d906b01 --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.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.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Platform; +using osu.Game.Tests.Visual; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Pippidon.Tests +{ + public class TestSceneOsuGame : OsuTestScene + { + [BackgroundDependencyLoader] + private void load(GameHost host, OsuGameBase gameBase) + { + OsuGame game = new OsuGame(); + game.SetHost(host); + + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + }, + game + }; + } + } +} diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuPlayer.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuPlayer.cs new file mode 100644 index 0000000000..f00528900c --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuPlayer.cs @@ -0,0 +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 NUnit.Framework; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Pippidon.Tests +{ + [TestFixture] + public class TestSceneOsuPlayer : PlayerTestScene + { + protected override Ruleset CreatePlayerRuleset() => new PippidonRuleset(); + } +} diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs new file mode 100644 index 0000000000..fd6bd9b714 --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.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 System; +using osu.Framework; +using osu.Framework.Platform; +using osu.Game.Tests; + +namespace osu.Game.Rulesets.Pippidon.Tests +{ + public static class VisualTestRunner + { + [STAThread] + public static int Main(string[] args) + { + using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true)) + { + host.Run(new OsuTestBrowser()); + return 0; + } + } + } +} diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj new file mode 100644 index 0000000000..afa7b03536 --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -0,0 +1,26 @@ + + + osu.Game.Rulesets.Pippidon.Tests.VisualTestRunner + + + + + + false + + + + + + + + + + + + + WinExe + net5.0 + osu.Game.Rulesets.Pippidon.Tests + + \ No newline at end of file diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.sln b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.sln new file mode 100644 index 0000000000..bccffcd7ff --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.sln @@ -0,0 +1,96 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29123.88 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Rulesets.Pippidon", "osu.Game.Rulesets.Pippidon\osu.Game.Rulesets.Pippidon.csproj", "{5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Pippidon.Tests", "osu.Game.Rulesets.Pippidon.Tests\osu.Game.Rulesets.Pippidon.Tests.csproj", "{B4577C85-CB83-462A-BCE3-22FFEB16311D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + VisualTests|Any CPU = VisualTests|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.Release|Any CPU.Build.0 = Release|Any CPU + {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.VisualTests|Any CPU.ActiveCfg = Release|Any CPU + {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.VisualTests|Any CPU.Build.0 = Release|Any CPU + {B4577C85-CB83-462A-BCE3-22FFEB16311D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B4577C85-CB83-462A-BCE3-22FFEB16311D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B4577C85-CB83-462A-BCE3-22FFEB16311D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B4577C85-CB83-462A-BCE3-22FFEB16311D}.Release|Any CPU.Build.0 = Release|Any CPU + {B4577C85-CB83-462A-BCE3-22FFEB16311D}.VisualTests|Any CPU.ActiveCfg = Debug|Any CPU + {B4577C85-CB83-462A-BCE3-22FFEB16311D}.VisualTests|Any CPU.Build.0 = Debug|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {671B0BEC-2403-45B0-9357-2C97CC517668} + EndGlobalSection + GlobalSection(MonoDevelopProperties) = preSolution + Policies = $0 + $0.TextStylePolicy = $1 + $1.EolMarker = Windows + $1.inheritsSet = VisualStudio + $1.inheritsScope = text/plain + $1.scope = text/x-csharp + $0.CSharpFormattingPolicy = $2 + $2.IndentSwitchSection = True + $2.NewLinesForBracesInProperties = True + $2.NewLinesForBracesInAccessors = True + $2.NewLinesForBracesInAnonymousMethods = True + $2.NewLinesForBracesInControlBlocks = True + $2.NewLinesForBracesInAnonymousTypes = True + $2.NewLinesForBracesInObjectCollectionArrayInitializers = True + $2.NewLinesForBracesInLambdaExpressionBody = True + $2.NewLineForElse = True + $2.NewLineForCatch = True + $2.NewLineForFinally = True + $2.NewLineForMembersInObjectInit = True + $2.NewLineForMembersInAnonymousTypes = True + $2.NewLineForClausesInQuery = True + $2.SpacingAfterMethodDeclarationName = False + $2.SpaceAfterMethodCallName = False + $2.SpaceBeforeOpenSquareBracket = False + $2.inheritsSet = Mono + $2.inheritsScope = text/x-csharp + $2.scope = text/x-csharp + EndGlobalSection + GlobalSection(MonoDevelopProperties) = preSolution + Policies = $0 + $0.TextStylePolicy = $1 + $1.EolMarker = Windows + $1.inheritsSet = VisualStudio + $1.inheritsScope = text/plain + $1.scope = text/x-csharp + $0.CSharpFormattingPolicy = $2 + $2.IndentSwitchSection = True + $2.NewLinesForBracesInProperties = True + $2.NewLinesForBracesInAccessors = True + $2.NewLinesForBracesInAnonymousMethods = True + $2.NewLinesForBracesInControlBlocks = True + $2.NewLinesForBracesInAnonymousTypes = True + $2.NewLinesForBracesInObjectCollectionArrayInitializers = True + $2.NewLinesForBracesInLambdaExpressionBody = True + $2.NewLineForElse = True + $2.NewLineForCatch = True + $2.NewLineForFinally = True + $2.NewLineForMembersInObjectInit = True + $2.NewLineForMembersInAnonymousTypes = True + $2.NewLineForClausesInQuery = True + $2.SpacingAfterMethodDeclarationName = False + $2.SpaceAfterMethodCallName = False + $2.SpaceBeforeOpenSquareBracket = False + $2.inheritsSet = Mono + $2.inheritsScope = text/x-csharp + $2.scope = text/x-csharp + EndGlobalSection +EndGlobal diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.sln.DotSettings b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.sln.DotSettings new file mode 100644 index 0000000000..c3e274569d --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.sln.DotSettings @@ -0,0 +1,877 @@ + + True + True + True + True + ExplicitlyExcluded + ExplicitlyExcluded + SOLUTION + WARNING + WARNING + WARNING + HINT + HINT + WARNING + + True + WARNING + WARNING + HINT + HINT + HINT + HINT + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + HINT + WARNING + HINT + SUGGESTION + HINT + HINT + HINT + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + HINT + WARNING + WARNING + HINT + WARNING + WARNING + DO_NOT_SHOW + HINT + WARNING + DO_NOT_SHOW + WARNING + HINT + HINT + HINT + ERROR + WARNING + HINT + HINT + HINT + WARNING + WARNING + HINT + DO_NOT_SHOW + HINT + HINT + HINT + HINT + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + HINT + WARNING + HINT + HINT + HINT + HINT + HINT + WARNING + WARNING + HINT + WARNING + WARNING + WARNING + WARNING + HINT + DO_NOT_SHOW + DO_NOT_SHOW + DO_NOT_SHOW + WARNING + + WARNING + WARNING + WARNING + WARNING + WARNING + ERROR + WARNING + WARNING + HINT + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + HINT + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + HINT + DO_NOT_SHOW + DO_NOT_SHOW + DO_NOT_SHOW + WARNING + WARNING + WARNING + WARNING + WARNING + HINT + WARNING + HINT + HINT + HINT + HINT + HINT + HINT + HINT + + HINT + WARNING + WARNING + WARNING + WARNING + + True + WARNING + WARNING + WARNING + WARNING + WARNING + HINT + HINT + WARNING + WARNING + <?xml version="1.0" encoding="utf-16"?><Profile name="Code Cleanup (peppy)"><CSArrangeThisQualifier>True</CSArrangeThisQualifier><CSUseVar><BehavourStyle>CAN_CHANGE_TO_EXPLICIT</BehavourStyle><LocalVariableStyle>ALWAYS_EXPLICIT</LocalVariableStyle><ForeachVariableStyle>ALWAYS_EXPLICIT</ForeachVariableStyle></CSUseVar><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSUpdateFileHeader>True</CSUpdateFileHeader><CSCodeStyleAttributes ArrangeTypeAccessModifier="False" ArrangeTypeMemberAccessModifier="False" SortModifiers="True" RemoveRedundantParentheses="True" AddMissingParentheses="False" ArrangeBraces="False" ArrangeAttributes="False" ArrangeArgumentsStyle="False" /><XAMLCollapseEmptyTags>False</XAMLCollapseEmptyTags><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CSArrangeQualifiers>True</CSArrangeQualifiers></Profile> + Code Cleanup (peppy) + ExpressionBody + ExpressionBody + True + True + True + True + True + True + True + True + True + NEXT_LINE + 1 + 1 + NEXT_LINE + 1 + 1 + True + NEVER + NEVER + False + NEVER + False + True + False + False + True + True + False + CHOP_IF_LONG + True + 200 + CHOP_IF_LONG + False + False + AABB + API + BPM + GC + GL + GLSL + HID + HUD + ID + IL + IP + IPC + JIT + LTRB + MD5 + NS + OS + PM + RGB + RNG + SHA + SRGB + TK + SS + PP + GMT + QAT + BNG + UI + False + HINT + <?xml version="1.0" encoding="utf-16"?> +<Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns"> + <TypePattern DisplayName="COM interfaces or structs"> + <TypePattern.Match> + <Or> + <And> + <Kind Is="Interface" /> + <Or> + <HasAttribute Name="System.Runtime.InteropServices.InterfaceTypeAttribute" /> + <HasAttribute Name="System.Runtime.InteropServices.ComImport" /> + </Or> + </And> + <Kind Is="Struct" /> + </Or> + </TypePattern.Match> + </TypePattern> + <TypePattern DisplayName="NUnit Test Fixtures" RemoveRegions="All"> + <TypePattern.Match> + <And> + <Kind Is="Class" /> + <HasAttribute Name="NUnit.Framework.TestFixtureAttribute" Inherited="True" /> + </And> + </TypePattern.Match> + <Entry DisplayName="Setup/Teardown Methods"> + <Entry.Match> + <And> + <Kind Is="Method" /> + <Or> + <HasAttribute Name="NUnit.Framework.SetUpAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.TearDownAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.FixtureSetUpAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.FixtureTearDownAttribute" Inherited="True" /> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="All other members" /> + <Entry Priority="100" DisplayName="Test Methods"> + <Entry.Match> + <And> + <Kind Is="Method" /> + <HasAttribute Name="NUnit.Framework.TestAttribute" /> + </And> + </Entry.Match> + <Entry.SortBy> + <Name /> + </Entry.SortBy> + </Entry> + </TypePattern> + <TypePattern DisplayName="Default Pattern"> + <Group DisplayName="Fields/Properties"> + <Group DisplayName="Public Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Public Properties"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + <Group DisplayName="Internal Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Internal Properties"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + <Group DisplayName="Protected Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Protected Properties"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + <Group DisplayName="Private Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Private Properties"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Constructor/Destructor"> + <Entry DisplayName="Ctor"> + <Entry.Match> + <Kind Is="Constructor" /> + </Entry.Match> + </Entry> + <Region Name="Disposal"> + <Entry DisplayName="Dtor"> + <Entry.Match> + <Kind Is="Destructor" /> + </Entry.Match> + </Entry> + <Entry DisplayName="Dispose()"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Kind Is="Method" /> + <Name Is="Dispose" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Dispose(true)"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Or> + <Virtual /> + <Override /> + </Or> + <Kind Is="Method" /> + <Name Is="Dispose" /> + </And> + </Entry.Match> + </Entry> + </Region> + </Group> + <Group DisplayName="Methods"> + <Group DisplayName="Public"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Internal"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Protected"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Private"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + </Group> + </TypePattern> +</Patterns> + Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. +See the LICENCE file in the repository root for full licence text. + + <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /> + <Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb"><ExtraRule Prefix="_" Suffix="" Style="aaBb" /></Policy> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Private" Description="private methods"><ElementKinds><Kind Name="ASYNC_METHOD" /><Kind Name="METHOD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> + <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public" Description="internal/protected/public methods"><ElementKinds><Kind Name="ASYNC_METHOD" /><Kind Name="METHOD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> + <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Private" Description="private properties"><ElementKinds><Kind Name="PROPERTY" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> + <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public" Description="internal/protected/public properties"><ElementKinds><Kind Name="PROPERTY" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="I" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + True + True + True + True + True + True + True + True + True + True + True + True + o!f – Object Initializer: Anchor&Origin + True + constant("Centre") + 0 + True + True + 2.0 + InCSharpFile + ofao + True + Anchor = Anchor.$anchor$, +Origin = Anchor.$anchor$, + True + True + o!f – InternalChildren = [] + True + True + 2.0 + InCSharpFile + ofic + True + InternalChildren = new Drawable[] +{ + $END$ +}; + True + True + o!f – new GridContainer { .. } + True + True + 2.0 + InCSharpFile + ofgc + True + new GridContainer +{ + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] { $END$ }, + new Drawable[] { } + } +}; + True + True + o!f – new FillFlowContainer { .. } + True + True + 2.0 + InCSharpFile + offf + True + new FillFlowContainer +{ + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + $END$ + } +}, + True + True + o!f – new Container { .. } + True + True + 2.0 + InCSharpFile + ofcont + True + new Container +{ + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + $END$ + } +}, + True + True + o!f – BackgroundDependencyLoader load() + True + True + 2.0 + InCSharpFile + ofbdl + True + [BackgroundDependencyLoader] +private void load() +{ + $END$ +} + True + True + o!f – new Box { .. } + True + True + 2.0 + InCSharpFile + ofbox + True + new Box +{ + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, +}, + True + True + o!f – Children = [] + True + True + 2.0 + InCSharpFile + ofc + True + Children = new Drawable[] +{ + $END$ +}; + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Beatmaps/PippidonBeatmapConverter.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Beatmaps/PippidonBeatmapConverter.cs new file mode 100644 index 0000000000..8f0b31ef1b --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Beatmaps/PippidonBeatmapConverter.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.Collections.Generic; +using System.Linq; +using System.Threading; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Pippidon.Objects; +using osu.Game.Rulesets.Pippidon.UI; +using osuTK; + +namespace osu.Game.Rulesets.Pippidon.Beatmaps +{ + public class PippidonBeatmapConverter : BeatmapConverter + { + private readonly float minPosition; + private readonly float maxPosition; + + public PippidonBeatmapConverter(IBeatmap beatmap, Ruleset ruleset) + : base(beatmap, ruleset) + { + minPosition = beatmap.HitObjects.Min(getUsablePosition); + maxPosition = beatmap.HitObjects.Max(getUsablePosition); + } + + public override bool CanConvert() => Beatmap.HitObjects.All(h => h is IHasXPosition && h is IHasYPosition); + + protected override IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap, CancellationToken cancellationToken) + { + yield return new PippidonHitObject + { + Samples = original.Samples, + StartTime = original.StartTime, + Lane = getLane(original) + }; + } + + private int getLane(HitObject hitObject) => (int)MathHelper.Clamp( + (getUsablePosition(hitObject) - minPosition) / (maxPosition - minPosition) * PippidonPlayfield.LANE_COUNT, 0, PippidonPlayfield.LANE_COUNT - 1); + + private float getUsablePosition(HitObject h) => (h as IHasYPosition)?.Y ?? ((IHasXPosition)h).X; + } +} diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Mods/PippidonModAutoplay.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Mods/PippidonModAutoplay.cs new file mode 100644 index 0000000000..8ea334c99c --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Mods/PippidonModAutoplay.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.Collections.Generic; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Pippidon.Objects; +using osu.Game.Rulesets.Pippidon.Replays; +using osu.Game.Scoring; +using osu.Game.Users; + +namespace osu.Game.Rulesets.Pippidon.Mods +{ + public class PippidonModAutoplay : ModAutoplay + { + public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score + { + ScoreInfo = new ScoreInfo + { + User = new User { Username = "sample" }, + }, + Replay = new PippidonAutoGenerator(beatmap).Generate(), + }; + } +} diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Objects/Drawables/DrawablePippidonHitObject.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Objects/Drawables/DrawablePippidonHitObject.cs new file mode 100644 index 0000000000..e458cacef9 --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Objects/Drawables/DrawablePippidonHitObject.cs @@ -0,0 +1,75 @@ +// 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.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Game.Audio; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Pippidon.UI; +using osu.Game.Rulesets.Scoring; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Pippidon.Objects.Drawables +{ + public class DrawablePippidonHitObject : DrawableHitObject + { + private BindableNumber currentLane; + + public DrawablePippidonHitObject(PippidonHitObject hitObject) + : base(hitObject) + { + Size = new Vector2(40); + + Origin = Anchor.Centre; + Y = hitObject.Lane * PippidonPlayfield.LANE_HEIGHT; + } + + [BackgroundDependencyLoader] + private void load(PippidonPlayfield playfield, TextureStore textures) + { + AddInternal(new Sprite + { + RelativeSizeAxes = Axes.Both, + Texture = textures.Get("coin"), + }); + + currentLane = playfield.CurrentLane.GetBoundCopy(); + } + + public override IEnumerable GetSamples() => new[] + { + new HitSampleInfo(HitSampleInfo.HIT_NORMAL, SampleControlPoint.DEFAULT_BANK) + }; + + protected override void CheckForResult(bool userTriggered, double timeOffset) + { + if (timeOffset >= 0) + ApplyResult(r => r.Type = currentLane.Value == HitObject.Lane ? HitResult.Perfect : HitResult.Miss); + } + + protected override void UpdateHitStateTransforms(ArmedState state) + { + switch (state) + { + case ArmedState.Hit: + this.ScaleTo(5, 1500, Easing.OutQuint).FadeOut(1500, Easing.OutQuint).Expire(); + break; + + case ArmedState.Miss: + + const double duration = 1000; + + this.ScaleTo(0.8f, duration, Easing.OutQuint); + this.MoveToOffset(new Vector2(0, 10), duration, Easing.In); + this.FadeColour(Color4.Red, duration / 2, Easing.OutQuint).Then().FadeOut(duration / 2, Easing.InQuint).Expire(); + break; + } + } + } +} diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Objects/PippidonHitObject.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Objects/PippidonHitObject.cs new file mode 100644 index 0000000000..9dd135479f --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Objects/PippidonHitObject.cs @@ -0,0 +1,18 @@ +// 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.Judgements; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Rulesets.Pippidon.Objects +{ + public class PippidonHitObject : HitObject + { + /// + /// Range = [-1,1] + /// + public int Lane; + + public override Judgement CreateJudgement() => new Judgement(); + } +} diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/PippidonDifficultyCalculator.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/PippidonDifficultyCalculator.cs new file mode 100644 index 0000000000..f6340f6c25 --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/PippidonDifficultyCalculator.cs @@ -0,0 +1,30 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Difficulty.Skills; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Pippidon +{ + public class PippidonDifficultyCalculator : DifficultyCalculator + { + public PippidonDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) + : base(ruleset, beatmap) + { + } + + protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) + { + return new DifficultyAttributes(mods, skills, 0); + } + + protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty(); + + protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) => new Skill[0]; + } +} diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/PippidonInputManager.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/PippidonInputManager.cs new file mode 100644 index 0000000000..c9e6e6faaa --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/PippidonInputManager.cs @@ -0,0 +1,26 @@ +// 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 osu.Framework.Input.Bindings; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Pippidon +{ + public class PippidonInputManager : RulesetInputManager + { + public PippidonInputManager(RulesetInfo ruleset) + : base(ruleset, 0, SimultaneousBindingMode.Unique) + { + } + } + + public enum PippidonAction + { + [Description("Move up")] + MoveUp, + + [Description("Move down")] + MoveDown, + } +} diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/PippidonRuleset.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/PippidonRuleset.cs new file mode 100644 index 0000000000..ede00f1510 --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/PippidonRuleset.cs @@ -0,0 +1,55 @@ +// 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.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Framework.Input.Bindings; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Pippidon.Beatmaps; +using osu.Game.Rulesets.Pippidon.Mods; +using osu.Game.Rulesets.Pippidon.UI; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Pippidon +{ + public class PippidonRuleset : Ruleset + { + public override string Description => "gather the osu!coins"; + + public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawablePippidonRuleset(this, beatmap, mods); + + public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new PippidonBeatmapConverter(beatmap, this); + + public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new PippidonDifficultyCalculator(this, beatmap); + + public override IEnumerable GetModsFor(ModType type) + { + switch (type) + { + case ModType.Automation: + return new[] { new PippidonModAutoplay() }; + + default: + return new Mod[] { null }; + } + } + + public override string ShortName => "pippidon"; + + public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[] + { + new KeyBinding(InputKey.W, PippidonAction.MoveUp), + new KeyBinding(InputKey.S, PippidonAction.MoveDown), + }; + + public override Drawable CreateIcon() => new Sprite + { + Margin = new MarginPadding { Top = 3 }, + Texture = new TextureStore(new TextureLoaderStore(CreateResourceStore()), false).Get("Textures/coin"), + }; + } +} diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Replays/PippidonAutoGenerator.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Replays/PippidonAutoGenerator.cs new file mode 100644 index 0000000000..bd99cdcdbd --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Replays/PippidonAutoGenerator.cs @@ -0,0 +1,68 @@ +// 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.Replays; +using osu.Game.Rulesets.Pippidon.Objects; +using osu.Game.Rulesets.Pippidon.UI; +using osu.Game.Rulesets.Replays; + +namespace osu.Game.Rulesets.Pippidon.Replays +{ + public class PippidonAutoGenerator : AutoGenerator + { + protected Replay Replay; + protected List Frames => Replay.Frames; + + public new Beatmap Beatmap => (Beatmap)base.Beatmap; + + public PippidonAutoGenerator(IBeatmap beatmap) + : base(beatmap) + { + Replay = new Replay(); + } + + public override Replay Generate() + { + int currentLane = 0; + + Frames.Add(new PippidonReplayFrame()); + + foreach (PippidonHitObject hitObject in Beatmap.HitObjects) + { + if (currentLane == hitObject.Lane) + continue; + + int totalTravel = Math.Abs(hitObject.Lane - currentLane); + var direction = hitObject.Lane > currentLane ? PippidonAction.MoveDown : PippidonAction.MoveUp; + + double time = hitObject.StartTime - 5; + + if (totalTravel == PippidonPlayfield.LANE_COUNT - 1) + addFrame(time, direction == PippidonAction.MoveDown ? PippidonAction.MoveUp : PippidonAction.MoveDown); + else + { + time -= totalTravel * KEY_UP_DELAY; + + for (int i = 0; i < totalTravel; i++) + { + addFrame(time, direction); + time += KEY_UP_DELAY; + } + } + + currentLane = hitObject.Lane; + } + + return Replay; + } + + private void addFrame(double time, PippidonAction direction) + { + Frames.Add(new PippidonReplayFrame(direction) { Time = time }); + Frames.Add(new PippidonReplayFrame { Time = time + KEY_UP_DELAY }); //Release the keys as well + } + } +} diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs new file mode 100644 index 0000000000..7652357b4d --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs @@ -0,0 +1,29 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Input.StateChanges; +using osu.Game.Replays; +using osu.Game.Rulesets.Replays; + +namespace osu.Game.Rulesets.Pippidon.Replays +{ + public class PippidonFramedReplayInputHandler : FramedReplayInputHandler + { + public PippidonFramedReplayInputHandler(Replay replay) + : base(replay) + { + } + + protected override bool IsImportant(PippidonReplayFrame frame) => frame.Actions.Any(); + + public override void CollectPendingInputs(List inputs) + { + inputs.Add(new ReplayState + { + PressedActions = CurrentFrame?.Actions ?? new List(), + }); + } + } +} diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Replays/PippidonReplayFrame.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Replays/PippidonReplayFrame.cs new file mode 100644 index 0000000000..468ac9c725 --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Replays/PippidonReplayFrame.cs @@ -0,0 +1,19 @@ +// 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.Rulesets.Replays; + +namespace osu.Game.Rulesets.Pippidon.Replays +{ + public class PippidonReplayFrame : ReplayFrame + { + public List Actions = new List(); + + public PippidonReplayFrame(PippidonAction? button = null) + { + if (button.HasValue) + Actions.Add(button.Value); + } + } +} diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Resources/Samples/Gameplay/normal-hitnormal.mp3 b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Resources/Samples/Gameplay/normal-hitnormal.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..90b13d1f734a4020945bed6706f6a86228b87938 GIT binary patch literal 8022 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 literal 0 HcmV?d00001 diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/UI/DrawablePippidonRuleset.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/UI/DrawablePippidonRuleset.cs new file mode 100644 index 0000000000..9a73dd7790 --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/UI/DrawablePippidonRuleset.cs @@ -0,0 +1,38 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Input; +using osu.Game.Beatmaps; +using osu.Game.Input.Handlers; +using osu.Game.Replays; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Pippidon.Objects; +using osu.Game.Rulesets.Pippidon.Objects.Drawables; +using osu.Game.Rulesets.Pippidon.Replays; +using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.UI.Scrolling; + +namespace osu.Game.Rulesets.Pippidon.UI +{ + [Cached] + public class DrawablePippidonRuleset : DrawableScrollingRuleset + { + public DrawablePippidonRuleset(PippidonRuleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) + : base(ruleset, beatmap, mods) + { + Direction.Value = ScrollingDirection.Left; + TimeRange.Value = 6000; + } + + protected override Playfield CreatePlayfield() => new PippidonPlayfield(); + + protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new PippidonFramedReplayInputHandler(replay); + + public override DrawableHitObject CreateDrawableRepresentation(PippidonHitObject h) => new DrawablePippidonHitObject(h); + + protected override PassThroughInputManager CreateInputManager() => new PippidonInputManager(Ruleset?.RulesetInfo); + } +} diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/UI/PippidonCharacter.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/UI/PippidonCharacter.cs new file mode 100644 index 0000000000..dd0a20f1b4 --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/UI/PippidonCharacter.cs @@ -0,0 +1,87 @@ +// 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.Audio.Track; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Framework.Input.Bindings; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.Containers; +using osuTK; + +namespace osu.Game.Rulesets.Pippidon.UI +{ + public class PippidonCharacter : BeatSyncedContainer, IKeyBindingHandler + { + public readonly BindableInt LanePosition = new BindableInt + { + Value = 0, + MinValue = 0, + MaxValue = PippidonPlayfield.LANE_COUNT - 1, + }; + + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + Size = new Vector2(PippidonPlayfield.LANE_HEIGHT); + + Child = new Sprite + { + FillMode = FillMode.Fit, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(1.2f), + RelativeSizeAxes = Axes.Both, + Texture = textures.Get("character") + }; + + LanePosition.BindValueChanged(e => { this.MoveToY(e.NewValue * PippidonPlayfield.LANE_HEIGHT); }); + } + + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) + { + if (effectPoint.KiaiMode) + { + bool direction = beatIndex % 2 == 1; + double duration = timingPoint.BeatLength / 2; + + Child.RotateTo(direction ? 10 : -10, duration * 2, Easing.InOutSine); + + Child.Animate(i => i.MoveToY(-10, duration, Easing.Out)) + .Then(i => i.MoveToY(0, duration, Easing.In)); + } + else + { + Child.ClearTransforms(); + Child.RotateTo(0, 500, Easing.Out); + Child.MoveTo(Vector2.Zero, 500, Easing.Out); + } + } + + public bool OnPressed(PippidonAction action) + { + switch (action) + { + case PippidonAction.MoveUp: + changeLane(-1); + return true; + + case PippidonAction.MoveDown: + changeLane(1); + return true; + + default: + return false; + } + } + + public void OnReleased(PippidonAction action) + { + } + + private void changeLane(int change) => LanePosition.Value = (LanePosition.Value + change + PippidonPlayfield.LANE_COUNT) % PippidonPlayfield.LANE_COUNT; + } +} diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/UI/PippidonPlayfield.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/UI/PippidonPlayfield.cs new file mode 100644 index 0000000000..0e50030162 --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/UI/PippidonPlayfield.cs @@ -0,0 +1,128 @@ +// 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.Audio.Track; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Textures; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Rulesets.UI.Scrolling; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Pippidon.UI +{ + [Cached] + public class PippidonPlayfield : ScrollingPlayfield + { + public const float LANE_HEIGHT = 70; + + public const int LANE_COUNT = 6; + + public BindableInt CurrentLane => pippidon.LanePosition; + + private PippidonCharacter pippidon; + + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + AddRangeInternal(new Drawable[] + { + new LaneContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Child = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding + { + Left = 200, + Top = LANE_HEIGHT / 2, + Bottom = LANE_HEIGHT / 2 + }, + Children = new Drawable[] + { + HitObjectContainer, + pippidon = new PippidonCharacter + { + Origin = Anchor.Centre, + }, + } + }, + }, + }); + } + + private class LaneContainer : BeatSyncedContainer + { + private OsuColour colours; + private FillFlowContainer fill; + + private readonly Container content = new Container + { + RelativeSizeAxes = Axes.Both, + }; + + protected override Container Content => content; + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + this.colours = colours; + + InternalChildren = new Drawable[] + { + fill = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Colour = colours.BlueLight, + Direction = FillDirection.Vertical, + }, + content, + }; + + for (int i = 0; i < LANE_COUNT; i++) + { + fill.Add(new Lane + { + RelativeSizeAxes = Axes.X, + Height = LANE_HEIGHT, + }); + } + } + + private class Lane : CompositeDrawable + { + public Lane() + { + InternalChildren = new Drawable[] + { + new Box + { + Colour = Color4.White, + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Height = 0.95f, + }, + }; + } + } + + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) + { + if (effectPoint.KiaiMode) + fill.FlashColour(colours.PinkLight, 800, Easing.In); + } + } + } +} diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj new file mode 100644 index 0000000000..e4a3d39d6d --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj @@ -0,0 +1,15 @@ + + + netstandard2.1 + osu.Game.Rulesets.Sample + Library + AnyCPU + osu.Game.Rulesets.Pippidon + + + + + + + + \ No newline at end of file diff --git a/Templates/osu.Game.Templates.csproj b/Templates/osu.Game.Templates.csproj new file mode 100644 index 0000000000..3894bf2166 --- /dev/null +++ b/Templates/osu.Game.Templates.csproj @@ -0,0 +1,24 @@ + + + Template + ppy.osu.Game.Templates + osu! templates + ppy Pty Ltd + https://github.com/ppy/osu-templates/blob/master/LICENCE.md + https://github.com/ppy/osu-templates + https://github.com/ppy/osu-templates + Automated release. + Copyright (c) 2021 ppy Pty Ltd + Templates to use when creating a ruleset for consumption in osu!. + dotnet-new;templates;osu + netstandard2.1 + true + false + content + + + + + + + From 3c3980b6bf7b32a750783143f282af22d2431725 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 5 Apr 2021 11:41:48 +0900 Subject: [PATCH 1231/1791] Update links --- Templates/osu.Game.Templates.csproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Templates/osu.Game.Templates.csproj b/Templates/osu.Game.Templates.csproj index 3894bf2166..38789e3246 100644 --- a/Templates/osu.Game.Templates.csproj +++ b/Templates/osu.Game.Templates.csproj @@ -4,9 +4,9 @@ ppy.osu.Game.Templates osu! templates ppy Pty Ltd - https://github.com/ppy/osu-templates/blob/master/LICENCE.md - https://github.com/ppy/osu-templates - https://github.com/ppy/osu-templates + https://github.com/ppy/osu/blob/master/LICENCE.md + https://github.com/ppy/osu/blob/master/Templates + https://github.com/ppy/osu Automated release. Copyright (c) 2021 ppy Pty Ltd Templates to use when creating a ruleset for consumption in osu!. From d1504e1b3e2d4d6aaf1f9b8fc5ee4e1840ef1d41 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 5 Apr 2021 11:47:37 +0900 Subject: [PATCH 1232/1791] Remove license file, fix link --- Templates/LICENSE | 21 --------------------- Templates/osu.Game.Templates.csproj | 2 +- 2 files changed, 1 insertion(+), 22 deletions(-) delete mode 100644 Templates/LICENSE diff --git a/Templates/LICENSE b/Templates/LICENSE deleted file mode 100644 index 3abffc40a7..0000000000 --- a/Templates/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2021 ppy Pty Ltd . - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/Templates/osu.Game.Templates.csproj b/Templates/osu.Game.Templates.csproj index 38789e3246..ebde5f70a5 100644 --- a/Templates/osu.Game.Templates.csproj +++ b/Templates/osu.Game.Templates.csproj @@ -4,7 +4,7 @@ ppy.osu.Game.Templates osu! templates ppy Pty Ltd - https://github.com/ppy/osu/blob/master/LICENCE.md + https://github.com/ppy/osu/blob/master/LICENCE https://github.com/ppy/osu/blob/master/Templates https://github.com/ppy/osu Automated release. From 4d9b886c07d1c6b641dc3927ffe5571892ba1921 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 5 Apr 2021 12:04:02 +0900 Subject: [PATCH 1233/1791] Add ruleset examples to solution --- .../osu.Game.Rulesets.EmptyFreeform.csproj | 2 +- .../osu.Game.Rulesets.Pippidon.csproj | 2 +- .../osu.Game.Rulesets.EmptyScrolling.csproj | 2 +- .../osu.Game.Rulesets.Pippidon.csproj | 2 +- osu.sln | 154 ++++++++++++++++++ 5 files changed, 158 insertions(+), 4 deletions(-) diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/osu.Game.Rulesets.EmptyFreeform.csproj b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/osu.Game.Rulesets.EmptyFreeform.csproj index 26349ed34f..cfe2bd1cb2 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/osu.Game.Rulesets.EmptyFreeform.csproj +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/osu.Game.Rulesets.EmptyFreeform.csproj @@ -10,6 +10,6 @@ - + \ No newline at end of file diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj index e4a3d39d6d..61b859f45b 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj @@ -10,6 +10,6 @@ - + \ No newline at end of file diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/osu.Game.Rulesets.EmptyScrolling.csproj b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/osu.Game.Rulesets.EmptyScrolling.csproj index ce0ada6b6e..9dce3c9a0a 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/osu.Game.Rulesets.EmptyScrolling.csproj +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/osu.Game.Rulesets.EmptyScrolling.csproj @@ -10,6 +10,6 @@ - + \ No newline at end of file diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj index e4a3d39d6d..61b859f45b 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj @@ -10,6 +10,6 @@ - + \ No newline at end of file diff --git a/osu.sln b/osu.sln index c9453359b1..5a251cb727 100644 --- a/osu.sln +++ b/osu.sln @@ -66,6 +66,36 @@ 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("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Templates", "Templates", "{70CFC05F-CF79-4A7F-81EC-B32F1E564480}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Rulesets", "Rulesets", "{CA1DD4A8-FA22-48E0-860F-D57A7ED7D426}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ruleset-empty", "ruleset-empty", "{6E22BB20-901E-49B3-90A1-B0E6377FE568}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ruleset-example", "ruleset-example", "{7DBBBA73-6D84-4EBA-8711-EBC2939B04B5}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ruleset-scrolling-empty", "ruleset-scrolling-empty", "{5CB72FDE-BA77-47D1-A556-FEB15AAD4523}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ruleset-scrolling-example", "ruleset-scrolling-example", "{0E0EDD4C-1E45-4E03-BC08-0102C98D34B3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.EmptyFreeform", "Templates\Rulesets\ruleset-empty\osu.Game.Rulesets.EmptyFreeform\osu.Game.Rulesets.EmptyFreeform.csproj", "{9014CA66-5217-49F6-8C1E-3430FD08EF61}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.EmptyFreeform.Tests", "Templates\Rulesets\ruleset-empty\osu.Game.Rulesets.EmptyFreeform.Tests\osu.Game.Rulesets.EmptyFreeform.Tests.csproj", "{561DFD5E-5896-40D1-9708-4D692F5BAE66}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Pippidon", "Templates\Rulesets\ruleset-example\osu.Game.Rulesets.Pippidon\osu.Game.Rulesets.Pippidon.csproj", "{B325271C-85E7-4DB3-8BBB-B70F242954F8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Pippidon.Tests", "Templates\Rulesets\ruleset-example\osu.Game.Rulesets.Pippidon.Tests\osu.Game.Rulesets.Pippidon.Tests.csproj", "{4C834F7F-07CA-46C7-8C7B-F10A1B3BC738}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.EmptyScrolling", "Templates\Rulesets\ruleset-scrolling-empty\osu.Game.Rulesets.EmptyScrolling\osu.Game.Rulesets.EmptyScrolling.csproj", "{AD923016-F318-49B7-B08B-89DED6DC2422}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.EmptyScrolling.Tests", "Templates\Rulesets\ruleset-scrolling-empty\osu.Game.Rulesets.EmptyScrolling.Tests\osu.Game.Rulesets.EmptyScrolling.Tests.csproj", "{B9B92246-02EB-4118-9C6F-85A0D726AA70}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Pippidon", "Templates\Rulesets\ruleset-scrolling-example\osu.Game.Rulesets.Pippidon\osu.Game.Rulesets.Pippidon.csproj", "{B9022390-8184-4548-9DB1-50EB8878D20A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Pippidon.Tests", "Templates\Rulesets\ruleset-scrolling-example\osu.Game.Rulesets.Pippidon.Tests\osu.Game.Rulesets.Pippidon.Tests.csproj", "{1743BF7C-E6AE-4A06-BAD9-166D62894303}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Templates", "Templates\osu.Game.Templates.csproj", "{7526A36E-09B9-4342-88CF-25969CF4158C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -412,6 +442,114 @@ 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 + {9014CA66-5217-49F6-8C1E-3430FD08EF61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9014CA66-5217-49F6-8C1E-3430FD08EF61}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9014CA66-5217-49F6-8C1E-3430FD08EF61}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {9014CA66-5217-49F6-8C1E-3430FD08EF61}.Debug|iPhone.Build.0 = Debug|Any CPU + {9014CA66-5217-49F6-8C1E-3430FD08EF61}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {9014CA66-5217-49F6-8C1E-3430FD08EF61}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {9014CA66-5217-49F6-8C1E-3430FD08EF61}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9014CA66-5217-49F6-8C1E-3430FD08EF61}.Release|Any CPU.Build.0 = Release|Any CPU + {9014CA66-5217-49F6-8C1E-3430FD08EF61}.Release|iPhone.ActiveCfg = Release|Any CPU + {9014CA66-5217-49F6-8C1E-3430FD08EF61}.Release|iPhone.Build.0 = Release|Any CPU + {9014CA66-5217-49F6-8C1E-3430FD08EF61}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {9014CA66-5217-49F6-8C1E-3430FD08EF61}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {561DFD5E-5896-40D1-9708-4D692F5BAE66}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {561DFD5E-5896-40D1-9708-4D692F5BAE66}.Debug|Any CPU.Build.0 = Debug|Any CPU + {561DFD5E-5896-40D1-9708-4D692F5BAE66}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {561DFD5E-5896-40D1-9708-4D692F5BAE66}.Debug|iPhone.Build.0 = Debug|Any CPU + {561DFD5E-5896-40D1-9708-4D692F5BAE66}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {561DFD5E-5896-40D1-9708-4D692F5BAE66}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {561DFD5E-5896-40D1-9708-4D692F5BAE66}.Release|Any CPU.ActiveCfg = Release|Any CPU + {561DFD5E-5896-40D1-9708-4D692F5BAE66}.Release|Any CPU.Build.0 = Release|Any CPU + {561DFD5E-5896-40D1-9708-4D692F5BAE66}.Release|iPhone.ActiveCfg = Release|Any CPU + {561DFD5E-5896-40D1-9708-4D692F5BAE66}.Release|iPhone.Build.0 = Release|Any CPU + {561DFD5E-5896-40D1-9708-4D692F5BAE66}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {561DFD5E-5896-40D1-9708-4D692F5BAE66}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {B325271C-85E7-4DB3-8BBB-B70F242954F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B325271C-85E7-4DB3-8BBB-B70F242954F8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B325271C-85E7-4DB3-8BBB-B70F242954F8}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {B325271C-85E7-4DB3-8BBB-B70F242954F8}.Debug|iPhone.Build.0 = Debug|Any CPU + {B325271C-85E7-4DB3-8BBB-B70F242954F8}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {B325271C-85E7-4DB3-8BBB-B70F242954F8}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {B325271C-85E7-4DB3-8BBB-B70F242954F8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B325271C-85E7-4DB3-8BBB-B70F242954F8}.Release|Any CPU.Build.0 = Release|Any CPU + {B325271C-85E7-4DB3-8BBB-B70F242954F8}.Release|iPhone.ActiveCfg = Release|Any CPU + {B325271C-85E7-4DB3-8BBB-B70F242954F8}.Release|iPhone.Build.0 = Release|Any CPU + {B325271C-85E7-4DB3-8BBB-B70F242954F8}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {B325271C-85E7-4DB3-8BBB-B70F242954F8}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {4C834F7F-07CA-46C7-8C7B-F10A1B3BC738}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4C834F7F-07CA-46C7-8C7B-F10A1B3BC738}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4C834F7F-07CA-46C7-8C7B-F10A1B3BC738}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {4C834F7F-07CA-46C7-8C7B-F10A1B3BC738}.Debug|iPhone.Build.0 = Debug|Any CPU + {4C834F7F-07CA-46C7-8C7B-F10A1B3BC738}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {4C834F7F-07CA-46C7-8C7B-F10A1B3BC738}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {4C834F7F-07CA-46C7-8C7B-F10A1B3BC738}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4C834F7F-07CA-46C7-8C7B-F10A1B3BC738}.Release|Any CPU.Build.0 = Release|Any CPU + {4C834F7F-07CA-46C7-8C7B-F10A1B3BC738}.Release|iPhone.ActiveCfg = Release|Any CPU + {4C834F7F-07CA-46C7-8C7B-F10A1B3BC738}.Release|iPhone.Build.0 = Release|Any CPU + {4C834F7F-07CA-46C7-8C7B-F10A1B3BC738}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {4C834F7F-07CA-46C7-8C7B-F10A1B3BC738}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {AD923016-F318-49B7-B08B-89DED6DC2422}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AD923016-F318-49B7-B08B-89DED6DC2422}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AD923016-F318-49B7-B08B-89DED6DC2422}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {AD923016-F318-49B7-B08B-89DED6DC2422}.Debug|iPhone.Build.0 = Debug|Any CPU + {AD923016-F318-49B7-B08B-89DED6DC2422}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {AD923016-F318-49B7-B08B-89DED6DC2422}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {AD923016-F318-49B7-B08B-89DED6DC2422}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AD923016-F318-49B7-B08B-89DED6DC2422}.Release|Any CPU.Build.0 = Release|Any CPU + {AD923016-F318-49B7-B08B-89DED6DC2422}.Release|iPhone.ActiveCfg = Release|Any CPU + {AD923016-F318-49B7-B08B-89DED6DC2422}.Release|iPhone.Build.0 = Release|Any CPU + {AD923016-F318-49B7-B08B-89DED6DC2422}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {AD923016-F318-49B7-B08B-89DED6DC2422}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {B9B92246-02EB-4118-9C6F-85A0D726AA70}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B9B92246-02EB-4118-9C6F-85A0D726AA70}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B9B92246-02EB-4118-9C6F-85A0D726AA70}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {B9B92246-02EB-4118-9C6F-85A0D726AA70}.Debug|iPhone.Build.0 = Debug|Any CPU + {B9B92246-02EB-4118-9C6F-85A0D726AA70}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {B9B92246-02EB-4118-9C6F-85A0D726AA70}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {B9B92246-02EB-4118-9C6F-85A0D726AA70}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B9B92246-02EB-4118-9C6F-85A0D726AA70}.Release|Any CPU.Build.0 = Release|Any CPU + {B9B92246-02EB-4118-9C6F-85A0D726AA70}.Release|iPhone.ActiveCfg = Release|Any CPU + {B9B92246-02EB-4118-9C6F-85A0D726AA70}.Release|iPhone.Build.0 = Release|Any CPU + {B9B92246-02EB-4118-9C6F-85A0D726AA70}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {B9B92246-02EB-4118-9C6F-85A0D726AA70}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {B9022390-8184-4548-9DB1-50EB8878D20A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B9022390-8184-4548-9DB1-50EB8878D20A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B9022390-8184-4548-9DB1-50EB8878D20A}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {B9022390-8184-4548-9DB1-50EB8878D20A}.Debug|iPhone.Build.0 = Debug|Any CPU + {B9022390-8184-4548-9DB1-50EB8878D20A}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {B9022390-8184-4548-9DB1-50EB8878D20A}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {B9022390-8184-4548-9DB1-50EB8878D20A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B9022390-8184-4548-9DB1-50EB8878D20A}.Release|Any CPU.Build.0 = Release|Any CPU + {B9022390-8184-4548-9DB1-50EB8878D20A}.Release|iPhone.ActiveCfg = Release|Any CPU + {B9022390-8184-4548-9DB1-50EB8878D20A}.Release|iPhone.Build.0 = Release|Any CPU + {B9022390-8184-4548-9DB1-50EB8878D20A}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {B9022390-8184-4548-9DB1-50EB8878D20A}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {1743BF7C-E6AE-4A06-BAD9-166D62894303}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1743BF7C-E6AE-4A06-BAD9-166D62894303}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1743BF7C-E6AE-4A06-BAD9-166D62894303}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {1743BF7C-E6AE-4A06-BAD9-166D62894303}.Debug|iPhone.Build.0 = Debug|Any CPU + {1743BF7C-E6AE-4A06-BAD9-166D62894303}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {1743BF7C-E6AE-4A06-BAD9-166D62894303}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {1743BF7C-E6AE-4A06-BAD9-166D62894303}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1743BF7C-E6AE-4A06-BAD9-166D62894303}.Release|Any CPU.Build.0 = Release|Any CPU + {1743BF7C-E6AE-4A06-BAD9-166D62894303}.Release|iPhone.ActiveCfg = Release|Any CPU + {1743BF7C-E6AE-4A06-BAD9-166D62894303}.Release|iPhone.Build.0 = Release|Any CPU + {1743BF7C-E6AE-4A06-BAD9-166D62894303}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {1743BF7C-E6AE-4A06-BAD9-166D62894303}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {7526A36E-09B9-4342-88CF-25969CF4158C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7526A36E-09B9-4342-88CF-25969CF4158C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7526A36E-09B9-4342-88CF-25969CF4158C}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {7526A36E-09B9-4342-88CF-25969CF4158C}.Debug|iPhone.Build.0 = Debug|Any CPU + {7526A36E-09B9-4342-88CF-25969CF4158C}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {7526A36E-09B9-4342-88CF-25969CF4158C}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {7526A36E-09B9-4342-88CF-25969CF4158C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7526A36E-09B9-4342-88CF-25969CF4158C}.Release|Any CPU.Build.0 = Release|Any CPU + {7526A36E-09B9-4342-88CF-25969CF4158C}.Release|iPhone.ActiveCfg = Release|Any CPU + {7526A36E-09B9-4342-88CF-25969CF4158C}.Release|iPhone.Build.0 = Release|Any CPU + {7526A36E-09B9-4342-88CF-25969CF4158C}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {7526A36E-09B9-4342-88CF-25969CF4158C}.Release|iPhoneSimulator.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -448,4 +586,20 @@ Global $2.inheritsScope = text/x-csharp $2.scope = text/x-csharp EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {CA1DD4A8-FA22-48E0-860F-D57A7ED7D426} = {70CFC05F-CF79-4A7F-81EC-B32F1E564480} + {6E22BB20-901E-49B3-90A1-B0E6377FE568} = {CA1DD4A8-FA22-48E0-860F-D57A7ED7D426} + {9014CA66-5217-49F6-8C1E-3430FD08EF61} = {6E22BB20-901E-49B3-90A1-B0E6377FE568} + {561DFD5E-5896-40D1-9708-4D692F5BAE66} = {6E22BB20-901E-49B3-90A1-B0E6377FE568} + {7DBBBA73-6D84-4EBA-8711-EBC2939B04B5} = {CA1DD4A8-FA22-48E0-860F-D57A7ED7D426} + {B325271C-85E7-4DB3-8BBB-B70F242954F8} = {7DBBBA73-6D84-4EBA-8711-EBC2939B04B5} + {4C834F7F-07CA-46C7-8C7B-F10A1B3BC738} = {7DBBBA73-6D84-4EBA-8711-EBC2939B04B5} + {5CB72FDE-BA77-47D1-A556-FEB15AAD4523} = {CA1DD4A8-FA22-48E0-860F-D57A7ED7D426} + {0E0EDD4C-1E45-4E03-BC08-0102C98D34B3} = {CA1DD4A8-FA22-48E0-860F-D57A7ED7D426} + {AD923016-F318-49B7-B08B-89DED6DC2422} = {5CB72FDE-BA77-47D1-A556-FEB15AAD4523} + {B9B92246-02EB-4118-9C6F-85A0D726AA70} = {5CB72FDE-BA77-47D1-A556-FEB15AAD4523} + {B9022390-8184-4548-9DB1-50EB8878D20A} = {0E0EDD4C-1E45-4E03-BC08-0102C98D34B3} + {1743BF7C-E6AE-4A06-BAD9-166D62894303} = {0E0EDD4C-1E45-4E03-BC08-0102C98D34B3} + {7526A36E-09B9-4342-88CF-25969CF4158C} = {70CFC05F-CF79-4A7F-81EC-B32F1E564480} + EndGlobalSection EndGlobal From 33d16a4b544b68ba444ddca8938a9b9c693c6fc4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 5 Apr 2021 12:22:38 +0900 Subject: [PATCH 1234/1791] Isolate rulesets subtree --- Templates/Directory.Build.props | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Templates/Directory.Build.props diff --git a/Templates/Directory.Build.props b/Templates/Directory.Build.props new file mode 100644 index 0000000000..0e470106e8 --- /dev/null +++ b/Templates/Directory.Build.props @@ -0,0 +1,3 @@ + + + From 73c59c4e1bfa4f8922271f3adebc6c2bc269862c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 5 Apr 2021 12:23:03 +0900 Subject: [PATCH 1235/1791] Fix ruleset templates not being included --- Templates/osu.Game.Templates.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Templates/osu.Game.Templates.csproj b/Templates/osu.Game.Templates.csproj index ebde5f70a5..31a24a301f 100644 --- a/Templates/osu.Game.Templates.csproj +++ b/Templates/osu.Game.Templates.csproj @@ -18,7 +18,7 @@ - + From b6681d01e55c65a13cdf3f2908a11adbff43cafe Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 5 Apr 2021 12:23:06 +0900 Subject: [PATCH 1236/1791] Add appveyor matrix --- appveyor_deploy.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/appveyor_deploy.yml b/appveyor_deploy.yml index 737e5c43ab..adf98848bc 100644 --- a/appveyor_deploy.yml +++ b/appveyor_deploy.yml @@ -16,6 +16,8 @@ environment: job_depends_on: osu-game - job_name: mania-ruleset job_depends_on: osu-game + - job_name: templates + job_depends_on: osu-game nuget: project_feed: true @@ -59,6 +61,22 @@ for: - cmd: dotnet remove osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj reference osu.Game\osu.Game.csproj - cmd: dotnet add osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj package ppy.osu.Game -v %APPVEYOR_REPO_TAG_NAME% - cmd: dotnet pack osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj /p:Version=%APPVEYOR_REPO_TAG_NAME% + - + matrix: + only: + - job_name: templates + build_script: + - cmd: dotnet remove Templates\Rulesets\ruleset-empty\osu.Game.Rulesets.EmptyFreeform\osu.Game.Rulesets.EmptyFreeform.csproj reference osu.Game\osu.Game.csproj + - cmd: dotnet remove Templates\Rulesets\ruleset-example\osu.Game.Rulesets.Pippidon\osu.Game.Rulesets.Pippidon.csproj reference osu.Game\osu.Game.csproj + - cmd: dotnet remove Templates\Rulesets\ruleset-scrolling-empty\osu.Game.Rulesets.EmptyScrolling\osu.Game.Rulesets.EmptyScrolling.csproj reference osu.Game\osu.Game.csproj + - cmd: dotnet remove Templates\Rulesets\ruleset-scrolling-example\osu.Game.Rulesets.Pippidon\osu.Game.Rulesets.Pippidon.csproj reference osu.Game\osu.Game.csproj + + - cmd: dotnet add Templates\Rulesets\ruleset-empty\osu.Game.Rulesets.EmptyFreeform\osu.Game.Rulesets.EmptyFreeform.csproj package ppy.osu.Game -v %APPVEYOR_REPO_TAG_NAME% + - cmd: dotnet add Templates\Rulesets\ruleset-example\osu.Game.Rulesets.Pippidon\osu.Game.Rulesets.Pippidon.csproj package ppy.osu.Game -v %APPVEYOR_REPO_TAG_NAME% + - cmd: dotnet add Templates\Rulesets\ruleset-scrolling-empty\osu.Game.Rulesets.EmptyScrolling\osu.Game.Rulesets.EmptyScrolling.csproj package ppy.osu.Game -v %APPVEYOR_REPO_TAG_NAME% + - cmd: dotnet add Templates\Rulesets\ruleset-scrolling-example\osu.Game.Rulesets.Pippidon\osu.Game.Rulesets.Pippidon.csproj package ppy.osu.Game -v %APPVEYOR_REPO_TAG_NAME% + + - cmd: dotnet pack Templates\osu.Game.Templates.csproj /p:Version=%APPVEYOR_REPO_TAG_NAME% artifacts: - path: '**\*.nupkg' From 3acc612a6702e9e7d7553fcec5fc6a53eaa0ee92 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 5 Apr 2021 13:28:46 +0900 Subject: [PATCH 1237/1791] Adjust scoring values to better fit osu!mania --- osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs | 4 ++-- osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs | 4 ++-- osu.Game/Rulesets/Judgements/Judgement.cs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs index 71cc0bdf1f..48b377c794 100644 --- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs @@ -7,8 +7,8 @@ namespace osu.Game.Rulesets.Mania.Scoring { internal class ManiaScoreProcessor : ScoreProcessor { - protected override double DefaultAccuracyPortion => 0.95; + protected override double DefaultAccuracyPortion => 0.99; - protected override double DefaultComboPortion => 0.05; + protected override double DefaultComboPortion => 0.01; } } diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index 9f16312121..20fa0732b9 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -77,7 +77,7 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(ScoringMode.Standardised, HitResult.Miss, HitResult.Great, 0)] // (3 * 0) / (4 * 300) * 300_000 + (0 / 4) * 700_000 [TestCase(ScoringMode.Standardised, HitResult.Meh, HitResult.Great, 387_500)] // (3 * 50) / (4 * 300) * 300_000 + (2 / 4) * 700_000 [TestCase(ScoringMode.Standardised, HitResult.Ok, HitResult.Great, 425_000)] // (3 * 100) / (4 * 300) * 300_000 + (2 / 4) * 700_000 - [TestCase(ScoringMode.Standardised, HitResult.Good, HitResult.Perfect, 478_571)] // (3 * 200) / (4 * 350) * 300_000 + (2 / 4) * 700_000 + [TestCase(ScoringMode.Standardised, HitResult.Good, HitResult.Perfect, 492_857)] // (3 * 200) / (4 * 350) * 300_000 + (2 / 4) * 700_000 [TestCase(ScoringMode.Standardised, HitResult.Great, HitResult.Great, 575_000)] // (3 * 300) / (4 * 300) * 300_000 + (2 / 4) * 700_000 [TestCase(ScoringMode.Standardised, HitResult.Perfect, HitResult.Perfect, 575_000)] // (3 * 350) / (4 * 350) * 300_000 + (2 / 4) * 700_000 [TestCase(ScoringMode.Standardised, HitResult.SmallTickMiss, HitResult.SmallTickHit, 700_000)] // (3 * 0) / (4 * 10) * 300_000 + 700_000 (max combo 0) @@ -89,7 +89,7 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(ScoringMode.Classic, HitResult.Miss, HitResult.Great, 0)] // (0 * 4 * 300) * (1 + 0 / 25) [TestCase(ScoringMode.Classic, HitResult.Meh, HitResult.Great, 156)] // (((3 * 50) / (4 * 300)) * 4 * 300) * (1 + 1 / 25) [TestCase(ScoringMode.Classic, HitResult.Ok, HitResult.Great, 312)] // (((3 * 100) / (4 * 300)) * 4 * 300) * (1 + 1 / 25) - [TestCase(ScoringMode.Classic, HitResult.Good, HitResult.Perfect, 535)] // (((3 * 200) / (4 * 350)) * 4 * 300) * (1 + 1 / 25) + [TestCase(ScoringMode.Classic, HitResult.Good, HitResult.Perfect, 594)] // (((3 * 200) / (4 * 350)) * 4 * 300) * (1 + 1 / 25) [TestCase(ScoringMode.Classic, HitResult.Great, HitResult.Great, 936)] // (((3 * 300) / (4 * 300)) * 4 * 300) * (1 + 1 / 25) [TestCase(ScoringMode.Classic, HitResult.Perfect, HitResult.Perfect, 936)] // (((3 * 350) / (4 * 350)) * 4 * 300) * (1 + 1 / 25) [TestCase(ScoringMode.Classic, HitResult.SmallTickMiss, HitResult.SmallTickHit, 0)] // (0 * 1 * 300) * (1 + 0 / 25) diff --git a/osu.Game/Rulesets/Judgements/Judgement.cs b/osu.Game/Rulesets/Judgements/Judgement.cs index 89a3a2b855..b1ca72b1c0 100644 --- a/osu.Game/Rulesets/Judgements/Judgement.cs +++ b/osu.Game/Rulesets/Judgements/Judgement.cs @@ -181,7 +181,7 @@ namespace osu.Game.Rulesets.Judgements return 300; case HitResult.Perfect: - return 350; + return 315; case HitResult.SmallBonus: return SMALL_BONUS_SCORE; From fe9efc277d38e9df9819be91b07be73c86a30b29 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 5 Apr 2021 13:56:04 +0900 Subject: [PATCH 1238/1791] Rename README header --- Templates/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Templates/README.md b/Templates/README.md index 75ee76ddba..cf25a89273 100644 --- a/Templates/README.md +++ b/Templates/README.md @@ -1,4 +1,4 @@ -# osu-templates +# Templates Templates for use when creating osu! dependent projects. Create a fully-testable (and ready for git) custom ruleset in just two lines. From 0f171f092f0fa12a9f600437ce751d235c2436ec Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 5 Apr 2021 14:24:47 +0900 Subject: [PATCH 1239/1791] Add template projects to desktop slnf --- osu.Desktop.slnf | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Desktop.slnf b/osu.Desktop.slnf index d2c14d321a..68541dbcfc 100644 --- a/osu.Desktop.slnf +++ b/osu.Desktop.slnf @@ -15,7 +15,16 @@ "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", + + "Templates\\Rulesets\\ruleset-empty\\osu.Game.Rulesets.EmptyFreeform\\osu.Game.Rulesets.EmptyFreeform.csproj", + "Templates\\Rulesets\\ruleset-empty\\osu.Game.Rulesets.EmptyFreeform.Tests\\osu.Game.Rulesets.EmptyFreeform.Tests.csproj", + "Templates\\Rulesets\\ruleset-example\\osu.Game.Rulesets.Pippidon\\osu.Game.Rulesets.Pippidon.csproj", + "Templates\\Rulesets\\ruleset-example\\osu.Game.Rulesets.Pippidon.Tests\\osu.Game.Rulesets.Pippidon.Tests.csproj", + "Templates\\Rulesets\\ruleset-scrolling-empty\\osu.Game.Rulesets.EmptyScrolling\\osu.Game.Rulesets.EmptyScrolling.csproj", + "Templates\\Rulesets\\ruleset-scrolling-empty\\osu.Game.Rulesets.EmptyScrolling.Tests\\osu.Game.Rulesets.EmptyScrolling.Tests.csproj", + "Templates\\Rulesets\\ruleset-scrolling-example\\osu.Game.Rulesets.Pippidon\\osu.Game.Rulesets.Pippidon.csproj", + "Templates\\Rulesets\\ruleset-scrolling-example\\osu.Game.Rulesets.Pippidon.Tests\\osu.Game.Rulesets.Pippidon.Tests.csproj", ] } } \ No newline at end of file From d3f61b4aab145b35621622767ff54257f97ddbc4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 5 Apr 2021 14:42:15 +0900 Subject: [PATCH 1240/1791] Remove templates project from sln --- osu.sln | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/osu.sln b/osu.sln index 5a251cb727..b5018db362 100644 --- a/osu.sln +++ b/osu.sln @@ -94,8 +94,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Pippidon" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Pippidon.Tests", "Templates\Rulesets\ruleset-scrolling-example\osu.Game.Rulesets.Pippidon.Tests\osu.Game.Rulesets.Pippidon.Tests.csproj", "{1743BF7C-E6AE-4A06-BAD9-166D62894303}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Templates", "Templates\osu.Game.Templates.csproj", "{7526A36E-09B9-4342-88CF-25969CF4158C}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -538,18 +536,6 @@ Global {1743BF7C-E6AE-4A06-BAD9-166D62894303}.Release|iPhone.Build.0 = Release|Any CPU {1743BF7C-E6AE-4A06-BAD9-166D62894303}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {1743BF7C-E6AE-4A06-BAD9-166D62894303}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {7526A36E-09B9-4342-88CF-25969CF4158C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7526A36E-09B9-4342-88CF-25969CF4158C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7526A36E-09B9-4342-88CF-25969CF4158C}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {7526A36E-09B9-4342-88CF-25969CF4158C}.Debug|iPhone.Build.0 = Debug|Any CPU - {7526A36E-09B9-4342-88CF-25969CF4158C}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {7526A36E-09B9-4342-88CF-25969CF4158C}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {7526A36E-09B9-4342-88CF-25969CF4158C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7526A36E-09B9-4342-88CF-25969CF4158C}.Release|Any CPU.Build.0 = Release|Any CPU - {7526A36E-09B9-4342-88CF-25969CF4158C}.Release|iPhone.ActiveCfg = Release|Any CPU - {7526A36E-09B9-4342-88CF-25969CF4158C}.Release|iPhone.Build.0 = Release|Any CPU - {7526A36E-09B9-4342-88CF-25969CF4158C}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {7526A36E-09B9-4342-88CF-25969CF4158C}.Release|iPhoneSimulator.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -600,6 +586,5 @@ Global {B9B92246-02EB-4118-9C6F-85A0D726AA70} = {5CB72FDE-BA77-47D1-A556-FEB15AAD4523} {B9022390-8184-4548-9DB1-50EB8878D20A} = {0E0EDD4C-1E45-4E03-BC08-0102C98D34B3} {1743BF7C-E6AE-4A06-BAD9-166D62894303} = {0E0EDD4C-1E45-4E03-BC08-0102C98D34B3} - {7526A36E-09B9-4342-88CF-25969CF4158C} = {70CFC05F-CF79-4A7F-81EC-B32F1E564480} EndGlobalSection EndGlobal From 85e1bc85bfe56f45702f06eb416d57d58b61a879 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 5 Apr 2021 15:21:53 +0900 Subject: [PATCH 1241/1791] Update DotSettings and .editorconfig --- .../Rulesets/ruleset-empty/.editorconfig | 185 +++- ...ame.Rulesets.EmptyFreeform.sln.DotSettings | 897 ++++++++++-------- .../Rulesets/ruleset-example/.editorconfig | 185 +++- ...osu.Game.Rulesets.Pippidon.sln.DotSettings | 897 ++++++++++-------- .../ruleset-scrolling-empty/.editorconfig | 185 +++- ...me.Rulesets.EmptyScrolling.sln.DotSettings | 897 ++++++++++-------- .../ruleset-scrolling-example/.editorconfig | 185 +++- ...osu.Game.Rulesets.Pippidon.sln.DotSettings | 889 +++++++++-------- 8 files changed, 2620 insertions(+), 1700 deletions(-) diff --git a/Templates/Rulesets/ruleset-empty/.editorconfig b/Templates/Rulesets/ruleset-empty/.editorconfig index 24825b7c42..f3badda9b3 100644 --- a/Templates/Rulesets/ruleset-empty/.editorconfig +++ b/Templates/Rulesets/ruleset-empty/.editorconfig @@ -12,16 +12,189 @@ trim_trailing_whitespace = true #PascalCase for public and protected members dotnet_naming_style.pascalcase.capitalization = pascal_case -dotnet_naming_symbols.public_members.applicable_accessibilities = public,internal,protected,protected_internal -dotnet_naming_symbols.public_members.applicable_kinds = property,method,field,event,delegate -dotnet_naming_rule.public_members_pascalcase.severity = suggestion +dotnet_naming_symbols.public_members.applicable_accessibilities = public,internal,protected,protected_internal,private_protected +dotnet_naming_symbols.public_members.applicable_kinds = property,method,field,event +dotnet_naming_rule.public_members_pascalcase.severity = error dotnet_naming_rule.public_members_pascalcase.symbols = public_members dotnet_naming_rule.public_members_pascalcase.style = pascalcase #camelCase for private members dotnet_naming_style.camelcase.capitalization = camel_case + dotnet_naming_symbols.private_members.applicable_accessibilities = private -dotnet_naming_symbols.private_members.applicable_kinds = property,method,field,event,delegate -dotnet_naming_rule.private_members_camelcase.severity = suggestion +dotnet_naming_symbols.private_members.applicable_kinds = property,method,field,event +dotnet_naming_rule.private_members_camelcase.severity = warning dotnet_naming_rule.private_members_camelcase.symbols = private_members -dotnet_naming_rule.private_members_camelcase.style = camelcase \ No newline at end of file +dotnet_naming_rule.private_members_camelcase.style = camelcase + +dotnet_naming_symbols.local_function.applicable_kinds = local_function +dotnet_naming_rule.local_function_camelcase.severity = warning +dotnet_naming_rule.local_function_camelcase.symbols = local_function +dotnet_naming_rule.local_function_camelcase.style = camelcase + +#all_lower for private and local constants/static readonlys +dotnet_naming_style.all_lower.capitalization = all_lower +dotnet_naming_style.all_lower.word_separator = _ + +dotnet_naming_symbols.private_constants.applicable_accessibilities = private +dotnet_naming_symbols.private_constants.required_modifiers = const +dotnet_naming_symbols.private_constants.applicable_kinds = field +dotnet_naming_rule.private_const_all_lower.severity = warning +dotnet_naming_rule.private_const_all_lower.symbols = private_constants +dotnet_naming_rule.private_const_all_lower.style = all_lower + +dotnet_naming_symbols.private_static_readonly.applicable_accessibilities = private +dotnet_naming_symbols.private_static_readonly.required_modifiers = static,readonly +dotnet_naming_symbols.private_static_readonly.applicable_kinds = field +dotnet_naming_rule.private_static_readonly_all_lower.severity = warning +dotnet_naming_rule.private_static_readonly_all_lower.symbols = private_static_readonly +dotnet_naming_rule.private_static_readonly_all_lower.style = all_lower + +dotnet_naming_symbols.local_constants.applicable_kinds = local +dotnet_naming_symbols.local_constants.required_modifiers = const +dotnet_naming_rule.local_const_all_lower.severity = warning +dotnet_naming_rule.local_const_all_lower.symbols = local_constants +dotnet_naming_rule.local_const_all_lower.style = all_lower + +#ALL_UPPER for non private constants/static readonlys +dotnet_naming_style.all_upper.capitalization = all_upper +dotnet_naming_style.all_upper.word_separator = _ + +dotnet_naming_symbols.public_constants.applicable_accessibilities = public,internal,protected,protected_internal,private_protected +dotnet_naming_symbols.public_constants.required_modifiers = const +dotnet_naming_symbols.public_constants.applicable_kinds = field +dotnet_naming_rule.public_const_all_upper.severity = warning +dotnet_naming_rule.public_const_all_upper.symbols = public_constants +dotnet_naming_rule.public_const_all_upper.style = all_upper + +dotnet_naming_symbols.public_static_readonly.applicable_accessibilities = public,internal,protected,protected_internal,private_protected +dotnet_naming_symbols.public_static_readonly.required_modifiers = static,readonly +dotnet_naming_symbols.public_static_readonly.applicable_kinds = field +dotnet_naming_rule.public_static_readonly_all_upper.severity = warning +dotnet_naming_rule.public_static_readonly_all_upper.symbols = public_static_readonly +dotnet_naming_rule.public_static_readonly_all_upper.style = all_upper + +#Roslyn formating options + +#Formatting - indentation options +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = false +csharp_indent_labels = one_less_than_current +csharp_indent_switch_labels = true + +#Formatting - new line options +csharp_new_line_before_catch = true +csharp_new_line_before_else = true +csharp_new_line_before_finally = true +csharp_new_line_before_open_brace = all +#csharp_new_line_before_members_in_anonymous_types = true +#csharp_new_line_before_members_in_object_initializers = true # Currently no effect in VS/dotnet format (16.4), and makes Rider confusing +csharp_new_line_between_query_expression_clauses = true + +#Formatting - organize using options +dotnet_sort_system_directives_first = true + +#Formatting - spacing options +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_parameter_list_parentheses = false + +#Formatting - wrapping options +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true + +#Roslyn language styles + +#Style - this. qualification +dotnet_style_qualification_for_field = false:warning +dotnet_style_qualification_for_property = false:warning +dotnet_style_qualification_for_method = false:warning +dotnet_style_qualification_for_event = false:warning + +#Style - type names +dotnet_style_predefined_type_for_locals_parameters_members = true:warning +dotnet_style_predefined_type_for_member_access = true:warning +csharp_style_var_when_type_is_apparent = true:none +csharp_style_var_for_built_in_types = true:none +csharp_style_var_elsewhere = true:silent + +#Style - modifiers +dotnet_style_require_accessibility_modifiers = for_non_interface_members:warning +csharp_preferred_modifier_order = public,private,protected,internal,new,abstract,virtual,sealed,override,static,readonly,extern,unsafe,volatile,async:warning + +#Style - parentheses +# Skipped because roslyn cannot separate +-*/ with << >> + +#Style - expression bodies +csharp_style_expression_bodied_accessors = true:warning +csharp_style_expression_bodied_constructors = false:none +csharp_style_expression_bodied_indexers = true:warning +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_operators = true:warning +csharp_style_expression_bodied_properties = true:warning +csharp_style_expression_bodied_local_functions = true:silent + +#Style - expression preferences +dotnet_style_object_initializer = true:warning +dotnet_style_collection_initializer = true:warning +dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning +dotnet_style_prefer_auto_properties = true:warning +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_prefer_compound_assignment = true:warning + +#Style - null/type checks +dotnet_style_coalesce_expression = true:warning +dotnet_style_null_propagation = true:warning +csharp_style_pattern_matching_over_is_with_cast_check = true:warning +csharp_style_pattern_matching_over_as_with_null_check = true:warning +csharp_style_throw_expression = true:silent +csharp_style_conditional_delegate_call = true:warning + +#Style - unused +dotnet_style_readonly_field = true:silent +dotnet_code_quality_unused_parameters = non_public:silent +csharp_style_unused_value_expression_statement_preference = discard_variable:silent +csharp_style_unused_value_assignment_preference = discard_variable:warning + +#Style - variable declaration +csharp_style_inlined_variable_declaration = true:warning +csharp_style_deconstructed_variable_declaration = true:warning + +#Style - other C# 7.x features +dotnet_style_prefer_inferred_tuple_names = true:warning +csharp_prefer_simple_default_expression = true:warning +csharp_style_pattern_local_over_anonymous_function = true:warning +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent + +#Style - C# 8 features +csharp_prefer_static_local_function = true:warning +csharp_prefer_simple_using_statement = true:silent +csharp_style_prefer_index_operator = true:warning +csharp_style_prefer_range_operator = true:warning +csharp_style_prefer_switch_expression = false:none + +#Supressing roslyn built-in analyzers +# Suppress: EC112 + +#Private method is unused +dotnet_diagnostic.IDE0051.severity = silent +#Private member is unused +dotnet_diagnostic.IDE0052.severity = silent + +#Rules for disposable +dotnet_diagnostic.IDE0067.severity = none +dotnet_diagnostic.IDE0068.severity = none +dotnet_diagnostic.IDE0069.severity = none + +#Disable operator overloads requiring alternate named methods +dotnet_diagnostic.CA2225.severity = none + +# Banned APIs +dotnet_diagnostic.RS0030.severity = error \ No newline at end of file diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.sln.DotSettings b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.sln.DotSettings index 1cbe36794a..aa8f8739c1 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.sln.DotSettings +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.sln.DotSettings @@ -12,14 +12,15 @@ HINT HINT WARNING - + WARNING True WARNING WARNING HINT + DO_NOT_SHOW HINT - HINT - HINT + WARNING + WARNING WARNING WARNING WARNING @@ -29,7 +30,7 @@ WARNING WARNING WARNING - WARNING + WARNING WARNING WARNING WARNING @@ -59,22 +60,35 @@ WARNING WARNING WARNING + WARNING WARNING WARNING WARNING WARNING - HINT + WARNING + WARNING + WARNING WARNING WARNING HINT WARNING + HINT WARNING - DO_NOT_SHOW + DO_NOT_SHOW + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING HINT WARNING DO_NOT_SHOW WARNING HINT + DO_NOT_SHOW HINT HINT ERROR @@ -92,6 +106,9 @@ HINT WARNING WARNING + DO_NOT_SHOW + WARNING + WARNING WARNING WARNING WARNING @@ -107,6 +124,7 @@ HINT HINT HINT + DO_NOT_SHOW HINT HINT WARNING @@ -120,11 +138,12 @@ DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW - WARNING - + WARNING WARNING WARNING WARNING + WARNING + WARNING WARNING WARNING ERROR @@ -171,7 +190,7 @@ WARNING WARNING WARNING - HINT + WARNING WARNING WARNING WARNING @@ -181,7 +200,10 @@ WARNING WARNING WARNING + WARNING HINT + WARNING + WARNING DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW @@ -199,12 +221,16 @@ HINT HINT HINT - HINT + HINT + HINT + DO_NOT_SHOW WARNING WARNING WARNING + WARNING WARNING + WARNING True WARNING @@ -216,11 +242,21 @@ HINT WARNING WARNING - <?xml version="1.0" encoding="utf-16"?><Profile name="Code Cleanup (peppy)"><CSArrangeThisQualifier>True</CSArrangeThisQualifier><CSUseVar><BehavourStyle>CAN_CHANGE_TO_EXPLICIT</BehavourStyle><LocalVariableStyle>ALWAYS_EXPLICIT</LocalVariableStyle><ForeachVariableStyle>ALWAYS_EXPLICIT</ForeachVariableStyle></CSUseVar><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSUpdateFileHeader>True</CSUpdateFileHeader><CSCodeStyleAttributes ArrangeTypeAccessModifier="False" ArrangeTypeMemberAccessModifier="False" SortModifiers="True" RemoveRedundantParentheses="True" AddMissingParentheses="False" ArrangeBraces="False" ArrangeAttributes="False" ArrangeArgumentsStyle="False" /><XAMLCollapseEmptyFreeformScrollingTags>False</XAMLCollapseEmptyFreeformScrollingTags><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CSArrangeQualifiers>True</CSArrangeQualifiers></Profile> + <?xml version="1.0" encoding="utf-16"?><Profile name="Code Cleanup (peppy)"><CSArrangeThisQualifier>True</CSArrangeThisQualifier><CSUseVar><BehavourStyle>CAN_CHANGE_TO_EXPLICIT</BehavourStyle><LocalVariableStyle>ALWAYS_EXPLICIT</LocalVariableStyle><ForeachVariableStyle>ALWAYS_EXPLICIT</ForeachVariableStyle></CSUseVar><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSUpdateFileHeader>True</CSUpdateFileHeader><CSCodeStyleAttributes ArrangeTypeAccessModifier="False" ArrangeTypeMemberAccessModifier="False" SortModifiers="True" RemoveRedundantParentheses="True" AddMissingParentheses="False" ArrangeBraces="False" ArrangeAttributes="False" ArrangeArgumentsStyle="False" /><XAMLCollapseEmptyTags>False</XAMLCollapseEmptyTags><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CSArrangeQualifiers>True</CSArrangeQualifiers></Profile> Code Cleanup (peppy) + RequiredForMultiline + RequiredForMultiline + RequiredForMultiline + RequiredForMultiline + RequiredForMultiline + RequiredForMultiline + RequiredForMultiline + RequiredForMultiline + Explicit ExpressionBody - ExpressionBody + BlockBody True + NEXT_LINE True True True @@ -232,14 +268,24 @@ NEXT_LINE 1 1 + NEXT_LINE + MULTILINE + True + True + True + True NEXT_LINE 1 1 True + NEXT_LINE NEVER NEVER + True False + True NEVER + False False True False @@ -247,6 +293,7 @@ True True False + False CHOP_IF_LONG True 200 @@ -260,9 +307,11 @@ GL GLSL HID + HTML HUD ID IL + IOS IP IPC JIT @@ -276,397 +325,397 @@ SHA SRGB TK - SS - PP - GMT - QAT - BNG + SS + PP + GMT + QAT + BNG UI False HINT <?xml version="1.0" encoding="utf-16"?> <Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns"> - <TypePattern DisplayName="COM interfaces or structs"> - <TypePattern.Match> - <Or> - <And> - <Kind Is="Interface" /> - <Or> - <HasAttribute Name="System.Runtime.InteropServices.InterfaceTypeAttribute" /> - <HasAttribute Name="System.Runtime.InteropServices.ComImport" /> - </Or> - </And> - <Kind Is="Struct" /> - </Or> - </TypePattern.Match> - </TypePattern> - <TypePattern DisplayName="NUnit Test Fixtures" RemoveRegions="All"> - <TypePattern.Match> - <And> - <Kind Is="Class" /> - <HasAttribute Name="NUnit.Framework.TestFixtureAttribute" Inherited="True" /> - </And> - </TypePattern.Match> - <Entry DisplayName="Setup/Teardown Methods"> - <Entry.Match> - <And> - <Kind Is="Method" /> - <Or> - <HasAttribute Name="NUnit.Framework.SetUpAttribute" Inherited="True" /> - <HasAttribute Name="NUnit.Framework.TearDownAttribute" Inherited="True" /> - <HasAttribute Name="NUnit.Framework.FixtureSetUpAttribute" Inherited="True" /> - <HasAttribute Name="NUnit.Framework.FixtureTearDownAttribute" Inherited="True" /> - </Or> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="All other members" /> - <Entry Priority="100" DisplayName="Test Methods"> - <Entry.Match> - <And> - <Kind Is="Method" /> - <HasAttribute Name="NUnit.Framework.TestAttribute" /> - </And> - </Entry.Match> - <Entry.SortBy> - <Name /> - </Entry.SortBy> - </Entry> - </TypePattern> - <TypePattern DisplayName="Default Pattern"> - <Group DisplayName="Fields/Properties"> - <Group DisplayName="Public Fields"> - <Entry DisplayName="Constant Fields"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Or> - <Kind Is="Constant" /> - <Readonly /> - <And> - <Static /> - <Readonly /> - </And> - </Or> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Static Fields"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Static /> - <Not> - <Readonly /> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Normal Fields"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Not> - <Or> - <Static /> - <Readonly /> - </Or> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Entry DisplayName="Public Properties"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Kind Is="Property" /> - </And> - </Entry.Match> - </Entry> - <Group DisplayName="Internal Fields"> - <Entry DisplayName="Constant Fields"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Or> - <Kind Is="Constant" /> - <Readonly /> - <And> - <Static /> - <Readonly /> - </And> - </Or> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Static Fields"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Static /> - <Not> - <Readonly /> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Normal Fields"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Not> - <Or> - <Static /> - <Readonly /> - </Or> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Entry DisplayName="Internal Properties"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Kind Is="Property" /> - </And> - </Entry.Match> - </Entry> - <Group DisplayName="Protected Fields"> - <Entry DisplayName="Constant Fields"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Or> - <Kind Is="Constant" /> - <Readonly /> - <And> - <Static /> - <Readonly /> - </And> - </Or> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Static Fields"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Static /> - <Not> - <Readonly /> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Normal Fields"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Not> - <Or> - <Static /> - <Readonly /> - </Or> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Entry DisplayName="Protected Properties"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Kind Is="Property" /> - </And> - </Entry.Match> - </Entry> - <Group DisplayName="Private Fields"> - <Entry DisplayName="Constant Fields"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Or> - <Kind Is="Constant" /> - <Readonly /> - <And> - <Static /> - <Readonly /> - </And> - </Or> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Static Fields"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Static /> - <Not> - <Readonly /> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Normal Fields"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Not> - <Or> - <Static /> - <Readonly /> - </Or> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Entry DisplayName="Private Properties"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Kind Is="Property" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Group DisplayName="Constructor/Destructor"> - <Entry DisplayName="Ctor"> - <Entry.Match> - <Kind Is="Constructor" /> - </Entry.Match> - </Entry> - <Region Name="Disposal"> - <Entry DisplayName="Dtor"> - <Entry.Match> - <Kind Is="Destructor" /> - </Entry.Match> - </Entry> - <Entry DisplayName="Dispose()"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Kind Is="Method" /> - <Name Is="Dispose" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Dispose(true)"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Or> - <Virtual /> - <Override /> - </Or> - <Kind Is="Method" /> - <Name Is="Dispose" /> - </And> - </Entry.Match> - </Entry> - </Region> - </Group> - <Group DisplayName="Methods"> - <Group DisplayName="Public"> - <Entry DisplayName="Static Methods"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Static /> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Methods"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Not> - <Static /> - </Not> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Group DisplayName="Internal"> - <Entry DisplayName="Static Methods"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Static /> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Methods"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Not> - <Static /> - </Not> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Group DisplayName="Protected"> - <Entry DisplayName="Static Methods"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Static /> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Methods"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Not> - <Static /> - </Not> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Group DisplayName="Private"> - <Entry DisplayName="Static Methods"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Static /> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Methods"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Not> - <Static /> - </Not> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - </Group> - </Group> - </TypePattern> + <TypePattern DisplayName="COM interfaces or structs"> + <TypePattern.Match> + <Or> + <And> + <Kind Is="Interface" /> + <Or> + <HasAttribute Name="System.Runtime.InteropServices.InterfaceTypeAttribute" /> + <HasAttribute Name="System.Runtime.InteropServices.ComImport" /> + </Or> + </And> + <Kind Is="Struct" /> + </Or> + </TypePattern.Match> + </TypePattern> + <TypePattern DisplayName="NUnit Test Fixtures" RemoveRegions="All"> + <TypePattern.Match> + <And> + <Kind Is="Class" /> + <HasAttribute Name="NUnit.Framework.TestFixtureAttribute" Inherited="True" /> + </And> + </TypePattern.Match> + <Entry DisplayName="Setup/Teardown Methods"> + <Entry.Match> + <And> + <Kind Is="Method" /> + <Or> + <HasAttribute Name="NUnit.Framework.SetUpAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.TearDownAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.FixtureSetUpAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.FixtureTearDownAttribute" Inherited="True" /> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="All other members" /> + <Entry Priority="100" DisplayName="Test Methods"> + <Entry.Match> + <And> + <Kind Is="Method" /> + <HasAttribute Name="NUnit.Framework.TestAttribute" /> + </And> + </Entry.Match> + <Entry.SortBy> + <Name /> + </Entry.SortBy> + </Entry> + </TypePattern> + <TypePattern DisplayName="Default Pattern"> + <Group DisplayName="Fields/Properties"> + <Group DisplayName="Public Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Public Properties"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + <Group DisplayName="Internal Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Internal Properties"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + <Group DisplayName="Protected Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Protected Properties"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + <Group DisplayName="Private Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Private Properties"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Constructor/Destructor"> + <Entry DisplayName="Ctor"> + <Entry.Match> + <Kind Is="Constructor" /> + </Entry.Match> + </Entry> + <Region Name="Disposal"> + <Entry DisplayName="Dtor"> + <Entry.Match> + <Kind Is="Destructor" /> + </Entry.Match> + </Entry> + <Entry DisplayName="Dispose()"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Kind Is="Method" /> + <Name Is="Dispose" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Dispose(true)"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Or> + <Virtual /> + <Override /> + </Or> + <Kind Is="Method" /> + <Name Is="Dispose" /> + </And> + </Entry.Match> + </Entry> + </Region> + </Group> + <Group DisplayName="Methods"> + <Group DisplayName="Public"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Internal"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Protected"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Private"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + </Group> + </TypePattern> </Patterns> Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. See the LICENCE file in the repository root for full licence text. @@ -726,6 +775,8 @@ See the LICENCE file in the repository root for full licence text. <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + + True True True True @@ -736,6 +787,7 @@ See the LICENCE file in the repository root for full licence text. True True True + TestFolder True True o!f – Object Initializer: Anchor&Origin @@ -761,7 +813,7 @@ Origin = Anchor.$anchor$, True InternalChildren = new Drawable[] { - $END$ + $END$ }; True True @@ -774,12 +826,12 @@ Origin = Anchor.$anchor$, True new GridContainer { - RelativeSizeAxes = Axes.Both, - Content = new[] - { - new Drawable[] { $END$ }, - new Drawable[] { } - } + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] { $END$ }, + new Drawable[] { } + } }; True True @@ -792,12 +844,12 @@ Origin = Anchor.$anchor$, True new FillFlowContainer { - RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - $END$ - } + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + $END$ + } }, True True @@ -810,11 +862,11 @@ Origin = Anchor.$anchor$, True new Container { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - $END$ - } + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + $END$ + } }, True True @@ -828,7 +880,7 @@ Origin = Anchor.$anchor$, [BackgroundDependencyLoader] private void load() { - $END$ + $END$ } True True @@ -841,8 +893,8 @@ private void load() True new Box { - Colour = Color4.Black, - RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, }, True True @@ -855,23 +907,28 @@ private void load() True Children = new Drawable[] { - $END$ + $END$ }; True True True True + True True True True + True True True True True True True + True True True True + True + True True True diff --git a/Templates/Rulesets/ruleset-example/.editorconfig b/Templates/Rulesets/ruleset-example/.editorconfig index 24825b7c42..f3badda9b3 100644 --- a/Templates/Rulesets/ruleset-example/.editorconfig +++ b/Templates/Rulesets/ruleset-example/.editorconfig @@ -12,16 +12,189 @@ trim_trailing_whitespace = true #PascalCase for public and protected members dotnet_naming_style.pascalcase.capitalization = pascal_case -dotnet_naming_symbols.public_members.applicable_accessibilities = public,internal,protected,protected_internal -dotnet_naming_symbols.public_members.applicable_kinds = property,method,field,event,delegate -dotnet_naming_rule.public_members_pascalcase.severity = suggestion +dotnet_naming_symbols.public_members.applicable_accessibilities = public,internal,protected,protected_internal,private_protected +dotnet_naming_symbols.public_members.applicable_kinds = property,method,field,event +dotnet_naming_rule.public_members_pascalcase.severity = error dotnet_naming_rule.public_members_pascalcase.symbols = public_members dotnet_naming_rule.public_members_pascalcase.style = pascalcase #camelCase for private members dotnet_naming_style.camelcase.capitalization = camel_case + dotnet_naming_symbols.private_members.applicable_accessibilities = private -dotnet_naming_symbols.private_members.applicable_kinds = property,method,field,event,delegate -dotnet_naming_rule.private_members_camelcase.severity = suggestion +dotnet_naming_symbols.private_members.applicable_kinds = property,method,field,event +dotnet_naming_rule.private_members_camelcase.severity = warning dotnet_naming_rule.private_members_camelcase.symbols = private_members -dotnet_naming_rule.private_members_camelcase.style = camelcase \ No newline at end of file +dotnet_naming_rule.private_members_camelcase.style = camelcase + +dotnet_naming_symbols.local_function.applicable_kinds = local_function +dotnet_naming_rule.local_function_camelcase.severity = warning +dotnet_naming_rule.local_function_camelcase.symbols = local_function +dotnet_naming_rule.local_function_camelcase.style = camelcase + +#all_lower for private and local constants/static readonlys +dotnet_naming_style.all_lower.capitalization = all_lower +dotnet_naming_style.all_lower.word_separator = _ + +dotnet_naming_symbols.private_constants.applicable_accessibilities = private +dotnet_naming_symbols.private_constants.required_modifiers = const +dotnet_naming_symbols.private_constants.applicable_kinds = field +dotnet_naming_rule.private_const_all_lower.severity = warning +dotnet_naming_rule.private_const_all_lower.symbols = private_constants +dotnet_naming_rule.private_const_all_lower.style = all_lower + +dotnet_naming_symbols.private_static_readonly.applicable_accessibilities = private +dotnet_naming_symbols.private_static_readonly.required_modifiers = static,readonly +dotnet_naming_symbols.private_static_readonly.applicable_kinds = field +dotnet_naming_rule.private_static_readonly_all_lower.severity = warning +dotnet_naming_rule.private_static_readonly_all_lower.symbols = private_static_readonly +dotnet_naming_rule.private_static_readonly_all_lower.style = all_lower + +dotnet_naming_symbols.local_constants.applicable_kinds = local +dotnet_naming_symbols.local_constants.required_modifiers = const +dotnet_naming_rule.local_const_all_lower.severity = warning +dotnet_naming_rule.local_const_all_lower.symbols = local_constants +dotnet_naming_rule.local_const_all_lower.style = all_lower + +#ALL_UPPER for non private constants/static readonlys +dotnet_naming_style.all_upper.capitalization = all_upper +dotnet_naming_style.all_upper.word_separator = _ + +dotnet_naming_symbols.public_constants.applicable_accessibilities = public,internal,protected,protected_internal,private_protected +dotnet_naming_symbols.public_constants.required_modifiers = const +dotnet_naming_symbols.public_constants.applicable_kinds = field +dotnet_naming_rule.public_const_all_upper.severity = warning +dotnet_naming_rule.public_const_all_upper.symbols = public_constants +dotnet_naming_rule.public_const_all_upper.style = all_upper + +dotnet_naming_symbols.public_static_readonly.applicable_accessibilities = public,internal,protected,protected_internal,private_protected +dotnet_naming_symbols.public_static_readonly.required_modifiers = static,readonly +dotnet_naming_symbols.public_static_readonly.applicable_kinds = field +dotnet_naming_rule.public_static_readonly_all_upper.severity = warning +dotnet_naming_rule.public_static_readonly_all_upper.symbols = public_static_readonly +dotnet_naming_rule.public_static_readonly_all_upper.style = all_upper + +#Roslyn formating options + +#Formatting - indentation options +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = false +csharp_indent_labels = one_less_than_current +csharp_indent_switch_labels = true + +#Formatting - new line options +csharp_new_line_before_catch = true +csharp_new_line_before_else = true +csharp_new_line_before_finally = true +csharp_new_line_before_open_brace = all +#csharp_new_line_before_members_in_anonymous_types = true +#csharp_new_line_before_members_in_object_initializers = true # Currently no effect in VS/dotnet format (16.4), and makes Rider confusing +csharp_new_line_between_query_expression_clauses = true + +#Formatting - organize using options +dotnet_sort_system_directives_first = true + +#Formatting - spacing options +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_parameter_list_parentheses = false + +#Formatting - wrapping options +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true + +#Roslyn language styles + +#Style - this. qualification +dotnet_style_qualification_for_field = false:warning +dotnet_style_qualification_for_property = false:warning +dotnet_style_qualification_for_method = false:warning +dotnet_style_qualification_for_event = false:warning + +#Style - type names +dotnet_style_predefined_type_for_locals_parameters_members = true:warning +dotnet_style_predefined_type_for_member_access = true:warning +csharp_style_var_when_type_is_apparent = true:none +csharp_style_var_for_built_in_types = true:none +csharp_style_var_elsewhere = true:silent + +#Style - modifiers +dotnet_style_require_accessibility_modifiers = for_non_interface_members:warning +csharp_preferred_modifier_order = public,private,protected,internal,new,abstract,virtual,sealed,override,static,readonly,extern,unsafe,volatile,async:warning + +#Style - parentheses +# Skipped because roslyn cannot separate +-*/ with << >> + +#Style - expression bodies +csharp_style_expression_bodied_accessors = true:warning +csharp_style_expression_bodied_constructors = false:none +csharp_style_expression_bodied_indexers = true:warning +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_operators = true:warning +csharp_style_expression_bodied_properties = true:warning +csharp_style_expression_bodied_local_functions = true:silent + +#Style - expression preferences +dotnet_style_object_initializer = true:warning +dotnet_style_collection_initializer = true:warning +dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning +dotnet_style_prefer_auto_properties = true:warning +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_prefer_compound_assignment = true:warning + +#Style - null/type checks +dotnet_style_coalesce_expression = true:warning +dotnet_style_null_propagation = true:warning +csharp_style_pattern_matching_over_is_with_cast_check = true:warning +csharp_style_pattern_matching_over_as_with_null_check = true:warning +csharp_style_throw_expression = true:silent +csharp_style_conditional_delegate_call = true:warning + +#Style - unused +dotnet_style_readonly_field = true:silent +dotnet_code_quality_unused_parameters = non_public:silent +csharp_style_unused_value_expression_statement_preference = discard_variable:silent +csharp_style_unused_value_assignment_preference = discard_variable:warning + +#Style - variable declaration +csharp_style_inlined_variable_declaration = true:warning +csharp_style_deconstructed_variable_declaration = true:warning + +#Style - other C# 7.x features +dotnet_style_prefer_inferred_tuple_names = true:warning +csharp_prefer_simple_default_expression = true:warning +csharp_style_pattern_local_over_anonymous_function = true:warning +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent + +#Style - C# 8 features +csharp_prefer_static_local_function = true:warning +csharp_prefer_simple_using_statement = true:silent +csharp_style_prefer_index_operator = true:warning +csharp_style_prefer_range_operator = true:warning +csharp_style_prefer_switch_expression = false:none + +#Supressing roslyn built-in analyzers +# Suppress: EC112 + +#Private method is unused +dotnet_diagnostic.IDE0051.severity = silent +#Private member is unused +dotnet_diagnostic.IDE0052.severity = silent + +#Rules for disposable +dotnet_diagnostic.IDE0067.severity = none +dotnet_diagnostic.IDE0068.severity = none +dotnet_diagnostic.IDE0069.severity = none + +#Disable operator overloads requiring alternate named methods +dotnet_diagnostic.CA2225.severity = none + +# Banned APIs +dotnet_diagnostic.RS0030.severity = error \ No newline at end of file diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.sln.DotSettings b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.sln.DotSettings index 190a1046f5..aa8f8739c1 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.sln.DotSettings +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.sln.DotSettings @@ -12,14 +12,15 @@ HINT HINT WARNING - + WARNING True WARNING WARNING HINT + DO_NOT_SHOW HINT - HINT - HINT + WARNING + WARNING WARNING WARNING WARNING @@ -29,7 +30,7 @@ WARNING WARNING WARNING - WARNING + WARNING WARNING WARNING WARNING @@ -59,22 +60,35 @@ WARNING WARNING WARNING + WARNING WARNING WARNING WARNING WARNING - HINT + WARNING + WARNING + WARNING WARNING WARNING HINT WARNING + HINT WARNING - DO_NOT_SHOW + DO_NOT_SHOW + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING HINT WARNING DO_NOT_SHOW WARNING HINT + DO_NOT_SHOW HINT HINT ERROR @@ -92,6 +106,9 @@ HINT WARNING WARNING + DO_NOT_SHOW + WARNING + WARNING WARNING WARNING WARNING @@ -107,6 +124,7 @@ HINT HINT HINT + DO_NOT_SHOW HINT HINT WARNING @@ -120,11 +138,12 @@ DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW - WARNING - + WARNING WARNING WARNING WARNING + WARNING + WARNING WARNING WARNING ERROR @@ -171,7 +190,7 @@ WARNING WARNING WARNING - HINT + WARNING WARNING WARNING WARNING @@ -181,7 +200,10 @@ WARNING WARNING WARNING + WARNING HINT + WARNING + WARNING DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW @@ -199,12 +221,16 @@ HINT HINT HINT - HINT + HINT + HINT + DO_NOT_SHOW WARNING WARNING WARNING + WARNING WARNING + WARNING True WARNING @@ -216,11 +242,21 @@ HINT WARNING WARNING - <?xml version="1.0" encoding="utf-16"?><Profile name="Code Cleanup (peppy)"><CSArrangeThisQualifier>True</CSArrangeThisQualifier><CSUseVar><BehavourStyle>CAN_CHANGE_TO_EXPLICIT</BehavourStyle><LocalVariableStyle>ALWAYS_EXPLICIT</LocalVariableStyle><ForeachVariableStyle>ALWAYS_EXPLICIT</ForeachVariableStyle></CSUseVar><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSUpdateFileHeader>True</CSUpdateFileHeader><CSCodeStyleAttributes ArrangeTypeAccessModifier="False" ArrangeTypeMemberAccessModifier="False" SortModifiers="True" RemoveRedundantParentheses="True" AddMissingParentheses="False" ArrangeBraces="False" ArrangeAttributes="False" ArrangeArgumentsStyle="False" /><XAMLCollapsePippidonScrollingTags>False</XAMLCollapsePippidonScrollingTags><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CSArrangeQualifiers>True</CSArrangeQualifiers></Profile> + <?xml version="1.0" encoding="utf-16"?><Profile name="Code Cleanup (peppy)"><CSArrangeThisQualifier>True</CSArrangeThisQualifier><CSUseVar><BehavourStyle>CAN_CHANGE_TO_EXPLICIT</BehavourStyle><LocalVariableStyle>ALWAYS_EXPLICIT</LocalVariableStyle><ForeachVariableStyle>ALWAYS_EXPLICIT</ForeachVariableStyle></CSUseVar><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSUpdateFileHeader>True</CSUpdateFileHeader><CSCodeStyleAttributes ArrangeTypeAccessModifier="False" ArrangeTypeMemberAccessModifier="False" SortModifiers="True" RemoveRedundantParentheses="True" AddMissingParentheses="False" ArrangeBraces="False" ArrangeAttributes="False" ArrangeArgumentsStyle="False" /><XAMLCollapseEmptyTags>False</XAMLCollapseEmptyTags><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CSArrangeQualifiers>True</CSArrangeQualifiers></Profile> Code Cleanup (peppy) + RequiredForMultiline + RequiredForMultiline + RequiredForMultiline + RequiredForMultiline + RequiredForMultiline + RequiredForMultiline + RequiredForMultiline + RequiredForMultiline + Explicit ExpressionBody - ExpressionBody + BlockBody True + NEXT_LINE True True True @@ -232,14 +268,24 @@ NEXT_LINE 1 1 + NEXT_LINE + MULTILINE + True + True + True + True NEXT_LINE 1 1 True + NEXT_LINE NEVER NEVER + True False + True NEVER + False False True False @@ -247,6 +293,7 @@ True True False + False CHOP_IF_LONG True 200 @@ -260,9 +307,11 @@ GL GLSL HID + HTML HUD ID IL + IOS IP IPC JIT @@ -276,397 +325,397 @@ SHA SRGB TK - SS - PP - GMT - QAT - BNG + SS + PP + GMT + QAT + BNG UI False HINT <?xml version="1.0" encoding="utf-16"?> <Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns"> - <TypePattern DisplayName="COM interfaces or structs"> - <TypePattern.Match> - <Or> - <And> - <Kind Is="Interface" /> - <Or> - <HasAttribute Name="System.Runtime.InteropServices.InterfaceTypeAttribute" /> - <HasAttribute Name="System.Runtime.InteropServices.ComImport" /> - </Or> - </And> - <Kind Is="Struct" /> - </Or> - </TypePattern.Match> - </TypePattern> - <TypePattern DisplayName="NUnit Test Fixtures" RemoveRegions="All"> - <TypePattern.Match> - <And> - <Kind Is="Class" /> - <HasAttribute Name="NUnit.Framework.TestFixtureAttribute" Inherited="True" /> - </And> - </TypePattern.Match> - <Entry DisplayName="Setup/Teardown Methods"> - <Entry.Match> - <And> - <Kind Is="Method" /> - <Or> - <HasAttribute Name="NUnit.Framework.SetUpAttribute" Inherited="True" /> - <HasAttribute Name="NUnit.Framework.TearDownAttribute" Inherited="True" /> - <HasAttribute Name="NUnit.Framework.FixtureSetUpAttribute" Inherited="True" /> - <HasAttribute Name="NUnit.Framework.FixtureTearDownAttribute" Inherited="True" /> - </Or> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="All other members" /> - <Entry Priority="100" DisplayName="Test Methods"> - <Entry.Match> - <And> - <Kind Is="Method" /> - <HasAttribute Name="NUnit.Framework.TestAttribute" /> - </And> - </Entry.Match> - <Entry.SortBy> - <Name /> - </Entry.SortBy> - </Entry> - </TypePattern> - <TypePattern DisplayName="Default Pattern"> - <Group DisplayName="Fields/Properties"> - <Group DisplayName="Public Fields"> - <Entry DisplayName="Constant Fields"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Or> - <Kind Is="Constant" /> - <Readonly /> - <And> - <Static /> - <Readonly /> - </And> - </Or> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Static Fields"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Static /> - <Not> - <Readonly /> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Normal Fields"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Not> - <Or> - <Static /> - <Readonly /> - </Or> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Entry DisplayName="Public Properties"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Kind Is="Property" /> - </And> - </Entry.Match> - </Entry> - <Group DisplayName="Internal Fields"> - <Entry DisplayName="Constant Fields"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Or> - <Kind Is="Constant" /> - <Readonly /> - <And> - <Static /> - <Readonly /> - </And> - </Or> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Static Fields"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Static /> - <Not> - <Readonly /> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Normal Fields"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Not> - <Or> - <Static /> - <Readonly /> - </Or> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Entry DisplayName="Internal Properties"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Kind Is="Property" /> - </And> - </Entry.Match> - </Entry> - <Group DisplayName="Protected Fields"> - <Entry DisplayName="Constant Fields"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Or> - <Kind Is="Constant" /> - <Readonly /> - <And> - <Static /> - <Readonly /> - </And> - </Or> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Static Fields"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Static /> - <Not> - <Readonly /> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Normal Fields"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Not> - <Or> - <Static /> - <Readonly /> - </Or> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Entry DisplayName="Protected Properties"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Kind Is="Property" /> - </And> - </Entry.Match> - </Entry> - <Group DisplayName="Private Fields"> - <Entry DisplayName="Constant Fields"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Or> - <Kind Is="Constant" /> - <Readonly /> - <And> - <Static /> - <Readonly /> - </And> - </Or> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Static Fields"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Static /> - <Not> - <Readonly /> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Normal Fields"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Not> - <Or> - <Static /> - <Readonly /> - </Or> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Entry DisplayName="Private Properties"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Kind Is="Property" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Group DisplayName="Constructor/Destructor"> - <Entry DisplayName="Ctor"> - <Entry.Match> - <Kind Is="Constructor" /> - </Entry.Match> - </Entry> - <Region Name="Disposal"> - <Entry DisplayName="Dtor"> - <Entry.Match> - <Kind Is="Destructor" /> - </Entry.Match> - </Entry> - <Entry DisplayName="Dispose()"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Kind Is="Method" /> - <Name Is="Dispose" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Dispose(true)"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Or> - <Virtual /> - <Override /> - </Or> - <Kind Is="Method" /> - <Name Is="Dispose" /> - </And> - </Entry.Match> - </Entry> - </Region> - </Group> - <Group DisplayName="Methods"> - <Group DisplayName="Public"> - <Entry DisplayName="Static Methods"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Static /> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Methods"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Not> - <Static /> - </Not> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Group DisplayName="Internal"> - <Entry DisplayName="Static Methods"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Static /> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Methods"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Not> - <Static /> - </Not> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Group DisplayName="Protected"> - <Entry DisplayName="Static Methods"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Static /> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Methods"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Not> - <Static /> - </Not> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Group DisplayName="Private"> - <Entry DisplayName="Static Methods"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Static /> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Methods"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Not> - <Static /> - </Not> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - </Group> - </Group> - </TypePattern> + <TypePattern DisplayName="COM interfaces or structs"> + <TypePattern.Match> + <Or> + <And> + <Kind Is="Interface" /> + <Or> + <HasAttribute Name="System.Runtime.InteropServices.InterfaceTypeAttribute" /> + <HasAttribute Name="System.Runtime.InteropServices.ComImport" /> + </Or> + </And> + <Kind Is="Struct" /> + </Or> + </TypePattern.Match> + </TypePattern> + <TypePattern DisplayName="NUnit Test Fixtures" RemoveRegions="All"> + <TypePattern.Match> + <And> + <Kind Is="Class" /> + <HasAttribute Name="NUnit.Framework.TestFixtureAttribute" Inherited="True" /> + </And> + </TypePattern.Match> + <Entry DisplayName="Setup/Teardown Methods"> + <Entry.Match> + <And> + <Kind Is="Method" /> + <Or> + <HasAttribute Name="NUnit.Framework.SetUpAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.TearDownAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.FixtureSetUpAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.FixtureTearDownAttribute" Inherited="True" /> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="All other members" /> + <Entry Priority="100" DisplayName="Test Methods"> + <Entry.Match> + <And> + <Kind Is="Method" /> + <HasAttribute Name="NUnit.Framework.TestAttribute" /> + </And> + </Entry.Match> + <Entry.SortBy> + <Name /> + </Entry.SortBy> + </Entry> + </TypePattern> + <TypePattern DisplayName="Default Pattern"> + <Group DisplayName="Fields/Properties"> + <Group DisplayName="Public Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Public Properties"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + <Group DisplayName="Internal Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Internal Properties"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + <Group DisplayName="Protected Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Protected Properties"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + <Group DisplayName="Private Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Private Properties"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Constructor/Destructor"> + <Entry DisplayName="Ctor"> + <Entry.Match> + <Kind Is="Constructor" /> + </Entry.Match> + </Entry> + <Region Name="Disposal"> + <Entry DisplayName="Dtor"> + <Entry.Match> + <Kind Is="Destructor" /> + </Entry.Match> + </Entry> + <Entry DisplayName="Dispose()"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Kind Is="Method" /> + <Name Is="Dispose" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Dispose(true)"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Or> + <Virtual /> + <Override /> + </Or> + <Kind Is="Method" /> + <Name Is="Dispose" /> + </And> + </Entry.Match> + </Entry> + </Region> + </Group> + <Group DisplayName="Methods"> + <Group DisplayName="Public"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Internal"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Protected"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Private"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + </Group> + </TypePattern> </Patterns> Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. See the LICENCE file in the repository root for full licence text. @@ -726,6 +775,8 @@ See the LICENCE file in the repository root for full licence text. <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + + True True True True @@ -736,6 +787,7 @@ See the LICENCE file in the repository root for full licence text. True True True + TestFolder True True o!f – Object Initializer: Anchor&Origin @@ -761,7 +813,7 @@ Origin = Anchor.$anchor$, True InternalChildren = new Drawable[] { - $END$ + $END$ }; True True @@ -774,12 +826,12 @@ Origin = Anchor.$anchor$, True new GridContainer { - RelativeSizeAxes = Axes.Both, - Content = new[] - { - new Drawable[] { $END$ }, - new Drawable[] { } - } + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] { $END$ }, + new Drawable[] { } + } }; True True @@ -792,12 +844,12 @@ Origin = Anchor.$anchor$, True new FillFlowContainer { - RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - $END$ - } + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + $END$ + } }, True True @@ -810,11 +862,11 @@ Origin = Anchor.$anchor$, True new Container { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - $END$ - } + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + $END$ + } }, True True @@ -828,7 +880,7 @@ Origin = Anchor.$anchor$, [BackgroundDependencyLoader] private void load() { - $END$ + $END$ } True True @@ -841,8 +893,8 @@ private void load() True new Box { - Colour = Color4.Black, - RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, }, True True @@ -855,23 +907,28 @@ private void load() True Children = new Drawable[] { - $END$ + $END$ }; True True True True + True True True True + True True True True True True True + True True True True + True + True True True diff --git a/Templates/Rulesets/ruleset-scrolling-empty/.editorconfig b/Templates/Rulesets/ruleset-scrolling-empty/.editorconfig index 24825b7c42..f3badda9b3 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/.editorconfig +++ b/Templates/Rulesets/ruleset-scrolling-empty/.editorconfig @@ -12,16 +12,189 @@ trim_trailing_whitespace = true #PascalCase for public and protected members dotnet_naming_style.pascalcase.capitalization = pascal_case -dotnet_naming_symbols.public_members.applicable_accessibilities = public,internal,protected,protected_internal -dotnet_naming_symbols.public_members.applicable_kinds = property,method,field,event,delegate -dotnet_naming_rule.public_members_pascalcase.severity = suggestion +dotnet_naming_symbols.public_members.applicable_accessibilities = public,internal,protected,protected_internal,private_protected +dotnet_naming_symbols.public_members.applicable_kinds = property,method,field,event +dotnet_naming_rule.public_members_pascalcase.severity = error dotnet_naming_rule.public_members_pascalcase.symbols = public_members dotnet_naming_rule.public_members_pascalcase.style = pascalcase #camelCase for private members dotnet_naming_style.camelcase.capitalization = camel_case + dotnet_naming_symbols.private_members.applicable_accessibilities = private -dotnet_naming_symbols.private_members.applicable_kinds = property,method,field,event,delegate -dotnet_naming_rule.private_members_camelcase.severity = suggestion +dotnet_naming_symbols.private_members.applicable_kinds = property,method,field,event +dotnet_naming_rule.private_members_camelcase.severity = warning dotnet_naming_rule.private_members_camelcase.symbols = private_members -dotnet_naming_rule.private_members_camelcase.style = camelcase \ No newline at end of file +dotnet_naming_rule.private_members_camelcase.style = camelcase + +dotnet_naming_symbols.local_function.applicable_kinds = local_function +dotnet_naming_rule.local_function_camelcase.severity = warning +dotnet_naming_rule.local_function_camelcase.symbols = local_function +dotnet_naming_rule.local_function_camelcase.style = camelcase + +#all_lower for private and local constants/static readonlys +dotnet_naming_style.all_lower.capitalization = all_lower +dotnet_naming_style.all_lower.word_separator = _ + +dotnet_naming_symbols.private_constants.applicable_accessibilities = private +dotnet_naming_symbols.private_constants.required_modifiers = const +dotnet_naming_symbols.private_constants.applicable_kinds = field +dotnet_naming_rule.private_const_all_lower.severity = warning +dotnet_naming_rule.private_const_all_lower.symbols = private_constants +dotnet_naming_rule.private_const_all_lower.style = all_lower + +dotnet_naming_symbols.private_static_readonly.applicable_accessibilities = private +dotnet_naming_symbols.private_static_readonly.required_modifiers = static,readonly +dotnet_naming_symbols.private_static_readonly.applicable_kinds = field +dotnet_naming_rule.private_static_readonly_all_lower.severity = warning +dotnet_naming_rule.private_static_readonly_all_lower.symbols = private_static_readonly +dotnet_naming_rule.private_static_readonly_all_lower.style = all_lower + +dotnet_naming_symbols.local_constants.applicable_kinds = local +dotnet_naming_symbols.local_constants.required_modifiers = const +dotnet_naming_rule.local_const_all_lower.severity = warning +dotnet_naming_rule.local_const_all_lower.symbols = local_constants +dotnet_naming_rule.local_const_all_lower.style = all_lower + +#ALL_UPPER for non private constants/static readonlys +dotnet_naming_style.all_upper.capitalization = all_upper +dotnet_naming_style.all_upper.word_separator = _ + +dotnet_naming_symbols.public_constants.applicable_accessibilities = public,internal,protected,protected_internal,private_protected +dotnet_naming_symbols.public_constants.required_modifiers = const +dotnet_naming_symbols.public_constants.applicable_kinds = field +dotnet_naming_rule.public_const_all_upper.severity = warning +dotnet_naming_rule.public_const_all_upper.symbols = public_constants +dotnet_naming_rule.public_const_all_upper.style = all_upper + +dotnet_naming_symbols.public_static_readonly.applicable_accessibilities = public,internal,protected,protected_internal,private_protected +dotnet_naming_symbols.public_static_readonly.required_modifiers = static,readonly +dotnet_naming_symbols.public_static_readonly.applicable_kinds = field +dotnet_naming_rule.public_static_readonly_all_upper.severity = warning +dotnet_naming_rule.public_static_readonly_all_upper.symbols = public_static_readonly +dotnet_naming_rule.public_static_readonly_all_upper.style = all_upper + +#Roslyn formating options + +#Formatting - indentation options +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = false +csharp_indent_labels = one_less_than_current +csharp_indent_switch_labels = true + +#Formatting - new line options +csharp_new_line_before_catch = true +csharp_new_line_before_else = true +csharp_new_line_before_finally = true +csharp_new_line_before_open_brace = all +#csharp_new_line_before_members_in_anonymous_types = true +#csharp_new_line_before_members_in_object_initializers = true # Currently no effect in VS/dotnet format (16.4), and makes Rider confusing +csharp_new_line_between_query_expression_clauses = true + +#Formatting - organize using options +dotnet_sort_system_directives_first = true + +#Formatting - spacing options +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_parameter_list_parentheses = false + +#Formatting - wrapping options +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true + +#Roslyn language styles + +#Style - this. qualification +dotnet_style_qualification_for_field = false:warning +dotnet_style_qualification_for_property = false:warning +dotnet_style_qualification_for_method = false:warning +dotnet_style_qualification_for_event = false:warning + +#Style - type names +dotnet_style_predefined_type_for_locals_parameters_members = true:warning +dotnet_style_predefined_type_for_member_access = true:warning +csharp_style_var_when_type_is_apparent = true:none +csharp_style_var_for_built_in_types = true:none +csharp_style_var_elsewhere = true:silent + +#Style - modifiers +dotnet_style_require_accessibility_modifiers = for_non_interface_members:warning +csharp_preferred_modifier_order = public,private,protected,internal,new,abstract,virtual,sealed,override,static,readonly,extern,unsafe,volatile,async:warning + +#Style - parentheses +# Skipped because roslyn cannot separate +-*/ with << >> + +#Style - expression bodies +csharp_style_expression_bodied_accessors = true:warning +csharp_style_expression_bodied_constructors = false:none +csharp_style_expression_bodied_indexers = true:warning +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_operators = true:warning +csharp_style_expression_bodied_properties = true:warning +csharp_style_expression_bodied_local_functions = true:silent + +#Style - expression preferences +dotnet_style_object_initializer = true:warning +dotnet_style_collection_initializer = true:warning +dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning +dotnet_style_prefer_auto_properties = true:warning +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_prefer_compound_assignment = true:warning + +#Style - null/type checks +dotnet_style_coalesce_expression = true:warning +dotnet_style_null_propagation = true:warning +csharp_style_pattern_matching_over_is_with_cast_check = true:warning +csharp_style_pattern_matching_over_as_with_null_check = true:warning +csharp_style_throw_expression = true:silent +csharp_style_conditional_delegate_call = true:warning + +#Style - unused +dotnet_style_readonly_field = true:silent +dotnet_code_quality_unused_parameters = non_public:silent +csharp_style_unused_value_expression_statement_preference = discard_variable:silent +csharp_style_unused_value_assignment_preference = discard_variable:warning + +#Style - variable declaration +csharp_style_inlined_variable_declaration = true:warning +csharp_style_deconstructed_variable_declaration = true:warning + +#Style - other C# 7.x features +dotnet_style_prefer_inferred_tuple_names = true:warning +csharp_prefer_simple_default_expression = true:warning +csharp_style_pattern_local_over_anonymous_function = true:warning +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent + +#Style - C# 8 features +csharp_prefer_static_local_function = true:warning +csharp_prefer_simple_using_statement = true:silent +csharp_style_prefer_index_operator = true:warning +csharp_style_prefer_range_operator = true:warning +csharp_style_prefer_switch_expression = false:none + +#Supressing roslyn built-in analyzers +# Suppress: EC112 + +#Private method is unused +dotnet_diagnostic.IDE0051.severity = silent +#Private member is unused +dotnet_diagnostic.IDE0052.severity = silent + +#Rules for disposable +dotnet_diagnostic.IDE0067.severity = none +dotnet_diagnostic.IDE0068.severity = none +dotnet_diagnostic.IDE0069.severity = none + +#Disable operator overloads requiring alternate named methods +dotnet_diagnostic.CA2225.severity = none + +# Banned APIs +dotnet_diagnostic.RS0030.severity = error \ No newline at end of file diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.sln.DotSettings b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.sln.DotSettings index 1ceac22be9..aa8f8739c1 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.sln.DotSettings +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.sln.DotSettings @@ -12,14 +12,15 @@ HINT HINT WARNING - + WARNING True WARNING WARNING HINT + DO_NOT_SHOW HINT - HINT - HINT + WARNING + WARNING WARNING WARNING WARNING @@ -29,7 +30,7 @@ WARNING WARNING WARNING - WARNING + WARNING WARNING WARNING WARNING @@ -59,22 +60,35 @@ WARNING WARNING WARNING + WARNING WARNING WARNING WARNING WARNING - HINT + WARNING + WARNING + WARNING WARNING WARNING HINT WARNING + HINT WARNING - DO_NOT_SHOW + DO_NOT_SHOW + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING HINT WARNING DO_NOT_SHOW WARNING HINT + DO_NOT_SHOW HINT HINT ERROR @@ -92,6 +106,9 @@ HINT WARNING WARNING + DO_NOT_SHOW + WARNING + WARNING WARNING WARNING WARNING @@ -107,6 +124,7 @@ HINT HINT HINT + DO_NOT_SHOW HINT HINT WARNING @@ -120,11 +138,12 @@ DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW - WARNING - + WARNING WARNING WARNING WARNING + WARNING + WARNING WARNING WARNING ERROR @@ -171,7 +190,7 @@ WARNING WARNING WARNING - HINT + WARNING WARNING WARNING WARNING @@ -181,7 +200,10 @@ WARNING WARNING WARNING + WARNING HINT + WARNING + WARNING DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW @@ -199,12 +221,16 @@ HINT HINT HINT - HINT + HINT + HINT + DO_NOT_SHOW WARNING WARNING WARNING + WARNING WARNING + WARNING True WARNING @@ -216,11 +242,21 @@ HINT WARNING WARNING - <?xml version="1.0" encoding="utf-16"?><Profile name="Code Cleanup (peppy)"><CSArrangeThisQualifier>True</CSArrangeThisQualifier><CSUseVar><BehavourStyle>CAN_CHANGE_TO_EXPLICIT</BehavourStyle><LocalVariableStyle>ALWAYS_EXPLICIT</LocalVariableStyle><ForeachVariableStyle>ALWAYS_EXPLICIT</ForeachVariableStyle></CSUseVar><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSUpdateFileHeader>True</CSUpdateFileHeader><CSCodeStyleAttributes ArrangeTypeAccessModifier="False" ArrangeTypeMemberAccessModifier="False" SortModifiers="True" RemoveRedundantParentheses="True" AddMissingParentheses="False" ArrangeBraces="False" ArrangeAttributes="False" ArrangeArgumentsStyle="False" /><XAMLCollapseEmptyScrollingTags>False</XAMLCollapseEmptyScrollingTags><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CSArrangeQualifiers>True</CSArrangeQualifiers></Profile> + <?xml version="1.0" encoding="utf-16"?><Profile name="Code Cleanup (peppy)"><CSArrangeThisQualifier>True</CSArrangeThisQualifier><CSUseVar><BehavourStyle>CAN_CHANGE_TO_EXPLICIT</BehavourStyle><LocalVariableStyle>ALWAYS_EXPLICIT</LocalVariableStyle><ForeachVariableStyle>ALWAYS_EXPLICIT</ForeachVariableStyle></CSUseVar><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSUpdateFileHeader>True</CSUpdateFileHeader><CSCodeStyleAttributes ArrangeTypeAccessModifier="False" ArrangeTypeMemberAccessModifier="False" SortModifiers="True" RemoveRedundantParentheses="True" AddMissingParentheses="False" ArrangeBraces="False" ArrangeAttributes="False" ArrangeArgumentsStyle="False" /><XAMLCollapseEmptyTags>False</XAMLCollapseEmptyTags><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CSArrangeQualifiers>True</CSArrangeQualifiers></Profile> Code Cleanup (peppy) + RequiredForMultiline + RequiredForMultiline + RequiredForMultiline + RequiredForMultiline + RequiredForMultiline + RequiredForMultiline + RequiredForMultiline + RequiredForMultiline + Explicit ExpressionBody - ExpressionBody + BlockBody True + NEXT_LINE True True True @@ -232,14 +268,24 @@ NEXT_LINE 1 1 + NEXT_LINE + MULTILINE + True + True + True + True NEXT_LINE 1 1 True + NEXT_LINE NEVER NEVER + True False + True NEVER + False False True False @@ -247,6 +293,7 @@ True True False + False CHOP_IF_LONG True 200 @@ -260,9 +307,11 @@ GL GLSL HID + HTML HUD ID IL + IOS IP IPC JIT @@ -276,397 +325,397 @@ SHA SRGB TK - SS - PP - GMT - QAT - BNG + SS + PP + GMT + QAT + BNG UI False HINT <?xml version="1.0" encoding="utf-16"?> <Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns"> - <TypePattern DisplayName="COM interfaces or structs"> - <TypePattern.Match> - <Or> - <And> - <Kind Is="Interface" /> - <Or> - <HasAttribute Name="System.Runtime.InteropServices.InterfaceTypeAttribute" /> - <HasAttribute Name="System.Runtime.InteropServices.ComImport" /> - </Or> - </And> - <Kind Is="Struct" /> - </Or> - </TypePattern.Match> - </TypePattern> - <TypePattern DisplayName="NUnit Test Fixtures" RemoveRegions="All"> - <TypePattern.Match> - <And> - <Kind Is="Class" /> - <HasAttribute Name="NUnit.Framework.TestFixtureAttribute" Inherited="True" /> - </And> - </TypePattern.Match> - <Entry DisplayName="Setup/Teardown Methods"> - <Entry.Match> - <And> - <Kind Is="Method" /> - <Or> - <HasAttribute Name="NUnit.Framework.SetUpAttribute" Inherited="True" /> - <HasAttribute Name="NUnit.Framework.TearDownAttribute" Inherited="True" /> - <HasAttribute Name="NUnit.Framework.FixtureSetUpAttribute" Inherited="True" /> - <HasAttribute Name="NUnit.Framework.FixtureTearDownAttribute" Inherited="True" /> - </Or> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="All other members" /> - <Entry Priority="100" DisplayName="Test Methods"> - <Entry.Match> - <And> - <Kind Is="Method" /> - <HasAttribute Name="NUnit.Framework.TestAttribute" /> - </And> - </Entry.Match> - <Entry.SortBy> - <Name /> - </Entry.SortBy> - </Entry> - </TypePattern> - <TypePattern DisplayName="Default Pattern"> - <Group DisplayName="Fields/Properties"> - <Group DisplayName="Public Fields"> - <Entry DisplayName="Constant Fields"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Or> - <Kind Is="Constant" /> - <Readonly /> - <And> - <Static /> - <Readonly /> - </And> - </Or> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Static Fields"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Static /> - <Not> - <Readonly /> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Normal Fields"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Not> - <Or> - <Static /> - <Readonly /> - </Or> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Entry DisplayName="Public Properties"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Kind Is="Property" /> - </And> - </Entry.Match> - </Entry> - <Group DisplayName="Internal Fields"> - <Entry DisplayName="Constant Fields"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Or> - <Kind Is="Constant" /> - <Readonly /> - <And> - <Static /> - <Readonly /> - </And> - </Or> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Static Fields"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Static /> - <Not> - <Readonly /> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Normal Fields"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Not> - <Or> - <Static /> - <Readonly /> - </Or> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Entry DisplayName="Internal Properties"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Kind Is="Property" /> - </And> - </Entry.Match> - </Entry> - <Group DisplayName="Protected Fields"> - <Entry DisplayName="Constant Fields"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Or> - <Kind Is="Constant" /> - <Readonly /> - <And> - <Static /> - <Readonly /> - </And> - </Or> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Static Fields"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Static /> - <Not> - <Readonly /> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Normal Fields"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Not> - <Or> - <Static /> - <Readonly /> - </Or> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Entry DisplayName="Protected Properties"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Kind Is="Property" /> - </And> - </Entry.Match> - </Entry> - <Group DisplayName="Private Fields"> - <Entry DisplayName="Constant Fields"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Or> - <Kind Is="Constant" /> - <Readonly /> - <And> - <Static /> - <Readonly /> - </And> - </Or> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Static Fields"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Static /> - <Not> - <Readonly /> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Normal Fields"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Not> - <Or> - <Static /> - <Readonly /> - </Or> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Entry DisplayName="Private Properties"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Kind Is="Property" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Group DisplayName="Constructor/Destructor"> - <Entry DisplayName="Ctor"> - <Entry.Match> - <Kind Is="Constructor" /> - </Entry.Match> - </Entry> - <Region Name="Disposal"> - <Entry DisplayName="Dtor"> - <Entry.Match> - <Kind Is="Destructor" /> - </Entry.Match> - </Entry> - <Entry DisplayName="Dispose()"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Kind Is="Method" /> - <Name Is="Dispose" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Dispose(true)"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Or> - <Virtual /> - <Override /> - </Or> - <Kind Is="Method" /> - <Name Is="Dispose" /> - </And> - </Entry.Match> - </Entry> - </Region> - </Group> - <Group DisplayName="Methods"> - <Group DisplayName="Public"> - <Entry DisplayName="Static Methods"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Static /> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Methods"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Not> - <Static /> - </Not> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Group DisplayName="Internal"> - <Entry DisplayName="Static Methods"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Static /> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Methods"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Not> - <Static /> - </Not> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Group DisplayName="Protected"> - <Entry DisplayName="Static Methods"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Static /> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Methods"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Not> - <Static /> - </Not> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Group DisplayName="Private"> - <Entry DisplayName="Static Methods"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Static /> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Methods"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Not> - <Static /> - </Not> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - </Group> - </Group> - </TypePattern> + <TypePattern DisplayName="COM interfaces or structs"> + <TypePattern.Match> + <Or> + <And> + <Kind Is="Interface" /> + <Or> + <HasAttribute Name="System.Runtime.InteropServices.InterfaceTypeAttribute" /> + <HasAttribute Name="System.Runtime.InteropServices.ComImport" /> + </Or> + </And> + <Kind Is="Struct" /> + </Or> + </TypePattern.Match> + </TypePattern> + <TypePattern DisplayName="NUnit Test Fixtures" RemoveRegions="All"> + <TypePattern.Match> + <And> + <Kind Is="Class" /> + <HasAttribute Name="NUnit.Framework.TestFixtureAttribute" Inherited="True" /> + </And> + </TypePattern.Match> + <Entry DisplayName="Setup/Teardown Methods"> + <Entry.Match> + <And> + <Kind Is="Method" /> + <Or> + <HasAttribute Name="NUnit.Framework.SetUpAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.TearDownAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.FixtureSetUpAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.FixtureTearDownAttribute" Inherited="True" /> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="All other members" /> + <Entry Priority="100" DisplayName="Test Methods"> + <Entry.Match> + <And> + <Kind Is="Method" /> + <HasAttribute Name="NUnit.Framework.TestAttribute" /> + </And> + </Entry.Match> + <Entry.SortBy> + <Name /> + </Entry.SortBy> + </Entry> + </TypePattern> + <TypePattern DisplayName="Default Pattern"> + <Group DisplayName="Fields/Properties"> + <Group DisplayName="Public Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Public Properties"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + <Group DisplayName="Internal Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Internal Properties"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + <Group DisplayName="Protected Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Protected Properties"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + <Group DisplayName="Private Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Private Properties"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Constructor/Destructor"> + <Entry DisplayName="Ctor"> + <Entry.Match> + <Kind Is="Constructor" /> + </Entry.Match> + </Entry> + <Region Name="Disposal"> + <Entry DisplayName="Dtor"> + <Entry.Match> + <Kind Is="Destructor" /> + </Entry.Match> + </Entry> + <Entry DisplayName="Dispose()"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Kind Is="Method" /> + <Name Is="Dispose" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Dispose(true)"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Or> + <Virtual /> + <Override /> + </Or> + <Kind Is="Method" /> + <Name Is="Dispose" /> + </And> + </Entry.Match> + </Entry> + </Region> + </Group> + <Group DisplayName="Methods"> + <Group DisplayName="Public"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Internal"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Protected"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Private"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + </Group> + </TypePattern> </Patterns> Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. See the LICENCE file in the repository root for full licence text. @@ -726,6 +775,8 @@ See the LICENCE file in the repository root for full licence text. <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + + True True True True @@ -736,6 +787,7 @@ See the LICENCE file in the repository root for full licence text. True True True + TestFolder True True o!f – Object Initializer: Anchor&Origin @@ -761,7 +813,7 @@ Origin = Anchor.$anchor$, True InternalChildren = new Drawable[] { - $END$ + $END$ }; True True @@ -774,12 +826,12 @@ Origin = Anchor.$anchor$, True new GridContainer { - RelativeSizeAxes = Axes.Both, - Content = new[] - { - new Drawable[] { $END$ }, - new Drawable[] { } - } + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] { $END$ }, + new Drawable[] { } + } }; True True @@ -792,12 +844,12 @@ Origin = Anchor.$anchor$, True new FillFlowContainer { - RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - $END$ - } + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + $END$ + } }, True True @@ -810,11 +862,11 @@ Origin = Anchor.$anchor$, True new Container { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - $END$ - } + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + $END$ + } }, True True @@ -828,7 +880,7 @@ Origin = Anchor.$anchor$, [BackgroundDependencyLoader] private void load() { - $END$ + $END$ } True True @@ -841,8 +893,8 @@ private void load() True new Box { - Colour = Color4.Black, - RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, }, True True @@ -855,23 +907,28 @@ private void load() True Children = new Drawable[] { - $END$ + $END$ }; True True True True + True True True True + True True True True True True True + True True True True + True + True True True diff --git a/Templates/Rulesets/ruleset-scrolling-example/.editorconfig b/Templates/Rulesets/ruleset-scrolling-example/.editorconfig index 24825b7c42..f3badda9b3 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/.editorconfig +++ b/Templates/Rulesets/ruleset-scrolling-example/.editorconfig @@ -12,16 +12,189 @@ trim_trailing_whitespace = true #PascalCase for public and protected members dotnet_naming_style.pascalcase.capitalization = pascal_case -dotnet_naming_symbols.public_members.applicable_accessibilities = public,internal,protected,protected_internal -dotnet_naming_symbols.public_members.applicable_kinds = property,method,field,event,delegate -dotnet_naming_rule.public_members_pascalcase.severity = suggestion +dotnet_naming_symbols.public_members.applicable_accessibilities = public,internal,protected,protected_internal,private_protected +dotnet_naming_symbols.public_members.applicable_kinds = property,method,field,event +dotnet_naming_rule.public_members_pascalcase.severity = error dotnet_naming_rule.public_members_pascalcase.symbols = public_members dotnet_naming_rule.public_members_pascalcase.style = pascalcase #camelCase for private members dotnet_naming_style.camelcase.capitalization = camel_case + dotnet_naming_symbols.private_members.applicable_accessibilities = private -dotnet_naming_symbols.private_members.applicable_kinds = property,method,field,event,delegate -dotnet_naming_rule.private_members_camelcase.severity = suggestion +dotnet_naming_symbols.private_members.applicable_kinds = property,method,field,event +dotnet_naming_rule.private_members_camelcase.severity = warning dotnet_naming_rule.private_members_camelcase.symbols = private_members -dotnet_naming_rule.private_members_camelcase.style = camelcase \ No newline at end of file +dotnet_naming_rule.private_members_camelcase.style = camelcase + +dotnet_naming_symbols.local_function.applicable_kinds = local_function +dotnet_naming_rule.local_function_camelcase.severity = warning +dotnet_naming_rule.local_function_camelcase.symbols = local_function +dotnet_naming_rule.local_function_camelcase.style = camelcase + +#all_lower for private and local constants/static readonlys +dotnet_naming_style.all_lower.capitalization = all_lower +dotnet_naming_style.all_lower.word_separator = _ + +dotnet_naming_symbols.private_constants.applicable_accessibilities = private +dotnet_naming_symbols.private_constants.required_modifiers = const +dotnet_naming_symbols.private_constants.applicable_kinds = field +dotnet_naming_rule.private_const_all_lower.severity = warning +dotnet_naming_rule.private_const_all_lower.symbols = private_constants +dotnet_naming_rule.private_const_all_lower.style = all_lower + +dotnet_naming_symbols.private_static_readonly.applicable_accessibilities = private +dotnet_naming_symbols.private_static_readonly.required_modifiers = static,readonly +dotnet_naming_symbols.private_static_readonly.applicable_kinds = field +dotnet_naming_rule.private_static_readonly_all_lower.severity = warning +dotnet_naming_rule.private_static_readonly_all_lower.symbols = private_static_readonly +dotnet_naming_rule.private_static_readonly_all_lower.style = all_lower + +dotnet_naming_symbols.local_constants.applicable_kinds = local +dotnet_naming_symbols.local_constants.required_modifiers = const +dotnet_naming_rule.local_const_all_lower.severity = warning +dotnet_naming_rule.local_const_all_lower.symbols = local_constants +dotnet_naming_rule.local_const_all_lower.style = all_lower + +#ALL_UPPER for non private constants/static readonlys +dotnet_naming_style.all_upper.capitalization = all_upper +dotnet_naming_style.all_upper.word_separator = _ + +dotnet_naming_symbols.public_constants.applicable_accessibilities = public,internal,protected,protected_internal,private_protected +dotnet_naming_symbols.public_constants.required_modifiers = const +dotnet_naming_symbols.public_constants.applicable_kinds = field +dotnet_naming_rule.public_const_all_upper.severity = warning +dotnet_naming_rule.public_const_all_upper.symbols = public_constants +dotnet_naming_rule.public_const_all_upper.style = all_upper + +dotnet_naming_symbols.public_static_readonly.applicable_accessibilities = public,internal,protected,protected_internal,private_protected +dotnet_naming_symbols.public_static_readonly.required_modifiers = static,readonly +dotnet_naming_symbols.public_static_readonly.applicable_kinds = field +dotnet_naming_rule.public_static_readonly_all_upper.severity = warning +dotnet_naming_rule.public_static_readonly_all_upper.symbols = public_static_readonly +dotnet_naming_rule.public_static_readonly_all_upper.style = all_upper + +#Roslyn formating options + +#Formatting - indentation options +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = false +csharp_indent_labels = one_less_than_current +csharp_indent_switch_labels = true + +#Formatting - new line options +csharp_new_line_before_catch = true +csharp_new_line_before_else = true +csharp_new_line_before_finally = true +csharp_new_line_before_open_brace = all +#csharp_new_line_before_members_in_anonymous_types = true +#csharp_new_line_before_members_in_object_initializers = true # Currently no effect in VS/dotnet format (16.4), and makes Rider confusing +csharp_new_line_between_query_expression_clauses = true + +#Formatting - organize using options +dotnet_sort_system_directives_first = true + +#Formatting - spacing options +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_parameter_list_parentheses = false + +#Formatting - wrapping options +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true + +#Roslyn language styles + +#Style - this. qualification +dotnet_style_qualification_for_field = false:warning +dotnet_style_qualification_for_property = false:warning +dotnet_style_qualification_for_method = false:warning +dotnet_style_qualification_for_event = false:warning + +#Style - type names +dotnet_style_predefined_type_for_locals_parameters_members = true:warning +dotnet_style_predefined_type_for_member_access = true:warning +csharp_style_var_when_type_is_apparent = true:none +csharp_style_var_for_built_in_types = true:none +csharp_style_var_elsewhere = true:silent + +#Style - modifiers +dotnet_style_require_accessibility_modifiers = for_non_interface_members:warning +csharp_preferred_modifier_order = public,private,protected,internal,new,abstract,virtual,sealed,override,static,readonly,extern,unsafe,volatile,async:warning + +#Style - parentheses +# Skipped because roslyn cannot separate +-*/ with << >> + +#Style - expression bodies +csharp_style_expression_bodied_accessors = true:warning +csharp_style_expression_bodied_constructors = false:none +csharp_style_expression_bodied_indexers = true:warning +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_operators = true:warning +csharp_style_expression_bodied_properties = true:warning +csharp_style_expression_bodied_local_functions = true:silent + +#Style - expression preferences +dotnet_style_object_initializer = true:warning +dotnet_style_collection_initializer = true:warning +dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning +dotnet_style_prefer_auto_properties = true:warning +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_prefer_compound_assignment = true:warning + +#Style - null/type checks +dotnet_style_coalesce_expression = true:warning +dotnet_style_null_propagation = true:warning +csharp_style_pattern_matching_over_is_with_cast_check = true:warning +csharp_style_pattern_matching_over_as_with_null_check = true:warning +csharp_style_throw_expression = true:silent +csharp_style_conditional_delegate_call = true:warning + +#Style - unused +dotnet_style_readonly_field = true:silent +dotnet_code_quality_unused_parameters = non_public:silent +csharp_style_unused_value_expression_statement_preference = discard_variable:silent +csharp_style_unused_value_assignment_preference = discard_variable:warning + +#Style - variable declaration +csharp_style_inlined_variable_declaration = true:warning +csharp_style_deconstructed_variable_declaration = true:warning + +#Style - other C# 7.x features +dotnet_style_prefer_inferred_tuple_names = true:warning +csharp_prefer_simple_default_expression = true:warning +csharp_style_pattern_local_over_anonymous_function = true:warning +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent + +#Style - C# 8 features +csharp_prefer_static_local_function = true:warning +csharp_prefer_simple_using_statement = true:silent +csharp_style_prefer_index_operator = true:warning +csharp_style_prefer_range_operator = true:warning +csharp_style_prefer_switch_expression = false:none + +#Supressing roslyn built-in analyzers +# Suppress: EC112 + +#Private method is unused +dotnet_diagnostic.IDE0051.severity = silent +#Private member is unused +dotnet_diagnostic.IDE0052.severity = silent + +#Rules for disposable +dotnet_diagnostic.IDE0067.severity = none +dotnet_diagnostic.IDE0068.severity = none +dotnet_diagnostic.IDE0069.severity = none + +#Disable operator overloads requiring alternate named methods +dotnet_diagnostic.CA2225.severity = none + +# Banned APIs +dotnet_diagnostic.RS0030.severity = error \ No newline at end of file diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.sln.DotSettings b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.sln.DotSettings index c3e274569d..aa8f8739c1 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.sln.DotSettings +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.sln.DotSettings @@ -12,14 +12,15 @@ HINT HINT WARNING - + WARNING True WARNING WARNING HINT + DO_NOT_SHOW HINT - HINT - HINT + WARNING + WARNING WARNING WARNING WARNING @@ -59,22 +60,35 @@ WARNING WARNING WARNING + WARNING WARNING WARNING WARNING WARNING - HINT + WARNING + WARNING + WARNING WARNING WARNING HINT WARNING + HINT WARNING DO_NOT_SHOW + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING HINT WARNING DO_NOT_SHOW WARNING HINT + DO_NOT_SHOW HINT HINT ERROR @@ -92,6 +106,9 @@ HINT WARNING WARNING + DO_NOT_SHOW + WARNING + WARNING WARNING WARNING WARNING @@ -107,6 +124,7 @@ HINT HINT HINT + DO_NOT_SHOW HINT HINT WARNING @@ -121,10 +139,11 @@ DO_NOT_SHOW DO_NOT_SHOW WARNING - WARNING WARNING WARNING + WARNING + WARNING WARNING WARNING ERROR @@ -171,7 +190,7 @@ WARNING WARNING WARNING - HINT + WARNING WARNING WARNING WARNING @@ -181,7 +200,10 @@ WARNING WARNING WARNING + WARNING HINT + WARNING + WARNING DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW @@ -199,12 +221,16 @@ HINT HINT HINT - HINT + HINT + HINT + DO_NOT_SHOW WARNING WARNING WARNING + WARNING WARNING + WARNING True WARNING @@ -218,9 +244,19 @@ WARNING <?xml version="1.0" encoding="utf-16"?><Profile name="Code Cleanup (peppy)"><CSArrangeThisQualifier>True</CSArrangeThisQualifier><CSUseVar><BehavourStyle>CAN_CHANGE_TO_EXPLICIT</BehavourStyle><LocalVariableStyle>ALWAYS_EXPLICIT</LocalVariableStyle><ForeachVariableStyle>ALWAYS_EXPLICIT</ForeachVariableStyle></CSUseVar><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSUpdateFileHeader>True</CSUpdateFileHeader><CSCodeStyleAttributes ArrangeTypeAccessModifier="False" ArrangeTypeMemberAccessModifier="False" SortModifiers="True" RemoveRedundantParentheses="True" AddMissingParentheses="False" ArrangeBraces="False" ArrangeAttributes="False" ArrangeArgumentsStyle="False" /><XAMLCollapseEmptyTags>False</XAMLCollapseEmptyTags><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CSArrangeQualifiers>True</CSArrangeQualifiers></Profile> Code Cleanup (peppy) + RequiredForMultiline + RequiredForMultiline + RequiredForMultiline + RequiredForMultiline + RequiredForMultiline + RequiredForMultiline + RequiredForMultiline + RequiredForMultiline + Explicit ExpressionBody - ExpressionBody + BlockBody True + NEXT_LINE True True True @@ -232,14 +268,24 @@ NEXT_LINE 1 1 + NEXT_LINE + MULTILINE + True + True + True + True NEXT_LINE 1 1 True + NEXT_LINE NEVER NEVER + True False + True NEVER + False False True False @@ -247,6 +293,7 @@ True True False + False CHOP_IF_LONG True 200 @@ -260,9 +307,11 @@ GL GLSL HID + HTML HUD ID IL + IOS IP IPC JIT @@ -276,397 +325,397 @@ SHA SRGB TK - SS - PP - GMT - QAT - BNG + SS + PP + GMT + QAT + BNG UI False HINT <?xml version="1.0" encoding="utf-16"?> <Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns"> - <TypePattern DisplayName="COM interfaces or structs"> - <TypePattern.Match> - <Or> - <And> - <Kind Is="Interface" /> - <Or> - <HasAttribute Name="System.Runtime.InteropServices.InterfaceTypeAttribute" /> - <HasAttribute Name="System.Runtime.InteropServices.ComImport" /> - </Or> - </And> - <Kind Is="Struct" /> - </Or> - </TypePattern.Match> - </TypePattern> - <TypePattern DisplayName="NUnit Test Fixtures" RemoveRegions="All"> - <TypePattern.Match> - <And> - <Kind Is="Class" /> - <HasAttribute Name="NUnit.Framework.TestFixtureAttribute" Inherited="True" /> - </And> - </TypePattern.Match> - <Entry DisplayName="Setup/Teardown Methods"> - <Entry.Match> - <And> - <Kind Is="Method" /> - <Or> - <HasAttribute Name="NUnit.Framework.SetUpAttribute" Inherited="True" /> - <HasAttribute Name="NUnit.Framework.TearDownAttribute" Inherited="True" /> - <HasAttribute Name="NUnit.Framework.FixtureSetUpAttribute" Inherited="True" /> - <HasAttribute Name="NUnit.Framework.FixtureTearDownAttribute" Inherited="True" /> - </Or> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="All other members" /> - <Entry Priority="100" DisplayName="Test Methods"> - <Entry.Match> - <And> - <Kind Is="Method" /> - <HasAttribute Name="NUnit.Framework.TestAttribute" /> - </And> - </Entry.Match> - <Entry.SortBy> - <Name /> - </Entry.SortBy> - </Entry> - </TypePattern> - <TypePattern DisplayName="Default Pattern"> - <Group DisplayName="Fields/Properties"> - <Group DisplayName="Public Fields"> - <Entry DisplayName="Constant Fields"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Or> - <Kind Is="Constant" /> - <Readonly /> - <And> - <Static /> - <Readonly /> - </And> - </Or> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Static Fields"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Static /> - <Not> - <Readonly /> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Normal Fields"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Not> - <Or> - <Static /> - <Readonly /> - </Or> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Entry DisplayName="Public Properties"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Kind Is="Property" /> - </And> - </Entry.Match> - </Entry> - <Group DisplayName="Internal Fields"> - <Entry DisplayName="Constant Fields"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Or> - <Kind Is="Constant" /> - <Readonly /> - <And> - <Static /> - <Readonly /> - </And> - </Or> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Static Fields"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Static /> - <Not> - <Readonly /> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Normal Fields"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Not> - <Or> - <Static /> - <Readonly /> - </Or> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Entry DisplayName="Internal Properties"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Kind Is="Property" /> - </And> - </Entry.Match> - </Entry> - <Group DisplayName="Protected Fields"> - <Entry DisplayName="Constant Fields"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Or> - <Kind Is="Constant" /> - <Readonly /> - <And> - <Static /> - <Readonly /> - </And> - </Or> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Static Fields"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Static /> - <Not> - <Readonly /> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Normal Fields"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Not> - <Or> - <Static /> - <Readonly /> - </Or> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Entry DisplayName="Protected Properties"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Kind Is="Property" /> - </And> - </Entry.Match> - </Entry> - <Group DisplayName="Private Fields"> - <Entry DisplayName="Constant Fields"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Or> - <Kind Is="Constant" /> - <Readonly /> - <And> - <Static /> - <Readonly /> - </And> - </Or> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Static Fields"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Static /> - <Not> - <Readonly /> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Normal Fields"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Not> - <Or> - <Static /> - <Readonly /> - </Or> - </Not> - <Kind Is="Field" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Entry DisplayName="Private Properties"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Kind Is="Property" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Group DisplayName="Constructor/Destructor"> - <Entry DisplayName="Ctor"> - <Entry.Match> - <Kind Is="Constructor" /> - </Entry.Match> - </Entry> - <Region Name="Disposal"> - <Entry DisplayName="Dtor"> - <Entry.Match> - <Kind Is="Destructor" /> - </Entry.Match> - </Entry> - <Entry DisplayName="Dispose()"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Kind Is="Method" /> - <Name Is="Dispose" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Dispose(true)"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Or> - <Virtual /> - <Override /> - </Or> - <Kind Is="Method" /> - <Name Is="Dispose" /> - </And> - </Entry.Match> - </Entry> - </Region> - </Group> - <Group DisplayName="Methods"> - <Group DisplayName="Public"> - <Entry DisplayName="Static Methods"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Static /> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Methods"> - <Entry.Match> - <And> - <Access Is="Public" /> - <Not> - <Static /> - </Not> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Group DisplayName="Internal"> - <Entry DisplayName="Static Methods"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Static /> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Methods"> - <Entry.Match> - <And> - <Access Is="Internal" /> - <Not> - <Static /> - </Not> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Group DisplayName="Protected"> - <Entry DisplayName="Static Methods"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Static /> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Methods"> - <Entry.Match> - <And> - <Access Is="Protected" /> - <Not> - <Static /> - </Not> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - </Group> - <Group DisplayName="Private"> - <Entry DisplayName="Static Methods"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Static /> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - <Entry DisplayName="Methods"> - <Entry.Match> - <And> - <Access Is="Private" /> - <Not> - <Static /> - </Not> - <Kind Is="Method" /> - </And> - </Entry.Match> - </Entry> - </Group> - </Group> - </TypePattern> + <TypePattern DisplayName="COM interfaces or structs"> + <TypePattern.Match> + <Or> + <And> + <Kind Is="Interface" /> + <Or> + <HasAttribute Name="System.Runtime.InteropServices.InterfaceTypeAttribute" /> + <HasAttribute Name="System.Runtime.InteropServices.ComImport" /> + </Or> + </And> + <Kind Is="Struct" /> + </Or> + </TypePattern.Match> + </TypePattern> + <TypePattern DisplayName="NUnit Test Fixtures" RemoveRegions="All"> + <TypePattern.Match> + <And> + <Kind Is="Class" /> + <HasAttribute Name="NUnit.Framework.TestFixtureAttribute" Inherited="True" /> + </And> + </TypePattern.Match> + <Entry DisplayName="Setup/Teardown Methods"> + <Entry.Match> + <And> + <Kind Is="Method" /> + <Or> + <HasAttribute Name="NUnit.Framework.SetUpAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.TearDownAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.FixtureSetUpAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.FixtureTearDownAttribute" Inherited="True" /> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="All other members" /> + <Entry Priority="100" DisplayName="Test Methods"> + <Entry.Match> + <And> + <Kind Is="Method" /> + <HasAttribute Name="NUnit.Framework.TestAttribute" /> + </And> + </Entry.Match> + <Entry.SortBy> + <Name /> + </Entry.SortBy> + </Entry> + </TypePattern> + <TypePattern DisplayName="Default Pattern"> + <Group DisplayName="Fields/Properties"> + <Group DisplayName="Public Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Public Properties"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + <Group DisplayName="Internal Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Internal Properties"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + <Group DisplayName="Protected Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Protected Properties"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + <Group DisplayName="Private Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Private Properties"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Constructor/Destructor"> + <Entry DisplayName="Ctor"> + <Entry.Match> + <Kind Is="Constructor" /> + </Entry.Match> + </Entry> + <Region Name="Disposal"> + <Entry DisplayName="Dtor"> + <Entry.Match> + <Kind Is="Destructor" /> + </Entry.Match> + </Entry> + <Entry DisplayName="Dispose()"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Kind Is="Method" /> + <Name Is="Dispose" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Dispose(true)"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Or> + <Virtual /> + <Override /> + </Or> + <Kind Is="Method" /> + <Name Is="Dispose" /> + </And> + </Entry.Match> + </Entry> + </Region> + </Group> + <Group DisplayName="Methods"> + <Group DisplayName="Public"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Internal"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Protected"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Private"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + </Group> + </TypePattern> </Patterns> Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. See the LICENCE file in the repository root for full licence text. @@ -726,6 +775,8 @@ See the LICENCE file in the repository root for full licence text. <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + + True True True True @@ -736,6 +787,7 @@ See the LICENCE file in the repository root for full licence text. True True True + TestFolder True True o!f – Object Initializer: Anchor&Origin @@ -761,7 +813,7 @@ Origin = Anchor.$anchor$, True InternalChildren = new Drawable[] { - $END$ + $END$ }; True True @@ -774,12 +826,12 @@ Origin = Anchor.$anchor$, True new GridContainer { - RelativeSizeAxes = Axes.Both, - Content = new[] - { - new Drawable[] { $END$ }, - new Drawable[] { } - } + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] { $END$ }, + new Drawable[] { } + } }; True True @@ -792,12 +844,12 @@ Origin = Anchor.$anchor$, True new FillFlowContainer { - RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - $END$ - } + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + $END$ + } }, True True @@ -810,11 +862,11 @@ Origin = Anchor.$anchor$, True new Container { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - $END$ - } + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + $END$ + } }, True True @@ -828,7 +880,7 @@ Origin = Anchor.$anchor$, [BackgroundDependencyLoader] private void load() { - $END$ + $END$ } True True @@ -841,8 +893,8 @@ private void load() True new Box { - Colour = Color4.Black, - RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, }, True True @@ -855,23 +907,28 @@ private void load() True Children = new Drawable[] { - $END$ + $END$ }; True True True True + True True True True + True True True True True True True + True True True True + True + True True True From 78759ceb6c3abfb870ec39b668e7f776300ade00 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Apr 2021 16:06:31 +0900 Subject: [PATCH 1242/1791] Update link to templates --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e09b4d86a5..0d6af2aeba 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ If your platform is not listed above, there is still a chance you can manually b ## Developing a custom ruleset -osu! is designed to have extensible modular gameplay modes, called "rulesets". Building one of these allows a developer to harness the power of osu! for their own game style. To get started working on a ruleset, we have some templates available [here](https://github.com/ppy/osu-templates). +osu! is designed to have extensible modular gameplay modes, called "rulesets". Building one of these allows a developer to harness the power of osu! for their own game style. To get started working on a ruleset, we have some templates available [here](https://github.com/ppy/osu/tree/master/Templates). You can see some examples of custom rulesets by visiting the [custom ruleset directory](https://github.com/ppy/osu/issues/5852). From 42e816fcae108ac2b4a3e6e92f87035e46dd965f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 5 Apr 2021 17:47:12 +0900 Subject: [PATCH 1243/1791] Add failing tests --- .../OsuBeatmapConversionTest.cs | 1 + ...ti-segment-slider-expected-conversion.json | 36 +++++++++++++++++++ .../Testing/Beatmaps/multi-segment-slider.osu | 17 +++++++++ .../Formats/LegacyBeatmapDecoderTest.cs | 33 +++++++++++++++++ .../Resources/multi-segment-slider.osu | 6 ++++ 5 files changed, 93 insertions(+) create mode 100644 osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/multi-segment-slider-expected-conversion.json create mode 100644 osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/multi-segment-slider.osu diff --git a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs index 7d32895083..5f44e1b6b6 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs @@ -23,6 +23,7 @@ namespace osu.Game.Rulesets.Osu.Tests [TestCase("repeat-slider")] [TestCase("uneven-repeat-slider")] [TestCase("old-stacking")] + [TestCase("multi-segment-slider")] public void Test(string name) => base.Test(name); protected override IEnumerable CreateConvertValue(HitObject hitObject) diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/multi-segment-slider-expected-conversion.json b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/multi-segment-slider-expected-conversion.json new file mode 100644 index 0000000000..8a056b3039 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/multi-segment-slider-expected-conversion.json @@ -0,0 +1,36 @@ +{ + "Mappings": [{ + "StartTime": 347893, + "Objects": [{ + "StartTime": 347893, + "EndTime": 347893, + "X": 329, + "Y": 245, + "StackOffset": { + "X": 0, + "Y": 0 + } + }, + { + "StartTime": 348193, + "EndTime": 348193, + "X": 183.0447, + "Y": 245.24292, + "StackOffset": { + "X": 0, + "Y": 0 + } + }, + { + "StartTime": 348457, + "EndTime": 348457, + "X": 329, + "Y": 245, + "StackOffset": { + "X": 0, + "Y": 0 + } + } + ] + }] +} \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/multi-segment-slider.osu b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/multi-segment-slider.osu new file mode 100644 index 0000000000..843c32b8ef --- /dev/null +++ b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/multi-segment-slider.osu @@ -0,0 +1,17 @@ +osu file format v14 + +[General] +Mode: 0 + +[Difficulty] +CircleSize:4 +OverallDifficulty:7 +ApproachRate:8 +SliderMultiplier:2 +SliderTickRate:1 + +[TimingPoints] +337093,300,4,2,1,40,1,0 + +[HitObjects] +329,245,347893,2,0,B|319:311|199:343|183:245|183:245,2,200,8|8|8,0:0|0:0|0:0,0:0:0:0: diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 4b9e9dd88c..fb18be3ae1 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -707,6 +707,39 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.That(third.ControlPoints[5].Type.Value, Is.EqualTo(null)); Assert.That(third.ControlPoints[6].Position.Value, Is.EqualTo(new Vector2(480, 0))); Assert.That(third.ControlPoints[6].Type.Value, Is.EqualTo(null)); + + // Last control point duplicated + var fourth = ((IHasPath)decoded.HitObjects[3]).Path; + + Assert.That(fourth.ControlPoints[0].Position.Value, Is.EqualTo(Vector2.Zero)); + Assert.That(fourth.ControlPoints[0].Type.Value, Is.EqualTo(PathType.Bezier)); + Assert.That(fourth.ControlPoints[1].Position.Value, Is.EqualTo(new Vector2(1, 1))); + Assert.That(fourth.ControlPoints[1].Type.Value, Is.EqualTo(null)); + Assert.That(fourth.ControlPoints[2].Position.Value, Is.EqualTo(new Vector2(2, 2))); + Assert.That(fourth.ControlPoints[2].Type.Value, Is.EqualTo(null)); + Assert.That(fourth.ControlPoints[3].Position.Value, Is.EqualTo(new Vector2(3, 3))); + Assert.That(fourth.ControlPoints[3].Type.Value, Is.EqualTo(null)); + Assert.That(fourth.ControlPoints[4].Position.Value, Is.EqualTo(new Vector2(3, 3))); + Assert.That(fourth.ControlPoints[4].Type.Value, Is.EqualTo(null)); + + // Last control point in segment duplicated + var fifth = ((IHasPath)decoded.HitObjects[4]).Path; + + Assert.That(fifth.ControlPoints[0].Position.Value, Is.EqualTo(Vector2.Zero)); + Assert.That(fifth.ControlPoints[0].Type.Value, Is.EqualTo(PathType.Bezier)); + Assert.That(fifth.ControlPoints[1].Position.Value, Is.EqualTo(new Vector2(1, 1))); + Assert.That(fifth.ControlPoints[1].Type.Value, Is.EqualTo(null)); + Assert.That(fifth.ControlPoints[2].Position.Value, Is.EqualTo(new Vector2(2, 2))); + Assert.That(fifth.ControlPoints[2].Type.Value, Is.EqualTo(null)); + Assert.That(fifth.ControlPoints[3].Position.Value, Is.EqualTo(new Vector2(3, 3))); + Assert.That(fifth.ControlPoints[3].Type.Value, Is.EqualTo(null)); + Assert.That(fifth.ControlPoints[4].Position.Value, Is.EqualTo(new Vector2(3, 3))); + Assert.That(fifth.ControlPoints[4].Type.Value, Is.EqualTo(null)); + + Assert.That(fifth.ControlPoints[5].Position.Value, Is.EqualTo(new Vector2(4, 4))); + Assert.That(fifth.ControlPoints[5].Type.Value, Is.EqualTo(PathType.Bezier)); + Assert.That(fifth.ControlPoints[6].Position.Value, Is.EqualTo(new Vector2(5, 5))); + Assert.That(fifth.ControlPoints[6].Type.Value, Is.EqualTo(null)); } } } diff --git a/osu.Game.Tests/Resources/multi-segment-slider.osu b/osu.Game.Tests/Resources/multi-segment-slider.osu index 6eabe640e4..504a6c3c24 100644 --- a/osu.Game.Tests/Resources/multi-segment-slider.osu +++ b/osu.Game.Tests/Resources/multi-segment-slider.osu @@ -9,3 +9,9 @@ osu file format v128 // Implicit multi-segment 32,192,3000,6,0,B|32:384|256:384|256:192|256:192|256:0|512:0|512:192,1,800 + +// Last control point duplicated +0,0,4000,2,0,B|1:1|2:2|3:3|3:3,2,200 + +// Last control point in segment duplicated +0,0,4000,2,0,B|1:1|2:2|3:3|3:3|B|4:4|5:5,2,200 From 4b29d0ebe2eee8475170d35540a5ebe95dbbe0d1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 5 Apr 2021 17:49:36 +0900 Subject: [PATCH 1244/1791] Fix last control point starting new segment --- osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 8419dd66de..e8a5463cce 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -336,9 +336,14 @@ namespace osu.Game.Rulesets.Objects.Legacy while (++endIndex < vertices.Length - endPointLength) { + // Keep incrementing while an implicit segment doesn't need to be started if (vertices[endIndex].Position.Value != vertices[endIndex - 1].Position.Value) continue; + // The last control point of each segment is not allowed to start a new implicit segment. + if (endIndex == vertices.Length - endPointLength - 1) + continue; + // Force a type on the last point, and return the current control point set as a segment. vertices[endIndex - 1].Type.Value = type; yield return vertices.AsMemory().Slice(startIndex, endIndex - startIndex); From a3faf0a28e12528dae79108b1ad4b83f920303f7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 5 Apr 2021 18:07:07 +0900 Subject: [PATCH 1245/1791] Increment start time --- osu.Game.Tests/Resources/multi-segment-slider.osu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Resources/multi-segment-slider.osu b/osu.Game.Tests/Resources/multi-segment-slider.osu index 504a6c3c24..cc86710067 100644 --- a/osu.Game.Tests/Resources/multi-segment-slider.osu +++ b/osu.Game.Tests/Resources/multi-segment-slider.osu @@ -14,4 +14,4 @@ osu file format v128 0,0,4000,2,0,B|1:1|2:2|3:3|3:3,2,200 // Last control point in segment duplicated -0,0,4000,2,0,B|1:1|2:2|3:3|3:3|B|4:4|5:5,2,200 +0,0,5000,2,0,B|1:1|2:2|3:3|3:3|B|4:4|5:5,2,200 From eee3d83ed25375fa58b262cfc48ecc3548b3138a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Apr 2021 19:36:18 +0900 Subject: [PATCH 1246/1791] Disable sdk linker for android debug releases Aimed to improve build time (especially for CI builds). The additional lines come from visual studio. I'm intentionally committing its output so it doesn't cause a diff on further csproj changes. --- osu.Android/osu.Android.csproj | 5 +++++ .../osu.Game.Rulesets.Catch.Tests.Android.csproj | 5 +++++ .../osu.Game.Rulesets.Mania.Tests.Android.csproj | 5 +++++ .../osu.Game.Rulesets.Osu.Tests.Android.csproj | 5 +++++ .../osu.Game.Rulesets.Taiko.Tests.Android.csproj | 5 +++++ osu.Game.Tests.Android/osu.Game.Tests.Android.csproj | 5 +++++ 6 files changed, 30 insertions(+) diff --git a/osu.Android/osu.Android.csproj b/osu.Android/osu.Android.csproj index 2051beae21..54857ac87d 100644 --- a/osu.Android/osu.Android.csproj +++ b/osu.Android/osu.Android.csproj @@ -20,6 +20,11 @@ d8 r8 + + None + cjk;mideast;other;rare;west + true + diff --git a/osu.Game.Rulesets.Catch.Tests.Android/osu.Game.Rulesets.Catch.Tests.Android.csproj b/osu.Game.Rulesets.Catch.Tests.Android/osu.Game.Rulesets.Catch.Tests.Android.csproj index 2e6c10a02e..94fdba4a3e 100644 --- a/osu.Game.Rulesets.Catch.Tests.Android/osu.Game.Rulesets.Catch.Tests.Android.csproj +++ b/osu.Game.Rulesets.Catch.Tests.Android/osu.Game.Rulesets.Catch.Tests.Android.csproj @@ -14,6 +14,11 @@ Properties\AndroidManifest.xml armeabi-v7a;x86;arm64-v8a + + None + cjk;mideast;other;rare;west + true + diff --git a/osu.Game.Rulesets.Mania.Tests.Android/osu.Game.Rulesets.Mania.Tests.Android.csproj b/osu.Game.Rulesets.Mania.Tests.Android/osu.Game.Rulesets.Mania.Tests.Android.csproj index 8c134c7114..9674186039 100644 --- a/osu.Game.Rulesets.Mania.Tests.Android/osu.Game.Rulesets.Mania.Tests.Android.csproj +++ b/osu.Game.Rulesets.Mania.Tests.Android/osu.Game.Rulesets.Mania.Tests.Android.csproj @@ -14,6 +14,11 @@ Properties\AndroidManifest.xml armeabi-v7a;x86;arm64-v8a + + None + cjk;mideast;other;rare;west + true + diff --git a/osu.Game.Rulesets.Osu.Tests.Android/osu.Game.Rulesets.Osu.Tests.Android.csproj b/osu.Game.Rulesets.Osu.Tests.Android/osu.Game.Rulesets.Osu.Tests.Android.csproj index 22fa605176..f4b673f10b 100644 --- a/osu.Game.Rulesets.Osu.Tests.Android/osu.Game.Rulesets.Osu.Tests.Android.csproj +++ b/osu.Game.Rulesets.Osu.Tests.Android/osu.Game.Rulesets.Osu.Tests.Android.csproj @@ -14,6 +14,11 @@ Properties\AndroidManifest.xml armeabi-v7a;x86;arm64-v8a + + None + cjk;mideast;other;rare;west + true + diff --git a/osu.Game.Rulesets.Taiko.Tests.Android/osu.Game.Rulesets.Taiko.Tests.Android.csproj b/osu.Game.Rulesets.Taiko.Tests.Android/osu.Game.Rulesets.Taiko.Tests.Android.csproj index a48110b354..4d4dabebe6 100644 --- a/osu.Game.Rulesets.Taiko.Tests.Android/osu.Game.Rulesets.Taiko.Tests.Android.csproj +++ b/osu.Game.Rulesets.Taiko.Tests.Android/osu.Game.Rulesets.Taiko.Tests.Android.csproj @@ -14,6 +14,11 @@ Properties\AndroidManifest.xml armeabi-v7a;x86;arm64-v8a + + None + cjk;mideast;other;rare;west + true + diff --git a/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj b/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj index bf256f486c..b45a3249ff 100644 --- a/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj +++ b/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj @@ -23,6 +23,11 @@ $(NoWarn);CA2007 + + None + cjk;mideast;other;rare;west + true + %(RecursiveDir)%(Filename)%(Extension) From d0510222aeec68a7984dcb42411bff0a5fff3241 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 5 Apr 2021 19:59:54 +0900 Subject: [PATCH 1247/1791] Fix legacy beatmap encoding --- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index d06478b9de..b581c46ec5 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -329,7 +329,13 @@ namespace osu.Game.Beatmaps.Formats if (point.Type.Value != null) { - if (point.Type.Value != lastType) + // We've reached a new segment! + + // To preserve compatibility with osu-stable as much as possible, segments with the same type are converted to use implicit segments by duplicating the control point. + // One exception to this is when the last control point of the last segment was itself a duplicate, which can't be supported by osu-stable. + bool lastPointWasDuplicate = i > 1 && pathData.Path.ControlPoints[i - 1].Position.Value == pathData.Path.ControlPoints[i - 2].Position.Value; + + if (lastPointWasDuplicate || point.Type.Value != lastType) { switch (point.Type.Value) { From 57983ae61f665229cbf8445d76d3f1ad9386a95e Mon Sep 17 00:00:00 2001 From: Samuel Cattini-Schultz Date: Mon, 5 Apr 2021 22:14:31 +1000 Subject: [PATCH 1248/1791] Fix whitespace --- osu.Game/Rulesets/Difficulty/Skills/Skill.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Difficulty/Skills/Skill.cs b/osu.Game/Rulesets/Difficulty/Skills/Skill.cs index b3d7ce3c40..b317c140d8 100644 --- a/osu.Game/Rulesets/Difficulty/Skills/Skill.cs +++ b/osu.Game/Rulesets/Difficulty/Skills/Skill.cs @@ -18,13 +18,13 @@ namespace osu.Game.Rulesets.Difficulty.Skills /// protected readonly LimitedCapacityStack Previous = new LimitedCapacityStack(2); // Contained objects not used yet - /// /// Mods for use in skill calculations. /// protected IReadOnlyList Mods => mods; private readonly Mod[] mods; + protected Skill(Mod[] mods) { this.mods = mods; From 5bdd15f7468a2319a3370850d966458e0936b232 Mon Sep 17 00:00:00 2001 From: Samuel Cattini-Schultz Date: Mon, 5 Apr 2021 22:14:37 +1000 Subject: [PATCH 1249/1791] Refactor Skill.Process() to not require calling base.Process() --- osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs | 2 +- osu.Game/Rulesets/Difficulty/Skills/Skill.cs | 11 +++++++---- osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs | 4 +--- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 14ada8ca09..5780fe39fa 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Difficulty { foreach (var skill in skills) { - skill.Process(hitObject); + skill.ProcessInternal(hitObject); } } diff --git a/osu.Game/Rulesets/Difficulty/Skills/Skill.cs b/osu.Game/Rulesets/Difficulty/Skills/Skill.cs index b317c140d8..534dee3ba8 100644 --- a/osu.Game/Rulesets/Difficulty/Skills/Skill.cs +++ b/osu.Game/Rulesets/Difficulty/Skills/Skill.cs @@ -30,14 +30,17 @@ namespace osu.Game.Rulesets.Difficulty.Skills this.mods = mods; } + internal void ProcessInternal(DifficultyHitObject current) + { + Process(current); + Previous.Push(current); + } + /// /// Process a . /// /// The to process. - public virtual void Process(DifficultyHitObject current) - { - Previous.Push(current); - } + protected abstract void Process(DifficultyHitObject current); /// /// Returns the calculated difficulty value representing all s that have been processed up to this point. diff --git a/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs b/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs index c324f8e414..71cee36812 100644 --- a/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs +++ b/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Difficulty.Skills /// /// Process a and update current strain values accordingly. /// - public sealed override void Process(DifficultyHitObject current) + protected sealed override void Process(DifficultyHitObject current) { // The first object doesn't generate a strain, so we begin with an incremented section end if (Previous.Count == 0) @@ -72,8 +72,6 @@ namespace osu.Game.Rulesets.Difficulty.Skills CurrentStrain += StrainValueOf(current) * SkillMultiplier; currentSectionPeak = Math.Max(CurrentStrain, currentSectionPeak); - - base.Process(current); } /// From beebdb073471fdd6bf075fdb1a3d243018e8c8e7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Apr 2021 22:30:51 +0900 Subject: [PATCH 1250/1791] Clean up implementation --- .../Gameplay/TestSceneGameplayMenuOverlay.cs | 2 +- .../Input/Bindings/GlobalActionContainer.cs | 20 ++++++++++++------- osu.Game/Input/Bindings/GlobalInputManager.cs | 6 +----- osu.Game/OsuGame.cs | 1 - .../Visual/OsuManualInputManagerTestScene.cs | 2 +- 5 files changed, 16 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs index d69ac665cc..75e8194708 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs @@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.Gameplay [BackgroundDependencyLoader] private void load(OsuGameBase game) { - Child = globalActionContainer = new GlobalActionContainer(game); + Child = globalActionContainer = new GlobalActionContainer(game, null); } [SetUp] diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 6d038c43cf..ab8a40dbe3 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.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; using System.Collections.Generic; using System.ComponentModel; using System.Linq; @@ -13,15 +12,17 @@ namespace osu.Game.Input.Bindings { public class GlobalActionContainer : DatabasedKeyBindingContainer, IHandleGlobalKeyboardInput { + private readonly GlobalInputManager globalInputManager; + private readonly Drawable handler; - public GlobalActionContainer(OsuGameBase game, bool nested = false) + public GlobalActionContainer(OsuGameBase game, GlobalInputManager globalInputManager) : base(matchingMode: KeyCombinationMatchingMode.Modifiers) { + this.globalInputManager = globalInputManager; + if (game is IKeyBindingHandler) handler = game; - - GetInputQueue = () => base.KeyBindingInputQueue.ToArray(); } public override IEnumerable DefaultKeyBindings => GlobalKeyBindings.Concat(InGameKeyBindings).Concat(AudioControlKeyBindings).Concat(EditorKeyBindings); @@ -94,10 +95,15 @@ namespace osu.Game.Input.Bindings new KeyBinding(InputKey.F3, GlobalAction.MusicPlay) }; - public Func GetInputQueue { get; set; } + protected override IEnumerable KeyBindingInputQueue + { + get + { + var inputQueue = globalInputManager?.NonPositionalInputQueue ?? base.KeyBindingInputQueue; - protected override IEnumerable KeyBindingInputQueue => - handler == null ? GetInputQueue() : GetInputQueue().Prepend(handler); + return handler != null ? inputQueue.Prepend(handler) : inputQueue; + } + } } public enum GlobalAction diff --git a/osu.Game/Input/Bindings/GlobalInputManager.cs b/osu.Game/Input/Bindings/GlobalInputManager.cs index 475397408a..91373712fb 100644 --- a/osu.Game/Input/Bindings/GlobalInputManager.cs +++ b/osu.Game/Input/Bindings/GlobalInputManager.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.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input; @@ -23,10 +22,7 @@ namespace osu.Game.Input.Bindings RelativeSizeAxes = Axes.Both, }, // to avoid positional input being blocked by children, ensure the GlobalActionContainer is above everything. - GlobalBindings = new GlobalActionContainer(game, true) - { - GetInputQueue = () => NonPositionalInputQueue.ToArray() - }, + GlobalBindings = new GlobalActionContainer(game, this) }; } } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 5fa315a464..809e5d3c1b 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -27,7 +27,6 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics.Sprites; using osu.Framework.Input; using osu.Framework.Input.Bindings; -using osu.Framework.Input.Events; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Collections; diff --git a/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs b/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs index 64f4d7b95b..b3073c8bea 100644 --- a/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs +++ b/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs @@ -33,7 +33,7 @@ namespace osu.Game.Tests.Visual InputManager = new ManualInputManager { UseParentInput = true, - Child = new GlobalActionContainer(null) + Child = new GlobalActionContainer(null, null) .WithChild((cursorContainer = new MenuCursorContainer { RelativeSizeAxes = Axes.Both }) .WithChild(content = new OsuTooltipContainer(cursorContainer.Cursor) { RelativeSizeAxes = Axes.Both })) }, From e486e521ffcea5d099f5563af6cbba98dd2a1bbb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Apr 2021 22:46:01 +0900 Subject: [PATCH 1251/1791] Fix regressed test --- osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 3e25e22b5f..00f9bf3432 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osu.Game.Overlays.Mods; using osu.Game.Overlays.Toolbar; @@ -146,7 +147,7 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("Move mouse to backButton", () => InputManager.MoveMouseTo(backButtonPosition)); // BackButton handles hover using its child button, so this checks whether or not any of BackButton's children are hovered. - AddUntilStep("Back button is hovered", () => InputManager.HoveredDrawables.Any(d => d.Parent == Game.BackButton)); + AddUntilStep("Back button is hovered", () => Game.ChildrenOfType().First().Children.Any(c => c.IsHovered)); AddStep("Click back button", () => InputManager.Click(MouseButton.Left)); AddUntilStep("Overlay was hidden", () => songSelect.ModSelectOverlay.State.Value == Visibility.Hidden); From ffe7edc16ac13f4460d3dbfc327d1622bfb7e32e Mon Sep 17 00:00:00 2001 From: Samuel Cattini-Schultz Date: Tue, 6 Apr 2021 11:06:10 +1000 Subject: [PATCH 1252/1791] Update xmldocs Co-authored-by: Dan Balasescu --- osu.Game/Rulesets/Difficulty/Utils/ReverseQueue.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Difficulty/Utils/ReverseQueue.cs b/osu.Game/Rulesets/Difficulty/Utils/ReverseQueue.cs index 08b2936876..e94fbd4e0f 100644 --- a/osu.Game/Rulesets/Difficulty/Utils/ReverseQueue.cs +++ b/osu.Game/Rulesets/Difficulty/Utils/ReverseQueue.cs @@ -8,7 +8,7 @@ using System.Collections.Generic; namespace osu.Game.Rulesets.Difficulty.Utils { /// - /// An indexed queue where items are indexed beginning from the end instead of the start. + /// An indexed queue where items are indexed beginning from the most recently enqueued item. /// public class ReverseQueue : IEnumerable { @@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Difficulty.Utils } /// - /// Dequeues an item from the and returns it. + /// Dequeues the least recently enqueued item from the and returns it. /// /// The item dequeued from the . public T Dequeue() @@ -97,7 +97,7 @@ namespace osu.Game.Rulesets.Difficulty.Utils } /// - /// Returns an enumerator which enumerates items in the starting from the most recently enqueued one. + /// Returns an enumerator which enumerates items in the starting from the most recently enqueued item. /// public IEnumerator GetEnumerator() { From 65f93d6f9d9fbc8a9b3c68b10e6303ed425aaf71 Mon Sep 17 00:00:00 2001 From: Samuel Cattini-Schultz Date: Tue, 6 Apr 2021 11:30:58 +1000 Subject: [PATCH 1253/1791] Add more descriptive xmldoc for ReverseQueue --- osu.Game/Rulesets/Difficulty/Utils/ReverseQueue.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Rulesets/Difficulty/Utils/ReverseQueue.cs b/osu.Game/Rulesets/Difficulty/Utils/ReverseQueue.cs index e94fbd4e0f..f104b8105a 100644 --- a/osu.Game/Rulesets/Difficulty/Utils/ReverseQueue.cs +++ b/osu.Game/Rulesets/Difficulty/Utils/ReverseQueue.cs @@ -9,6 +9,8 @@ namespace osu.Game.Rulesets.Difficulty.Utils { /// /// An indexed queue where items are indexed beginning from the most recently enqueued item. + /// Enqueuing an item pushes all existing indexes up by one and inserts the item at index 0. + /// Dequeuing an item removes the item from the highest index and returns it. /// public class ReverseQueue : IEnumerable { From 5cd43b3a7fc2f267628e5b1b979affa52d4f9e2d Mon Sep 17 00:00:00 2001 From: Samuel Cattini-Schultz Date: Tue, 6 Apr 2021 11:53:31 +1000 Subject: [PATCH 1254/1791] Set default history retention to 0 for Skill and override in StrainSkill Some skills might not even require history retention, so why waste the allocations? --- osu.Game/Rulesets/Difficulty/Skills/Skill.cs | 10 +++++----- osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs | 13 +++++++++++++ 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Difficulty/Skills/Skill.cs b/osu.Game/Rulesets/Difficulty/Skills/Skill.cs index 94d823f2ad..ebd389d823 100644 --- a/osu.Game/Rulesets/Difficulty/Skills/Skill.cs +++ b/osu.Game/Rulesets/Difficulty/Skills/Skill.cs @@ -22,8 +22,9 @@ namespace osu.Game.Rulesets.Difficulty.Skills /// Soft capacity of the queue. /// will automatically resize if it exceeds capacity, but will do so at a very slight performance impact. /// The actual capacity will be set to this value + 1 to allow for storage of the current object before the next can be processed. + /// Setting to zero (default) will cause to be uninstanciated. /// - protected virtual int PreviousCollectionSoftCapacity => 1; + protected virtual int PreviousCollectionSoftCapacity => 0; /// /// Mods for use in skill calculations. @@ -35,7 +36,9 @@ namespace osu.Game.Rulesets.Difficulty.Skills protected Skill(Mod[] mods) { this.mods = mods; - Previous = new ReverseQueue(PreviousCollectionSoftCapacity + 1); + + if (PreviousCollectionSoftCapacity > 0) + Previous = new ReverseQueue(PreviousCollectionSoftCapacity + 1); } internal void ProcessInternal(DifficultyHitObject current) @@ -51,8 +54,6 @@ namespace osu.Game.Rulesets.Difficulty.Skills /// The to be processed. protected virtual void RemoveExtraneousHistory(DifficultyHitObject current) { - while (Previous.Count > 1) - Previous.Dequeue(); } /// @@ -61,7 +62,6 @@ namespace osu.Game.Rulesets.Difficulty.Skills /// The that was just processed. protected virtual void AddToHistory(DifficultyHitObject current) { - Previous.Enqueue(current); } /// diff --git a/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs b/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs index 71cee36812..16816be35c 100644 --- a/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs +++ b/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs @@ -20,6 +20,8 @@ namespace osu.Game.Rulesets.Difficulty.Skills /// protected abstract double SkillMultiplier { get; } + protected override int PreviousCollectionSoftCapacity => 1; + /// /// Determines how quickly strain decays for the given skill. /// For example a value of 0.15 indicates that strain decays to 15% of its original value in one second. @@ -52,6 +54,17 @@ namespace osu.Game.Rulesets.Difficulty.Skills { } + protected override void RemoveExtraneousHistory(DifficultyHitObject current) + { + while (Previous.Count > 1) + Previous.Dequeue(); + } + + protected override void AddToHistory(DifficultyHitObject current) + { + Previous.Enqueue(current); + } + /// /// Process a and update current strain values accordingly. /// From a2544100d418cdbe3b7de383d9dfb2a2e2a95f38 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 6 Apr 2021 14:10:59 +0900 Subject: [PATCH 1255/1791] Fix floating point error in slider path encoding --- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index b581c46ec5..5eb5fcdfa0 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -329,13 +329,25 @@ namespace osu.Game.Beatmaps.Formats if (point.Type.Value != null) { - // We've reached a new segment! + // We've reached a new (explicit) segment! - // To preserve compatibility with osu-stable as much as possible, segments with the same type are converted to use implicit segments by duplicating the control point. - // One exception to this is when the last control point of the last segment was itself a duplicate, which can't be supported by osu-stable. - bool lastPointWasDuplicate = i > 1 && pathData.Path.ControlPoints[i - 1].Position.Value == pathData.Path.ControlPoints[i - 2].Position.Value; + // Explicit segments have a new format in which the type is injected into the middle of the control point string. + // To preserve compatibility with osu-stable as much as possible, explicit segments with the same type are converted to use implicit segments by duplicating the control point. + bool needsExplicitSegment = point.Type.Value != lastType; - if (lastPointWasDuplicate || point.Type.Value != lastType) + // One exception to this is when the last two control points of the last segment were duplicated. This is not a scenario supported by osu!stable. + // Lazer does not add implicit segments for the last two control points of _any_ explicit segment, so an explicit segment is forced in order to maintain consistency with the decoder. + if (i > 1) + { + // We need to use the absolute control point position to determine equality, otherwise floating point issues may arise. + Vector2 p1 = position + pathData.Path.ControlPoints[i - 1].Position.Value; + Vector2 p2 = position + pathData.Path.ControlPoints[i - 2].Position.Value; + + if ((int)p1.X == (int)p2.X && (int)p1.Y == (int)p2.Y) + needsExplicitSegment = true; + } + + if (needsExplicitSegment) { switch (point.Type.Value) { From 8ff13845d1b911031fef877656337bba471a3b0f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Apr 2021 14:24:22 +0900 Subject: [PATCH 1256/1791] Add marker showing where 00:00:000 is --- .../Screens/Edit/Compose/Components/Timeline/Timeline.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 0697dbb392..86a30b7e2d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -9,6 +9,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Audio; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Configuration; @@ -91,6 +92,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline }, ticks = new TimelineTickDisplay(), controlPoints = new TimelineControlPointDisplay(), + new Box + { + Name = "zero marker", + RelativeSizeAxes = Axes.Y, + Width = 2, + Origin = Anchor.TopCentre, + Colour = colours.YellowDarker, + }, } }, }); From 35dd1c68aab5084a5bb0e3a0b984d0847fefd390 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Apr 2021 14:27:45 +0900 Subject: [PATCH 1257/1791] Fix drag/selection events not propagating correctly to TimelineBlueprintContainer when before time zero --- .../Components/Timeline/TimelineBlueprintContainer.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index 3623f8ad8e..e94634e6c4 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -36,6 +36,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private SelectionBlueprint placementBlueprint; private readonly Box backgroundBox; + // we only care about checking vertical validity. + // this allows selecting and dragging selections before time=0. + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) + { + float localY = ToLocalSpace(screenSpacePos).Y; + return DrawRectangle.Top <= localY && DrawRectangle.Bottom >= localY; + } public TimelineBlueprintContainer(HitObjectComposer composer) : base(composer) From 7d301a633639e9856ad0e05014f2ccbbde1e7f10 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Apr 2021 14:28:10 +0900 Subject: [PATCH 1258/1791] Improve timeline hover display before time zero with a gradient fade --- .../Timeline/TimelineBlueprintContainer.cs | 49 ++++++++++++++++--- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index e94634e6c4..d17a7551dd 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -7,6 +7,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; @@ -16,6 +17,7 @@ using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts; +using osuTK; using osuTK.Graphics; namespace osu.Game.Screens.Edit.Compose.Components.Timeline @@ -35,7 +37,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private Bindable placement; private SelectionBlueprint placementBlueprint; - private readonly Box backgroundBox; + private Box backgroundBox; + private Box backgroundBoxIntro; + // we only care about checking vertical validity. // this allows selecting and dragging selections before time=0. public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) @@ -52,12 +56,26 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Origin = Anchor.Centre; Height = 0.6f; + } - AddInternal(backgroundBox = new Box + [BackgroundDependencyLoader] + private void load() + { + AddRangeInternal(new[] { - Colour = Color4.Black, - RelativeSizeAxes = Axes.Both, - Alpha = 0.1f, + backgroundBoxIntro = new Box + { + RelativeSizeAxes = Axes.Y, + Width = 200, + Origin = Anchor.TopRight, + Alpha = 0.1f, + }, + backgroundBox = new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + Alpha = 0.1f, + } }); } @@ -68,6 +86,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline placement = beatmap.PlacementObject.GetBoundCopy(); placement.ValueChanged += placementChanged; + + updateHoverState(); + FinishTransforms(true); } private void placementChanged(ValueChangedEvent obj) @@ -94,13 +115,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline protected override bool OnHover(HoverEvent e) { - backgroundBox.FadeColour(colours.BlueLighter, 120, Easing.OutQuint); + updateHoverState(); return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { - backgroundBox.FadeColour(Color4.Black, 600, Easing.OutQuint); + updateHoverState(); base.OnHoverLost(e); } @@ -134,6 +155,20 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline updateStacking(); } + private void updateHoverState() + { + if (IsHovered) + { + backgroundBox.FadeColour(colours.BlueLighter, 120, Easing.OutQuint); + backgroundBoxIntro.FadeColour(ColourInfo.GradientHorizontal(Color4.Black, colours.BlueLighter), 120, Easing.OutQuint); + } + else + { + backgroundBox.FadeColour(Color4.Black, 600, Easing.OutQuint); + backgroundBoxIntro.FadeColour(Color4.Black, 600, Easing.OutQuint); + } + } + private void updateStacking() { // because only blueprints of objects which are alive (via pooling) are displayed in the timeline, it's feasible to do this every-update. From 9c1320e18ba20c94637e35994d4ae526f3321f76 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 6 Apr 2021 14:33:46 +0900 Subject: [PATCH 1259/1791] Add test --- .../Formats/LegacyBeatmapEncoderTest.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs index 0784109158..920cc36776 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs @@ -18,10 +18,14 @@ using osu.Game.IO; using osu.Game.IO.Serialization; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Taiko; using osu.Game.Skinning; using osu.Game.Tests.Resources; +using osuTK; namespace osu.Game.Tests.Beatmaps.Formats { @@ -45,6 +49,33 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.IsTrue(areComboColoursEqual(decodedAfterEncode.beatmapSkin.Configuration, decoded.beatmapSkin.Configuration)); } + [Test] + public void TestEncodeMultiSegmentSliderWithFloatingPointError() + { + var beatmap = new Beatmap + { + HitObjects = + { + new Slider + { + Position = new Vector2(0.6f), + Path = new SliderPath(new[] + { + new PathControlPoint(Vector2.Zero, PathType.Bezier), + new PathControlPoint(new Vector2(0.5f)), + new PathControlPoint(new Vector2(0.51f)), // This is actually on the same position as the previous one in legacy beatmaps (truncated to int). + new PathControlPoint(new Vector2(1f), PathType.Bezier), + new PathControlPoint(new Vector2(2f)) + }) + }, + } + }; + + var decodedAfterEncode = decodeFromLegacy(encodeToLegacy((beatmap, new TestLegacySkin(beatmaps_resource_store, string.Empty))), string.Empty); + var decodedSlider = (Slider)decodedAfterEncode.beatmap.HitObjects[0]; + Assert.That(decodedSlider.Path.ControlPoints.Count, Is.EqualTo(5)); + } + private bool areComboColoursEqual(IHasComboColours a, IHasComboColours b) { // equal to null, no need to SequenceEqual From 53c1bc666cde003e07fd396d49a0b75ec77358f8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Apr 2021 15:05:34 +0900 Subject: [PATCH 1260/1791] Make addition of nested GlobalActionContainer in OsuGameTestScene optional --- .../Visual/Navigation/OsuGameTestScene.cs | 2 ++ .../Visual/OsuManualInputManagerTestScene.cs | 19 ++++++++++++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs index bf5338d81a..f9a991f756 100644 --- a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs +++ b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs @@ -36,6 +36,8 @@ namespace osu.Game.Tests.Visual.Navigation protected override bool UseFreshStoragePerRun => true; + protected override bool CreateNestedActionContainer => false; + [BackgroundDependencyLoader] private void load(GameHost host) { diff --git a/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs b/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs index b3073c8bea..7dad636da7 100644 --- a/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs +++ b/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs @@ -24,18 +24,31 @@ namespace osu.Game.Tests.Visual private readonly TriangleButton buttonTest; private readonly TriangleButton buttonLocal; + /// + /// Whether to create a nested container to handle s that result from local (manual) test input. + /// This should be disabled when instantiating an instance else actions will be lost. + /// + protected virtual bool CreateNestedActionContainer => true; + protected OsuManualInputManagerTestScene() { MenuCursorContainer cursorContainer; + CompositeDrawable mainContent = + (cursorContainer = new MenuCursorContainer { RelativeSizeAxes = Axes.Both }) + .WithChild(content = new OsuTooltipContainer(cursorContainer.Cursor) { RelativeSizeAxes = Axes.Both }); + + if (CreateNestedActionContainer) + { + mainContent = new GlobalActionContainer(null, null).WithChild(mainContent); + } + base.Content.AddRange(new Drawable[] { InputManager = new ManualInputManager { UseParentInput = true, - Child = new GlobalActionContainer(null, null) - .WithChild((cursorContainer = new MenuCursorContainer { RelativeSizeAxes = Axes.Both }) - .WithChild(content = new OsuTooltipContainer(cursorContainer.Cursor) { RelativeSizeAxes = Axes.Both })) + Child = mainContent }, new Container { From 316a557a99d159b1887508e353dd610be4f9053f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Apr 2021 15:34:34 +0900 Subject: [PATCH 1261/1791] Split select area background into own class to reduce hover state complexity --- .../Timeline/TimelineBlueprintContainer.cs | 66 +++++++++---------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index d17a7551dd..eb26a0d16f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; @@ -37,8 +38,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private Bindable placement; private SelectionBlueprint placementBlueprint; - private Box backgroundBox; - private Box backgroundBoxIntro; + private SelectableAreaBackground backgroundBox; // we only care about checking vertical validity. // this allows selecting and dragging selections before time=0. @@ -61,21 +61,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [BackgroundDependencyLoader] private void load() { - AddRangeInternal(new[] + AddInternal(backgroundBox = new SelectableAreaBackground { - backgroundBoxIntro = new Box - { - RelativeSizeAxes = Axes.Y, - Width = 200, - Origin = Anchor.TopRight, - Alpha = 0.1f, - }, - backgroundBox = new Box - { - Colour = Color4.Black, - RelativeSizeAxes = Axes.Both, - Alpha = 0.1f, - } + Colour = Color4.Black }); } @@ -87,7 +75,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline placement = beatmap.PlacementObject.GetBoundCopy(); placement.ValueChanged += placementChanged; - updateHoverState(); FinishTransforms(true); } @@ -115,13 +102,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline protected override bool OnHover(HoverEvent e) { - updateHoverState(); + backgroundBox.FadeColour(colours.BlueLighter, 120, Easing.OutQuint); return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { - updateHoverState(); + backgroundBox.FadeColour(Color4.Black, 600, Easing.OutQuint); base.OnHoverLost(e); } @@ -155,20 +142,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline updateStacking(); } - private void updateHoverState() - { - if (IsHovered) - { - backgroundBox.FadeColour(colours.BlueLighter, 120, Easing.OutQuint); - backgroundBoxIntro.FadeColour(ColourInfo.GradientHorizontal(Color4.Black, colours.BlueLighter), 120, Easing.OutQuint); - } - else - { - backgroundBox.FadeColour(Color4.Black, 600, Easing.OutQuint); - backgroundBoxIntro.FadeColour(Color4.Black, 600, Easing.OutQuint); - } - } - private void updateStacking() { // because only blueprints of objects which are alive (via pooling) are displayed in the timeline, it's feasible to do this every-update. @@ -237,6 +210,33 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } } + private class SelectableAreaBackground : CompositeDrawable + { + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.Both; + Alpha = 0.1f; + + AddRangeInternal(new[] + { + // fade out over intro time, outside the valid time bounds. + new Box + { + RelativeSizeAxes = Axes.Y, + Width = 200, + Origin = Anchor.TopRight, + Colour = ColourInfo.GradientHorizontal(Color4.White.Opacity(0), Color4.White), + }, + new Box + { + Colour = Color4.White, + RelativeSizeAxes = Axes.Both, + } + }); + } + } + internal class TimelineSelectionHandler : SelectionHandler { // for now we always allow movement. snapping is provided by the Timeline's "distance" snap implementation From 9d0839be8ff83793883386c0701da939a5f7d954 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Apr 2021 15:35:07 +0900 Subject: [PATCH 1262/1791] Remove no longer necessary FinishTranforms call --- .../Compose/Components/Timeline/TimelineBlueprintContainer.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index eb26a0d16f..be34c8d57e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -74,8 +74,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline placement = beatmap.PlacementObject.GetBoundCopy(); placement.ValueChanged += placementChanged; - - FinishTransforms(true); } private void placementChanged(ValueChangedEvent obj) From 933c4010da4cf1f23d27326fca1c7ad9288ec1bb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Apr 2021 16:17:20 +0900 Subject: [PATCH 1263/1791] Allow creating OnlineViewContainers with no placeholder button --- osu.Game/Online/OnlineViewContainer.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/OnlineViewContainer.cs b/osu.Game/Online/OnlineViewContainer.cs index 8868f90524..4955aa9058 100644 --- a/osu.Game/Online/OnlineViewContainer.cs +++ b/osu.Game/Online/OnlineViewContainer.cs @@ -23,13 +23,17 @@ namespace osu.Game.Online private readonly string placeholderMessage; - private Placeholder placeholder; + private Drawable placeholder; private const double transform_duration = 300; [Resolved] protected IAPIProvider API { get; private set; } + /// + /// Construct a new instance of an online view container. + /// + /// The message to display when not logged in. If empty, no button will display. public OnlineViewContainer(string placeholderMessage) { this.placeholderMessage = placeholderMessage; @@ -40,10 +44,10 @@ namespace osu.Game.Online [BackgroundDependencyLoader] private void load(IAPIProvider api) { - InternalChildren = new Drawable[] + InternalChildren = new[] { Content, - placeholder = new LoginPlaceholder(placeholderMessage), + placeholder = string.IsNullOrEmpty(placeholderMessage) ? Empty() : new LoginPlaceholder(placeholderMessage), LoadingSpinner = new LoadingSpinner { Alpha = 0, From dafa8bbe4e51935b5b7264e134018d064fd42503 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Apr 2021 16:17:38 +0900 Subject: [PATCH 1264/1791] Refactor BeatmapDetails to use GridContainer to keep a consistent layout --- osu.Game/Screens/Select/BeatmapDetails.cs | 173 ++++++++++++---------- 1 file changed, 91 insertions(+), 82 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index 40029cc19a..41bde40221 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -8,8 +8,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Sprites; using System.Linq; +using osu.Framework.Bindables; using osu.Game.Online.API; -using osu.Framework.Threading; using osu.Framework.Graphics.Shapes; using osu.Framework.Extensions.Color4Extensions; using osu.Game.Screens.Select.Details; @@ -28,11 +28,8 @@ namespace osu.Game.Screens.Select private const float spacing = 10; private const float transition_duration = 250; - private readonly FillFlowContainer top, statsFlow; private readonly AdvancedStats advanced; - private readonly DetailBox ratingsContainer; private readonly UserRatings ratings; - private readonly OsuScrollContainer metadataScroll; private readonly MetadataSection description, source, tags; private readonly Container failRetryContainer; private readonly FailRetryGraph failRetryGraph; @@ -41,8 +38,6 @@ namespace osu.Game.Screens.Select [Resolved] private IAPIProvider api { get; set; } - private ScheduledDelegate pendingBeatmapSwitch; - [Resolved] private RulesetStore rulesets { get; set; } @@ -57,8 +52,7 @@ namespace osu.Game.Screens.Select beatmap = value; - pendingBeatmapSwitch?.Cancel(); - pendingBeatmapSwitch = Schedule(updateStatistics); + Scheduler.AddOnce(updateStatistics); } } @@ -75,99 +69,115 @@ namespace osu.Game.Screens.Select { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Horizontal = spacing }, - Children = new Drawable[] + Child = new GridContainer { - top = new FillFlowContainer + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Horizontal, - Children = new Drawable[] + new Dimension(GridSizeMode.AutoSize), + new Dimension() + }, + Content = new[] + { + new Drawable[] { - statsFlow = new FillFlowContainer + new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Width = 0.5f, - Spacing = new Vector2(spacing), - Padding = new MarginPadding { Right = spacing / 2 }, - Children = new[] + Direction = FillDirection.Horizontal, + Children = new Drawable[] { - new DetailBox + new FillFlowContainer { - Child = advanced = new AdvancedStats + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Width = 0.5f, + Spacing = new Vector2(spacing), + Padding = new MarginPadding { Right = spacing / 2 }, + Children = new[] + { + new DetailBox().WithChild(advanced = new AdvancedStats + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Horizontal = spacing, Top = spacing * 2, Bottom = spacing }, + }), + new DetailBox().WithChild(new OnlineViewContainer(string.Empty) + { + RelativeSizeAxes = Axes.X, + Height = 134, + Padding = new MarginPadding { Horizontal = spacing, Top = spacing }, + Child = ratings = new UserRatings + { + RelativeSizeAxes = Axes.Both, + }, + }), + }, + }, + new OsuScrollContainer + { + RelativeSizeAxes = Axes.Both, + Width = 0.5f, + ScrollbarVisible = false, + Padding = new MarginPadding { Left = spacing / 2 }, + Child = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Horizontal = spacing, Top = spacing * 2, Bottom = spacing }, + LayoutDuration = transition_duration, + LayoutEasing = Easing.OutQuad, + Spacing = new Vector2(spacing * 2), + Margin = new MarginPadding { Top = spacing * 2 }, + Children = new[] + { + description = new MetadataSection("Description"), + source = new MetadataSection("Source"), + tags = new MetadataSection("Tags"), + }, }, }, - ratingsContainer = new DetailBox - { - Child = ratings = new UserRatings - { - RelativeSizeAxes = Axes.X, - Height = 134, - Padding = new MarginPadding { Horizontal = spacing, Top = spacing }, - }, - }, - }, - }, - metadataScroll = new OsuScrollContainer - { - RelativeSizeAxes = Axes.X, - Width = 0.5f, - ScrollbarVisible = false, - Padding = new MarginPadding { Left = spacing / 2 }, - Child = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - LayoutDuration = transition_duration, - LayoutEasing = Easing.OutQuad, - Spacing = new Vector2(spacing * 2), - Margin = new MarginPadding { Top = spacing * 2 }, - Children = new[] - { - description = new MetadataSection("Description"), - source = new MetadataSection("Source"), - tags = new MetadataSection("Tags"), - }, }, }, }, - }, - failRetryContainer = new OnlineViewContainer("Sign in to view more details") - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.X, - Children = new Drawable[] + new Drawable[] { - new OsuSpriteText - { - Text = "Points of Failure", - Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 14), - }, - failRetryGraph = new FailRetryGraph + failRetryContainer = new OnlineViewContainer("Sign in to view more details") { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Top = 14 + spacing / 2 }, + Children = new Drawable[] + { + new OsuSpriteText + { + Text = "Points of Failure", + Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 14), + }, + failRetryGraph = new FailRetryGraph + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = 14 + spacing / 2 }, + }, + loading = new LoadingLayer(true) + }, }, - loading = new LoadingLayer(true) - }, - }, - }, + } + } + } }, }; } - protected override void UpdateAfterChildren() - { - base.UpdateAfterChildren(); + private IBindable apiOnlineState; - metadataScroll.Height = statsFlow.DrawHeight; - failRetryContainer.Height = DrawHeight - Padding.TotalVertical - (top.DrawHeight + spacing / 2); + protected override void LoadComplete() + { + base.LoadComplete(); + + apiOnlineState = api.State.GetBoundCopy(); + apiOnlineState.BindValueChanged(state => + { + Scheduler.AddOnce(updateStatistics); + }); } private void updateStatistics() @@ -185,7 +195,7 @@ namespace osu.Game.Screens.Select } // for now, let's early abort if an OnlineBeatmapID is not present (should have been populated at import time). - if (Beatmap?.OnlineBeatmapID == null) + if (Beatmap?.OnlineBeatmapID == null || apiOnlineState.Value != APIState.Online) { updateMetrics(); return; @@ -237,17 +247,16 @@ namespace osu.Game.Screens.Select var hasRatings = beatmap?.BeatmapSet?.Metrics?.Ratings?.Any() ?? false; var hasRetriesFails = (beatmap?.Metrics?.Retries?.Any() ?? false) || (beatmap?.Metrics?.Fails?.Any() ?? false); - bool isOnline = api.State.Value == APIState.Online; - - if (hasRatings && isOnline) + if (hasRatings) { ratings.Metrics = beatmap.BeatmapSet.Metrics; - ratingsContainer.FadeIn(transition_duration); + ratings.FadeIn(transition_duration); } else { + // loading or just has no data server-side. ratings.Metrics = new BeatmapSetMetrics { Ratings = new int[10] }; - ratingsContainer.FadeTo(isOnline ? 0.25f : 0, transition_duration); + ratings.FadeTo(0.25f, transition_duration); } if (hasRetriesFails) From 59e6c46644d89d19ad1915db2d657367153246fa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Apr 2021 16:23:27 +0900 Subject: [PATCH 1265/1791] Remove unnecessary online state logic --- osu.Game/Screens/Select/BeatmapDetails.cs | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index 41bde40221..86a21dcc42 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Sprites; using System.Linq; -using osu.Framework.Bindables; using osu.Game.Online.API; using osu.Framework.Graphics.Shapes; using osu.Framework.Extensions.Color4Extensions; @@ -167,19 +166,6 @@ namespace osu.Game.Screens.Select }; } - private IBindable apiOnlineState; - - protected override void LoadComplete() - { - base.LoadComplete(); - - apiOnlineState = api.State.GetBoundCopy(); - apiOnlineState.BindValueChanged(state => - { - Scheduler.AddOnce(updateStatistics); - }); - } - private void updateStatistics() { advanced.Beatmap = Beatmap; @@ -195,7 +181,7 @@ namespace osu.Game.Screens.Select } // for now, let's early abort if an OnlineBeatmapID is not present (should have been populated at import time). - if (Beatmap?.OnlineBeatmapID == null || apiOnlineState.Value != APIState.Online) + if (Beatmap?.OnlineBeatmapID == null) { updateMetrics(); return; From 1934e8e1fe0c2c66cecbacba3db59299f839a915 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Apr 2021 16:29:07 +0900 Subject: [PATCH 1266/1791] Fix loading layer being in the wrong place --- osu.Game/Screens/Select/BeatmapDetails.cs | 145 +++++++++++----------- 1 file changed, 74 insertions(+), 71 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index 86a21dcc42..46aa568039 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -68,99 +68,102 @@ namespace osu.Game.Screens.Select { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Horizontal = spacing }, - Child = new GridContainer + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - RowDimensions = new[] + new GridContainer { - new Dimension(GridSizeMode.AutoSize), - new Dimension() - }, - Content = new[] - { - new Drawable[] + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] { - new FillFlowContainer + new Dimension(GridSizeMode.AutoSize), + new Dimension() + }, + Content = new[] + { + new Drawable[] { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Horizontal, - Children = new Drawable[] + new FillFlowContainer { - new FillFlowContainer + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Children = new Drawable[] { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Width = 0.5f, - Spacing = new Vector2(spacing), - Padding = new MarginPadding { Right = spacing / 2 }, - Children = new[] - { - new DetailBox().WithChild(advanced = new AdvancedStats - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Horizontal = spacing, Top = spacing * 2, Bottom = spacing }, - }), - new DetailBox().WithChild(new OnlineViewContainer(string.Empty) - { - RelativeSizeAxes = Axes.X, - Height = 134, - Padding = new MarginPadding { Horizontal = spacing, Top = spacing }, - Child = ratings = new UserRatings - { - RelativeSizeAxes = Axes.Both, - }, - }), - }, - }, - new OsuScrollContainer - { - RelativeSizeAxes = Axes.Both, - Width = 0.5f, - ScrollbarVisible = false, - Padding = new MarginPadding { Left = spacing / 2 }, - Child = new FillFlowContainer + new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - LayoutDuration = transition_duration, - LayoutEasing = Easing.OutQuad, - Spacing = new Vector2(spacing * 2), - Margin = new MarginPadding { Top = spacing * 2 }, + Width = 0.5f, + Spacing = new Vector2(spacing), + Padding = new MarginPadding { Right = spacing / 2 }, Children = new[] { - description = new MetadataSection("Description"), - source = new MetadataSection("Source"), - tags = new MetadataSection("Tags"), + new DetailBox().WithChild(advanced = new AdvancedStats + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Horizontal = spacing, Top = spacing * 2, Bottom = spacing }, + }), + new DetailBox().WithChild(new OnlineViewContainer(string.Empty) + { + RelativeSizeAxes = Axes.X, + Height = 134, + Padding = new MarginPadding { Horizontal = spacing, Top = spacing }, + Child = ratings = new UserRatings + { + RelativeSizeAxes = Axes.Both, + }, + }), + }, + }, + new OsuScrollContainer + { + RelativeSizeAxes = Axes.Both, + Width = 0.5f, + ScrollbarVisible = false, + Padding = new MarginPadding { Left = spacing / 2 }, + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + LayoutDuration = transition_duration, + LayoutEasing = Easing.OutQuad, + Spacing = new Vector2(spacing * 2), + Margin = new MarginPadding { Top = spacing * 2 }, + Children = new[] + { + description = new MetadataSection("Description"), + source = new MetadataSection("Source"), + tags = new MetadataSection("Tags"), + }, }, }, }, }, }, - }, - new Drawable[] - { - failRetryContainer = new OnlineViewContainer("Sign in to view more details") + new Drawable[] { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + failRetryContainer = new OnlineViewContainer("Sign in to view more details") { - new OsuSpriteText + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - Text = "Points of Failure", - Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 14), + new OsuSpriteText + { + Text = "Points of Failure", + Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 14), + }, + failRetryGraph = new FailRetryGraph + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = 14 + spacing / 2 }, + }, }, - failRetryGraph = new FailRetryGraph - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Top = 14 + spacing / 2 }, - }, - loading = new LoadingLayer(true) }, - }, + } } - } + }, + loading = new LoadingLayer(true) } }, }; From 37e30b00bf6d6a494847565036d3538fb1f1e939 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 6 Apr 2021 16:39:02 +0900 Subject: [PATCH 1267/1791] Refactor to keep a consistent API --- osu.Game/Rulesets/Difficulty/Skills/Skill.cs | 32 ++++--------------- .../Rulesets/Difficulty/Skills/StrainSkill.cs | 13 -------- 2 files changed, 7 insertions(+), 38 deletions(-) diff --git a/osu.Game/Rulesets/Difficulty/Skills/Skill.cs b/osu.Game/Rulesets/Difficulty/Skills/Skill.cs index ebd389d823..9f0fb987a7 100644 --- a/osu.Game/Rulesets/Difficulty/Skills/Skill.cs +++ b/osu.Game/Rulesets/Difficulty/Skills/Skill.cs @@ -19,12 +19,9 @@ namespace osu.Game.Rulesets.Difficulty.Skills protected readonly ReverseQueue Previous; /// - /// Soft capacity of the queue. - /// will automatically resize if it exceeds capacity, but will do so at a very slight performance impact. - /// The actual capacity will be set to this value + 1 to allow for storage of the current object before the next can be processed. - /// Setting to zero (default) will cause to be uninstanciated. + /// Number of previous s to keep inside the queue. /// - protected virtual int PreviousCollectionSoftCapacity => 0; + protected virtual int HistoryLength => 1; /// /// Mods for use in skill calculations. @@ -36,32 +33,17 @@ namespace osu.Game.Rulesets.Difficulty.Skills protected Skill(Mod[] mods) { this.mods = mods; - - if (PreviousCollectionSoftCapacity > 0) - Previous = new ReverseQueue(PreviousCollectionSoftCapacity + 1); + Previous = new ReverseQueue(HistoryLength + 1); } internal void ProcessInternal(DifficultyHitObject current) { - RemoveExtraneousHistory(current); + while (Previous.Count > HistoryLength) + Previous.Dequeue(); + Process(current); - AddToHistory(current); - } - /// - /// Remove objects from that are no longer needed for calculations from the current object onwards. - /// - /// The to be processed. - protected virtual void RemoveExtraneousHistory(DifficultyHitObject current) - { - } - - /// - /// Add the current to the queue (if required). - /// - /// The that was just processed. - protected virtual void AddToHistory(DifficultyHitObject current) - { + Previous.Enqueue(current); } /// diff --git a/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs b/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs index 16816be35c..71cee36812 100644 --- a/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs +++ b/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs @@ -20,8 +20,6 @@ namespace osu.Game.Rulesets.Difficulty.Skills /// protected abstract double SkillMultiplier { get; } - protected override int PreviousCollectionSoftCapacity => 1; - /// /// Determines how quickly strain decays for the given skill. /// For example a value of 0.15 indicates that strain decays to 15% of its original value in one second. @@ -54,17 +52,6 @@ namespace osu.Game.Rulesets.Difficulty.Skills { } - protected override void RemoveExtraneousHistory(DifficultyHitObject current) - { - while (Previous.Count > 1) - Previous.Dequeue(); - } - - protected override void AddToHistory(DifficultyHitObject current) - { - Previous.Enqueue(current); - } - /// /// Process a and update current strain values accordingly. /// From f08b340e811ba6fd7ec5cfcc3f8b854002ab62bf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Apr 2021 16:49:13 +0900 Subject: [PATCH 1268/1791] Add nullability hinting --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index ab8a40dbe3..cda962f0ab 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Input.Bindings; @@ -12,11 +13,12 @@ namespace osu.Game.Input.Bindings { public class GlobalActionContainer : DatabasedKeyBindingContainer, IHandleGlobalKeyboardInput { + [CanBeNull] private readonly GlobalInputManager globalInputManager; private readonly Drawable handler; - public GlobalActionContainer(OsuGameBase game, GlobalInputManager globalInputManager) + public GlobalActionContainer(OsuGameBase game, [CanBeNull] GlobalInputManager globalInputManager) : base(matchingMode: KeyCombinationMatchingMode.Modifiers) { this.globalInputManager = globalInputManager; From 899d708dacf0394ee116931b5425fbce05cb93cc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Apr 2021 17:09:51 +0900 Subject: [PATCH 1269/1791] Move loading layer up one level to correct padding --- osu.Game/Screens/Select/BeatmapDetails.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index 46aa568039..ca0af2f3f5 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -163,9 +163,9 @@ namespace osu.Game.Screens.Select } } }, - loading = new LoadingLayer(true) - } + }, }, + loading = new LoadingLayer(true) }; } From 3113eefcf6ab960fcde6a9aff369591dc3f129ef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Apr 2021 17:12:00 +0900 Subject: [PATCH 1270/1791] Don't attempt to load content when not online --- osu.Game/Screens/Select/BeatmapDetails.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index ca0af2f3f5..26da4279f0 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -184,7 +184,7 @@ namespace osu.Game.Screens.Select } // for now, let's early abort if an OnlineBeatmapID is not present (should have been populated at import time). - if (Beatmap?.OnlineBeatmapID == null) + if (Beatmap?.OnlineBeatmapID == null || api.State.Value == APIState.Offline) { updateMetrics(); return; From d81f270e21f81f5ff91f5a5d27a04e30687d2543 Mon Sep 17 00:00:00 2001 From: Leon Gebler Date: Mon, 5 Apr 2021 18:01:16 +0200 Subject: [PATCH 1271/1791] Always encode perfect curves as explicit segments --- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 5eb5fcdfa0..0bb1aa873f 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -333,9 +333,10 @@ namespace osu.Game.Beatmaps.Formats // Explicit segments have a new format in which the type is injected into the middle of the control point string. // To preserve compatibility with osu-stable as much as possible, explicit segments with the same type are converted to use implicit segments by duplicating the control point. - bool needsExplicitSegment = point.Type.Value != lastType; + // One exception are consecutive perfect curves, which aren't supported in osu-stable + bool needsExplicitSegment = point.Type.Value != lastType || point.Type.Value == PathType.PerfectCurve; - // One exception to this is when the last two control points of the last segment were duplicated. This is not a scenario supported by osu!stable. + // Another exception to this is when the last two control points of the last segment were duplicated. This is not a scenario supported by osu!stable. // Lazer does not add implicit segments for the last two control points of _any_ explicit segment, so an explicit segment is forced in order to maintain consistency with the decoder. if (i > 1) { From dd902441b098983802cb1bbda3fae0ad8c86fa5f Mon Sep 17 00:00:00 2001 From: Leon Gebler Date: Mon, 5 Apr 2021 17:21:45 +0200 Subject: [PATCH 1272/1791] Add tests for consecutive perfect-curve segments --- .../Formats/LegacyBeatmapDecoderTest.cs | 30 +++++++++++++++++++ .../Resources/multi-segment-slider.osu | 7 +++++ .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 2 +- 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index fb18be3ae1..0f82492e51 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -740,6 +740,36 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.That(fifth.ControlPoints[5].Type.Value, Is.EqualTo(PathType.Bezier)); Assert.That(fifth.ControlPoints[6].Position.Value, Is.EqualTo(new Vector2(5, 5))); Assert.That(fifth.ControlPoints[6].Type.Value, Is.EqualTo(null)); + + // Implicit perfect-curve multi-segment(Should convert to bezier to match stable) + var sixth = ((IHasPath)decoded.HitObjects[5]).Path; + + Assert.That(sixth.ControlPoints[0].Position.Value, Is.EqualTo(Vector2.Zero)); + Assert.That(sixth.ControlPoints[0].Type.Value == PathType.Bezier); + Assert.That(sixth.ControlPoints[1].Position.Value, Is.EqualTo(new Vector2(75, 145))); + Assert.That(sixth.ControlPoints[1].Type.Value == null); + Assert.That(sixth.ControlPoints[2].Position.Value, Is.EqualTo(new Vector2(170, 75))); + + Assert.That(sixth.ControlPoints[2].Type.Value == PathType.Bezier); + Assert.That(sixth.ControlPoints[3].Position.Value, Is.EqualTo(new Vector2(300, 145))); + Assert.That(sixth.ControlPoints[3].Type.Value == null); + Assert.That(sixth.ControlPoints[4].Position.Value, Is.EqualTo(new Vector2(410, 20))); + Assert.That(sixth.ControlPoints[4].Type.Value == null); + + // Explicit perfect-curve multi-segment(Should not convert to bezier) + var seventh = ((IHasPath)decoded.HitObjects[6]).Path; + + Assert.That(seventh.ControlPoints[0].Position.Value, Is.EqualTo(Vector2.Zero)); + Assert.That(seventh.ControlPoints[0].Type.Value == PathType.PerfectCurve); + Assert.That(seventh.ControlPoints[1].Position.Value, Is.EqualTo(new Vector2(75, 145))); + Assert.That(seventh.ControlPoints[1].Type.Value == null); + Assert.That(seventh.ControlPoints[2].Position.Value, Is.EqualTo(new Vector2(170, 75))); + + Assert.That(seventh.ControlPoints[2].Type.Value == PathType.PerfectCurve); + Assert.That(seventh.ControlPoints[3].Position.Value, Is.EqualTo(new Vector2(300, 145))); + Assert.That(seventh.ControlPoints[3].Type.Value == null); + Assert.That(seventh.ControlPoints[4].Position.Value, Is.EqualTo(new Vector2(410, 20))); + Assert.That(seventh.ControlPoints[4].Type.Value == null); } } } diff --git a/osu.Game.Tests/Resources/multi-segment-slider.osu b/osu.Game.Tests/Resources/multi-segment-slider.osu index cc86710067..135132e35c 100644 --- a/osu.Game.Tests/Resources/multi-segment-slider.osu +++ b/osu.Game.Tests/Resources/multi-segment-slider.osu @@ -15,3 +15,10 @@ osu file format v128 // Last control point in segment duplicated 0,0,5000,2,0,B|1:1|2:2|3:3|3:3|B|4:4|5:5,2,200 + +// Implicit perfect-curve multi-segment (Should convert to bezier to match stable) +0,0,6000,2,0,P|75:145|170:75|170:75|300:145|410:20,1,475,0:0:0:0: + +// Explicit perfect-curve multi-segment (Should not convert to bezier) +0,0,7000,2,0,P|75:145|P|170:75|300:145|410:20,1,650,0:0:0:0: + diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 0bb1aa873f..da44b96ed3 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -333,7 +333,7 @@ namespace osu.Game.Beatmaps.Formats // Explicit segments have a new format in which the type is injected into the middle of the control point string. // To preserve compatibility with osu-stable as much as possible, explicit segments with the same type are converted to use implicit segments by duplicating the control point. - // One exception are consecutive perfect curves, which aren't supported in osu-stable + // One exception are consecutive perfect curves, which aren't supported in osu!stable and can lead to decoding issues if encoded as implicit segments bool needsExplicitSegment = point.Type.Value != lastType || point.Type.Value == PathType.PerfectCurve; // Another exception to this is when the last two control points of the last segment were duplicated. This is not a scenario supported by osu!stable. From d5ba77b2c2b5984b279c7809a1f58878c92e0936 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 6 Apr 2021 21:22:28 +0900 Subject: [PATCH 1273/1791] Add spectating user state --- osu.Game/Online/Multiplayer/MultiplayerUserState.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Online/Multiplayer/MultiplayerUserState.cs b/osu.Game/Online/Multiplayer/MultiplayerUserState.cs index e54c71cd85..c467ff84bb 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerUserState.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerUserState.cs @@ -55,5 +55,10 @@ namespace osu.Game.Online.Multiplayer /// The user is currently viewing results. This is a reserved state, and is set by the server. /// Results, + + /// + /// The user is currently spectating this room. + /// + Spectating } } From 6de91d7b6bb38caa25ea455a2868b548b083d5ab Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 6 Apr 2021 21:37:21 +0900 Subject: [PATCH 1274/1791] Add spectate button + test --- .../TestSceneMultiplayerSpectateButton.cs | 53 +++++++++++ .../Multiplayer/StatefulMultiplayerClient.cs | 27 ++++++ .../Match/MultiplayerSpectateButton.cs | 92 +++++++++++++++++++ 3 files changed, 172 insertions(+) create mode 100644 osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs create mode 100644 osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs new file mode 100644 index 0000000000..730fee2fba --- /dev/null +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -0,0 +1,53 @@ +// 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.Game.Online.Multiplayer; +using osu.Game.Screens.OnlinePlay.Multiplayer.Match; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Multiplayer +{ + public class TestSceneMultiplayerSpectateButton : MultiplayerTestScene + { + private MultiplayerSpectateButton button; + private IDisposable readyClickOperation; + + [SetUp] + public new void Setup() => Schedule(() => + { + Child = button = new MultiplayerSpectateButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(200, 50), + OnSpectateClick = async () => + { + readyClickOperation = OngoingOperationTracker.BeginOperation(); + await Client.ToggleSpectate(); + readyClickOperation.Dispose(); + } + }; + }); + + [TestCase(MultiplayerUserState.Idle)] + [TestCase(MultiplayerUserState.Ready)] + public void TestToggleWhenIdle(MultiplayerUserState initialState) + { + addClickButtonStep(); + AddAssert("user is spectating", () => Client.Room?.Users[0].State == MultiplayerUserState.Spectating); + + addClickButtonStep(); + AddAssert("user is idle", () => Client.Room?.Users[0].State == MultiplayerUserState.Idle); + } + + private void addClickButtonStep() => AddStep("click button", () => + { + InputManager.MoveMouseTo(button); + InputManager.Click(MouseButton.Left); + }); + } +} diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index 0f7050596f..2ddc10db0f 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -249,6 +249,33 @@ namespace osu.Game.Online.Multiplayer } } + /// + /// Toggles the 's spectating state. + /// + /// If a toggle of the spectating state is not valid at this time. + public async Task ToggleSpectate() + { + var localUser = LocalUser; + + if (localUser == null) + return; + + switch (localUser.State) + { + case MultiplayerUserState.Idle: + case MultiplayerUserState.Ready: + await ChangeState(MultiplayerUserState.Spectating).ConfigureAwait(false); + return; + + case MultiplayerUserState.Spectating: + await ChangeState(MultiplayerUserState.Idle).ConfigureAwait(false); + return; + + default: + throw new InvalidOperationException($"Cannot toggle spectate when in {localUser.State}"); + } + } + public abstract Task TransferHost(int userId); public abstract Task ChangeSettings(MultiplayerRoomSettings settings); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs new file mode 100644 index 0000000000..50be7719d9 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs @@ -0,0 +1,92 @@ +// 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.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Graphics; +using osu.Game.Graphics.Backgrounds; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.Multiplayer; +using osuTK; + +namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match +{ + public class MultiplayerSpectateButton : MultiplayerRoomComposite + { + public Action OnSpectateClick + { + set => button.Action = value; + } + + [Resolved] + private OngoingOperationTracker ongoingOperationTracker { get; set; } + + [Resolved] + private OsuColour colours { get; set; } + + private IBindable operationInProgress; + + private readonly ButtonWithTrianglesExposed button; + + public MultiplayerSpectateButton() + { + InternalChild = button = new ButtonWithTrianglesExposed + { + RelativeSizeAxes = Axes.Both, + Size = Vector2.One, + Enabled = { Value = true }, + }; + } + + [BackgroundDependencyLoader] + private void load() + { + operationInProgress = ongoingOperationTracker.InProgress.GetBoundCopy(); + operationInProgress.BindValueChanged(_ => updateState()); + } + + protected override void OnRoomUpdated() + { + base.OnRoomUpdated(); + + updateState(); + } + + private void updateState() + { + var localUser = Client.LocalUser; + + if (localUser == null) + return; + + Debug.Assert(Room != null); + + switch (localUser.State) + { + default: + button.Text = "Spectate"; + button.BackgroundColour = colours.Blue; + button.Triangles.ColourDark = colours.Blue; + button.Triangles.ColourLight = colours.BlueLight; + break; + + case MultiplayerUserState.Spectating: + button.Text = "Stop spectating"; + button.BackgroundColour = colours.Red; + button.Triangles.ColourDark = colours.Red; + button.Triangles.ColourLight = colours.RedLight; + break; + } + + button.Enabled.Value = Client.Room?.State == MultiplayerRoomState.Open && !operationInProgress.Value; + } + + private class ButtonWithTrianglesExposed : TriangleButton + { + public new Triangles Triangles => base.Triangles; + } + } +} From ef658e9597484f008119240601b5921e6713b563 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 7 Apr 2021 15:54:16 +0900 Subject: [PATCH 1275/1791] Fix invalid array definition in slnf --- osu.Desktop.slnf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Desktop.slnf b/osu.Desktop.slnf index 68541dbcfc..503e5935f5 100644 --- a/osu.Desktop.slnf +++ b/osu.Desktop.slnf @@ -24,7 +24,7 @@ "Templates\\Rulesets\\ruleset-scrolling-empty\\osu.Game.Rulesets.EmptyScrolling\\osu.Game.Rulesets.EmptyScrolling.csproj", "Templates\\Rulesets\\ruleset-scrolling-empty\\osu.Game.Rulesets.EmptyScrolling.Tests\\osu.Game.Rulesets.EmptyScrolling.Tests.csproj", "Templates\\Rulesets\\ruleset-scrolling-example\\osu.Game.Rulesets.Pippidon\\osu.Game.Rulesets.Pippidon.csproj", - "Templates\\Rulesets\\ruleset-scrolling-example\\osu.Game.Rulesets.Pippidon.Tests\\osu.Game.Rulesets.Pippidon.Tests.csproj", + "Templates\\Rulesets\\ruleset-scrolling-example\\osu.Game.Rulesets.Pippidon.Tests\\osu.Game.Rulesets.Pippidon.Tests.csproj" ] } -} \ No newline at end of file +} From 1f57b6884d0e944907cf4ce6f2cc12c157fc32c6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 7 Apr 2021 16:19:10 +0900 Subject: [PATCH 1276/1791] Add ready button to test scene --- .../TestSceneMultiplayerSpectateButton.cs | 121 ++++++++++++++++-- 1 file changed, 108 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs index 730fee2fba..52a1909396 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -2,10 +2,22 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Platform; +using osu.Framework.Testing; +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.Screens.OnlinePlay.Multiplayer.Match; +using osu.Game.Tests.Resources; using osuTK; using osuTK.Input; @@ -13,22 +25,96 @@ namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneMultiplayerSpectateButton : MultiplayerTestScene { - private MultiplayerSpectateButton button; + private MultiplayerSpectateButton spectateButton; + private MultiplayerReadyButton readyButton; + + private readonly Bindable selectedItem = new Bindable(); + + private BeatmapSetInfo importedSet; + private BeatmapManager beatmaps; + private RulesetStore rulesets; + private IDisposable readyClickOperation; + protected override Container Content => content; + private readonly Container content; + + public TestSceneMultiplayerSpectateButton() + { + base.Content.Add(content = new Container + { + RelativeSizeAxes = Axes.Both + }); + } + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + + return dependencies; + } + + [BackgroundDependencyLoader] + private void load(GameHost host, AudioManager audio) + { + Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default)); + + var beatmapTracker = new OnlinePlayBeatmapAvailablilityTracker { SelectedItem = { BindTarget = selectedItem } }; + base.Content.Add(beatmapTracker); + Dependencies.Cache(beatmapTracker); + + beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); + } + [SetUp] public new void Setup() => Schedule(() => { - Child = button = new MultiplayerSpectateButton + importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); + Beatmap.Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()); + selectedItem.Value = new PlaylistItem { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(200, 50), - OnSpectateClick = async () => + Beatmap = { Value = Beatmap.Value.BeatmapInfo }, + Ruleset = { Value = Beatmap.Value.BeatmapInfo.Ruleset }, + }; + + Child = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] { - readyClickOperation = OngoingOperationTracker.BeginOperation(); - await Client.ToggleSpectate(); - readyClickOperation.Dispose(); + spectateButton = new MultiplayerSpectateButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(200, 50), + OnSpectateClick = async () => + { + readyClickOperation = OngoingOperationTracker.BeginOperation(); + await Client.ToggleSpectate(); + readyClickOperation.Dispose(); + } + }, + readyButton = new MultiplayerReadyButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(200, 50), + OnReadyClick = async () => + { + readyClickOperation = OngoingOperationTracker.BeginOperation(); + + if (Client.IsHost && Client.LocalUser?.State == MultiplayerUserState.Ready) + { + await Client.StartMatch(); + return; + } + + await Client.ToggleReady(); + readyClickOperation.Dispose(); + } + } } }; }); @@ -37,17 +123,26 @@ namespace osu.Game.Tests.Visual.Multiplayer [TestCase(MultiplayerUserState.Ready)] public void TestToggleWhenIdle(MultiplayerUserState initialState) { - addClickButtonStep(); + addClickSpectateButtonStep(); AddAssert("user is spectating", () => Client.Room?.Users[0].State == MultiplayerUserState.Spectating); - addClickButtonStep(); + addClickSpectateButtonStep(); AddAssert("user is idle", () => Client.Room?.Users[0].State == MultiplayerUserState.Idle); } - private void addClickButtonStep() => AddStep("click button", () => + private void addClickSpectateButtonStep() => AddStep("click spectate button", () => { - InputManager.MoveMouseTo(button); + InputManager.MoveMouseTo(spectateButton); InputManager.Click(MouseButton.Left); }); + + private void addClickReadyButtonStep() => AddStep("click ready button", () => + { + InputManager.MoveMouseTo(readyButton); + InputManager.Click(MouseButton.Left); + }); + + private void assertReadyButtonEnablement(bool shouldBeEnabled) + => AddAssert($"ready button {(shouldBeEnabled ? "is" : "is not")} enabled", () => readyButton.ChildrenOfType().Single().Enabled.Value == shouldBeEnabled); } } From 6be9c9f0f4e8188d4e50b55e26f0025cc2d06292 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 7 Apr 2021 16:35:36 +0900 Subject: [PATCH 1277/1791] Link up ready button to spectate state --- .../TestSceneMultiplayerSpectateButton.cs | 51 +++++++++++++++++++ .../Match/MultiplayerReadyButton.cs | 11 +++- .../Multiplayer/TestMultiplayerClient.cs | 6 +++ 3 files changed, 67 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs index 52a1909396..631511eac5 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -18,6 +18,7 @@ using osu.Game.Online.Rooms; using osu.Game.Rulesets; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; using osu.Game.Tests.Resources; +using osu.Game.Users; using osuTK; using osuTK.Input; @@ -119,6 +120,12 @@ namespace osu.Game.Tests.Visual.Multiplayer }; }); + [Test] + public void TestEnabledWhenRoomOpen() + { + assertSpectateButtonEnablement(true); + } + [TestCase(MultiplayerUserState.Idle)] [TestCase(MultiplayerUserState.Ready)] public void TestToggleWhenIdle(MultiplayerUserState initialState) @@ -130,6 +137,47 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("user is idle", () => Client.Room?.Users[0].State == MultiplayerUserState.Idle); } + [TestCase(MultiplayerRoomState.WaitingForLoad)] + [TestCase(MultiplayerRoomState.Playing)] + [TestCase(MultiplayerRoomState.Closed)] + public void TestDisabledDuringGameplayOrClosed(MultiplayerRoomState roomState) + { + AddStep($"change user to {roomState}", () => Client.ChangeRoomState(roomState)); + assertSpectateButtonEnablement(false); + } + + [Test] + public void TestReadyButtonDisabledWhenHostAndNoReadyUsers() + { + addClickSpectateButtonStep(); + assertReadyButtonEnablement(false); + } + + [Test] + public void TestReadyButtonEnabledWhenHostAndUsersReady() + { + AddStep("add user", () => Client.AddUser(new User { Id = 55 })); + AddStep("set user ready", () => Client.ChangeUserState(55, MultiplayerUserState.Ready)); + + addClickSpectateButtonStep(); + assertReadyButtonEnablement(true); + } + + [Test] + public void TestReadyButtonDisabledWhenNotHostAndUsersReady() + { + AddStep("add user and transfer host", () => + { + Client.AddUser(new User { Id = 55 }); + Client.TransferHost(55); + }); + + AddStep("set user ready", () => Client.ChangeUserState(55, MultiplayerUserState.Ready)); + + addClickSpectateButtonStep(); + assertReadyButtonEnablement(false); + } + private void addClickSpectateButtonStep() => AddStep("click spectate button", () => { InputManager.MoveMouseTo(spectateButton); @@ -142,6 +190,9 @@ namespace osu.Game.Tests.Visual.Multiplayer InputManager.Click(MouseButton.Left); }); + private void assertSpectateButtonEnablement(bool shouldBeEnabled) + => AddAssert($"spectate button {(shouldBeEnabled ? "is" : "is not")} enabled", () => spectateButton.ChildrenOfType().Single().Enabled.Value == shouldBeEnabled); + private void assertReadyButtonEnablement(bool shouldBeEnabled) => AddAssert($"ready button {(shouldBeEnabled ? "is" : "is not")} enabled", () => readyButton.ChildrenOfType().Single().Enabled.Value == shouldBeEnabled); } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs index c9fb234ccc..6919be2d56 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs @@ -78,8 +78,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match Debug.Assert(Room != null); int newCountReady = Room.Users.Count(u => u.State == MultiplayerUserState.Ready); + int newCountTotal = Room.Users.Count(u => u.State != MultiplayerUserState.Spectating); - string countText = $"({newCountReady} / {Room.Users.Count} ready)"; + string countText = $"({newCountReady} / {newCountTotal} ready)"; switch (localUser.State) { @@ -88,6 +89,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match updateButtonColour(true); break; + case MultiplayerUserState.Spectating: case MultiplayerUserState.Ready: if (Room?.Host?.Equals(localUser) == true) { @@ -105,6 +107,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match button.Enabled.Value = Client.Room?.State == MultiplayerRoomState.Open && !operationInProgress.Value; + // When the local user is the host and spectating the match, the "start match" state should be enabled. + if (localUser.State == MultiplayerUserState.Spectating) + { + button.Enabled.Value &= Room?.Host?.Equals(localUser) == true; + button.Enabled.Value &= newCountReady > 0; + } + if (newCountReady != countReady) { countReady = newCountReady; diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 09fcc1ff47..4c954c7d27 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -58,6 +58,12 @@ namespace osu.Game.Tests.Visual.Multiplayer }); } + public void ChangeRoomState(MultiplayerRoomState newState) + { + Debug.Assert(Room != null); + ((IMultiplayerClient)this).RoomStateChanged(newState); + } + public void ChangeUserState(int userId, MultiplayerUserState newState) { Debug.Assert(Room != null); From f5667125a017e56d153c388638c2f2a13d624389 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 7 Apr 2021 16:37:43 +0900 Subject: [PATCH 1278/1791] Remove unnecessary method --- .../Multiplayer/TestSceneMultiplayerSpectateButton.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs index 631511eac5..8fe1f99e1d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -184,12 +184,6 @@ namespace osu.Game.Tests.Visual.Multiplayer InputManager.Click(MouseButton.Left); }); - private void addClickReadyButtonStep() => AddStep("click ready button", () => - { - InputManager.MoveMouseTo(readyButton); - InputManager.Click(MouseButton.Left); - }); - private void assertSpectateButtonEnablement(bool shouldBeEnabled) => AddAssert($"spectate button {(shouldBeEnabled ? "is" : "is not")} enabled", () => spectateButton.ChildrenOfType().Single().Enabled.Value == shouldBeEnabled); From c744f77cfafdab402bbc1e34793402baf6c0995c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 7 Apr 2021 16:40:24 +0900 Subject: [PATCH 1279/1791] Add participant panel state --- .../Multiplayer/TestSceneMultiplayerParticipantsList.cs | 7 +++++++ .../OnlinePlay/Multiplayer/Participants/StateDisplay.cs | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs index e713cff233..7f8f04b718 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs @@ -126,6 +126,13 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("ready mark invisible", () => !this.ChildrenOfType().Single().IsPresent); } + [Test] + public void TestToggleSpectateState() + { + AddStep("make user spectating", () => Client.ChangeState(MultiplayerUserState.Spectating)); + AddStep("make user idle", () => Client.ChangeState(MultiplayerUserState.Idle)); + } + [Test] public void TestCrownChangesStateWhenHostTransferred() { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs index c571b51c83..e6a407acec 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs @@ -135,6 +135,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants icon.Colour = colours.BlueLighter; break; + case MultiplayerUserState.Spectating: + text.Text = "spectating"; + icon.Icon = FontAwesome.Solid.Eye; + icon.Colour = colours.BlueLight; + break; + default: throw new ArgumentOutOfRangeException(nameof(state), state, null); } From 56c13148f1861b38b71cd87fdcaf5619b6527c1d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 7 Apr 2021 16:45:06 +0900 Subject: [PATCH 1280/1791] Fix typo in class name --- .../TestSceneOnlinePlayBeatmapAvailabilityTracker.cs | 8 ++++---- .../Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs | 4 ++-- ...Tracker.cs => OnlinePlayBeatmapAvailabilityTracker.cs} | 2 +- osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs | 2 +- osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs | 6 +++--- 5 files changed, 11 insertions(+), 11 deletions(-) rename osu.Game/Online/Rooms/{OnlinePlayBeatmapAvailablilityTracker.cs => OnlinePlayBeatmapAvailabilityTracker.cs} (97%) diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index 8c30802ce3..8dab570e30 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -38,7 +38,7 @@ namespace osu.Game.Tests.Online private BeatmapSetInfo testBeatmapSet; private readonly Bindable selectedItem = new Bindable(); - private OnlinePlayBeatmapAvailablilityTracker availablilityTracker; + private OnlinePlayBeatmapAvailabilityTracker availabilityTracker; [BackgroundDependencyLoader] private void load(AudioManager audio, GameHost host) @@ -67,7 +67,7 @@ namespace osu.Game.Tests.Online Ruleset = { Value = testBeatmapInfo.Ruleset }, }; - Child = availablilityTracker = new OnlinePlayBeatmapAvailablilityTracker + Child = availabilityTracker = new OnlinePlayBeatmapAvailabilityTracker { SelectedItem = { BindTarget = selectedItem, } }; @@ -118,7 +118,7 @@ namespace osu.Game.Tests.Online }); addAvailabilityCheckStep("state still not downloaded", BeatmapAvailability.NotDownloaded); - AddStep("recreate tracker", () => Child = availablilityTracker = new OnlinePlayBeatmapAvailablilityTracker + AddStep("recreate tracker", () => Child = availabilityTracker = new OnlinePlayBeatmapAvailabilityTracker { SelectedItem = { BindTarget = selectedItem } }); @@ -127,7 +127,7 @@ namespace osu.Game.Tests.Online private void addAvailabilityCheckStep(string description, Func expected) { - AddAssert(description, () => availablilityTracker.Availability.Value.Equals(expected.Invoke())); + AddAssert(description, () => availabilityTracker.Availability.Value.Equals(expected.Invoke())); } private static BeatmapInfo getTestBeatmapInfo(string archiveFile) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs index b44e5b1e5b..dad1237991 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs @@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public class TestSceneMultiplayerReadyButton : MultiplayerTestScene { private MultiplayerReadyButton button; - private OnlinePlayBeatmapAvailablilityTracker beatmapTracker; + private OnlinePlayBeatmapAvailabilityTracker beatmapTracker; private BeatmapSetInfo importedSet; private readonly Bindable selectedItem = new Bindable(); @@ -44,7 +44,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default)); beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); - Add(beatmapTracker = new OnlinePlayBeatmapAvailablilityTracker + Add(beatmapTracker = new OnlinePlayBeatmapAvailabilityTracker { SelectedItem = { BindTarget = selectedItem } }); diff --git a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailablilityTracker.cs b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs similarity index 97% rename from osu.Game/Online/Rooms/OnlinePlayBeatmapAvailablilityTracker.cs rename to osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs index 8278162353..72ea84d4a8 100644 --- a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailablilityTracker.cs +++ b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs @@ -16,7 +16,7 @@ namespace osu.Game.Online.Rooms /// This differs from a regular download tracking composite as this accounts for the /// databased beatmap set's checksum, to disallow from playing with an altered version of the beatmap. /// - public class OnlinePlayBeatmapAvailablilityTracker : DownloadTrackingComposite + public class OnlinePlayBeatmapAvailabilityTracker : DownloadTrackingComposite { public readonly IBindable SelectedItem = new Bindable(); diff --git a/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs b/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs index 6eb675976a..8f85608b29 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs @@ -16,7 +16,7 @@ namespace osu.Game.Screens.OnlinePlay.Components private IBindable availability; [BackgroundDependencyLoader] - private void load(OnlinePlayBeatmapAvailablilityTracker beatmapTracker) + private void load(OnlinePlayBeatmapAvailabilityTracker beatmapTracker) { availability = beatmapTracker.Availability.GetBoundCopy(); diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 4a689314db..706da05d15 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -56,15 +56,15 @@ namespace osu.Game.Screens.OnlinePlay.Match private IBindable> managerUpdated; [Cached] - protected OnlinePlayBeatmapAvailablilityTracker BeatmapAvailablilityTracker { get; } + protected OnlinePlayBeatmapAvailabilityTracker BeatmapAvailabilityTracker { get; } - protected IBindable BeatmapAvailability => BeatmapAvailablilityTracker.Availability; + protected IBindable BeatmapAvailability => BeatmapAvailabilityTracker.Availability; protected RoomSubScreen() { AddRangeInternal(new Drawable[] { - BeatmapAvailablilityTracker = new OnlinePlayBeatmapAvailablilityTracker + BeatmapAvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker { SelectedItem = { BindTarget = SelectedItem } }, From abd637ffaa5345f71f248823de47a3d5d4e805f3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 7 Apr 2021 17:35:18 +0900 Subject: [PATCH 1281/1791] Add button to footer --- .../TestSceneMultiplayerMatchFooter.cs | 27 +++++++++++++ .../Match/MultiplayerMatchFooter.cs | 40 ++++++++++++++++--- 2 files changed, 62 insertions(+), 5 deletions(-) create mode 100644 osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs new file mode 100644 index 0000000000..08c94b8135 --- /dev/null +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs @@ -0,0 +1,27 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Online.Rooms; +using osu.Game.Screens.OnlinePlay.Multiplayer.Match; + +namespace osu.Game.Tests.Visual.Multiplayer +{ + public class TestSceneMultiplayerMatchFooter : MultiplayerTestScene + { + [Cached] + private readonly OnlinePlayBeatmapAvailablilityTracker availablilityTracker = new OnlinePlayBeatmapAvailablilityTracker(); + + [BackgroundDependencyLoader] + private void load() + { + Child = new MultiplayerMatchFooter + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Height = 50 + }; + } + } +} diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchFooter.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchFooter.cs index fdc1ae9d3c..d4f5428bfb 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchFooter.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchFooter.cs @@ -8,21 +8,28 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; -using osuTK; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { public class MultiplayerMatchFooter : CompositeDrawable { public const float HEIGHT = 50; + private const float ready_button_width = 600; + private const float spectate_button_width = 200; public Action OnReadyClick { set => readyButton.OnReadyClick = value; } + public Action OnSpectateClick + { + set => spectateButton.OnSpectateClick = value; + } + private readonly Drawable background; private readonly MultiplayerReadyButton readyButton; + private readonly MultiplayerSpectateButton spectateButton; public MultiplayerMatchFooter() { @@ -32,11 +39,34 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match InternalChildren = new[] { background = new Box { RelativeSizeAxes = Axes.Both }, - readyButton = new MultiplayerReadyButton + new GridContainer { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(600, 50), + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] + { + null, + spectateButton = new MultiplayerSpectateButton + { + RelativeSizeAxes = Axes.Both, + }, + null, + readyButton = new MultiplayerReadyButton + { + RelativeSizeAxes = Axes.Both, + }, + null + } + }, + ColumnDimensions = new[] + { + new Dimension(), + new Dimension(maxSize: spectate_button_width), + new Dimension(GridSizeMode.Absolute, 10), + new Dimension(maxSize: ready_button_width), + new Dimension() + } } }; } From 08858e6426c520bb038ca774da64902dc21e70e9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Apr 2021 17:41:05 +0900 Subject: [PATCH 1282/1791] Reorder defaults to give non-global areas priority for global actions --- .../Input/Bindings/DatabasedKeyBindingContainer.cs | 12 ++++++++++-- osu.Game/Input/Bindings/GlobalActionContainer.cs | 4 ++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs index d12eaa10f6..cd8b486f23 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs @@ -64,12 +64,20 @@ namespace osu.Game.Input.Bindings protected override void ReloadMappings() { + var defaults = DefaultKeyBindings.ToList(); + if (ruleset != null && !ruleset.ID.HasValue) // if the provided ruleset is not stored to the database, we have no way to retrieve custom bindings. // fallback to defaults instead. - KeyBindings = DefaultKeyBindings; + KeyBindings = defaults; else - KeyBindings = store.Query(ruleset?.ID, variant).ToList(); + { + KeyBindings = store.Query(ruleset?.ID, variant) + // this ordering is important to ensure that we read entries from the database in the order + // enforced by DefaultKeyBindings. allow for song select to handle actions that may otherwise + // have been eaten by the music controller due to query order. + .OrderBy(b => defaults.FindIndex(d => (int)d.Action == b.IntAction)).ToList(); + } } } } diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index c2f707a4e8..042960d54c 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -28,10 +28,10 @@ namespace osu.Game.Input.Bindings } public override IEnumerable DefaultKeyBindings => GlobalKeyBindings + .Concat(EditorKeyBindings) .Concat(InGameKeyBindings) - .Concat(AudioControlKeyBindings) .Concat(SongSelectKeyBindings) - .Concat(EditorKeyBindings); + .Concat(AudioControlKeyBindings); public IEnumerable GlobalKeyBindings => new[] { From b4c6894d13d96a9ba65162492459a2fe0a46de3e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Apr 2021 18:29:31 +0900 Subject: [PATCH 1283/1791] Add test coverage for song select footer area --- .../SongSelect/TestSceneSongSelectFooter.cs | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectFooter.cs diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectFooter.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectFooter.cs new file mode 100644 index 0000000000..0ac65b357c --- /dev/null +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectFooter.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 osu.Framework.Graphics; +using osu.Game.Screens.Select; + +namespace osu.Game.Tests.Visual.SongSelect +{ + public class TestSceneSongSelectFooter : OsuManualInputManagerTestScene + { + public TestSceneSongSelectFooter() + { + AddStep("Create footer", () => + { + Footer footer; + AddRange(new Drawable[] + { + footer = new Footer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + }); + + footer.AddButton(new FooterButtonMods(), null); + footer.AddButton(new FooterButtonRandom + { + NextRandom = () => { }, + PreviousRandom = () => { }, + }, null); + footer.AddButton(new FooterButtonOptions(), null); + }); + } + } +} From 0f2c03d54bb19c36ccc85bead76f4ce574ac7af9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Apr 2021 18:29:45 +0900 Subject: [PATCH 1284/1791] Add back "rewind" text, showing temporarily after a rewind occurs --- osu.Game/Screens/Select/FooterButton.cs | 1 + osu.Game/Screens/Select/FooterButtonRandom.cs | 23 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/osu.Game/Screens/Select/FooterButton.cs b/osu.Game/Screens/Select/FooterButton.cs index dfcdd1b45f..7528651fd9 100644 --- a/osu.Game/Screens/Select/FooterButton.cs +++ b/osu.Game/Screens/Select/FooterButton.cs @@ -106,6 +106,7 @@ namespace osu.Game.Screens.Select AutoSizeAxes = Axes.Both, Child = SpriteText = new OsuSpriteText { + AlwaysPresent = true, Anchor = Anchor.Centre, Origin = Anchor.Centre, } diff --git a/osu.Game/Screens/Select/FooterButtonRandom.cs b/osu.Game/Screens/Select/FooterButtonRandom.cs index b314971cb3..2d14111137 100644 --- a/osu.Game/Screens/Select/FooterButtonRandom.cs +++ b/osu.Game/Screens/Select/FooterButtonRandom.cs @@ -4,8 +4,11 @@ using System; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; using osu.Game.Input.Bindings; +using osuTK; namespace osu.Game.Screens.Select { @@ -22,10 +25,30 @@ namespace osu.Game.Screens.Select SelectedColour = colours.Green; DeselectedColour = SelectedColour.Opacity(0.5f); Text = @"random"; + Action = () => { if (rewindSearch) { + const double fade_time = 500; + + OsuSpriteText rewindSpriteText; + + TextContainer.Add(rewindSpriteText = new OsuSpriteText + { + Alpha = 0, + Text = @"rewind", + AlwaysPresent = true, // make sure the button is sized large enough to always show this + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }); + + rewindSpriteText.FadeOutFromOne(fade_time, Easing.In); + rewindSpriteText.MoveTo(Vector2.Zero).MoveTo(new Vector2(0, 10), fade_time, Easing.In); + rewindSpriteText.Expire(); + + SpriteText.FadeInFromZero(fade_time, Easing.In); + PreviousRandom.Invoke(); } else From aa424165b3790cdccd6d094b7310d68de6a16e6a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Apr 2021 18:45:57 +0900 Subject: [PATCH 1285/1791] Fix broken taiko test --- osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs index fa6c9da174..9b36b064bc 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning [BackgroundDependencyLoader] private void load() { - SetContents(() => new TaikoInputManager(new RulesetInfo { ID = 1 }) + SetContents(() => new TaikoInputManager(new TaikoRuleset().RulesetInfo) { RelativeSizeAxes = Axes.Both, Child = new Container From e7f47c635fdbad79b71831e8d4b86b0708138440 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Apr 2021 19:00:04 +0900 Subject: [PATCH 1286/1791] Fix gameplay mouse cursor being overridden by menu cursor Closes https://github.com/ppy/osu/issues/12313. --- osu.Game/OsuGameBase.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index b2bbd0b48b..21313d0596 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -308,17 +308,15 @@ namespace osu.Game AddInternal(RulesetConfigCache); - MenuCursorContainer = new MenuCursorContainer { RelativeSizeAxes = Axes.Both }; - - GlobalInputManager globalInput; - - MenuCursorContainer.Child = globalInput = new GlobalInputManager(this) + var globalInput = new GlobalInputManager(this) { RelativeSizeAxes = Axes.Both, - Child = content = new OsuTooltipContainer(MenuCursorContainer.Cursor) { RelativeSizeAxes = Axes.Both } + Child = MenuCursorContainer = new MenuCursorContainer { RelativeSizeAxes = Axes.Both } }; - base.Content.Add(CreateScalingContainer().WithChild(MenuCursorContainer)); + MenuCursorContainer.Child = content = new OsuTooltipContainer(MenuCursorContainer.Cursor) { RelativeSizeAxes = Axes.Both }; + + base.Content.Add(CreateScalingContainer().WithChild(globalInput)); KeyBindingStore.Register(globalInput.GlobalBindings); dependencies.Cache(globalInput.GlobalBindings); From 7d37c4df8cc6d1e043b06cb1b887bc443b7ad5c2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 7 Apr 2021 20:17:20 +0900 Subject: [PATCH 1287/1791] Fix broken osu tests --- osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs b/osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs index cad98185ce..233aaf2ed9 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Tests get { if (content == null) - base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 })); + base.Content.Add(content = new OsuInputManager(new OsuRuleset().RulesetInfo)); return content; } From 93c5935ebc4adc5989122b0f502fab6c44dd5064 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 7 Apr 2021 20:46:30 +0900 Subject: [PATCH 1288/1791] Add match subscreen support + test --- .../TestSceneMultiplayerMatchSubScreen.cs | 61 +++++++++++++++++++ .../Multiplayer/MultiplayerMatchSubScreen.cs | 19 +++++- .../Multiplayer/TestMultiplayerClient.cs | 3 + 3 files changed, 81 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index 8869718fd1..6f8ec7fcfb 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -3,13 +3,21 @@ using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; +using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; using osu.Game.Tests.Beatmaps; +using osu.Game.Tests.Resources; +using osu.Game.Users; using osuTK.Input; namespace osu.Game.Tests.Visual.Multiplayer @@ -18,11 +26,25 @@ namespace osu.Game.Tests.Visual.Multiplayer { private MultiplayerMatchSubScreen screen; + private BeatmapManager beatmaps; + private RulesetStore rulesets; + private BeatmapSetInfo importedSet; + public TestSceneMultiplayerMatchSubScreen() : base(false) { } + [BackgroundDependencyLoader] + private void load(GameHost host, AudioManager audio) + { + Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default)); + beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); + + importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); + } + [SetUp] public new void Setup() => Schedule(() => { @@ -73,5 +95,44 @@ namespace osu.Game.Tests.Visual.Multiplayer AddWaitStep("wait", 10); } + + [Test] + public void TestStartMatchWhileSpectating() + { + AddStep("set playlist", () => + { + Room.Playlist.Add(new PlaylistItem + { + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()).BeatmapInfo }, + Ruleset = { Value = new OsuRuleset().RulesetInfo }, + }); + }); + + AddStep("click create button", () => + { + InputManager.MoveMouseTo(this.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + + AddStep("join other user (ready)", () => + { + Client.AddUser(new User { Id = 55 }); + Client.ChangeUserState(55, MultiplayerUserState.Ready); + }); + + AddStep("click spectate button", () => + { + InputManager.MoveMouseTo(this.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + + AddStep("click ready button", () => + { + InputManager.MoveMouseTo(this.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + + AddAssert("match started", () => Client.Room?.State == MultiplayerRoomState.WaitingForLoad); + } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index ceeee67806..90cef0107c 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -221,7 +221,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { new MultiplayerMatchFooter { - OnReadyClick = onReadyClick + OnReadyClick = onReadyClick, + OnSpectateClick = onSpectateClick } } }, @@ -363,7 +364,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer Debug.Assert(readyClickOperation == null); readyClickOperation = ongoingOperationTracker.BeginOperation(); - if (client.IsHost && client.LocalUser?.State == MultiplayerUserState.Ready) + if (client.IsHost && (client.LocalUser?.State == MultiplayerUserState.Ready || client.LocalUser?.State == MultiplayerUserState.Spectating)) { client.StartMatch() .ContinueWith(t => @@ -390,6 +391,20 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer } } + private void onSpectateClick() + { + Debug.Assert(readyClickOperation == null); + readyClickOperation = ongoingOperationTracker.BeginOperation(); + + client.ToggleSpectate().ContinueWith(t => endOperation()); + + void endOperation() + { + readyClickOperation?.Dispose(); + readyClickOperation = null; + } + } + private void onRoomUpdated() { // user mods may have changed. diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 4c954c7d27..b5cd3dad02 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -77,6 +77,7 @@ namespace osu.Game.Tests.Visual.Multiplayer case MultiplayerUserState.Loaded: if (Room.Users.All(u => u.State != MultiplayerUserState.WaitingForLoad)) { + ChangeRoomState(MultiplayerRoomState.Playing); foreach (var u in Room.Users.Where(u => u.State == MultiplayerUserState.Loaded)) ChangeUserState(u.UserID, MultiplayerUserState.Playing); @@ -88,6 +89,7 @@ namespace osu.Game.Tests.Visual.Multiplayer case MultiplayerUserState.FinishedPlay: if (Room.Users.All(u => u.State != MultiplayerUserState.Playing)) { + ChangeRoomState(MultiplayerRoomState.Open); foreach (var u in Room.Users.Where(u => u.State == MultiplayerUserState.FinishedPlay)) ChangeUserState(u.UserID, MultiplayerUserState.Results); @@ -179,6 +181,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Debug.Assert(Room != null); + ChangeRoomState(MultiplayerRoomState.WaitingForLoad); foreach (var user in Room.Users.Where(u => u.State == MultiplayerUserState.Ready)) ChangeUserState(user.UserID, MultiplayerUserState.WaitingForLoad); From 1f4c17b8f856a1d010fd2e45345d199c51dfba14 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 7 Apr 2021 21:20:44 +0900 Subject: [PATCH 1289/1791] Apply changes to AllowScreenSuspension bindable --- .../Screens/Play/ScreenSuspensionHandler.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Play/ScreenSuspensionHandler.cs b/osu.Game/Screens/Play/ScreenSuspensionHandler.cs index 8585a5c309..30ca15c311 100644 --- a/osu.Game/Screens/Play/ScreenSuspensionHandler.cs +++ b/osu.Game/Screens/Play/ScreenSuspensionHandler.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Diagnostics; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -19,6 +18,8 @@ namespace osu.Game.Screens.Play private readonly GameplayClockContainer gameplayClockContainer; private Bindable isPaused; + private readonly Bindable disableSuspensionBindable = new Bindable(); + [Resolved] private GameHost host { get; set; } @@ -31,12 +32,14 @@ namespace osu.Game.Screens.Play { base.LoadComplete(); - // This is the only usage game-wide of suspension changes. - // Assert to ensure we don't accidentally forget this in the future. - Debug.Assert(host.AllowScreenSuspension.Value); - isPaused = gameplayClockContainer.IsPaused.GetBoundCopy(); - isPaused.BindValueChanged(paused => host.AllowScreenSuspension.Value = paused.NewValue, true); + isPaused.BindValueChanged(paused => + { + if (paused.NewValue) + host.AllowScreenSuspension.RemoveSource(disableSuspensionBindable); + else + host.AllowScreenSuspension.AddSource(disableSuspensionBindable); + }, true); } protected override void Dispose(bool isDisposing) @@ -44,9 +47,7 @@ namespace osu.Game.Screens.Play base.Dispose(isDisposing); isPaused?.UnbindAll(); - - if (host != null) - host.AllowScreenSuspension.Value = true; + host?.AllowScreenSuspension.RemoveSource(disableSuspensionBindable); } } } From b24ce66a0d4bfcc9f53d84fe998b6d4027ef8a5a Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Wed, 7 Apr 2021 14:35:33 +0200 Subject: [PATCH 1290/1791] Add check/issue classes --- osu.Game/Rulesets/Edit/Checker.cs | 25 ++++++ .../Edit/Verify/Components/BeatmapCheck.cs | 19 +++++ .../Screens/Edit/Verify/Components/Check.cs | 41 +++++++++ .../Edit/Verify/Components/CheckMetadata.cs | 62 ++++++++++++++ .../Screens/Edit/Verify/Components/Issue.cs | 83 ++++++++++++++++++ .../Edit/Verify/Components/IssueTemplate.cs | 84 +++++++++++++++++++ osu.Game/Screens/Edit/Verify/Issue.cs | 10 --- 7 files changed, 314 insertions(+), 10 deletions(-) create mode 100644 osu.Game/Rulesets/Edit/Checker.cs create mode 100644 osu.Game/Screens/Edit/Verify/Components/BeatmapCheck.cs create mode 100644 osu.Game/Screens/Edit/Verify/Components/Check.cs create mode 100644 osu.Game/Screens/Edit/Verify/Components/CheckMetadata.cs create mode 100644 osu.Game/Screens/Edit/Verify/Components/Issue.cs create mode 100644 osu.Game/Screens/Edit/Verify/Components/IssueTemplate.cs delete mode 100644 osu.Game/Screens/Edit/Verify/Issue.cs diff --git a/osu.Game/Rulesets/Edit/Checker.cs b/osu.Game/Rulesets/Edit/Checker.cs new file mode 100644 index 0000000000..1c267c3435 --- /dev/null +++ b/osu.Game/Rulesets/Edit/Checker.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.Collections.Generic; +using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Checks; +using osu.Game.Screens.Edit.Verify.Components; + +namespace osu.Game.Rulesets.Edit +{ + public abstract class Checker + { + // These are all mode-invariant, hence here instead of in e.g. `OsuChecker`. + private readonly List beatmapChecks = new List + { + new CheckMetadataVowels() + }; + + public virtual IEnumerable Run(IBeatmap beatmap) + { + return beatmapChecks.SelectMany(check => check.Run(beatmap)); + } + } +} diff --git a/osu.Game/Screens/Edit/Verify/Components/BeatmapCheck.cs b/osu.Game/Screens/Edit/Verify/Components/BeatmapCheck.cs new file mode 100644 index 0000000000..7297dab60d --- /dev/null +++ b/osu.Game/Screens/Edit/Verify/Components/BeatmapCheck.cs @@ -0,0 +1,19 @@ +// 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; + +namespace osu.Game.Screens.Edit.Verify.Components +{ + public abstract class BeatmapCheck : Check + { + /// + /// Returns zero, one, or several issues detected by this + /// check on the given beatmap. + /// + /// The beatmap to run the check on. + /// + public abstract override IEnumerable Run(IBeatmap beatmap); + } +} diff --git a/osu.Game/Screens/Edit/Verify/Components/Check.cs b/osu.Game/Screens/Edit/Verify/Components/Check.cs new file mode 100644 index 0000000000..2ae21fd350 --- /dev/null +++ b/osu.Game/Screens/Edit/Verify/Components/Check.cs @@ -0,0 +1,41 @@ +// 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; + +namespace osu.Game.Screens.Edit.Verify.Components +{ + public abstract class Check + { + /// + /// Returns the for this check. + /// Basically, its information. + /// + /// + public abstract CheckMetadata Metadata(); + + /// + /// The templates for issues that this check may use. + /// Basically, what issues this check can detect. + /// + /// + public abstract IEnumerable Templates(); + + protected Check() + { + foreach (var template in Templates()) + template.Origin = this; + } + } + + public abstract class Check : Check + { + /// + /// Returns zero, one, or several issues detected by + /// this check on the given object. + /// + /// The object to run the check on. + /// + public abstract IEnumerable Run(T obj); + } +} diff --git a/osu.Game/Screens/Edit/Verify/Components/CheckMetadata.cs b/osu.Game/Screens/Edit/Verify/Components/CheckMetadata.cs new file mode 100644 index 0000000000..1cac99d47d --- /dev/null +++ b/osu.Game/Screens/Edit/Verify/Components/CheckMetadata.cs @@ -0,0 +1,62 @@ +// 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.Edit.Verify +{ + public class CheckMetadata + { + /// + /// The category of an issue. + /// + public enum CheckCategory + { + /// Anything to do with control points. + Timing, + + /// Anything to do with artist, title, creator, etc. + Metadata, + + /// Anything to do with non-audio files, e.g. background, skin, sprites, and video. + Resources, + + /// Anything to do with audio files, e.g. song and hitsounds. + Audio, + + /// Anything to do with files that don't fit into the above, e.g. unused, osu, or osb. + Files, + + /// Anything to do with hitobjects unrelated to spread. + Compose, + + /// Anything to do with difficulty levels or their progression. + Spread, + + /// Anything to do with variables like CS, OD, AR, HP, and global SV. + Settings, + + /// Anything to do with hitobject feedback. + Hitsounds, + + /// Anything to do with storyboarding, breaks, video offset, etc. + Events + } + + /// + /// The category this check belongs to. E.g. , + /// , or . + /// + public readonly CheckCategory Category; + + /// + /// Describes the issue(s) that this check looks for. Keep this brief, such that + /// it fits into "No {description}". E.g. "Offscreen objects" / "Too short sliders". + /// + public readonly string Description; + + public CheckMetadata(CheckCategory category, string description) + { + Category = category; + Description = description; + } + } +} diff --git a/osu.Game/Screens/Edit/Verify/Components/Issue.cs b/osu.Game/Screens/Edit/Verify/Components/Issue.cs new file mode 100644 index 0000000000..fe81cb9335 --- /dev/null +++ b/osu.Game/Screens/Edit/Verify/Components/Issue.cs @@ -0,0 +1,83 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Game.Extensions; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Screens.Edit.Verify.Components +{ + public class Issue + { + /// + /// The time which this issue is associated with, if any, otherwise null. + /// + public double? Time; + + /// + /// The hitobjects which this issue is associated with. Empty by default. + /// + public IReadOnlyList HitObjects; + + /// + /// The template which this issue is using. This provides properties + /// such as the , and the + /// . + /// + public IssueTemplate Template; + + /// + /// The arguments that give this issue its context, based on the + /// . These are then substituted into the + /// . + /// E.g. timestamps, which diff is being compared to, what some volume is, etc. + /// + public object[] Arguments; + + public Issue(IssueTemplate template, params object[] args) + { + Time = null; + HitObjects = System.Array.Empty(); + Template = template; + Arguments = args; + + if (template.Origin == null) + { + throw new ArgumentException( + "A template had no origin. Make sure the `Templates()` method contains all templates used." + ); + } + } + + public Issue(double? time, IssueTemplate template, params object[] args) + : this(template, args) + { + Time = time; + } + + public Issue(IEnumerable hitObjects, IssueTemplate template, params object[] args) + : this(template, args) + { + Time = hitObjects.FirstOrDefault()?.StartTime; + HitObjects = hitObjects.ToArray(); + } + + public override string ToString() + { + return Template.Message(Arguments); + } + + public string GetEditorTimestamp() + { + // TODO: Editor timestamp formatting is handled in https://github.com/ppy/osu/pull/12030 + // We may be able to use that here too (if we decouple it from the HitObjectComposer class). + + if (Time == null) + return string.Empty; + + return Time.Value.ToEditorFormattedString(); + } + } +} diff --git a/osu.Game/Screens/Edit/Verify/Components/IssueTemplate.cs b/osu.Game/Screens/Edit/Verify/Components/IssueTemplate.cs new file mode 100644 index 0000000000..b178fa7122 --- /dev/null +++ b/osu.Game/Screens/Edit/Verify/Components/IssueTemplate.cs @@ -0,0 +1,84 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Humanizer; +using osu.Framework.Graphics; +using osuTK.Graphics; + +namespace osu.Game.Screens.Edit.Verify.Components +{ + public class IssueTemplate + { + /// + /// The type, or severity, of an issue. This decides its priority. + /// + public enum IssueType + { + /// A must-fix in the vast majority of cases. + Problem = 3, + + /// A possible mistake. Often requires critical thinking. + Warning = 2, + + // TODO: Try/catch all checks run and return error templates if exceptions occur. + /// An error occurred and a complete check could not be made. + Error = 1, + + // TODO: Negligible issues should be hidden by default. + /// A possible mistake so minor/unlikely that it can often be safely ignored. + Negligible = 0, + } + + /// + /// The check that this template originates from. + /// + public Check Origin; + + /// + /// The type of the issue. E.g. , + /// , or . + /// + public readonly IssueType Type; + + /// + /// The unformatted message given when this issue is detected. + /// This gets populated later when an issue is constructed with this template. + /// E.g. "Inconsistent snapping (1/{0}) with [{1}] (1/{2})." + /// + public readonly string UnformattedMessage; + + public IssueTemplate(IssueType type, string unformattedMessage) + { + Type = type; + UnformattedMessage = unformattedMessage; + } + + /// + /// Returns the formatted message given the arguments used to format it. + /// + /// The arguments used to format the message. + /// + public string Message(params object[] args) => UnformattedMessage.FormatWith(args); + + public static readonly Color4 PROBLEM_RED = new Colour4(1.0f, 0.4f, 0.4f, 1.0f); + public static readonly Color4 WARNING_YELLOW = new Colour4(1.0f, 0.8f, 0.2f, 1.0f); + public static readonly Color4 NEGLIGIBLE_GREEN = new Colour4(0.33f, 0.8f, 0.5f, 1.0f); + public static readonly Color4 ERROR_GRAY = new Colour4(0.5f, 0.5f, 0.5f, 1.0f); + + /// + /// Returns the colour corresponding to the type of this issue. + /// + /// + public Colour4 TypeColour() + { + return Type switch + { + IssueType.Problem => PROBLEM_RED, + IssueType.Warning => WARNING_YELLOW, + IssueType.Negligible => NEGLIGIBLE_GREEN, + IssueType.Error => ERROR_GRAY, + _ => Color4.White + }; + } + } +} diff --git a/osu.Game/Screens/Edit/Verify/Issue.cs b/osu.Game/Screens/Edit/Verify/Issue.cs deleted file mode 100644 index 25e913d819..0000000000 --- a/osu.Game/Screens/Edit/Verify/Issue.cs +++ /dev/null @@ -1,10 +0,0 @@ -// 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.Edit.Verify -{ - public class Issue - { - public readonly double Time; - } -} From 0343ef7f147b12a8bd71dfced08c6b503b6b8b79 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Wed, 7 Apr 2021 14:36:43 +0200 Subject: [PATCH 1291/1791] Add ruleset-specific checker --- osu.Game.Rulesets.Osu/Edit/OsuChecker.cs | 30 ++++++++++++++++++++++++ osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 ++ osu.Game/Rulesets/Ruleset.cs | 2 ++ 3 files changed, 34 insertions(+) create mode 100644 osu.Game.Rulesets.Osu/Edit/OsuChecker.cs diff --git a/osu.Game.Rulesets.Osu/Edit/OsuChecker.cs b/osu.Game.Rulesets.Osu/Edit/OsuChecker.cs new file mode 100644 index 0000000000..9918b53c85 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/OsuChecker.cs @@ -0,0 +1,30 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Osu.Edit.Checks; +using osu.Game.Screens.Edit.Verify.Components; + +namespace osu.Game.Rulesets.Osu.Edit +{ + public class OsuChecker : Checker + { + public readonly List beatmapChecks = new List + { + new CheckConsecutiveCircles() + }; + + public override IEnumerable Run(IBeatmap beatmap) + { + // Also run mode-invariant checks. + foreach (var issue in base.Run(beatmap)) + yield return issue; + + foreach (var issue in beatmapChecks.SelectMany(check => check.Run(beatmap))) + yield return issue; + } + } +} diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 838d707d64..74679bd578 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -206,6 +206,8 @@ namespace osu.Game.Rulesets.Osu public override HitObjectComposer CreateHitObjectComposer() => new OsuHitObjectComposer(this); + public override Checker CreateChecker() => new OsuChecker(); + public override string Description => "osu!"; public override string ShortName => SHORT_NAME; diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 38d30a2e31..71f80c9982 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -202,6 +202,8 @@ namespace osu.Game.Rulesets public virtual HitObjectComposer CreateHitObjectComposer() => null; + public virtual Checker CreateChecker() => null; + public virtual Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.Solid.QuestionCircle }; public virtual IResourceStore CreateResourceStore() => new NamespacedResourceStore(new DllResourceStore(GetType().Assembly), @"Resources"); From 9c4604e3c5fdbfa9f5201f39ee41584fa5b47d18 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Wed, 7 Apr 2021 14:36:53 +0200 Subject: [PATCH 1292/1791] Add example checks --- .../Edit/Checks/CheckConsecutiveCircles.cs | 86 +++++++++++++++++++ osu.Game/Checks/CheckMetadataVowels.cs | 65 ++++++++++++++ 2 files changed, 151 insertions(+) create mode 100644 osu.Game.Rulesets.Osu/Edit/Checks/CheckConsecutiveCircles.cs create mode 100644 osu.Game/Checks/CheckMetadataVowels.cs diff --git a/osu.Game.Rulesets.Osu/Edit/Checks/CheckConsecutiveCircles.cs b/osu.Game.Rulesets.Osu/Edit/Checks/CheckConsecutiveCircles.cs new file mode 100644 index 0000000000..c41c0dac2b --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Checks/CheckConsecutiveCircles.cs @@ -0,0 +1,86 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Screens.Edit.Verify; +using osu.Game.Screens.Edit.Verify.Components; + +namespace osu.Game.Rulesets.Osu.Edit.Checks +{ + public class CheckConsecutiveCircles : BeatmapCheck + { + private const double consecutive_threshold = 3; + private const double delta_time_min_expected = 300; + private const double delta_time_min_threshold = 100; + + public override CheckMetadata Metadata() => new CheckMetadata + ( + category: CheckMetadata.CheckCategory.Spread, + description: "Too many or fast consecutive circles." + ); + + private IssueTemplate templateManyInARow = new IssueTemplate + ( + type: IssueTemplate.IssueType.Problem, + unformattedMessage: "There are {0} circles in a row here, expected at most {1}." + ); + + private IssueTemplate templateTooFast = new IssueTemplate + ( + type: IssueTemplate.IssueType.Warning, + unformattedMessage: "These circles are too fast ({0:0} ms), expected at least {1:0} ms." + ); + + private IssueTemplate templateAlmostTooFast = new IssueTemplate + ( + type: IssueTemplate.IssueType.Negligible, + unformattedMessage: "These circles are almost too fast ({0:0} ms), expected at least {1:0} ms." + ); + + public override IEnumerable Templates() => new[] + { + templateManyInARow, + templateTooFast, + templateAlmostTooFast + }; + + public override IEnumerable Run(IBeatmap beatmap) + { + List prevCircles = new List(); + + foreach (HitObject hitobject in beatmap.HitObjects) + { + if (!(hitobject is HitCircle circle) || hitobject == beatmap.HitObjects.Last()) + { + if (prevCircles.Count > consecutive_threshold) + { + yield return new Issue( + prevCircles, + templateManyInARow, + prevCircles.Count, consecutive_threshold + ); + } + + prevCircles.Clear(); + continue; + } + + double? prevDeltaTime = circle.StartTime - prevCircles.LastOrDefault()?.StartTime; + prevCircles.Add(circle); + + if (prevDeltaTime == null || prevDeltaTime >= delta_time_min_expected) + continue; + + yield return new Issue( + prevCircles.TakeLast(2), + prevDeltaTime < delta_time_min_threshold ? templateTooFast : templateAlmostTooFast, + prevDeltaTime, delta_time_min_expected + ); + } + } + } +} diff --git a/osu.Game/Checks/CheckMetadataVowels.cs b/osu.Game/Checks/CheckMetadataVowels.cs new file mode 100644 index 0000000000..8bcfe89c3a --- /dev/null +++ b/osu.Game/Checks/CheckMetadataVowels.cs @@ -0,0 +1,65 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Screens.Edit.Verify; +using osu.Game.Screens.Edit.Verify.Components; + +namespace osu.Game.Checks +{ + public class CheckMetadataVowels : BeatmapCheck + { + private static readonly char[] vowels = { 'a', 'e', 'i', 'o', 'u' }; + + public override CheckMetadata Metadata() => new CheckMetadata + ( + category: CheckMetadata.CheckCategory.Metadata, + description: "Metadata fields contain vowels" + ); + + public override IEnumerable Templates() => new[] + { + templateArtistHasVowels + }; + + private IssueTemplate templateArtistHasVowels = new IssueTemplate + ( + type: IssueTemplate.IssueType.Warning, + unformattedMessage: "The {0} field \"{1}\" contains the vowel(s) {2}." + ); + + public override IEnumerable Run(IBeatmap beatmap) + { + foreach (var issue in GetVowelIssues("artist", beatmap.Metadata.Artist)) + yield return issue; + + foreach (var issue in GetVowelIssues("unicode artist", beatmap.Metadata.ArtistUnicode)) + yield return issue; + + foreach (var issue in GetVowelIssues("title", beatmap.Metadata.Title)) + yield return issue; + + foreach (var issue in GetVowelIssues("unicode title", beatmap.Metadata.TitleUnicode)) + yield return issue; + } + + private IEnumerable GetVowelIssues(string fieldName, string fieldValue) + { + if (fieldValue == null) + // Unicode fields can be null if same as respective romanized fields. + yield break; + + List matches = vowels.Where(c => fieldValue.ToLower().Contains(c)).ToList(); + + if (!matches.Any()) + yield break; + + yield return new Issue( + templateArtistHasVowels, + fieldName, fieldValue, string.Join(", ", matches) + ); + } + } +} From bab36e529a5b9e1ff5b2300e9def2b5bc11687a7 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Wed, 7 Apr 2021 14:38:43 +0200 Subject: [PATCH 1293/1791] Update UI with new components --- osu.Game/Screens/Edit/Verify/IssueSettings.cs | 47 +++++++++ osu.Game/Screens/Edit/Verify/IssueTable.cs | 91 +++++++++-------- osu.Game/Screens/Edit/Verify/VerifyScreen.cs | 98 +++++++++++++++---- .../Screens/Edit/Verify/VisibilitySettings.cs | 52 ++++++++++ 4 files changed, 229 insertions(+), 59 deletions(-) create mode 100644 osu.Game/Screens/Edit/Verify/IssueSettings.cs create mode 100644 osu.Game/Screens/Edit/Verify/VisibilitySettings.cs diff --git a/osu.Game/Screens/Edit/Verify/IssueSettings.cs b/osu.Game/Screens/Edit/Verify/IssueSettings.cs new file mode 100644 index 0000000000..608be877de --- /dev/null +++ b/osu.Game/Screens/Edit/Verify/IssueSettings.cs @@ -0,0 +1,47 @@ +// 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.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; + +namespace osu.Game.Screens.Edit.Verify +{ + public class IssueSettings : CompositeDrawable + { + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + RelativeSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + new Box + { + Colour = colours.Gray3, + RelativeSizeAxes = Axes.Both, + }, + new OsuScrollContainer + { + RelativeSizeAxes = Axes.Both, + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = createSections() + }, + } + }; + } + + private IReadOnlyList createSections() => new Drawable[] + { + new VisibilitySettings() + }; + } +} diff --git a/osu.Game/Screens/Edit/Verify/IssueTable.cs b/osu.Game/Screens/Edit/Verify/IssueTable.cs index 6476cebe48..c70695a849 100644 --- a/osu.Game/Screens/Edit/Verify/IssueTable.cs +++ b/osu.Game/Screens/Edit/Verify/IssueTable.cs @@ -4,16 +4,16 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; -using osu.Game.Extensions; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Input.Bindings; +using osu.Game.Screens.Edit.Verify.Components; using osuTK.Graphics; namespace osu.Game.Screens.Edit.Verify @@ -34,6 +34,9 @@ namespace osu.Game.Screens.Edit.Verify Padding = new MarginPadding { Horizontal = horizontal_inset }; RowSize = new Dimension(GridSizeMode.Absolute, row_height); + Masking = true; + CornerRadius = 6; + AddInternal(backgroundFlow = new FillFlowContainer { RelativeSizeAxes = Axes.Both, @@ -55,7 +58,7 @@ namespace osu.Game.Screens.Edit.Verify foreach (var issue in value) { - backgroundFlow.Add(new IssueTable.RowBackground(issue)); + backgroundFlow.Add(new RowBackground(issue)); } Columns = createHeaders(); @@ -68,9 +71,10 @@ namespace osu.Game.Screens.Edit.Verify var columns = new List { new TableColumn(string.Empty, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), + new TableColumn("Type", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), new TableColumn("Time", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), - new TableColumn(), - new TableColumn("Attributes", Anchor.CentreLeft), + new TableColumn("Message", Anchor.CentreLeft), + new TableColumn("Category", Anchor.CentreRight, new Dimension(GridSizeMode.AutoSize)), }; return columns.ToArray(); @@ -81,21 +85,36 @@ namespace osu.Game.Screens.Edit.Verify new OsuSpriteText { Text = $"#{index + 1}", + Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Medium) + }, + new OsuSpriteText + { + Text = issue.Template.Type.ToString(), + Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold), + Margin = new MarginPadding { Left = 10 }, + Colour = issue.Template.TypeColour() + }, + new OsuSpriteText + { + Text = issue.GetEditorTimestamp(), Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold), Margin = new MarginPadding(10) }, new OsuSpriteText { - Text = issue.Time.ToEditorFormattedString(), - Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) + Text = issue.ToString(), + Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Medium) }, - null, - null //new ControlGroupAttributes(issue), + new OsuSpriteText + { + Text = issue.Template.Origin.Metadata().Category.ToString(), + Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold), + Margin = new MarginPadding(10) + } }; public class RowBackground : OsuClickableContainer { - private readonly Issue issue; private const int fade_duration = 100; private readonly Box hoveredBackground; @@ -104,13 +123,15 @@ namespace osu.Game.Screens.Edit.Verify private EditorClock clock { get; set; } [Resolved] - private Bindable selectedIssue { get; set; } + private Editor editor { get; set; } + + [Resolved] + private EditorBeatmap editorBeatmap { get; set; } public RowBackground(Issue issue) { - this.issue = issue; RelativeSizeAxes = Axes.X; - Height = 25; + Height = row_height; AlwaysPresent = true; @@ -128,41 +149,29 @@ namespace osu.Game.Screens.Edit.Verify Action = () => { - selectedIssue.Value = issue; - clock.SeekSmoothlyTo(issue.Time); + // Supposed to work like clicking timestamps outside of the game. + // TODO: Is there already defined behaviour for this I may be able to call? + + if (issue.Time != null) + { + clock.Seek(issue.Time.Value); + editor.OnPressed(GlobalAction.EditorComposeMode); + } + + if (!issue.HitObjects.Any()) + return; + + editorBeatmap.SelectedHitObjects.Clear(); + editorBeatmap.SelectedHitObjects.AddRange(issue.HitObjects); }; } private Color4 colourHover; - private Color4 colourSelected; [BackgroundDependencyLoader] private void load(OsuColour colours) { hoveredBackground.Colour = colourHover = colours.BlueDarker; - colourSelected = colours.YellowDarker; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - selectedIssue.BindValueChanged(group => { Selected = issue == group.NewValue; }, true); - } - - private bool selected; - - protected bool Selected - { - get => selected; - set - { - if (value == selected) - return; - - selected = value; - updateState(); - } } protected override bool OnHover(HoverEvent e) @@ -179,9 +188,9 @@ namespace osu.Game.Screens.Edit.Verify private void updateState() { - hoveredBackground.FadeColour(selected ? colourSelected : colourHover, 450, Easing.OutQuint); + hoveredBackground.FadeColour(colourHover, 450, Easing.OutQuint); - if (selected || IsHovered) + if (IsHovered) hoveredBackground.FadeIn(fade_duration, Easing.OutQuint); else hoveredBackground.FadeOut(fade_duration, Easing.OutQuint); diff --git a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs index c15cefae83..88397fdbff 100644 --- a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs +++ b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs @@ -1,40 +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 osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Edit; +using osuTK; namespace osu.Game.Screens.Edit.Verify { - public class VerifyScreen : EditorScreenWithTimeline + public class VerifyScreen : EditorScreen { + private Ruleset ruleset; + private static Checker checker; // TODO: Should not be static, but apparently needs to be? + public VerifyScreen() : base(EditorScreenMode.Verify) { } - protected override Drawable CreateMainContent() => new GridContainer + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { - RelativeSizeAxes = Axes.Both, - ColumnDimensions = new[] - { - new Dimension(), - new Dimension(GridSizeMode.Absolute, 200), - }, - Content = new[] - { - new Drawable[] - { - new ControlPointList() - }, - } - }; + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - public class ControlPointList : CompositeDrawable + ruleset = parent.Get>().Value.BeatmapInfo.Ruleset?.CreateInstance(); + checker = ruleset?.CreateChecker(); + + return dependencies; + } + + [BackgroundDependencyLoader] + private void load() + { + Child = new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(20), + Child = new GridContainer + { + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.Absolute, 200), + }, + Content = new[] + { + new Drawable[] + { + new IssueList(), + new IssueSettings(), + }, + } + } + }; + } + + public class IssueList : CompositeDrawable { private IssueTable table; @@ -60,9 +90,41 @@ namespace osu.Game.Screens.Edit.Verify { RelativeSizeAxes = Axes.Both, Child = table = new IssueTable(), - } + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Margin = new MarginPadding(20), + Children = new Drawable[] + { + new TriangleButton + { + Text = "Refresh", + Action = refresh, + Size = new Vector2(120, 40), + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + }, + } + }, }; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + refresh(); + } + + private void refresh() + { + table.Issues = checker.Run(Beatmap) + .OrderByDescending(issue => issue.Template.Type) + .ThenByDescending(issue => issue.Template.Origin.Metadata().Category); + } } } } diff --git a/osu.Game/Screens/Edit/Verify/VisibilitySettings.cs b/osu.Game/Screens/Edit/Verify/VisibilitySettings.cs new file mode 100644 index 0000000000..6488c616e4 --- /dev/null +++ b/osu.Game/Screens/Edit/Verify/VisibilitySettings.cs @@ -0,0 +1,52 @@ +// 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.UserInterfaceV2; +using osuTK; + +namespace osu.Game.Screens.Edit.Verify +{ + internal class VisibilitySettings : CompositeDrawable + { + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + Padding = new MarginPadding(10); + + InternalChildren = new Drawable[] + { + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(10), + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new LabelledSwitchButton + { + Label = "Show problems", + Current = new Bindable(true) + }, + new LabelledSwitchButton + { + Label = "Show warnings", + Current = new Bindable(true) + }, + new LabelledSwitchButton + { + Label = "Show negligibles" + } + } + }, + }; + } + } +} From 2791d454d22a8d61e254531635d7a6d7c8bb746e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 7 Apr 2021 22:21:22 +0900 Subject: [PATCH 1294/1791] Don't send spectating user state yet --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 4529dfd0a7..37e11cc576 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -96,6 +96,9 @@ namespace osu.Game.Online.Multiplayer if (!IsConnected.Value) return Task.CompletedTask; + if (newState == MultiplayerUserState.Spectating) + return Task.CompletedTask; // Not supported yet. + return connection.InvokeAsync(nameof(IMultiplayerServer.ChangeState), newState); } From 214813154b775f1fa8c3a075e55690ce8ef2ee0a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 7 Apr 2021 22:28:22 +0900 Subject: [PATCH 1295/1791] Fix class name --- .../Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs | 2 +- .../Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs index 08c94b8135..6b03b53b4b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs @@ -11,7 +11,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public class TestSceneMultiplayerMatchFooter : MultiplayerTestScene { [Cached] - private readonly OnlinePlayBeatmapAvailablilityTracker availablilityTracker = new OnlinePlayBeatmapAvailablilityTracker(); + private readonly OnlinePlayBeatmapAvailabilityTracker availablilityTracker = new OnlinePlayBeatmapAvailabilityTracker(); [BackgroundDependencyLoader] private void load() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs index 8fe1f99e1d..e65e4a68a7 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -61,7 +61,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)); - var beatmapTracker = new OnlinePlayBeatmapAvailablilityTracker { SelectedItem = { BindTarget = selectedItem } }; + var beatmapTracker = new OnlinePlayBeatmapAvailabilityTracker { SelectedItem = { BindTarget = selectedItem } }; base.Content.Add(beatmapTracker); Dependencies.Cache(beatmapTracker); From 8cc1e8b8b0605e981b1d0be53cb9a0590383e532 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Apr 2021 23:11:01 +0900 Subject: [PATCH 1296/1791] Update framework --- .idea/.idea.osu.Desktop/.idea/indexLayout.xml | 2 +- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.idea/.idea.osu.Desktop/.idea/indexLayout.xml b/.idea/.idea.osu.Desktop/.idea/indexLayout.xml index 27ba142e96..7b08163ceb 100644 --- a/.idea/.idea.osu.Desktop/.idea/indexLayout.xml +++ b/.idea/.idea.osu.Desktop/.idea/indexLayout.xml @@ -1,6 +1,6 @@ - + diff --git a/osu.Android.props b/osu.Android.props index 73ee1d9d10..cba3975209 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 931b55222a..292e5b932f 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 64e9a01a92..36e581a80c 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 544fff5af6f9fa20e1aa04e5aaa0a5ea3865b220 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Apr 2021 23:18:45 +0900 Subject: [PATCH 1297/1791] Undo rider EAP changes for the time being --- .idea/.idea.osu.Desktop/.idea/indexLayout.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.idea/.idea.osu.Desktop/.idea/indexLayout.xml b/.idea/.idea.osu.Desktop/.idea/indexLayout.xml index 7b08163ceb..27ba142e96 100644 --- a/.idea/.idea.osu.Desktop/.idea/indexLayout.xml +++ b/.idea/.idea.osu.Desktop/.idea/indexLayout.xml @@ -1,6 +1,6 @@ - + From 648a9d52584896db0cd3ce3cc7b5bb350bb46d32 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 7 Apr 2021 23:15:09 +0900 Subject: [PATCH 1298/1791] Add multiplayer spectator player grid --- .../Multiplayer/Spectate/PlayerGrid.cs | 142 ++++++++++++++++++ .../Multiplayer/Spectate/PlayerGrid_Cell.cs | 78 ++++++++++ .../Multiplayer/Spectate/PlayerGrid_Facade.cs | 19 +++ 3 files changed, 239 insertions(+) create mode 100644 osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid.cs create mode 100644 osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid_Cell.cs create mode 100644 osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid_Facade.cs diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid.cs new file mode 100644 index 0000000000..a8bd1db9dc --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid.cs @@ -0,0 +1,142 @@ +// 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 osuTK; + +namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate +{ + public partial class PlayerGrid : CompositeDrawable + { + private const float player_spacing = 5; + + public Drawable MaximisedFacade => maximisedFacade; + + private readonly PlayerGridFacade maximisedFacade; + private readonly Container paddingContainer; + private readonly FillFlowContainer facadeContainer; + private readonly Container cellContainer; + + public PlayerGrid() + { + InternalChildren = new Drawable[] + { + paddingContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(player_spacing), + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Child = facadeContainer = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(player_spacing), + } + }, + maximisedFacade = new PlayerGridFacade { RelativeSizeAxes = Axes.Both } + } + }, + cellContainer = new Container { RelativeSizeAxes = Axes.Both } + }; + } + + public void AddContent(Drawable content) + { + var facade = new PlayerGridFacade(); + facadeContainer.Add(facade); + + var cell = new Cell(content) { ToggleMaximisationState = toggleMaximisationState }; + cell.SetFacade(facade); + + cellContainer.Add(cell); + } + + // A depth value that gets decremented every time a new instance is maximised in order to reduce underlaps. + private float maximisedInstanceDepth; + + private void toggleMaximisationState(Cell target) + { + // Iterate through all cells to ensure only one is maximised at any time. + foreach (var i in cellContainer) + { + if (i == target) + i.IsMaximised = !i.IsMaximised; + else + i.IsMaximised = false; + + if (i.IsMaximised) + { + // Transfer cell to the maximised facade. + i.SetFacade(maximisedFacade); + cellContainer.ChangeChildDepth(i, maximisedInstanceDepth -= 0.001f); + } + else + { + // Transfer cell back to its original facade. + i.SetFacade(facadeContainer[cellContainer.IndexOf(target)]); + } + } + } + + protected override void Update() + { + base.Update(); + + Vector2 cellsPerDimension; + + switch (facadeContainer.Count) + { + case 1: + cellsPerDimension = Vector2.One; + break; + + case 2: + cellsPerDimension = new Vector2(2, 1); + break; + + case 3: + case 4: + cellsPerDimension = new Vector2(2); + break; + + case 5: + case 6: + cellsPerDimension = new Vector2(3, 2); + break; + + case 7: + case 8: + case 9: + // 3 rows / 3 cols. + cellsPerDimension = new Vector2(3); + break; + + case 10: + case 11: + case 12: + // 3 rows / 4 cols. + cellsPerDimension = new Vector2(4, 3); + break; + + default: + // 4 rows / 4 cols. + cellsPerDimension = new Vector2(4); + break; + } + + // Total spacing between cells + Vector2 totalCellSpacing = player_spacing * (cellsPerDimension - Vector2.One); + + Vector2 fullSize = paddingContainer.ChildSize - totalCellSpacing; + Vector2 cellSize = Vector2.Divide(fullSize, new Vector2(cellsPerDimension.X, cellsPerDimension.Y)); + + foreach (var cell in facadeContainer) + cell.Size = cellSize; + } + } +} diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid_Cell.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid_Cell.cs new file mode 100644 index 0000000000..1f6e718aa7 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid_Cell.cs @@ -0,0 +1,78 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using JetBrains.Annotations; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Events; +using osuTK; + +namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate +{ + public partial class PlayerGrid + { + private class Cell : CompositeDrawable + { + public Action ToggleMaximisationState; + public bool IsMaximised; + + private PlayerGridFacade facade; + private bool isTracking = true; + + public Cell(Drawable content) + { + Origin = Anchor.Centre; + + InternalChild = content; + } + + protected override void Update() + { + base.Update(); + + if (isTracking) + { + Position = getFinalPosition(); + Size = getFinalSize(); + } + } + + public void SetFacade([NotNull] PlayerGridFacade newFacade) + { + PlayerGridFacade lastFacade = facade; + facade = newFacade; + + if (lastFacade == null || lastFacade == newFacade) + return; + + isTracking = false; + + this.MoveTo(getFinalPosition(), 400, Easing.OutQuint).ResizeTo(getFinalSize(), 400, Easing.OutQuint) + .Then() + .OnComplete(_ => + { + if (facade == newFacade) + isTracking = true; + }); + } + + private Vector2 getFinalPosition() + { + var topLeft = Parent.ToLocalSpace(facade.ToScreenSpace(Vector2.Zero)); + return topLeft + facade.DrawSize / 2; + } + + private Vector2 getFinalSize() => facade.DrawSize; + + // Todo: Temporary? + protected override bool ShouldBeConsideredForInput(Drawable child) => false; + + protected override bool OnClick(ClickEvent e) + { + ToggleMaximisationState(this); + return true; + } + } + } +} diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid_Facade.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid_Facade.cs new file mode 100644 index 0000000000..c565e2fec6 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid_Facade.cs @@ -0,0 +1,19 @@ +// 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; + +namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate +{ + public partial class PlayerGrid + { + private class PlayerGridFacade : Drawable + { + public PlayerGridFacade() + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + } + } + } +} From 024adb699c44b5748eebc77a33c2010eb841f562 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 8 Apr 2021 00:06:14 +0900 Subject: [PATCH 1299/1791] Add test and fix several issues --- ...TestSceneMultiplayerSpectatorPlayerGrid.cs | 115 ++++++++++++++++++ .../Multiplayer/Spectate/PlayerGrid.cs | 30 +++-- .../Multiplayer/Spectate/PlayerGrid_Cell.cs | 26 +++- .../Multiplayer/Spectate/PlayerGrid_Facade.cs | 7 +- 4 files changed, 161 insertions(+), 17 deletions(-) create mode 100644 osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectatorPlayerGrid.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectatorPlayerGrid.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectatorPlayerGrid.cs new file mode 100644 index 0000000000..c0958c7fe8 --- /dev/null +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectatorPlayerGrid.cs @@ -0,0 +1,115 @@ +// 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.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Utils; +using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate; +using osuTK.Graphics; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Multiplayer +{ + public class TestSceneMultiplayerSpectatorPlayerGrid : OsuManualInputManagerTestScene + { + private PlayerGrid grid; + + [SetUp] + public void Setup() => Schedule(() => + { + Child = grid = new PlayerGrid { RelativeSizeAxes = Axes.Both }; + }); + + [Test] + public void TestMaximiseAndMinimise() + { + addCells(2); + + assertMaximisation(0, false, true); + assertMaximisation(1, false, true); + + clickCell(0); + assertMaximisation(0, true); + assertMaximisation(1, false, true); + clickCell(0); + assertMaximisation(0, false); + assertMaximisation(1, false, true); + + clickCell(1); + assertMaximisation(1, true); + assertMaximisation(0, false, true); + clickCell(1); + assertMaximisation(1, false); + assertMaximisation(0, false, true); + } + + [Test] + public void TestClickBothCellsSimultaneously() + { + addCells(2); + + AddStep("click cell 0 then 1", () => + { + InputManager.MoveMouseTo(grid.Content.ElementAt(0)); + InputManager.Click(MouseButton.Left); + + InputManager.MoveMouseTo(grid.Content.ElementAt(1)); + InputManager.Click(MouseButton.Left); + }); + + assertMaximisation(1, true); + assertMaximisation(0, false); + } + + [TestCase(1)] + [TestCase(2)] + [TestCase(3)] + [TestCase(4)] + [TestCase(5)] + [TestCase(9)] + [TestCase(11)] + [TestCase(12)] + [TestCase(15)] + [TestCase(16)] + public void TestCellCount(int count) + { + addCells(count); + AddWaitStep("wait for display", 2); + } + + private void addCells(int count) => AddStep($"add {count} grid cells", () => + { + for (int i = 0; i < count; i++) + grid.Add(new GridContent()); + }); + + private void clickCell(int index) => AddStep($"click cell index {index}", () => + { + InputManager.MoveMouseTo(grid.Content.ElementAt(index)); + InputManager.Click(MouseButton.Left); + }); + + private void assertMaximisation(int index, bool shouldBeMaximised, bool instant = false) + { + string assertionText = $"cell index {index} {(shouldBeMaximised ? "is" : "is not")} maximised"; + + if (instant) + AddAssert(assertionText, checkAction); + else + AddUntilStep(assertionText, checkAction); + + bool checkAction() => Precision.AlmostEquals(grid.MaximisedFacade.DrawSize, grid.Content.ElementAt(index).DrawSize, 10) == shouldBeMaximised; + } + + private class GridContent : Box + { + public GridContent() + { + RelativeSizeAxes = Axes.Both; + Colour = new Color4(RNG.NextSingle(), RNG.NextSingle(), RNG.NextSingle(), 1f); + } + } + } +} diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid.cs index a8bd1db9dc..f41948217c 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid.cs @@ -13,9 +13,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public Drawable MaximisedFacade => maximisedFacade; - private readonly PlayerGridFacade maximisedFacade; + private readonly Facade maximisedFacade; private readonly Container paddingContainer; - private readonly FillFlowContainer facadeContainer; + private readonly FillFlowContainer facadeContainer; private readonly Container cellContainer; public PlayerGrid() @@ -31,38 +31,50 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate new Container { RelativeSizeAxes = Axes.Both, - Child = facadeContainer = new FillFlowContainer + Child = facadeContainer = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Spacing = new Vector2(player_spacing), } }, - maximisedFacade = new PlayerGridFacade { RelativeSizeAxes = Axes.Both } + maximisedFacade = new Facade { RelativeSizeAxes = Axes.Both } } }, cellContainer = new Container { RelativeSizeAxes = Axes.Both } }; } - public void AddContent(Drawable content) + /// + /// Adds a new cell with content to this grid. + /// + /// The content the cell should contain. + /// If more than 16 cells are added. + public void Add(Drawable content) { - var facade = new PlayerGridFacade(); + int index = cellContainer.Count; + + var facade = new Facade(); facadeContainer.Add(facade); - var cell = new Cell(content) { ToggleMaximisationState = toggleMaximisationState }; + var cell = new Cell(index, content) { ToggleMaximisationState = toggleMaximisationState }; cell.SetFacade(facade); cellContainer.Add(cell); } + /// + /// The content added to this grid. + /// + public IEnumerable Content => cellContainer.OrderBy(c => c.FacadeIndex).Select(c => c.Content); + // A depth value that gets decremented every time a new instance is maximised in order to reduce underlaps. private float maximisedInstanceDepth; private void toggleMaximisationState(Cell target) { // Iterate through all cells to ensure only one is maximised at any time. - foreach (var i in cellContainer) + foreach (var i in cellContainer.ToList()) { if (i == target) i.IsMaximised = !i.IsMaximised; @@ -78,7 +90,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate else { // Transfer cell back to its original facade. - i.SetFacade(facadeContainer[cellContainer.IndexOf(target)]); + i.SetFacade(facadeContainer[i.FacadeIndex]); } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid_Cell.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid_Cell.cs index 1f6e718aa7..c2ac190d40 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid_Cell.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid_Cell.cs @@ -14,17 +14,28 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { private class Cell : CompositeDrawable { + /// + /// The index of the original facade of this cell. + /// + public readonly int FacadeIndex; + + /// + /// The contained content. + /// + public readonly Drawable Content; + public Action ToggleMaximisationState; public bool IsMaximised; - private PlayerGridFacade facade; + private Facade facade; private bool isTracking = true; - public Cell(Drawable content) + public Cell(int facadeIndex, Drawable content) { - Origin = Anchor.Centre; + FacadeIndex = facadeIndex; - InternalChild = content; + Origin = Anchor.Centre; + InternalChild = Content = content; } protected override void Update() @@ -38,9 +49,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate } } - public void SetFacade([NotNull] PlayerGridFacade newFacade) + /// + /// Makes this cell track a new facade. + /// + public void SetFacade([NotNull] Facade newFacade) { - PlayerGridFacade lastFacade = facade; + Facade lastFacade = facade; facade = newFacade; if (lastFacade == null || lastFacade == newFacade) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid_Facade.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid_Facade.cs index c565e2fec6..6b363c6040 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid_Facade.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid_Facade.cs @@ -7,9 +7,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { public partial class PlayerGrid { - private class PlayerGridFacade : Drawable + /// + /// A facade of the grid which is used as a dummy object to store the required position/size of cells. + /// + private class Facade : Drawable { - public PlayerGridFacade() + public Facade() { Anchor = Anchor.Centre; Origin = Anchor.Centre; From 5dc939c2f365d09395f0fe1880bb88a3d8dbda21 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 8 Apr 2021 00:06:32 +0900 Subject: [PATCH 1300/1791] More documentation --- .../OnlinePlay/Multiplayer/Spectate/PlayerGrid.cs | 15 ++++++++++++++- .../Multiplayer/Spectate/PlayerGrid_Cell.cs | 10 ++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid.cs index f41948217c..830378f129 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid.cs @@ -1,16 +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 System.Collections.Generic; +using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osuTK; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { + /// + /// A grid of players playing the multiplayer match. + /// public partial class PlayerGrid : CompositeDrawable { private const float player_spacing = 5; + /// + /// The currently-maximised facade. + /// public Drawable MaximisedFacade => maximisedFacade; private readonly Facade maximisedFacade; @@ -52,6 +61,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// If more than 16 cells are added. public void Add(Drawable content) { + if (cellContainer.Count == 16) + throw new InvalidOperationException("Only 16 cells are supported."); + int index = cellContainer.Count; var facade = new Facade(); @@ -99,6 +111,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { base.Update(); + // Different layouts are used for varying cell counts in order to maximise dimensions. Vector2 cellsPerDimension; switch (facadeContainer.Count) @@ -141,7 +154,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate break; } - // Total spacing between cells + // Total inter-cell spacing. Vector2 totalCellSpacing = player_spacing * (cellsPerDimension - Vector2.One); Vector2 fullSize = paddingContainer.ChildSize - totalCellSpacing; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid_Cell.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid_Cell.cs index c2ac190d40..37d88693ee 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid_Cell.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid_Cell.cs @@ -12,6 +12,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { public partial class PlayerGrid { + /// + /// A cell of the grid. Contains the content and tracks to the linked facade. + /// private class Cell : CompositeDrawable { /// @@ -24,7 +27,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// public readonly Drawable Content; + /// + /// An action that toggles the maximisation state of this cell. + /// public Action ToggleMaximisationState; + + /// + /// Whether this cell is currently maximised. + /// public bool IsMaximised; private Facade facade; From 9d0293070971e17132b201448afbb42c5f40b295 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Wed, 7 Apr 2021 17:18:55 +0200 Subject: [PATCH 1301/1791] Add regression test for type changes --- .../TestSceneSliderControlPointPiece.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs index 9b67d18db6..d7dfc3bd42 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs @@ -105,6 +105,25 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertControlPointType(2, PathType.PerfectCurve); } + [Test] + public void TestDragControlPointPathAfterChangingType() + { + AddStep("change type to bezier", () => slider.Path.ControlPoints[2].Type.Value = PathType.Bezier); + AddStep("add point", () => slider.Path.ControlPoints.Add(new PathControlPoint(new Vector2(500, 10)))); + AddStep("change type to perfect", () => slider.Path.ControlPoints[3].Type.Value = PathType.PerfectCurve); + + moveMouseToControlPoint(4); + AddStep("hold", () => InputManager.PressButton(MouseButton.Left)); + + assertControlPointType(3, PathType.PerfectCurve); + + addMovementStep(new Vector2(350, 0.01f)); + AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left)); + + assertControlPointPosition(4, new Vector2(350, 0.01f)); + assertControlPointType(3, PathType.Bezier); + } + private void addMovementStep(Vector2 relativePosition) { AddStep($"move mouse to {relativePosition}", () => From b8ab1c768253402bdfb3644808cbfde05234ef07 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Wed, 7 Apr 2021 17:19:12 +0200 Subject: [PATCH 1302/1791] Track path type changes for `PointsInSegment` --- .../Sliders/Components/PathControlPointPiece.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) 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 394d2b039d..5fcf0656c5 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -50,6 +50,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components [Resolved] private OsuColour colours { get; set; } + private readonly List> pathTypes; + private IBindable sliderVersion; private IBindable sliderPosition; private IBindable sliderScale; @@ -59,8 +61,19 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { this.slider = slider; ControlPoint = controlPoint; + pathTypes = new List>(); + slider.Path.ControlPoints.BindCollectionChanged((_, args) => { + pathTypes.Clear(); + + foreach (var point in slider.Path.ControlPoints) + { + IBindable boundTypeCopy = point.Type.GetBoundCopy(); + pathTypes.Add(boundTypeCopy); + boundTypeCopy.BindValueChanged(_ => PointsInSegment = slider.Path.PointsInSegment(controlPoint)); + } + PointsInSegment = slider.Path.PointsInSegment(controlPoint); }, runOnceImmediately: true); From 0a6baf670eff5a90704b95277cf8a8b89dd44375 Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Wed, 7 Apr 2021 14:41:21 -0400 Subject: [PATCH 1303/1791] Send a warning notification if device is unplugged and low battery - Uses Xamarin.Essentials in osu.Game.PlayerLoader to check battery level - Encapsulated battery checking in the public BatteryManager class so battery level and plugged in status can be accessed and edited in TestPlayerLoader - When checking battery level, catch NotImplementedException thrown by Xamarin.Essentials.Battery on non-mobile platforms - Added visual unit tests for battery notification To mock battery status and level, we had to define a batteryManager object in TestPlayerLoader and add a new function ResetPlayerWithBattery() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Marlina José --- osu.Android/Properties/AndroidManifest.xml | 1 + .../Visual/Gameplay/TestScenePlayerLoader.cs | 48 +++++++++++++ osu.Game.Tests/osu.Game.Tests.csproj | 2 +- osu.Game/Configuration/SessionStatics.cs | 2 + osu.Game/Screens/Play/PlayerLoader.cs | 71 +++++++++++++++++++ osu.Game/osu.Game.csproj | 3 +- 6 files changed, 125 insertions(+), 2 deletions(-) diff --git a/osu.Android/Properties/AndroidManifest.xml b/osu.Android/Properties/AndroidManifest.xml index 770eaf2222..e717bab310 100644 --- a/osu.Android/Properties/AndroidManifest.xml +++ b/osu.Android/Properties/AndroidManifest.xml @@ -6,5 +6,6 @@ + \ No newline at end of file diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index 88fbf09ef4..a31d53e3b6 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -93,6 +93,26 @@ namespace osu.Game.Tests.Visual.Gameplay LoadScreen(loader = new TestPlayerLoader(() => player = new TestPlayer(interactive, interactive))); } + /// + /// Sets the input manager child to a new test player loader container instance with a custom battery level + /// + /// If the test player should behave like the production one. + /// If the player's device is plugged in. + /// A custom battery level for the test player. + private void resetPlayerWithBattery(bool interactive, bool pluggedIn, double batteryLevel) + { + Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); + Beatmap.Value.BeatmapInfo.EpilepsyWarning = epilepsyWarning; + + foreach (var mod in SelectedMods.Value.OfType()) + mod.ApplyToTrack(Beatmap.Value.Track); + + loader = new TestPlayerLoader(() => player = new TestPlayer(interactive, interactive)); + loader.batteryManager.ChargeLevel = batteryLevel; + loader.batteryManager.PluggedIn = pluggedIn; + LoadScreen(loader); + } + [Test] public void TestEarlyExitBeforePlayerConstruction() { @@ -270,6 +290,32 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for player load", () => player.IsLoaded); } + [TestCase(false, 1.0)] // not plugged in, full battery, no notification + [TestCase(false, 0.2)] // not plugged in, at warning level, no notification + [TestCase(true, 0.1)] // plugged in, charging, below warning level, no notification + [TestCase(false, 0.1)] // not plugged in, below warning level, notification + public void TestLowBatteryNotification(bool pluggedIn, double batteryLevel) + { + AddStep("reset notification lock", () => sessionStatics.GetBindable(Static.BatteryLowNotificationShownOnce).Value = false); + + // mock phone on battery + AddStep("load player", () => resetPlayerWithBattery(false, pluggedIn, batteryLevel)); + AddUntilStep("wait for player", () => player?.LoadState == LoadState.Ready); + int notificationCount = !pluggedIn && batteryLevel < PlayerLoader.battery_tolerance ? 1 : 0; + AddAssert("check for notification", () => notificationOverlay.UnreadCount.Value == notificationCount); + AddStep("click notification", () => + { + var scrollContainer = (OsuScrollContainer)notificationOverlay.Children.Last(); + var flowContainer = scrollContainer.Children.OfType>().First(); + var notification = flowContainer.First(); + + InputManager.MoveMouseTo(notification); + InputManager.Click(MouseButton.Left); + }); + + AddUntilStep("wait for player load", () => player.IsLoaded); + } + [TestCase(true)] [TestCase(false)] public void TestEpilepsyWarning(bool warning) @@ -310,6 +356,8 @@ namespace osu.Game.Tests.Visual.Gameplay public IReadOnlyList DisplayedMods => MetadataInfo.Mods.Value; + public new BatteryManager batteryManager => base.batteryManager; + public TestPlayerLoader(Func createPlayer) : base(createPlayer) { diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 0e1f6f6b0c..df6d17f615 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -22,4 +22,4 @@ - \ No newline at end of file + diff --git a/osu.Game/Configuration/SessionStatics.cs b/osu.Game/Configuration/SessionStatics.cs index 36eb6964dd..e8ee04731c 100644 --- a/osu.Game/Configuration/SessionStatics.cs +++ b/osu.Game/Configuration/SessionStatics.cs @@ -16,6 +16,7 @@ namespace osu.Game.Configuration { SetDefault(Static.LoginOverlayDisplayed, false); SetDefault(Static.MutedAudioNotificationShownOnce, false); + SetDefault(Static.BatteryLowNotificationShownOnce, false); SetDefault(Static.LastHoverSoundPlaybackTime, (double?)null); SetDefault(Static.SeasonalBackgrounds, null); } @@ -25,6 +26,7 @@ namespace osu.Game.Configuration { LoginOverlayDisplayed, MutedAudioNotificationShownOnce, + BatteryLowNotificationShownOnce, /// /// Info about seasonal backgrounds available fetched from API - see . diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 679b3c7313..01a51e6e7a 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -26,6 +26,7 @@ using osu.Game.Screens.Play.PlayerSettings; using osu.Game.Users; using osuTK; using osuTK.Graphics; +using Xamarin.Essentials; namespace osu.Game.Screens.Play { @@ -121,6 +122,7 @@ namespace osu.Game.Screens.Play private void load(SessionStatics sessionStatics) { muteWarningShownOnce = sessionStatics.GetBindable(Static.MutedAudioNotificationShownOnce); + batteryWarningShownOnce = sessionStatics.GetBindable(Static.BatteryLowNotificationShownOnce); InternalChild = (content = new LogoTrackingContainer { @@ -196,6 +198,7 @@ namespace osu.Game.Screens.Play Scheduler.Add(new ScheduledDelegate(pushWhenLoaded, Clock.CurrentTime + 1800, 0)); showMuteWarningIfNeeded(); + showBatteryWarningIfNeeded(); } public override void OnResuming(IScreen last) @@ -470,5 +473,73 @@ namespace osu.Game.Screens.Play } #endregion + + #region Low battery warning + private Bindable batteryWarningShownOnce; + + // Send a warning if battery is less than 20% + public const double battery_tolerance = 0.2; + + private void showBatteryWarningIfNeeded() + { + if (!batteryWarningShownOnce.Value) + { + // Checks if the notification has not been shown yet, device is unplugged and if device battery is low. + if (!batteryManager.PluggedIn && batteryManager.ChargeLevel < battery_tolerance) + { + Console.WriteLine("Battery level: {0}", batteryManager.ChargeLevel); + notificationOverlay?.Post(new BatteryWarningNotification()); + batteryWarningShownOnce.Value = true; + } + } + } + public BatteryManager batteryManager = new BatteryManager(); + public class BatteryManager + { + public double ChargeLevel; + public bool PluggedIn; + public BatteryManager() + { + // Attempt to get battery information using Xamarin.Essentials + // Xamarin.Essentials throws a NotSupportedOrImplementedException on .NET standard so this only works on iOS/Android + // https://github.com/xamarin/Essentials/blob/7218ab88f7fbe00ec3379bd54e6c0ce35ffc0c22/Xamarin.Essentials/Battery/Battery.netstandard.tvos.cs + try + { + ChargeLevel = Battery.ChargeLevel; + PluggedIn = Battery.PowerSource == BatteryPowerSource.Battery; + } + catch (NotImplementedException e) + { + Console.WriteLine("Could not access battery info: {0}", e); + ChargeLevel = 1.0; + PluggedIn = false; + } + } + } + + private class BatteryWarningNotification : SimpleNotification + { + public override bool IsImportant => true; + + public BatteryWarningNotification() + { + Text = "Your battery level is low! Charge your device to prevent interruptions."; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours, NotificationOverlay notificationOverlay) + { + Icon = FontAwesome.Solid.BatteryQuarter; + IconBackgound.Colour = colours.RedDark; + + Activated = delegate + { + notificationOverlay.Hide(); + return true; + }; + } + } + + #endregion } } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 292e5b932f..c68be5313d 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -1,4 +1,4 @@ - + netstandard2.1 Library @@ -35,5 +35,6 @@ + From d1d56c636a04a48e5cafdc3cf0cb1d1996d2162e Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Thu, 8 Apr 2021 01:43:06 +0200 Subject: [PATCH 1304/1791] Convert `pathTypes` to local variable Not entirely sure what holds the reference to pathTypes now (the binding to`slider.Path.ControlPoints` maybe?), but this does seem to work still. --- .../Blueprints/Sliders/Components/PathControlPointPiece.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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 5fcf0656c5..bb50fec5dc 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -50,8 +50,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components [Resolved] private OsuColour colours { get; set; } - private readonly List> pathTypes; - private IBindable sliderVersion; private IBindable sliderPosition; private IBindable sliderScale; @@ -61,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { this.slider = slider; ControlPoint = controlPoint; - pathTypes = new List>(); + var pathTypes = new List>(); slider.Path.ControlPoints.BindCollectionChanged((_, args) => { From d6490899e2946294bbdab7f0d0db6a5bd014ce5d Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Thu, 8 Apr 2021 03:21:56 +0200 Subject: [PATCH 1305/1791] Simplify slider path bindings Adds a slight performance overhead, but solves the memory leak and makes the code much easier to follow. --- .../Components/PathControlPointPiece.cs | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) 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 bb50fec5dc..20d4fe4ea9 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -50,7 +50,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components [Resolved] private OsuColour colours { get; set; } - private IBindable sliderVersion; private IBindable sliderPosition; private IBindable sliderScale; private IBindable controlPointPosition; @@ -59,20 +58,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { this.slider = slider; ControlPoint = controlPoint; - var pathTypes = new List>(); - slider.Path.ControlPoints.BindCollectionChanged((_, args) => + slider.Path.Version.BindValueChanged(_ => { - pathTypes.Clear(); - - foreach (var point in slider.Path.ControlPoints) - { - IBindable boundTypeCopy = point.Type.GetBoundCopy(); - pathTypes.Add(boundTypeCopy); - boundTypeCopy.BindValueChanged(_ => PointsInSegment = slider.Path.PointsInSegment(controlPoint)); - } - - PointsInSegment = slider.Path.PointsInSegment(controlPoint); + PointsInSegment = slider.Path.PointsInSegment(ControlPoint); + updatePathType(); }, runOnceImmediately: true); controlPoint.Type.BindValueChanged(_ => updateMarkerDisplay()); @@ -120,9 +110,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { base.LoadComplete(); - sliderVersion = slider.Path.Version.GetBoundCopy(); - sliderVersion.BindValueChanged(_ => updatePathType()); - sliderPosition = slider.PositionBindable.GetBoundCopy(); sliderPosition.BindValueChanged(_ => updateMarkerDisplay()); From 8aff53172de5f153b5926926bc0bf1c8e75f3ef5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Apr 2021 15:17:53 +0900 Subject: [PATCH 1306/1791] Remove necessity for nested PassThroughInputManger --- .../Gameplay/TestSceneGameplayMenuOverlay.cs | 2 +- .../Input/Bindings/GlobalActionContainer.cs | 23 ++++++++++----- osu.Game/Input/Bindings/GlobalInputManager.cs | 29 ------------------- osu.Game/OsuGameBase.cs | 15 ++++++---- .../Visual/OsuManualInputManagerTestScene.cs | 2 +- 5 files changed, 26 insertions(+), 45 deletions(-) delete mode 100644 osu.Game/Input/Bindings/GlobalInputManager.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs index 75e8194708..d69ac665cc 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs @@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.Gameplay [BackgroundDependencyLoader] private void load(OsuGameBase game) { - Child = globalActionContainer = new GlobalActionContainer(game, null); + Child = globalActionContainer = new GlobalActionContainer(game); } [SetUp] diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 042960d54c..671c3bc8bc 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.ComponentModel; using System.Linq; -using JetBrains.Annotations; using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Input.Bindings; @@ -13,20 +12,23 @@ namespace osu.Game.Input.Bindings { public class GlobalActionContainer : DatabasedKeyBindingContainer, IHandleGlobalKeyboardInput { - [CanBeNull] - private readonly GlobalInputManager globalInputManager; - private readonly Drawable handler; + private InputManager parentInputManager; - public GlobalActionContainer(OsuGameBase game, [CanBeNull] GlobalInputManager globalInputManager) + public GlobalActionContainer(OsuGameBase game) : base(matchingMode: KeyCombinationMatchingMode.Modifiers) { - this.globalInputManager = globalInputManager; - if (game is IKeyBindingHandler) handler = game; } + protected override void LoadComplete() + { + base.LoadComplete(); + + parentInputManager = GetContainingInputManager(); + } + public override IEnumerable DefaultKeyBindings => GlobalKeyBindings .Concat(EditorKeyBindings) .Concat(InGameKeyBindings) @@ -113,7 +115,12 @@ namespace osu.Game.Input.Bindings { get { - var inputQueue = globalInputManager?.NonPositionalInputQueue ?? base.KeyBindingInputQueue; + // To ensure the global actions are handled with priority, this GlobalActionContainer is actually placed after game content. + // It does not contain children as expected, so we need to forward the NonPositionalInputQueue from the parent input manager to correctly + // allow the whole game to handle these actions. + + // An eventual solution to this hack is to create localised action containers for individual components like SongSelect, but this will take some rearranging. + var inputQueue = parentInputManager?.NonPositionalInputQueue ?? base.KeyBindingInputQueue; return handler != null ? inputQueue.Prepend(handler) : inputQueue; } diff --git a/osu.Game/Input/Bindings/GlobalInputManager.cs b/osu.Game/Input/Bindings/GlobalInputManager.cs deleted file mode 100644 index 91373712fb..0000000000 --- a/osu.Game/Input/Bindings/GlobalInputManager.cs +++ /dev/null @@ -1,29 +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.Framework.Input; - -namespace osu.Game.Input.Bindings -{ - public class GlobalInputManager : PassThroughInputManager - { - public readonly GlobalActionContainer GlobalBindings; - - protected override Container Content { get; } - - public GlobalInputManager(OsuGameBase game) - { - InternalChildren = new Drawable[] - { - Content = new Container - { - RelativeSizeAxes = Axes.Both, - }, - // to avoid positional input being blocked by children, ensure the GlobalActionContainer is above everything. - GlobalBindings = new GlobalActionContainer(game, this) - }; - } - } -} diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 21313d0596..ca132df552 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -308,18 +308,21 @@ namespace osu.Game AddInternal(RulesetConfigCache); - var globalInput = new GlobalInputManager(this) + GlobalActionContainer globalBindings; + + var mainContent = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Child = MenuCursorContainer = new MenuCursorContainer { RelativeSizeAxes = Axes.Both } + MenuCursorContainer = new MenuCursorContainer { RelativeSizeAxes = Axes.Both }, + // to avoid positional input being blocked by children, ensure the GlobalActionContainer is above everything. + globalBindings = new GlobalActionContainer(this) }; MenuCursorContainer.Child = content = new OsuTooltipContainer(MenuCursorContainer.Cursor) { RelativeSizeAxes = Axes.Both }; - base.Content.Add(CreateScalingContainer().WithChild(globalInput)); + base.Content.Add(CreateScalingContainer().WithChildren(mainContent)); - KeyBindingStore.Register(globalInput.GlobalBindings); - dependencies.Cache(globalInput.GlobalBindings); + KeyBindingStore.Register(globalBindings); + dependencies.Cache(globalBindings); PreviewTrackManager previewTrackManager; dependencies.Cache(previewTrackManager = new PreviewTrackManager()); diff --git a/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs b/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs index 7dad636da7..01dd7a25c8 100644 --- a/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs +++ b/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs @@ -40,7 +40,7 @@ namespace osu.Game.Tests.Visual if (CreateNestedActionContainer) { - mainContent = new GlobalActionContainer(null, null).WithChild(mainContent); + mainContent = new GlobalActionContainer(null).WithChild(mainContent); } base.Content.AddRange(new Drawable[] From 545156d15ca19a67bc22c43aa6dded4cee3cd877 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Apr 2021 15:20:53 +0900 Subject: [PATCH 1307/1791] Add regression test coverage --- .../Visual/Navigation/TestSceneScreenNavigation.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 00f9bf3432..859cefe3a9 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -44,6 +44,20 @@ namespace osu.Game.Tests.Visual.Navigation exitViaEscapeAndConfirm(); } + /// + /// This tests that the F1 key will open the mod select overlay, and not be handled / blocked by the music controller (which has the same default binding + /// but should be handled *after* song select). + /// + [Test] + public void TestOpenModSelectOverlayUsingAction() + { + TestSongSelect songSelect = null; + + PushAndConfirm(() => songSelect = new TestSongSelect()); + AddStep("Show mods overlay", () => InputManager.Key(Key.F1)); + AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible); + } + [Test] public void TestRetryCountIncrements() { From b73860cb5f6de153df50f3dc64b067dd611a6f40 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Apr 2021 15:47:55 +0900 Subject: [PATCH 1308/1791] Slightly alter button colour scheme to make text more legible and reduce saturation --- .../Multiplayer/Match/MultiplayerSpectateButton.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs index 50be7719d9..4b3fb5d00f 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs @@ -68,16 +68,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { default: button.Text = "Spectate"; - button.BackgroundColour = colours.Blue; - button.Triangles.ColourDark = colours.Blue; - button.Triangles.ColourLight = colours.BlueLight; + button.BackgroundColour = colours.BlueDark; + button.Triangles.ColourDark = colours.BlueDarker; + button.Triangles.ColourLight = colours.Blue; break; case MultiplayerUserState.Spectating: button.Text = "Stop spectating"; - button.BackgroundColour = colours.Red; - button.Triangles.ColourDark = colours.Red; - button.Triangles.ColourLight = colours.RedLight; + button.BackgroundColour = colours.Gray4; + button.Triangles.ColourDark = colours.Gray5; + button.Triangles.ColourLight = colours.Gray6; break; } From a55e62188ebe87648bcc1ffe9f6af93f167072a3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Apr 2021 15:54:58 +0900 Subject: [PATCH 1309/1791] Change state icon to binoculars so the eye isn't staring at me --- .../Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs index e6a407acec..2616b07c1f 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs @@ -137,7 +137,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants case MultiplayerUserState.Spectating: text.Text = "spectating"; - icon.Icon = FontAwesome.Solid.Eye; + icon.Icon = FontAwesome.Solid.Binoculars; icon.Colour = colours.BlueLight; break; From 725edfcbf35bdbdca7f1e277722ed7d749e5f8f2 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Thu, 8 Apr 2021 09:05:35 +0200 Subject: [PATCH 1310/1791] Add path type menu change method --- .../Components/PathControlPointVisualiser.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index ce5dc4855e..ff2e4ea152 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -153,6 +153,18 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components } } + /// + /// Attempts to set the given control point piece to the given path type. + /// If that would fail, try to change the path such that it instead succeeds + /// in a UX-friendly way. + /// + /// The control point piece that we want to change the path type of. + /// The path type we want to assign to the given control point piece. + private void updatePathType(PathControlPointPiece piece, PathType? type) + { + piece.ControlPoint.Type.Value = type; + } + [Resolved(CanBeNull = true)] private IEditorChangeHandler changeHandler { get; set; } @@ -218,7 +230,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components var item = new PathTypeMenuItem(type, () => { foreach (var p in Pieces.Where(p => p.IsSelected.Value)) - p.ControlPoint.Type.Value = type; + updatePathType(p, type); }); if (countOfState == totalCount) From 0341023d13cf7926a04d67852ec995874e10e79f Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Thu, 8 Apr 2021 09:06:28 +0200 Subject: [PATCH 1311/1791] Improve UX of selecting PerfectCurve --- .../Components/PathControlPointVisualiser.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index ff2e4ea152..0b163cbc44 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -162,6 +162,22 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components /// The path type we want to assign to the given control point piece. private void updatePathType(PathControlPointPiece piece, PathType? type) { + int indexInSegment = piece.PointsInSegment.IndexOf(piece.ControlPoint); + + switch (type) + { + case PathType.PerfectCurve: + if (piece.PointsInSegment.Count > 3) + { + // Can't always create a circular arc out of 4 or more points, + // so we split the segment into one 3-point circular arc segment + // and one bezier segment. + piece.PointsInSegment[indexInSegment + 2].Type.Value = PathType.Bezier; + } + + break; + } + piece.ControlPoint.Type.Value = type; } From b38d332268bf678cda1746bb264a7adb32d7a7e5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 8 Apr 2021 16:29:11 +0900 Subject: [PATCH 1312/1791] Fix broken test --- .../Multiplayer/TestSceneMultiplayerReadyButton.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs index dad1237991..dfb4306e67 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs @@ -209,9 +209,16 @@ namespace osu.Game.Tests.Visual.Multiplayer { addClickButtonStep(); AddAssert("user waiting for load", () => Client.Room?.Users[0].State == MultiplayerUserState.WaitingForLoad); - AddAssert("ready button disabled", () => !button.ChildrenOfType().Single().Enabled.Value); + AddAssert("ready button disabled", () => !button.ChildrenOfType().Single().Enabled.Value); AddStep("transitioned to gameplay", () => readyClickOperation.Dispose()); + + AddStep("finish gameplay", () => + { + Client.ChangeUserState(Client.Room?.Users[0].UserID ?? 0, MultiplayerUserState.Loaded); + Client.ChangeUserState(Client.Room?.Users[0].UserID ?? 0, MultiplayerUserState.FinishedPlay); + }); + AddAssert("ready button enabled", () => button.ChildrenOfType().Single().Enabled.Value); } } From fd2a14a0bf07fd5e8b940f2f666455fc4eb923ba Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 8 Apr 2021 16:30:48 +0900 Subject: [PATCH 1313/1791] Only set button state once --- .../Multiplayer/Match/MultiplayerReadyButton.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs index 6919be2d56..f2dd9a6f25 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs @@ -105,14 +105,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match break; } - button.Enabled.Value = Client.Room?.State == MultiplayerRoomState.Open && !operationInProgress.Value; + bool enableButton = Client.Room?.State == MultiplayerRoomState.Open && !operationInProgress.Value; - // When the local user is the host and spectating the match, the "start match" state should be enabled. + // When the local user is the host and spectating the match, the "start match" state should be enabled if any users are ready. if (localUser.State == MultiplayerUserState.Spectating) - { - button.Enabled.Value &= Room?.Host?.Equals(localUser) == true; - button.Enabled.Value &= newCountReady > 0; - } + enableButton &= Room?.Host?.Equals(localUser) == true && newCountReady > 0; + + button.Enabled.Value = enableButton; if (newCountReady != countReady) { From be4520fe33ff6ec67367ef207a59a00fa47b8d61 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Thu, 8 Apr 2021 11:46:00 +0200 Subject: [PATCH 1314/1791] Fix index out of range possibility --- .../Components/PathControlPointVisualiser.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 0b163cbc44..1dfbf97682 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -167,13 +167,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components switch (type) { case PathType.PerfectCurve: - if (piece.PointsInSegment.Count > 3) - { - // Can't always create a circular arc out of 4 or more points, - // so we split the segment into one 3-point circular arc segment - // and one bezier segment. - piece.PointsInSegment[indexInSegment + 2].Type.Value = PathType.Bezier; - } + // Can't always create a circular arc out of 4 or more points, + // so we split the segment into one 3-point circular arc segment + // and one bezier segment. + int thirdPointIndex = indexInSegment + 2; + + if (piece.PointsInSegment.Count > thirdPointIndex + 1) + piece.PointsInSegment[thirdPointIndex].Type.Value = PathType.Bezier; break; } From 4110d1675d001919dca028a51f5e92b19eb5d611 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Thu, 8 Apr 2021 11:46:52 +0200 Subject: [PATCH 1315/1791] Add path type menu test cases --- .../TestScenePathControlPointVisualiser.cs | 101 +++++++++++++++++- 1 file changed, 99 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs index 738a21b17e..3e2d1cff0e 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs @@ -4,9 +4,11 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; +using osu.Framework.Graphics.UserInterface; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Tests.Visual; @@ -14,7 +16,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Tests.Editor { - public class TestScenePathControlPointVisualiser : OsuTestScene + public class TestScenePathControlPointVisualiser : OsuManualInputManagerTestScene { private Slider slider; private PathControlPointVisualiser visualiser; @@ -43,12 +45,107 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor }); } + [Test] + public void TestPerfectCurveTooManyPoints() + { + createVisualiser(true); + + addControlPointStep(new Vector2(200), PathType.Bezier); + addControlPointStep(new Vector2(300)); + addControlPointStep(new Vector2(500, 300)); + addControlPointStep(new Vector2(700, 200)); + addControlPointStep(new Vector2(500, 100)); + + // Must be both hovering and selecting the control point for the context menu to work. + moveMouseToControlPoint(1); + AddStep("select control point", () => visualiser.Pieces[1].IsSelected.Value = true); + addContextMenuItemStep("Perfect curve"); + + assertControlPointPathType(0, PathType.Bezier); + assertControlPointPathType(1, PathType.PerfectCurve); + AddAssert("point 3 is not inherited", () => slider.Path.ControlPoints[3].Type != null); + } + + [Test] + public void TestPerfectCurveLastThreePoints() + { + createVisualiser(true); + + addControlPointStep(new Vector2(200), PathType.Bezier); + addControlPointStep(new Vector2(300)); + addControlPointStep(new Vector2(500, 300)); + addControlPointStep(new Vector2(700, 200)); + addControlPointStep(new Vector2(500, 100)); + + moveMouseToControlPoint(2); + AddStep("select control point", () => visualiser.Pieces[2].IsSelected.Value = true); + addContextMenuItemStep("Perfect curve"); + + assertControlPointPathType(0, PathType.Bezier); + assertControlPointPathType(2, PathType.PerfectCurve); + assertControlPointPathType(4, null); + } + + [Test] + public void TestPerfectCurveLastTwoPoints() + { + createVisualiser(true); + + addControlPointStep(new Vector2(200), PathType.Bezier); + addControlPointStep(new Vector2(300)); + addControlPointStep(new Vector2(500, 300)); + addControlPointStep(new Vector2(700, 200)); + addControlPointStep(new Vector2(500, 100)); + + moveMouseToControlPoint(3); + AddStep("select control point", () => visualiser.Pieces[3].IsSelected.Value = true); + addContextMenuItemStep("Perfect curve"); + + assertControlPointPathType(0, PathType.Bezier); + AddAssert("point 3 is not inherited", () => slider.Path.ControlPoints[3].Type != null); + } + private void createVisualiser(bool allowSelection) => AddStep("create visualiser", () => Child = visualiser = new PathControlPointVisualiser(slider, allowSelection) { Anchor = Anchor.Centre, Origin = Anchor.Centre }); - private void addControlPointStep(Vector2 position) => AddStep($"add control point {position}", () => slider.Path.ControlPoints.Add(new PathControlPoint(position))); + private void addControlPointStep(Vector2 position) => addControlPointStep(position, null); + + private void addControlPointStep(Vector2 position, PathType? type) + { + AddStep($"add {type} control point at {position}", () => + { + slider.Path.ControlPoints.Add(new PathControlPoint(position, type)); + }); + } + + private void moveMouseToControlPoint(int index) + { + AddStep($"move mouse to control point {index}", () => + { + Vector2 position = slider.Path.ControlPoints[index].Position.Value; + InputManager.MoveMouseTo(visualiser.Pieces[0].Parent.ToScreenSpace(position)); + }); + } + + private void assertControlPointPathType(int controlPointIndex, PathType? type) + { + AddAssert($"point {controlPointIndex} is {type}", () => + { + return slider.Path.ControlPoints[controlPointIndex].Type.Value == type; + }); + } + + private void addContextMenuItemStep(string contextMenuText) + { + AddStep($"click context menu item \"{contextMenuText}\"", () => + { + MenuItem item = visualiser.ContextMenuItems[1].Items.FirstOrDefault(menuItem => menuItem.Text.Value == contextMenuText); + + item?.Action?.Value(); + }); + } } } From 7d2b54ca42b5d98ffaf69b5bbaba2cbf5f8059df Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Thu, 8 Apr 2021 12:32:45 +0200 Subject: [PATCH 1316/1791] Add change to Bezier test --- .../TestScenePathControlPointVisualiser.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs index 3e2d1cff0e..1d5a175a26 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs @@ -105,6 +105,26 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddAssert("point 3 is not inherited", () => slider.Path.ControlPoints[3].Type != null); } + [Test] + public void TestPerfectCurveChangeToBezier() + { + createVisualiser(true); + + addControlPointStep(new Vector2(200), PathType.Bezier); + addControlPointStep(new Vector2(300), PathType.PerfectCurve); + addControlPointStep(new Vector2(500, 300)); + addControlPointStep(new Vector2(700, 200), PathType.Bezier); + addControlPointStep(new Vector2(500, 100)); + + moveMouseToControlPoint(3); + AddStep("select control point", () => visualiser.Pieces[3].IsSelected.Value = true); + addContextMenuItemStep("Inherit"); + + assertControlPointPathType(0, PathType.Bezier); + assertControlPointPathType(1, PathType.Bezier); + assertControlPointPathType(3, null); + } + private void createVisualiser(bool allowSelection) => AddStep("create visualiser", () => Child = visualiser = new PathControlPointVisualiser(slider, allowSelection) { Anchor = Anchor.Centre, From 9a675a22195fe0d24576dbffec32285282f3209c Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Thu, 8 Apr 2021 12:33:43 +0200 Subject: [PATCH 1317/1791] Correct 4+ point perfect curves to Bezier --- .../Blueprints/Sliders/Components/PathControlPointPiece.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) 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 20d4fe4ea9..6b78cff33e 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -213,10 +213,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components if (ControlPoint.Type.Value != PathType.PerfectCurve) return; - ReadOnlySpan points = PointsInSegment.Select(p => p.Position.Value).ToArray(); - if (points.Length != 3) + if (PointsInSegment.Count > 3) + ControlPoint.Type.Value = PathType.Bezier; + + if (PointsInSegment.Count != 3) return; + ReadOnlySpan points = PointsInSegment.Select(p => p.Position.Value).ToArray(); RectangleF boundingBox = PathApproximator.CircularArcBoundingBox(points); if (boundingBox.Width >= 640 || boundingBox.Height >= 480) ControlPoint.Type.Value = PathType.Bezier; From 2d9448456671c8b7e1966341af66338f45923f85 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Thu, 8 Apr 2021 12:49:46 +0200 Subject: [PATCH 1318/1791] Use lambda expression Apparently CI dislikes this not being a lambda. --- .../Editor/TestScenePathControlPointVisualiser.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs index 1d5a175a26..440ab7c889 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs @@ -152,10 +152,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor private void assertControlPointPathType(int controlPointIndex, PathType? type) { - AddAssert($"point {controlPointIndex} is {type}", () => - { - return slider.Path.ControlPoints[controlPointIndex].Type.Value == type; - }); + AddAssert($"point {controlPointIndex} is {type}", () => slider.Path.ControlPoints[controlPointIndex].Type.Value == type); } private void addContextMenuItemStep(string contextMenuText) From 7713c8a45f0763a7aff7b24622e58a203c7d1c4b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 8 Apr 2021 20:19:41 +0900 Subject: [PATCH 1319/1791] Add support for sliderwhistle --- .../Objects/Drawables/DrawableSlider.cs | 10 +++++++--- osu.Game.Rulesets.Osu/Objects/Slider.cs | 3 +++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 04708a5ece..8167a9f243 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; using osuTK; @@ -110,13 +111,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.LoadSamples(); - var firstSample = HitObject.Samples.FirstOrDefault(); + var firstSample = HitObject.OriginalSamples.FirstOrDefault(); if (firstSample != null) { - var clone = HitObject.SampleControlPoint.ApplyTo(firstSample).With("sliderslide"); + var samples = new List { HitObject.SampleControlPoint.ApplyTo(firstSample).With("sliderslide") }; - slidingSample.Samples = new ISampleInfo[] { clone }; + if (HitObject.OriginalSamples.Any(s => s.Name == HitSampleInfo.HIT_WHISTLE)) + samples.Add(HitObject.SampleControlPoint.ApplyTo(firstSample).With("sliderwhistle")); + + slidingSample.Samples = samples.ToArray(); } } diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index e2b6c84896..54e781bbe9 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -81,6 +81,8 @@ namespace osu.Game.Rulesets.Osu.Objects public List> NodeSamples { get; set; } = new List>(); + public IList OriginalSamples { get; private set; } + private int repeatCount; public int RepeatCount @@ -147,6 +149,7 @@ namespace osu.Game.Rulesets.Osu.Objects // The samples should be attached to the slider tail, however this can only be done after LegacyLastTick is removed otherwise they would play earlier than they're intended to. // For now, the samples are attached to and played by the slider itself at the correct end time. // ToArray call is required as GetNodeSamples may fallback to Samples itself (without it it will get cleared due to the list reference being live). + OriginalSamples = Samples.ToList(); Samples = this.GetNodeSamples(repeatCount + 1).ToArray(); } From 7d291ed7d73f18bcb9bdac5f1aa26f511ee6ecf6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 8 Apr 2021 20:57:50 +0900 Subject: [PATCH 1320/1791] Don't serialise OriginalSamples --- osu.Game.Rulesets.Osu/Objects/Slider.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 54e781bbe9..4e71d82cab 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -81,6 +81,7 @@ namespace osu.Game.Rulesets.Osu.Objects public List> NodeSamples { get; set; } = new List>(); + [JsonIgnore] public IList OriginalSamples { get; private set; } private int repeatCount; From 70cd018a984cf2c80e354b367ffe5698e6dda39b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 8 Apr 2021 21:38:58 +0900 Subject: [PATCH 1321/1791] Fix intermittent test failure --- .../Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index 6f8ec7fcfb..839118de2f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -93,7 +93,7 @@ namespace osu.Game.Tests.Visual.Multiplayer InputManager.Click(MouseButton.Left); }); - AddWaitStep("wait", 10); + AddUntilStep("wait for join", () => Client.Room != null); } [Test] @@ -114,6 +114,8 @@ namespace osu.Game.Tests.Visual.Multiplayer InputManager.Click(MouseButton.Left); }); + AddUntilStep("wait for room join", () => Client.Room != null); + AddStep("join other user (ready)", () => { Client.AddUser(new User { Id = 55 }); From 8efa381d3a1f71eb8e169495bf5a54f99a4a751c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 8 Apr 2021 23:13:16 +0900 Subject: [PATCH 1322/1791] Actually use whistle sample for sliderwhistle --- .../Objects/Drawables/DrawableSlider.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 8167a9f243..e29e28b1fa 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -111,17 +111,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.LoadSamples(); - var firstSample = HitObject.OriginalSamples.FirstOrDefault(); + var slidingSamples = new List(); - if (firstSample != null) - { - var samples = new List { HitObject.SampleControlPoint.ApplyTo(firstSample).With("sliderslide") }; + var normalSample = HitObject.OriginalSamples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL); + if (normalSample != null) + slidingSamples.Add(HitObject.SampleControlPoint.ApplyTo(normalSample).With("sliderslide")); - if (HitObject.OriginalSamples.Any(s => s.Name == HitSampleInfo.HIT_WHISTLE)) - samples.Add(HitObject.SampleControlPoint.ApplyTo(firstSample).With("sliderwhistle")); + var whistleSample = HitObject.OriginalSamples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_WHISTLE); + if (whistleSample != null) + slidingSamples.Add(HitObject.SampleControlPoint.ApplyTo(whistleSample).With("sliderwhistle")); - slidingSample.Samples = samples.ToArray(); - } + slidingSample.Samples = slidingSamples.ToArray(); } public override void StopAllSamples() From 24ae5b91696c8141588ba379081d075196fe7e6b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Apr 2021 23:15:08 +0900 Subject: [PATCH 1323/1791] Fix slightly incorrect solo score submission routes --- osu.Game/Online/Solo/CreateSoloScoreRequest.cs | 2 +- osu.Game/Online/Solo/SubmitSoloScoreRequest.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Solo/CreateSoloScoreRequest.cs b/osu.Game/Online/Solo/CreateSoloScoreRequest.cs index ae5ac5e26c..dea5a77496 100644 --- a/osu.Game/Online/Solo/CreateSoloScoreRequest.cs +++ b/osu.Game/Online/Solo/CreateSoloScoreRequest.cs @@ -27,6 +27,6 @@ namespace osu.Game.Online.Solo return req; } - protected override string Target => $@"solo/{beatmapId}/scores"; + protected override string Target => $@"beatmaps/{beatmapId}/solo/scores"; } } diff --git a/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs b/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs index 98ba4fa052..85fa3eeb34 100644 --- a/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs +++ b/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs @@ -40,6 +40,6 @@ namespace osu.Game.Online.Solo return req; } - protected override string Target => $@"solo/{beatmapId}/scores/{scoreId}"; + protected override string Target => $@"beatmaps/{beatmapId}/solo/scores/{scoreId}"; } } From 5dd6f19ec5b74b9f0b68802562ad858b8b374434 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Apr 2021 23:15:34 +0900 Subject: [PATCH 1324/1791] Update rider metadata files for 2021.1 --- .idea/.idea.osu.Desktop/.idea/indexLayout.xml | 2 +- .idea/.idea.osu.Desktop/.idea/modules.xml | 8 -------- 2 files changed, 1 insertion(+), 9 deletions(-) delete mode 100644 .idea/.idea.osu.Desktop/.idea/modules.xml diff --git a/.idea/.idea.osu.Desktop/.idea/indexLayout.xml b/.idea/.idea.osu.Desktop/.idea/indexLayout.xml index 27ba142e96..7b08163ceb 100644 --- a/.idea/.idea.osu.Desktop/.idea/indexLayout.xml +++ b/.idea/.idea.osu.Desktop/.idea/indexLayout.xml @@ -1,6 +1,6 @@ - + diff --git a/.idea/.idea.osu.Desktop/.idea/modules.xml b/.idea/.idea.osu.Desktop/.idea/modules.xml deleted file mode 100644 index 680312ad27..0000000000 --- a/.idea/.idea.osu.Desktop/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file From 6bccb3aab6487e0edd0d8030378c2f18c2868194 Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Thu, 8 Apr 2021 19:34:35 -0400 Subject: [PATCH 1325/1791] Use DI to implement battery detection, add BatteryCutoff property - Removed the Xamarin.Essentials package from osu.Game and added it to osu.iOS and osu.Android only. - iOS and Android implementations use Xamarin.Essentials.Battery, while the Desktop implementation only returns 100% battery for now. - Added a BatteryCutoff property to PowerStatus so it can be different for each platform (default 20%, 25% on iOS) --- osu.Android/OsuGameAndroid.cs | 9 +++ osu.Android/osu.Android.csproj | 4 + osu.Desktop/OsuGameDesktop.cs | 3 + .../Visual/Gameplay/TestScenePlayerLoader.cs | 76 +++++++------------ osu.Game/OsuGameBase.cs | 5 ++ osu.Game/Screens/Play/PlayerLoader.cs | 37 ++------- osu.Game/Tests/OsuTestBrowser.cs | 5 ++ osu.Game/Utils/PowerStatus.cs | 22 ++++++ osu.iOS/OsuGameIOS.cs | 17 +++++ osu.iOS/osu.iOS.csproj | 4 + 10 files changed, 103 insertions(+), 79 deletions(-) create mode 100644 osu.Game/Utils/PowerStatus.cs diff --git a/osu.Android/OsuGameAndroid.cs b/osu.Android/OsuGameAndroid.cs index 21d6336b2c..4c82a175fc 100644 --- a/osu.Android/OsuGameAndroid.cs +++ b/osu.Android/OsuGameAndroid.cs @@ -7,6 +7,8 @@ using Android.OS; using osu.Framework.Allocation; using osu.Game; using osu.Game.Updater; +using osu.Game.Utils; +using Xamarin.Essentials; namespace osu.Android { @@ -19,6 +21,7 @@ namespace osu.Android : base(null) { gameActivity = activity; + powerStatus = new AndroidPowerStatus(); } public override Version AssemblyVersion @@ -72,5 +75,11 @@ namespace osu.Android } protected override UpdateManager CreateUpdateManager() => new SimpleUpdateManager(); + public class AndroidPowerStatus : PowerStatus + { + public override double ChargeLevel => Battery.ChargeLevel; + + public override bool IsCharging => Battery.PowerSource != BatteryPowerSource.Battery; + } } } diff --git a/osu.Android/osu.Android.csproj b/osu.Android/osu.Android.csproj index 54857ac87d..64d5e5b1c8 100644 --- a/osu.Android/osu.Android.csproj +++ b/osu.Android/osu.Android.csproj @@ -63,5 +63,9 @@ 5.0.0 + + + + \ No newline at end of file diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 0c21c75290..b6c57b219d 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -21,6 +21,8 @@ using osu.Game.Updater; using osu.Desktop.Windows; using osu.Framework.Threading; using osu.Game.IO; +using osu.Game.Utils; +using osu.Framework.Allocation; namespace osu.Desktop { @@ -33,6 +35,7 @@ namespace osu.Desktop : base(args) { noVersionOverlay = args?.Any(a => a == "--no-version-overlay") ?? false; + powerStatus = new DefaultPowerStatus(); } public override StableStorage GetStorageForStableInstall() diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index a31d53e3b6..c5cc10993c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -25,6 +25,7 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play; using osu.Game.Screens.Play.PlayerSettings; +using osu.Game.Utils; using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay @@ -48,6 +49,9 @@ namespace osu.Game.Tests.Visual.Gameplay [Cached] private readonly VolumeOverlay volumeOverlay; + [Resolved] + private PowerStatus powerStatus { get; set; } + private readonly ChangelogOverlay changelogOverlay; public TestScenePlayerLoader() @@ -93,26 +97,6 @@ namespace osu.Game.Tests.Visual.Gameplay LoadScreen(loader = new TestPlayerLoader(() => player = new TestPlayer(interactive, interactive))); } - /// - /// Sets the input manager child to a new test player loader container instance with a custom battery level - /// - /// If the test player should behave like the production one. - /// If the player's device is plugged in. - /// A custom battery level for the test player. - private void resetPlayerWithBattery(bool interactive, bool pluggedIn, double batteryLevel) - { - Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); - Beatmap.Value.BeatmapInfo.EpilepsyWarning = epilepsyWarning; - - foreach (var mod in SelectedMods.Value.OfType()) - mod.ApplyToTrack(Beatmap.Value.Track); - - loader = new TestPlayerLoader(() => player = new TestPlayer(interactive, interactive)); - loader.batteryManager.ChargeLevel = batteryLevel; - loader.batteryManager.PluggedIn = pluggedIn; - LoadScreen(loader); - } - [Test] public void TestEarlyExitBeforePlayerConstruction() { @@ -290,32 +274,6 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for player load", () => player.IsLoaded); } - [TestCase(false, 1.0)] // not plugged in, full battery, no notification - [TestCase(false, 0.2)] // not plugged in, at warning level, no notification - [TestCase(true, 0.1)] // plugged in, charging, below warning level, no notification - [TestCase(false, 0.1)] // not plugged in, below warning level, notification - public void TestLowBatteryNotification(bool pluggedIn, double batteryLevel) - { - AddStep("reset notification lock", () => sessionStatics.GetBindable(Static.BatteryLowNotificationShownOnce).Value = false); - - // mock phone on battery - AddStep("load player", () => resetPlayerWithBattery(false, pluggedIn, batteryLevel)); - AddUntilStep("wait for player", () => player?.LoadState == LoadState.Ready); - int notificationCount = !pluggedIn && batteryLevel < PlayerLoader.battery_tolerance ? 1 : 0; - AddAssert("check for notification", () => notificationOverlay.UnreadCount.Value == notificationCount); - AddStep("click notification", () => - { - var scrollContainer = (OsuScrollContainer)notificationOverlay.Children.Last(); - var flowContainer = scrollContainer.Children.OfType>().First(); - var notification = flowContainer.First(); - - InputManager.MoveMouseTo(notification); - InputManager.Click(MouseButton.Left); - }); - - AddUntilStep("wait for player load", () => player.IsLoaded); - } - [TestCase(true)] [TestCase(false)] public void TestEpilepsyWarning(bool warning) @@ -334,6 +292,30 @@ namespace osu.Game.Tests.Visual.Gameplay } } + [TestCase(false, 1.0)] // not charging, full battery --> no warning + [TestCase(false, 0.2)] // not charging, at cutoff --> warning + [TestCase(false, 0.1)] // charging, below cutoff --> warning + public void TestLowBatteryNotification(bool isCharging, double chargeLevel) + { + AddStep("reset notification lock", () => sessionStatics.GetBindable(Static.BatteryLowNotificationShownOnce).Value = false); + + // set charge status and level + AddStep("load player", () => resetPlayer(false, () => { powerStatus.IsCharging = isCharging; powerStatus.ChargeLevel = chargeLevel; })); + AddUntilStep("wait for player", () => player?.LoadState == LoadState.Ready); + int notificationCount = !isCharging && chargeLevel <= powerStatus.BatteryCutoff ? 1 : 0; + AddAssert("check for notification", () => notificationOverlay.UnreadCount.Value == notificationCount); + AddStep("click notification", () => + { + var scrollContainer = (OsuScrollContainer)notificationOverlay.Children.Last(); + var flowContainer = scrollContainer.Children.OfType>().First(); + var notification = flowContainer.First(); + + InputManager.MoveMouseTo(notification); + InputManager.Click(MouseButton.Left); + }); + AddUntilStep("wait for player load", () => player.IsLoaded); + } + [Test] public void TestEpilepsyWarningEarlyExit() { @@ -356,8 +338,6 @@ namespace osu.Game.Tests.Visual.Gameplay public IReadOnlyList DisplayedMods => MetadataInfo.Mods.Value; - public new BatteryManager batteryManager => base.batteryManager; - public TestPlayerLoader(Func createPlayer) : base(createPlayer) { diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 21313d0596..53873b0127 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -40,6 +40,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Skinning; +using osu.Game.Utils; using osuTK.Input; using RuntimeInfo = osu.Framework.RuntimeInfo; @@ -95,6 +96,8 @@ namespace osu.Game protected Storage Storage { get; set; } + protected PowerStatus powerStatus; + [Cached] [Cached(typeof(IBindable))] protected readonly Bindable Ruleset = new Bindable(); @@ -329,6 +332,8 @@ namespace osu.Game dependencies.CacheAs(MusicController); Ruleset.BindValueChanged(onRulesetChanged); + + dependencies.CacheAs(powerStatus); } private void onRulesetChanged(ValueChangedEvent r) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 01a51e6e7a..9710fa7e46 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -24,9 +24,9 @@ using osu.Game.Overlays.Notifications; using osu.Game.Screens.Menu; using osu.Game.Screens.Play.PlayerSettings; using osu.Game.Users; +using osu.Game.Utils; using osuTK; using osuTK.Graphics; -using Xamarin.Essentials; namespace osu.Game.Screens.Play { @@ -113,6 +113,9 @@ namespace osu.Game.Screens.Play [Resolved] private AudioManager audioManager { get; set; } + [Resolved] + private PowerStatus powerStatus { get; set; } + public PlayerLoader(Func createPlayer) { this.createPlayer = createPlayer; @@ -265,7 +268,6 @@ namespace osu.Game.Screens.Play } #endregion - protected override void Update() { base.Update(); @@ -477,45 +479,18 @@ namespace osu.Game.Screens.Play #region Low battery warning private Bindable batteryWarningShownOnce; - // Send a warning if battery is less than 20% - public const double battery_tolerance = 0.2; - private void showBatteryWarningIfNeeded() { if (!batteryWarningShownOnce.Value) { - // Checks if the notification has not been shown yet, device is unplugged and if device battery is low. - if (!batteryManager.PluggedIn && batteryManager.ChargeLevel < battery_tolerance) + // Checks if the notification has not been shown yet, device is unplugged and if device battery is at or below the cutoff + if (!powerStatus.IsCharging && powerStatus.ChargeLevel <= powerStatus.BatteryCutoff) { - Console.WriteLine("Battery level: {0}", batteryManager.ChargeLevel); notificationOverlay?.Post(new BatteryWarningNotification()); batteryWarningShownOnce.Value = true; } } } - public BatteryManager batteryManager = new BatteryManager(); - public class BatteryManager - { - public double ChargeLevel; - public bool PluggedIn; - public BatteryManager() - { - // Attempt to get battery information using Xamarin.Essentials - // Xamarin.Essentials throws a NotSupportedOrImplementedException on .NET standard so this only works on iOS/Android - // https://github.com/xamarin/Essentials/blob/7218ab88f7fbe00ec3379bd54e6c0ce35ffc0c22/Xamarin.Essentials/Battery/Battery.netstandard.tvos.cs - try - { - ChargeLevel = Battery.ChargeLevel; - PluggedIn = Battery.PowerSource == BatteryPowerSource.Battery; - } - catch (NotImplementedException e) - { - Console.WriteLine("Could not access battery info: {0}", e); - ChargeLevel = 1.0; - PluggedIn = false; - } - } - } private class BatteryWarningNotification : SimpleNotification { diff --git a/osu.Game/Tests/OsuTestBrowser.cs b/osu.Game/Tests/OsuTestBrowser.cs index 71b0b02fa6..afce7dd38b 100644 --- a/osu.Game/Tests/OsuTestBrowser.cs +++ b/osu.Game/Tests/OsuTestBrowser.cs @@ -7,11 +7,16 @@ using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Graphics; using osu.Game.Screens.Backgrounds; +using osu.Game.Utils; namespace osu.Game.Tests { public class OsuTestBrowser : OsuGameBase { + public OsuTestBrowser() + { + powerStatus = new DefaultPowerStatus(); + } protected override void LoadComplete() { base.LoadComplete(); diff --git a/osu.Game/Utils/PowerStatus.cs b/osu.Game/Utils/PowerStatus.cs new file mode 100644 index 0000000000..0657c98af1 --- /dev/null +++ b/osu.Game/Utils/PowerStatus.cs @@ -0,0 +1,22 @@ +// 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.Utils +{ + public abstract class PowerStatus + { + /// + /// The maximum battery level before a warning notification + /// is sent. + /// + public virtual double BatteryCutoff { get; } = 0.2; + public virtual double ChargeLevel { get; set; } + public virtual bool IsCharging { get; set; } + } + + public class DefaultPowerStatus : PowerStatus + { + public override double ChargeLevel { get; set; } = 1; + public override bool IsCharging { get; set; } = true; + } +} diff --git a/osu.iOS/OsuGameIOS.cs b/osu.iOS/OsuGameIOS.cs index 5125ad81e0..21dce338dc 100644 --- a/osu.iOS/OsuGameIOS.cs +++ b/osu.iOS/OsuGameIOS.cs @@ -5,13 +5,30 @@ using System; using Foundation; using osu.Game; using osu.Game.Updater; +using osu.Game.Utils; +using Xamarin.Essentials; namespace osu.iOS { public class OsuGameIOS : OsuGame { + public OsuGameIOS() + : base(null) + { + powerStatus = new IOSPowerStatus(); + } public override Version AssemblyVersion => new Version(NSBundle.MainBundle.InfoDictionary["CFBundleVersion"].ToString()); protected override UpdateManager CreateUpdateManager() => new SimpleUpdateManager(); + + public class IOSPowerStatus : PowerStatus + { + // The low battery alert appears at 20% on iOS + // https://github.com/ppy/osu/issues/12239 + public override double BatteryCutoff => 0.25; + public override double ChargeLevel => Battery.ChargeLevel; + + public override bool IsCharging => Battery.PowerSource != BatteryPowerSource.Battery; + } } } diff --git a/osu.iOS/osu.iOS.csproj b/osu.iOS/osu.iOS.csproj index 1e9a21865d..ed6f52c60e 100644 --- a/osu.iOS/osu.iOS.csproj +++ b/osu.iOS/osu.iOS.csproj @@ -116,5 +116,9 @@ false + + + + From 493c095535c3acdddfada39ab29ecfd430c7afe9 Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Thu, 8 Apr 2021 20:28:23 -0400 Subject: [PATCH 1326/1791] Fixed code style --- osu.Android/OsuGameAndroid.cs | 2 +- osu.Desktop/OsuGameDesktop.cs | 3 +-- osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs | 6 +++++- osu.Game/OsuGameBase.cs | 4 ++-- osu.Game/Screens/Play/PlayerLoader.cs | 1 + osu.Game/Tests/OsuTestBrowser.cs | 3 ++- osu.Game/Utils/PowerStatus.cs | 1 + osu.iOS/OsuGameIOS.cs | 2 +- 8 files changed, 14 insertions(+), 8 deletions(-) diff --git a/osu.Android/OsuGameAndroid.cs b/osu.Android/OsuGameAndroid.cs index 4c82a175fc..eb0d499c8f 100644 --- a/osu.Android/OsuGameAndroid.cs +++ b/osu.Android/OsuGameAndroid.cs @@ -21,7 +21,7 @@ namespace osu.Android : base(null) { gameActivity = activity; - powerStatus = new AndroidPowerStatus(); + PowerStatus = new AndroidPowerStatus(); } public override Version AssemblyVersion diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index b6c57b219d..48e28fa251 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -22,7 +22,6 @@ using osu.Desktop.Windows; using osu.Framework.Threading; using osu.Game.IO; using osu.Game.Utils; -using osu.Framework.Allocation; namespace osu.Desktop { @@ -35,7 +34,7 @@ namespace osu.Desktop : base(args) { noVersionOverlay = args?.Any(a => a == "--no-version-overlay") ?? false; - powerStatus = new DefaultPowerStatus(); + PowerStatus = new DefaultPowerStatus(); } public override StableStorage GetStorageForStableInstall() diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index c5cc10993c..b885f0a9be 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -300,7 +300,11 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("reset notification lock", () => sessionStatics.GetBindable(Static.BatteryLowNotificationShownOnce).Value = false); // set charge status and level - AddStep("load player", () => resetPlayer(false, () => { powerStatus.IsCharging = isCharging; powerStatus.ChargeLevel = chargeLevel; })); + AddStep("load player", () => resetPlayer(false, () => + { + powerStatus.IsCharging = isCharging; + powerStatus.ChargeLevel = chargeLevel; + })); AddUntilStep("wait for player", () => player?.LoadState == LoadState.Ready); int notificationCount = !isCharging && chargeLevel <= powerStatus.BatteryCutoff ? 1 : 0; AddAssert("check for notification", () => notificationOverlay.UnreadCount.Value == notificationCount); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 53873b0127..a907afd8af 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -96,7 +96,7 @@ namespace osu.Game protected Storage Storage { get; set; } - protected PowerStatus powerStatus; + protected PowerStatus PowerStatus; [Cached] [Cached(typeof(IBindable))] @@ -333,7 +333,7 @@ namespace osu.Game Ruleset.BindValueChanged(onRulesetChanged); - dependencies.CacheAs(powerStatus); + dependencies.CacheAs(PowerStatus); } private void onRulesetChanged(ValueChangedEvent r) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 9710fa7e46..6ba99d65d5 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -477,6 +477,7 @@ namespace osu.Game.Screens.Play #endregion #region Low battery warning + private Bindable batteryWarningShownOnce; private void showBatteryWarningIfNeeded() diff --git a/osu.Game/Tests/OsuTestBrowser.cs b/osu.Game/Tests/OsuTestBrowser.cs index afce7dd38b..d4fa2a11d3 100644 --- a/osu.Game/Tests/OsuTestBrowser.cs +++ b/osu.Game/Tests/OsuTestBrowser.cs @@ -15,8 +15,9 @@ namespace osu.Game.Tests { public OsuTestBrowser() { - powerStatus = new DefaultPowerStatus(); + PowerStatus = new DefaultPowerStatus(); } + protected override void LoadComplete() { base.LoadComplete(); diff --git a/osu.Game/Utils/PowerStatus.cs b/osu.Game/Utils/PowerStatus.cs index 0657c98af1..1caed5b6fa 100644 --- a/osu.Game/Utils/PowerStatus.cs +++ b/osu.Game/Utils/PowerStatus.cs @@ -10,6 +10,7 @@ namespace osu.Game.Utils /// is sent. /// public virtual double BatteryCutoff { get; } = 0.2; + public virtual double ChargeLevel { get; set; } public virtual bool IsCharging { get; set; } } diff --git a/osu.iOS/OsuGameIOS.cs b/osu.iOS/OsuGameIOS.cs index 21dce338dc..d417fa8f8d 100644 --- a/osu.iOS/OsuGameIOS.cs +++ b/osu.iOS/OsuGameIOS.cs @@ -15,7 +15,7 @@ namespace osu.iOS public OsuGameIOS() : base(null) { - powerStatus = new IOSPowerStatus(); + PowerStatus = new IOSPowerStatus(); } public override Version AssemblyVersion => new Version(NSBundle.MainBundle.InfoDictionary["CFBundleVersion"].ToString()); From 6b6a71d3c38e47f2e6441cb6a03dcb04c43b51ac Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Thu, 8 Apr 2021 20:38:16 -0400 Subject: [PATCH 1327/1791] trim whitespace --- osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index b885f0a9be..2a0f46f5ff 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -301,7 +301,7 @@ namespace osu.Game.Tests.Visual.Gameplay // set charge status and level AddStep("load player", () => resetPlayer(false, () => - { + { powerStatus.IsCharging = isCharging; powerStatus.ChargeLevel = chargeLevel; })); From 59d13b0dd3faa9baca04c6664437294a2ed820f6 Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Thu, 8 Apr 2021 21:53:42 -0400 Subject: [PATCH 1328/1791] Fixed indentation sorry about the style fixes... I'm using JetBrains Rider from now on. --- osu.Android/OsuGameAndroid.cs | 1 + .../Visual/Gameplay/TestScenePlayerLoader.cs | 18 +++++++++--------- osu.Game/Screens/Play/PlayerLoader.cs | 1 + 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/osu.Android/OsuGameAndroid.cs b/osu.Android/OsuGameAndroid.cs index eb0d499c8f..a0c6a1e354 100644 --- a/osu.Android/OsuGameAndroid.cs +++ b/osu.Android/OsuGameAndroid.cs @@ -75,6 +75,7 @@ namespace osu.Android } protected override UpdateManager CreateUpdateManager() => new SimpleUpdateManager(); + public class AndroidPowerStatus : PowerStatus { public override double ChargeLevel => Battery.ChargeLevel; diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index 2a0f46f5ff..1377459c62 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -302,21 +302,21 @@ namespace osu.Game.Tests.Visual.Gameplay // set charge status and level AddStep("load player", () => resetPlayer(false, () => { - powerStatus.IsCharging = isCharging; - powerStatus.ChargeLevel = chargeLevel; + powerStatus.IsCharging = isCharging; + powerStatus.ChargeLevel = chargeLevel; })); AddUntilStep("wait for player", () => player?.LoadState == LoadState.Ready); int notificationCount = !isCharging && chargeLevel <= powerStatus.BatteryCutoff ? 1 : 0; AddAssert("check for notification", () => notificationOverlay.UnreadCount.Value == notificationCount); AddStep("click notification", () => - { - var scrollContainer = (OsuScrollContainer)notificationOverlay.Children.Last(); - var flowContainer = scrollContainer.Children.OfType>().First(); - var notification = flowContainer.First(); + { + var scrollContainer = (OsuScrollContainer)notificationOverlay.Children.Last(); + var flowContainer = scrollContainer.Children.OfType>().First(); + var notification = flowContainer.First(); - InputManager.MoveMouseTo(notification); - InputManager.Click(MouseButton.Left); - }); + InputManager.MoveMouseTo(notification); + InputManager.Click(MouseButton.Left); + }); AddUntilStep("wait for player load", () => player.IsLoaded); } diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 6ba99d65d5..e1ab0b8ef5 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -268,6 +268,7 @@ namespace osu.Game.Screens.Play } #endregion + protected override void Update() { base.Update(); From 8293b06c0afe9dfe16e1340f9ffab8ca1e737fd5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Apr 2021 13:56:55 +0900 Subject: [PATCH 1329/1791] Remove obsolete code --- osu.Game/Beatmaps/BeatmapConverter.cs | 34 +------------------ osu.Game/Beatmaps/BeatmapStatistic.cs | 12 ------- osu.Game/Overlays/Settings/SettingsItem.cs | 7 ---- osu.Game/Rulesets/Judgements/Judgement.cs | 12 ------- .../Objects/Drawables/DrawableHitObject.cs | 18 ---------- osu.Game/Rulesets/Objects/HitObject.cs | 9 ----- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 6 ---- 7 files changed, 1 insertion(+), 97 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs index b291edd19d..f3434c5153 100644 --- a/osu.Game/Beatmaps/BeatmapConverter.cs +++ b/osu.Game/Beatmaps/BeatmapConverter.cs @@ -27,8 +27,6 @@ namespace osu.Game.Beatmaps public IBeatmap Beatmap { get; } - private CancellationToken cancellationToken; - protected BeatmapConverter(IBeatmap beatmap, Ruleset ruleset) { Beatmap = beatmap; @@ -41,8 +39,6 @@ namespace osu.Game.Beatmaps public IBeatmap Convert(CancellationToken cancellationToken = default) { - this.cancellationToken = cancellationToken; - // We always operate on a clone of the original beatmap, to not modify it game-wide return ConvertBeatmap(Beatmap.Clone(), cancellationToken); } @@ -55,19 +51,6 @@ namespace osu.Game.Beatmaps /// The converted Beatmap. protected virtual Beatmap ConvertBeatmap(IBeatmap original, CancellationToken cancellationToken) { -#pragma warning disable 618 - return ConvertBeatmap(original); -#pragma warning restore 618 - } - - /// - /// Performs the conversion of a Beatmap using this Beatmap Converter. - /// - /// The un-converted Beatmap. - /// The converted Beatmap. - [Obsolete("Use the cancellation-supporting override")] // Can be removed 20210318 - protected virtual Beatmap ConvertBeatmap(IBeatmap original) - { var beatmap = CreateBeatmap(); beatmap.BeatmapInfo = original.BeatmapInfo; @@ -121,21 +104,6 @@ namespace osu.Game.Beatmaps /// The un-converted Beatmap. /// The cancellation token. /// The converted hit object. - protected virtual IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap, CancellationToken cancellationToken) - { -#pragma warning disable 618 - return ConvertHitObject(original, beatmap); -#pragma warning restore 618 - } - - /// - /// Performs the conversion of a hit object. - /// This method is generally executed sequentially for all objects in a beatmap. - /// - /// The hit object to convert. - /// The un-converted Beatmap. - /// The converted hit object. - [Obsolete("Use the cancellation-supporting override")] // Can be removed 20210318 - protected virtual IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap) => Enumerable.Empty(); + protected virtual IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap, CancellationToken cancellationToken) => Enumerable.Empty(); } } diff --git a/osu.Game/Beatmaps/BeatmapStatistic.cs b/osu.Game/Beatmaps/BeatmapStatistic.cs index 9d87a20d60..7d7ba09fcf 100644 --- a/osu.Game/Beatmaps/BeatmapStatistic.cs +++ b/osu.Game/Beatmaps/BeatmapStatistic.cs @@ -3,16 +3,11 @@ using System; using osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; -using osuTK; namespace osu.Game.Beatmaps { public class BeatmapStatistic { - [Obsolete("Use CreateIcon instead")] // can be removed 20210203 - public IconUsage Icon = FontAwesome.Regular.QuestionCircle; - /// /// A function to create the icon for display purposes. Use default icons available via whenever possible for conformity. /// @@ -20,12 +15,5 @@ namespace osu.Game.Beatmaps public string Content; public string Name; - - public BeatmapStatistic() - { -#pragma warning disable 618 - CreateIcon = () => new SpriteIcon { Icon = Icon, Scale = new Vector2(0.7f) }; -#pragma warning restore 618 - } } } diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index 85765bf991..0bd9750b0b 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -57,13 +57,6 @@ namespace osu.Game.Overlays.Settings } } - [Obsolete("Use Current instead")] // Can be removed 20210406 - public Bindable Bindable - { - get => Current; - set => Current = value; - } - public virtual Bindable Current { get => controlWithCurrent.Current; diff --git a/osu.Game/Rulesets/Judgements/Judgement.cs b/osu.Game/Rulesets/Judgements/Judgement.cs index b1ca72b1c0..be69db5ca8 100644 --- a/osu.Game/Rulesets/Judgements/Judgement.cs +++ b/osu.Game/Rulesets/Judgements/Judgement.cs @@ -28,18 +28,6 @@ namespace osu.Game.Rulesets.Judgements /// protected const double DEFAULT_MAX_HEALTH_INCREASE = 0.05; - /// - /// Whether this should affect the current combo. - /// - [Obsolete("Has no effect. Use HitResult members instead (e.g. use small-tick or bonus to not affect combo).")] // Can be removed 20210328 - public virtual bool AffectsCombo => true; - - /// - /// Whether this should be counted as base (combo) or bonus score. - /// - [Obsolete("Has no effect. Use HitResult members instead (e.g. use small-tick or bonus to not affect combo).")] // Can be removed 20210328 - public virtual bool IsBonus => !AffectsCombo; - /// /// The maximum that can be achieved. /// diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index e5eaf5db88..e69c4e2d91 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -736,24 +736,6 @@ namespace osu.Game.Rulesets.Objects.Drawables if (!Result.HasResult) throw new InvalidOperationException($"{GetType().ReadableName()} applied a {nameof(JudgementResult)} but did not update {nameof(JudgementResult.Type)}."); - // Some (especially older) rulesets use scorable judgements instead of the newer ignorehit/ignoremiss judgements. - // Can be removed 20210328 - if (Result.Judgement.MaxResult == HitResult.IgnoreHit) - { - HitResult originalType = Result.Type; - - if (Result.Type == HitResult.Miss) - Result.Type = HitResult.IgnoreMiss; - else if (Result.Type >= HitResult.Meh && Result.Type <= HitResult.Perfect) - Result.Type = HitResult.IgnoreHit; - - if (Result.Type != originalType) - { - Logger.Log($"{GetType().ReadableName()} applied an invalid hit result ({originalType}) when {nameof(HitResult.IgnoreMiss)} or {nameof(HitResult.IgnoreHit)} is expected.\n" - + $"This has been automatically adjusted to {Result.Type}, and support will be removed from 2021-03-28 onwards.", level: LogLevel.Important); - } - } - if (!Result.Type.IsValidHitResult(Result.Judgement.MinResult, Result.Judgement.MaxResult)) { throw new InvalidOperationException( diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 826d411822..db02eafa92 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -139,15 +139,6 @@ namespace osu.Game.Rulesets.Objects } protected virtual void CreateNestedHitObjects(CancellationToken cancellationToken) - { - // ReSharper disable once MethodSupportsCancellation (https://youtrack.jetbrains.com/issue/RIDER-44520) -#pragma warning disable 618 - CreateNestedHitObjects(); -#pragma warning restore 618 - } - - [Obsolete("Use the cancellation-supporting override")] // Can be removed 20210318 - protected virtual void CreateNestedHitObjects() { } diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index b81fa79345..0fb5c2f4b5 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -346,12 +346,6 @@ namespace osu.Game.Rulesets.Scoring score.HitEvents = hitEvents; } - - /// - /// Create a for this processor. - /// - [Obsolete("Method is now unused.")] // Can be removed 20210328 - public virtual HitWindows CreateHitWindows() => new HitWindows(); } public enum ScoringMode From 76981f25478029cf948324f9e4c2e9e780ce57e1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Apr 2021 13:58:24 +0900 Subject: [PATCH 1330/1791] Remove unused using --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index e69c4e2d91..6da9f12b50 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -11,7 +11,6 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; -using osu.Framework.Logging; using osu.Framework.Threading; using osu.Game.Audio; using osu.Game.Rulesets.Judgements; From 51fee79ef19946f48262bd14fce5c7bc5254bcdb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Apr 2021 15:18:02 +0900 Subject: [PATCH 1331/1791] Fix scores not being accepted due to missing ruleset ID --- osu.Game/Online/Solo/CreateSoloScoreRequest.cs | 6 +++++- osu.Game/Screens/Play/SoloPlayer.cs | 5 ++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Solo/CreateSoloScoreRequest.cs b/osu.Game/Online/Solo/CreateSoloScoreRequest.cs index dea5a77496..0d2bea1f2a 100644 --- a/osu.Game/Online/Solo/CreateSoloScoreRequest.cs +++ b/osu.Game/Online/Solo/CreateSoloScoreRequest.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 System.Net.Http; using osu.Framework.IO.Network; using osu.Game.Online.API; @@ -11,11 +12,13 @@ namespace osu.Game.Online.Solo public class CreateSoloScoreRequest : APIRequest { private readonly int beatmapId; + private readonly int rulesetId; private readonly string versionHash; - public CreateSoloScoreRequest(int beatmapId, string versionHash) + public CreateSoloScoreRequest(int beatmapId, int rulesetId, string versionHash) { this.beatmapId = beatmapId; + this.rulesetId = rulesetId; this.versionHash = versionHash; } @@ -24,6 +27,7 @@ namespace osu.Game.Online.Solo var req = base.CreateWebRequest(); req.Method = HttpMethod.Post; req.AddParameter("version_hash", versionHash); + req.AddParameter("ruleset_id", rulesetId.ToString(CultureInfo.InvariantCulture)); return req; } diff --git a/osu.Game/Screens/Play/SoloPlayer.cs b/osu.Game/Screens/Play/SoloPlayer.cs index ee1ccdc5b3..d0ef4131dc 100644 --- a/osu.Game/Screens/Play/SoloPlayer.cs +++ b/osu.Game/Screens/Play/SoloPlayer.cs @@ -17,7 +17,10 @@ namespace osu.Game.Screens.Play if (!(Beatmap.Value.BeatmapInfo.OnlineBeatmapID is int beatmapId)) return null; - return new CreateSoloScoreRequest(beatmapId, Game.VersionHash); + if (!(Ruleset.Value.ID is int rulesetId)) + return null; + + return new CreateSoloScoreRequest(beatmapId, rulesetId, Game.VersionHash); } protected override bool HandleTokenRetrievalFailure(Exception exception) => false; From f2e811928bb30d2b4c5a69bfcce67f5dbac7a68e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Apr 2021 15:28:08 +0900 Subject: [PATCH 1332/1791] Rework slider hackery to not overwrite Samples --- .../Objects/Drawables/DrawableSlider.cs | 14 +++++++++++--- osu.Game.Rulesets.Osu/Objects/Slider.cs | 12 +++++------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index e29e28b1fa..82b5492de6 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -109,15 +109,23 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override void LoadSamples() { - base.LoadSamples(); + // Note: base.LoadSamples() isn't called since the slider plays the tail's hitsounds for the time being. + + if (HitObject.SampleControlPoint == null) + { + throw new InvalidOperationException($"{nameof(HitObject)}s must always have an attached {nameof(HitObject.SampleControlPoint)}." + + $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}."); + } + + Samples.Samples = HitObject.TailSamples.Select(s => HitObject.SampleControlPoint.ApplyTo(s)).Cast().ToArray(); var slidingSamples = new List(); - var normalSample = HitObject.OriginalSamples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL); + var normalSample = HitObject.Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL); if (normalSample != null) slidingSamples.Add(HitObject.SampleControlPoint.ApplyTo(normalSample).With("sliderslide")); - var whistleSample = HitObject.OriginalSamples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_WHISTLE); + var whistleSample = HitObject.Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_WHISTLE); if (whistleSample != null) slidingSamples.Add(HitObject.SampleControlPoint.ApplyTo(whistleSample).With("sliderwhistle")); diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 4e71d82cab..8ba9597dc3 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -82,7 +82,7 @@ namespace osu.Game.Rulesets.Osu.Objects public List> NodeSamples { get; set; } = new List>(); [JsonIgnore] - public IList OriginalSamples { get; private set; } + public IList TailSamples { get; private set; } private int repeatCount; @@ -146,12 +146,6 @@ namespace osu.Game.Rulesets.Osu.Objects Velocity = scoringDistance / timingPoint.BeatLength; TickDistance = scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier; - - // The samples should be attached to the slider tail, however this can only be done after LegacyLastTick is removed otherwise they would play earlier than they're intended to. - // For now, the samples are attached to and played by the slider itself at the correct end time. - // ToArray call is required as GetNodeSamples may fallback to Samples itself (without it it will get cleared due to the list reference being live). - OriginalSamples = Samples.ToList(); - Samples = this.GetNodeSamples(repeatCount + 1).ToArray(); } protected override void CreateNestedHitObjects(CancellationToken cancellationToken) @@ -242,6 +236,10 @@ namespace osu.Game.Rulesets.Osu.Objects if (HeadCircle != null) HeadCircle.Samples = this.GetNodeSamples(0); + + // The samples should be attached to the slider tail, however this can only be done after LegacyLastTick is removed otherwise they would play earlier than they're intended to. + // For now, the samples are played by the slider itself at the correct end time. + TailSamples = this.GetNodeSamples(repeatCount + 1); } public override Judgement CreateJudgement() => OnlyJudgeNestedObjects ? new OsuIgnoreJudgement() : new OsuJudgement(); From 9b0ce2999ffe05292826c048942058da5d1679d4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Apr 2021 15:28:42 +0900 Subject: [PATCH 1333/1791] Fix legacy encoder --- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index da44b96ed3..acbf57d25f 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -273,7 +273,7 @@ namespace osu.Game.Beatmaps.Formats if (hitObject is IHasPath path) { addPathData(writer, path, position); - writer.Write(getSampleBank(hitObject.Samples, zeroBanks: true)); + writer.Write(getSampleBank(hitObject.Samples)); } else { @@ -420,15 +420,15 @@ namespace osu.Game.Beatmaps.Formats writer.Write(FormattableString.Invariant($"{endTimeData.EndTime}{suffix}")); } - private string getSampleBank(IList samples, bool banksOnly = false, bool zeroBanks = false) + private string getSampleBank(IList samples, bool banksOnly = false) { LegacySampleBank normalBank = toLegacySampleBank(samples.SingleOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL)?.Bank); LegacySampleBank addBank = toLegacySampleBank(samples.FirstOrDefault(s => !string.IsNullOrEmpty(s.Name) && s.Name != HitSampleInfo.HIT_NORMAL)?.Bank); StringBuilder sb = new StringBuilder(); - sb.Append(FormattableString.Invariant($"{(zeroBanks ? 0 : (int)normalBank)}:")); - sb.Append(FormattableString.Invariant($"{(zeroBanks ? 0 : (int)addBank)}")); + sb.Append(FormattableString.Invariant($"{(int)normalBank}:")); + sb.Append(FormattableString.Invariant($"{(int)addBank}")); if (!banksOnly) { From b10ee7482d8b72986448b2c33ec81d898df16556 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 25 Dec 2020 15:43:50 +0900 Subject: [PATCH 1334/1791] Add a failing test to check catch replay accuracy --- .../TestSceneCatchReplay.cs | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 osu.Game.Rulesets.Catch.Tests/TestSceneCatchReplay.cs diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchReplay.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchReplay.cs new file mode 100644 index 0000000000..a10371b0f7 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchReplay.cs @@ -0,0 +1,53 @@ +// 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.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.UI; + +namespace osu.Game.Rulesets.Catch.Tests +{ + public class TestSceneCatchReplay : TestSceneCatchPlayer + { + protected override bool Autoplay => true; + + private const int object_count = 10; + + [Test] + public void TestReplayCatcherPositionIsFramePerfect() + { + AddUntilStep("caught all fruits", () => Player.ScoreProcessor.Combo.Value == object_count); + } + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) + { + var beatmap = new Beatmap + { + BeatmapInfo = + { + Ruleset = ruleset, + } + }; + + beatmap.ControlPointInfo.Add(0, new TimingControlPoint()); + + for (int i = 0; i < object_count / 2; i++) + { + beatmap.HitObjects.Add(new Fruit + { + StartTime = (i + 1) * 1000, + X = 0 + }); + beatmap.HitObjects.Add(new Fruit + { + StartTime = (i + 1) * 1000 + 1, + X = CatchPlayfield.WIDTH + }); + } + + return beatmap; + } + } +} From 6d0dc62502ecd82dd409b85ffcfdb6bee1a61f2a Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 9 Apr 2021 16:04:45 +0900 Subject: [PATCH 1335/1791] Make sure latest catcher position is used for catching logic A replay frame processed in CatchInputManager is applied to catcher in `CatcherArea`. The catcher position is then used for the catching logic for each hit object under `HitObjectContainer`. Thus, if `HitObjectContainer` came before `CatcherArea`, the replay input is delayed one frame. That was one reason why the catch autoplay misses hit objects (especially when fast-forwarded). --- osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index 73420a9eda..0e1ef90737 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -51,8 +51,11 @@ namespace osu.Game.Rulesets.Catch.UI { droppedObjectContainer, CatcherArea.MovableCatcher.CreateProxiedContent(), - HitObjectContainer, + HitObjectContainer.CreateProxy(), + // This ordering (`CatcherArea` before `HitObjectContainer`) is important to + // make sure the up-to-date catcher position is used for the catcher catching logic of hit objects. CatcherArea, + HitObjectContainer, }; } From bb15baf118f6e49306acc9688cd6790b39dc102c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Apr 2021 17:31:14 +0900 Subject: [PATCH 1336/1791] Add initial multiplayer spectator leaderboard --- .../MultiplayerSpectatorLeaderboard.cs | 82 +++++++++++++++++++ .../HUD/MultiplayerGameplayLeaderboard.cs | 19 +++-- 2 files changed, 94 insertions(+), 7 deletions(-) create mode 100644 osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorLeaderboard.cs diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorLeaderboard.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorLeaderboard.cs new file mode 100644 index 0000000000..35c3471ce2 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorLeaderboard.cs @@ -0,0 +1,82 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Timing; +using osu.Game.Online.Spectator; +using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.Play.HUD; + +namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate +{ + public class MultiplayerSpectatorLeaderboard : MultiplayerGameplayLeaderboard + { + private readonly Dictionary trackedData = new Dictionary(); + + public MultiplayerSpectatorLeaderboard(ScoreProcessor scoreProcessor, int[] userIds) + : base(scoreProcessor, userIds) + { + } + + public void AddSource(int userId, IClock source) => trackedData[userId] = new TrackedUserData(source); + + public void RemoveSource(int userId, IClock source) => trackedData.Remove(userId); + + protected override void OnIncomingFrames(int userId, FrameDataBundle bundle) + { + if (!trackedData.TryGetValue(userId, out var data)) + return; + + data.Frames.Add(new TimedFrameHeader(bundle.Frames.First().Time, bundle.Header)); + } + + protected override void Update() + { + base.Update(); + + foreach (var (userId, data) in trackedData) + { + var targetTime = data.Clock.CurrentTime; + + int frameIndex = data.Frames.BinarySearch(new TimedFrameHeader(targetTime)); + if (frameIndex < 0) + frameIndex = ~frameIndex; + frameIndex = Math.Clamp(frameIndex - 1, 0, data.Frames.Count - 1); + + SetCurrentFrame(userId, data.Frames[frameIndex].Header); + } + } + + private class TrackedUserData + { + public readonly IClock Clock; + public readonly List Frames = new List(); + + public TrackedUserData(IClock clock) + { + Clock = clock; + } + } + + private class TimedFrameHeader : IComparable + { + public readonly double Time; + public readonly FrameHeader Header; + + public TimedFrameHeader(double time) + { + Time = time; + } + + public TimedFrameHeader(double time, FrameHeader header) + { + Time = time; + Header = header; + } + + public int CompareTo(TimedFrameHeader other) => Time.CompareTo(other.Time); + } + } +} diff --git a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs index a3d27c4e71..65ad8be3f0 100644 --- a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs @@ -20,7 +20,6 @@ namespace osu.Game.Screens.Play.HUD public class MultiplayerGameplayLeaderboard : GameplayLeaderboard { private readonly ScoreProcessor scoreProcessor; - private readonly Dictionary userScores = new Dictionary(); [Resolved] @@ -116,13 +115,19 @@ namespace osu.Game.Screens.Play.HUD trackedData.UpdateScore(scoreProcessor, mode.NewValue); } - private void handleIncomingFrames(int userId, FrameDataBundle bundle) + private void handleIncomingFrames(int userId, FrameDataBundle bundle) => Schedule(() => { - if (userScores.TryGetValue(userId, out var trackedData)) - { - trackedData.LastHeader = bundle.Header; - trackedData.UpdateScore(scoreProcessor, scoringMode.Value); - } + if (userScores.ContainsKey(userId)) + OnIncomingFrames(userId, bundle); + }); + + protected virtual void OnIncomingFrames(int userId, FrameDataBundle bundle) => SetCurrentFrame(userId, bundle.Header); + + protected void SetCurrentFrame(int userId, FrameHeader header) + { + var trackedScore = userScores[userId]; + trackedScore.LastHeader = header; + trackedScore.UpdateScore(scoreProcessor, scoringMode.Value); } protected override void Dispose(bool isDisposing) From 0af6d77192e3af39430098a2dc35c26cdde55d69 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Fri, 9 Apr 2021 11:03:38 +0200 Subject: [PATCH 1337/1791] Test for path type transfer --- .../TestScenePathControlPointVisualiser.cs | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs index 440ab7c889..35b79aa8ac 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs @@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertControlPointPathType(0, PathType.Bezier); assertControlPointPathType(1, PathType.PerfectCurve); - AddAssert("point 3 is not inherited", () => slider.Path.ControlPoints[3].Type != null); + assertControlPointPathType(3, PathType.Bezier); } [Test] @@ -105,6 +105,27 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddAssert("point 3 is not inherited", () => slider.Path.ControlPoints[3].Type != null); } + [Test] + public void TestPerfectCurveTooManyPointsLinear() + { + createVisualiser(true); + + addControlPointStep(new Vector2(200), PathType.Linear); + addControlPointStep(new Vector2(300)); + addControlPointStep(new Vector2(500, 300)); + addControlPointStep(new Vector2(700, 200)); + addControlPointStep(new Vector2(500, 100)); + + // Must be both hovering and selecting the control point for the context menu to work. + moveMouseToControlPoint(1); + AddStep("select control point", () => visualiser.Pieces[1].IsSelected.Value = true); + addContextMenuItemStep("Perfect curve"); + + assertControlPointPathType(0, PathType.Linear); + assertControlPointPathType(1, PathType.PerfectCurve); + assertControlPointPathType(3, PathType.Linear); + } + [Test] public void TestPerfectCurveChangeToBezier() { From f64b2095bf6c06de31b2cc0ca7d9b888dc101347 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Fri, 9 Apr 2021 11:04:00 +0200 Subject: [PATCH 1338/1791] Carry over the previous path type --- .../Sliders/Components/PathControlPointVisualiser.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 1dfbf97682..44c3056910 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -169,11 +169,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components case PathType.PerfectCurve: // Can't always create a circular arc out of 4 or more points, // so we split the segment into one 3-point circular arc segment - // and one bezier segment. + // and one segment of the previous type. int thirdPointIndex = indexInSegment + 2; if (piece.PointsInSegment.Count > thirdPointIndex + 1) - piece.PointsInSegment[thirdPointIndex].Type.Value = PathType.Bezier; + piece.PointsInSegment[thirdPointIndex].Type.Value = piece.PointsInSegment[0].Type.Value; break; } From 3b86f0eb2f55839c4301cba1aefcc971ba205149 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Apr 2021 18:15:23 +0900 Subject: [PATCH 1339/1791] Fix exception with 0 frames --- .../Multiplayer/Spectate/MultiplayerSpectatorLeaderboard.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorLeaderboard.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorLeaderboard.cs index 35c3471ce2..08db5befa4 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorLeaderboard.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorLeaderboard.cs @@ -40,6 +40,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { var targetTime = data.Clock.CurrentTime; + if (data.Frames.Count == 0) + continue; + int frameIndex = data.Frames.BinarySearch(new TimedFrameHeader(targetTime)); if (frameIndex < 0) frameIndex = ~frameIndex; From 90e243eea5404e1632fc3f63c679c8fc38874c60 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Apr 2021 18:15:27 +0900 Subject: [PATCH 1340/1791] Rename methods --- .../Multiplayer/Spectate/MultiplayerSpectatorLeaderboard.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorLeaderboard.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorLeaderboard.cs index 08db5befa4..f16869b43d 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorLeaderboard.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorLeaderboard.cs @@ -20,9 +20,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { } - public void AddSource(int userId, IClock source) => trackedData[userId] = new TrackedUserData(source); + public void AddClock(int userId, IClock source) => trackedData[userId] = new TrackedUserData(source); - public void RemoveSource(int userId, IClock source) => trackedData.Remove(userId); + public void RemoveClock(int userId, IClock source) => trackedData.Remove(userId); protected override void OnIncomingFrames(int userId, FrameDataBundle bundle) { From 589ce4bdc2b09c7f96d99582aea7e7f8ef430eb8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Apr 2021 18:16:10 +0900 Subject: [PATCH 1341/1791] Add test --- ...estSceneMultiplayerSpectatorLeaderboard.cs | 207 ++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100644 osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectatorLeaderboard.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectatorLeaderboard.cs new file mode 100644 index 0000000000..57e9fb37cc --- /dev/null +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectatorLeaderboard.cs @@ -0,0 +1,207 @@ +// 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 System.Threading; +using System.Threading.Tasks; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Framework.Timing; +using osu.Framework.Utils; +using osu.Game.Database; +using osu.Game.Online; +using osu.Game.Online.Spectator; +using osu.Game.Replays.Legacy; +using osu.Game.Rulesets.Osu.Scoring; +using osu.Game.Scoring; +using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate; +using osu.Game.Screens.Play.HUD; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual.Multiplayer +{ + public class TestSceneMultiplayerSpectatorLeaderboard : MultiplayerTestScene + { + [Cached(typeof(SpectatorStreamingClient))] + private TestSpectatorStreamingClient streamingClient = new TestSpectatorStreamingClient(); + + [Cached(typeof(UserLookupCache))] + private UserLookupCache lookupCache = new TestUserLookupCache(); + + protected override Container Content => content; + private readonly Container content; + + private readonly Dictionary clocks = new Dictionary + { + { 55, new ManualClock() }, + { 56, new ManualClock() } + }; + + public TestSceneMultiplayerSpectatorLeaderboard() + { + base.Content.AddRange(new Drawable[] + { + streamingClient, + lookupCache, + content = new Container { RelativeSizeAxes = Axes.Both } + }); + } + + [SetUpSteps] + public new void SetUpSteps() + { + MultiplayerSpectatorLeaderboard leaderboard = null; + + AddStep("reset", () => + { + Clear(); + + foreach (var (userId, clock) in clocks) + { + streamingClient.EndPlay(userId, 0); + clock.CurrentTime = 0; + } + }); + + AddStep("create leaderboard", () => + { + foreach (var (userId, _) in clocks) + streamingClient.StartPlay(userId, 0); + + Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value); + + var playable = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value); + var scoreProcessor = new OsuScoreProcessor(); + scoreProcessor.ApplyBeatmap(playable); + + LoadComponentAsync(leaderboard = new MultiplayerSpectatorLeaderboard(scoreProcessor, clocks.Keys.ToArray()) { Expanded = { Value = true } }, Add); + }); + + AddUntilStep("wait for load", () => leaderboard.IsLoaded); + + AddStep("add clock sources", () => + { + foreach (var (userId, clock) in clocks) + leaderboard.AddClock(userId, clock); + }); + } + + [Test] + public void TestLeaderboardTracksCurrentTime() + { + AddStep("send frames", () => + { + // For user 55, send 100 frames in sets of 1. + // For user 56, send 100 frames in sets of 10. + for (int i = 0; i < 100; i++) + { + streamingClient.SendFrames(55, i, 1); + + if (i % 10 == 0) + streamingClient.SendFrames(56, i, 10); + } + }); + + assertCombo(55, 1); + assertCombo(56, 10); + + setTime(500); + assertCombo(55, 5); + assertCombo(56, 10); + + setTime(1100); + assertCombo(55, 11); + assertCombo(56, 20); + } + + private void setTime(double time) => AddStep($"set time {time}", () => + { + foreach (var (_, clock) in clocks) + clock.CurrentTime = time; + }); + + private void assertCombo(int userId, int expectedCombo) + => AddUntilStep($"player {userId} has {expectedCombo} combo", () => this.ChildrenOfType().Single(s => s.User?.Id == userId).Combo.Value == expectedCombo); + + private class TestSpectatorStreamingClient : SpectatorStreamingClient + { + private readonly Dictionary userBeatmapDictionary = new Dictionary(); + private readonly Dictionary userSentStateDictionary = new Dictionary(); + + public TestSpectatorStreamingClient() + : base(new DevelopmentEndpointConfiguration()) + { + } + + public void StartPlay(int userId, int beatmapId) + { + userBeatmapDictionary[userId] = beatmapId; + userSentStateDictionary[userId] = false; + sendState(userId, beatmapId); + } + + public void EndPlay(int userId, int beatmapId) + { + ((ISpectatorClient)this).UserFinishedPlaying(userId, new SpectatorState + { + BeatmapID = beatmapId, + RulesetID = 0, + }); + userSentStateDictionary[userId] = false; + } + + public void SendFrames(int userId, int index, int count) + { + var frames = new List(); + + for (int i = index; i < index + count; i++) + { + var buttonState = i == index + count - 1 ? ReplayButtonState.None : ReplayButtonState.Left1; + frames.Add(new LegacyReplayFrame(i * 100, RNG.Next(0, 512), RNG.Next(0, 512), buttonState)); + } + + var bundle = new FrameDataBundle(new ScoreInfo { Combo = index + count }, frames); + ((ISpectatorClient)this).UserSentFrames(userId, bundle); + if (!userSentStateDictionary[userId]) + sendState(userId, userBeatmapDictionary[userId]); + } + + public override void WatchUser(int userId) + { + if (userSentStateDictionary[userId]) + { + // usually the server would do this. + sendState(userId, userBeatmapDictionary[userId]); + } + + base.WatchUser(userId); + } + + private void sendState(int userId, int beatmapId) + { + ((ISpectatorClient)this).UserBeganPlaying(userId, new SpectatorState + { + BeatmapID = beatmapId, + RulesetID = 0, + }); + userSentStateDictionary[userId] = true; + } + } + + private class TestUserLookupCache : UserLookupCache + { + protected override Task ComputeValueAsync(int lookup, CancellationToken token = default) + { + return Task.FromResult(new User + { + Id = lookup, + Username = $"User {lookup}" + }); + } + } + } +} From b49997f531dad5e80f50fa6cbe1f4349a99be38f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Apr 2021 18:18:23 +0900 Subject: [PATCH 1342/1791] Add test for no frames --- .../TestSceneMultiplayerSpectatorLeaderboard.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectatorLeaderboard.cs index 57e9fb37cc..53efb4392f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectatorLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectatorLeaderboard.cs @@ -118,6 +118,13 @@ namespace osu.Game.Tests.Visual.Multiplayer assertCombo(56, 20); } + [Test] + public void TestNoFrames() + { + assertCombo(55, 0); + assertCombo(56, 1); + } + private void setTime(double time) => AddStep($"set time {time}", () => { foreach (var (_, clock) in clocks) From 9ddcd686ac09e3f0aa35c0f5a9c9ae01e7e44f0e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Apr 2021 18:23:38 +0900 Subject: [PATCH 1343/1791] Fix incorrect assert --- .../Multiplayer/TestSceneMultiplayerSpectatorLeaderboard.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectatorLeaderboard.cs index 53efb4392f..8589cffcc9 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectatorLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectatorLeaderboard.cs @@ -122,7 +122,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestNoFrames() { assertCombo(55, 0); - assertCombo(56, 1); + assertCombo(56, 0); } private void setTime(double time) => AddStep($"set time {time}", () => From e73f3f52d7730854a9f360dc2f1ae7df0ec834ea Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Apr 2021 18:23:41 +0900 Subject: [PATCH 1344/1791] Add some more asserts --- ...estSceneMultiplayerSpectatorLeaderboard.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectatorLeaderboard.cs index 8589cffcc9..3b2cfb1c7b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectatorLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectatorLeaderboard.cs @@ -95,8 +95,8 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("send frames", () => { - // For user 55, send 100 frames in sets of 1. - // For user 56, send 100 frames in sets of 10. + // For user 55, send frames in sets of 1. + // For user 56, send frames in sets of 10. for (int i = 0; i < 100; i++) { streamingClient.SendFrames(55, i, 1); @@ -109,13 +109,25 @@ namespace osu.Game.Tests.Visual.Multiplayer assertCombo(55, 1); assertCombo(56, 10); + // Advance to a point where only user 55's frame changes. setTime(500); assertCombo(55, 5); assertCombo(56, 10); + // Advance to a point where both user's frame changes. setTime(1100); assertCombo(55, 11); assertCombo(56, 20); + + // Advance user 56 only to a point where its frame changes. + setTime(56, 2100); + assertCombo(55, 11); + assertCombo(56, 30); + + // Advance both users beyond their last frame + setTime(101 * 100); + assertCombo(55, 100); + assertCombo(56, 100); } [Test] @@ -131,6 +143,9 @@ namespace osu.Game.Tests.Visual.Multiplayer clock.CurrentTime = time; }); + private void setTime(int userId, double time) + => AddStep($"set user {userId} time {time}", () => clocks[userId].CurrentTime = time); + private void assertCombo(int userId, int expectedCombo) => AddUntilStep($"player {userId} has {expectedCombo} combo", () => this.ChildrenOfType().Single(s => s.User?.Id == userId).Combo.Value == expectedCombo); From 7cbc8f2695b4868fa4be7a9b41ebd8ef42b1b0d4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Apr 2021 18:29:02 +0900 Subject: [PATCH 1345/1791] Add some xmldocs --- .../Play/HUD/MultiplayerGameplayLeaderboard.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs index 65ad8be3f0..f0d2ac4b7f 100644 --- a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs @@ -121,8 +121,21 @@ namespace osu.Game.Screens.Play.HUD OnIncomingFrames(userId, bundle); }); + /// + /// Invoked when new frames have arrived for a user. + /// + /// + /// By default, this immediately sets the current frame to be displayed for the user. + /// + /// The user which the frames arrived for. + /// The bundle of frames. protected virtual void OnIncomingFrames(int userId, FrameDataBundle bundle) => SetCurrentFrame(userId, bundle.Header); + /// + /// Sets the current frame to be displayed for a user. + /// + /// The user to set the frame of. + /// The frame to set. protected void SetCurrentFrame(int userId, FrameHeader header) { var trackedScore = userScores[userId]; From d2c37e6cf8828e5793c9aef36b76fd6ce986b537 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Apr 2021 18:41:58 +0900 Subject: [PATCH 1346/1791] Remove unnecessary parameter --- .../Multiplayer/Spectate/MultiplayerSpectatorLeaderboard.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorLeaderboard.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorLeaderboard.cs index f16869b43d..aa9f162036 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorLeaderboard.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorLeaderboard.cs @@ -22,7 +22,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public void AddClock(int userId, IClock source) => trackedData[userId] = new TrackedUserData(source); - public void RemoveClock(int userId, IClock source) => trackedData.Remove(userId); + public void RemoveClock(int userId) => trackedData.Remove(userId); protected override void OnIncomingFrames(int userId, FrameDataBundle bundle) { From 8a0da06e89eed9d93c32db7b045e6bd3c021853b Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 9 Apr 2021 22:40:21 +0900 Subject: [PATCH 1347/1791] Add a hover sample type for SongSelect buttons --- osu.Game/Graphics/UserInterface/HoverSounds.cs | 5 ++++- osu.Game/Screens/Select/FooterButton.cs | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/HoverSounds.cs b/osu.Game/Graphics/UserInterface/HoverSounds.cs index 00cf5798e7..49bcc3759b 100644 --- a/osu.Game/Graphics/UserInterface/HoverSounds.cs +++ b/osu.Game/Graphics/UserInterface/HoverSounds.cs @@ -53,6 +53,9 @@ namespace osu.Game.Graphics.UserInterface Soft, [Description("-toolbar")] - Toolbar + Toolbar, + + [Description("-songselect")] + SongSelect } } diff --git a/osu.Game/Screens/Select/FooterButton.cs b/osu.Game/Screens/Select/FooterButton.cs index 7528651fd9..afb3943a09 100644 --- a/osu.Game/Screens/Select/FooterButton.cs +++ b/osu.Game/Screens/Select/FooterButton.cs @@ -14,6 +14,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Containers; using osu.Game.Input.Bindings; using osu.Framework.Input.Bindings; +using osu.Game.Graphics.UserInterface; namespace osu.Game.Screens.Select { @@ -65,6 +66,7 @@ namespace osu.Game.Screens.Select private readonly Box light; public FooterButton() + : base(HoverSampleSet.SongSelect) { AutoSizeAxes = Axes.Both; Shear = SHEAR; From ffacd38e573bb0f7b01a531bfde6c1ca9383ac96 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 9 Apr 2021 22:41:51 +0900 Subject: [PATCH 1348/1791] Reduce the randomised pitch range of hover sounds --- osu.Game/Graphics/UserInterface/HoverSounds.cs | 2 +- osu.Game/Screens/Select/Carousel/CarouselHeader.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/HoverSounds.cs b/osu.Game/Graphics/UserInterface/HoverSounds.cs index 00cf5798e7..4ba79236d9 100644 --- a/osu.Game/Graphics/UserInterface/HoverSounds.cs +++ b/osu.Game/Graphics/UserInterface/HoverSounds.cs @@ -36,7 +36,7 @@ namespace osu.Game.Graphics.UserInterface public override void PlayHoverSample() { - sampleHover.Frequency.Value = 0.96 + RNG.NextDouble(0.08); + sampleHover.Frequency.Value = 0.98 + RNG.NextDouble(0.04); sampleHover.Play(); } } diff --git a/osu.Game/Screens/Select/Carousel/CarouselHeader.cs b/osu.Game/Screens/Select/Carousel/CarouselHeader.cs index 2fbf64de29..40ca3e0764 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselHeader.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselHeader.cs @@ -152,7 +152,7 @@ namespace osu.Game.Screens.Select.Carousel { if (sampleHover == null) return; - sampleHover.Frequency.Value = 0.90 + RNG.NextDouble(0.2); + sampleHover.Frequency.Value = 0.99 + RNG.NextDouble(0.02); sampleHover.Play(); } } From bfd3d0cce978c77d4bfa7bbe1a15262e4cc23c80 Mon Sep 17 00:00:00 2001 From: Samuel Cattini-Schultz Date: Sat, 10 Apr 2021 01:16:54 +1000 Subject: [PATCH 1349/1791] Implement custom enumerator for ReverseQueue to avoid allocations --- .../Rulesets/Difficulty/Utils/ReverseQueue.cs | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Difficulty/Utils/ReverseQueue.cs b/osu.Game/Rulesets/Difficulty/Utils/ReverseQueue.cs index f104b8105a..57db9df3ca 100644 --- a/osu.Game/Rulesets/Difficulty/Utils/ReverseQueue.cs +++ b/osu.Game/Rulesets/Difficulty/Utils/ReverseQueue.cs @@ -101,12 +101,33 @@ namespace osu.Game.Rulesets.Difficulty.Utils /// /// Returns an enumerator which enumerates items in the starting from the most recently enqueued item. /// - public IEnumerator GetEnumerator() - { - for (int i = Count - 1; i >= 0; i--) - yield return items[(start + i) % capacity]; - } + public IEnumerator GetEnumerator() => new Enumerator(this); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public struct Enumerator : IEnumerator + { + private ReverseQueue reverseQueue; + private int currentIndex; + + internal Enumerator(ReverseQueue reverseQueue) + { + this.reverseQueue = reverseQueue; + currentIndex = -1; // The first MoveNext() should bring the iterator to 0 + } + + public bool MoveNext() => ++currentIndex < reverseQueue.Count; + + public void Reset() => currentIndex = -1; + + public readonly T Current => reverseQueue[currentIndex]; + + readonly object IEnumerator.Current => Current; + + public void Dispose() + { + reverseQueue = null; + } + } } } From affc878db9ff3a0e322dc5d25c8bfcbb76d98a33 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 10 Apr 2021 01:03:15 +0900 Subject: [PATCH 1350/1791] Update resources --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index cba3975209..31308b80d8 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 292e5b932f..16b888a064 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -30,7 +30,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 36e581a80c..f72a1eb256 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + From b66ef2fdec193cc0227e974c56de92937aabf73b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 10 Apr 2021 02:14:28 +0900 Subject: [PATCH 1351/1791] 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 31308b80d8..196d122a2a 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 16b888a064..71a6f0e5cd 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 f72a1eb256..a389cc13dd 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 08311abc5ec93907337976e0d8ae0209684ef769 Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Fri, 9 Apr 2021 17:55:41 -0400 Subject: [PATCH 1352/1791] Remove setters, cache CreatePowerStatus() and use a dummy LocalPowerStatus class in test scene --- osu.Android/OsuGameAndroid.cs | 7 ++- osu.Desktop/OsuGameDesktop.cs | 2 - .../Visual/Gameplay/TestScenePlayerLoader.cs | 44 +++++++++++++++---- osu.Game/Configuration/SessionStatics.cs | 4 +- osu.Game/OsuGame.cs | 5 +++ osu.Game/OsuGameBase.cs | 4 +- osu.Game/Screens/Play/PlayerLoader.cs | 11 ++--- osu.Game/Tests/OsuTestBrowser.cs | 6 --- osu.Game/Utils/PowerStatus.cs | 26 ++++++----- osu.iOS/OsuGameIOS.cs | 12 ++--- 10 files changed, 74 insertions(+), 47 deletions(-) diff --git a/osu.Android/OsuGameAndroid.cs b/osu.Android/OsuGameAndroid.cs index a0c6a1e354..02f4fa1ad6 100644 --- a/osu.Android/OsuGameAndroid.cs +++ b/osu.Android/OsuGameAndroid.cs @@ -21,7 +21,6 @@ namespace osu.Android : base(null) { gameActivity = activity; - PowerStatus = new AndroidPowerStatus(); } public override Version AssemblyVersion @@ -76,8 +75,12 @@ namespace osu.Android protected override UpdateManager CreateUpdateManager() => new SimpleUpdateManager(); - public class AndroidPowerStatus : PowerStatus + protected override PowerStatus CreatePowerStatus() => new AndroidPowerStatus(); + + private class AndroidPowerStatus : PowerStatus { + public override double BatteryCutoff => 0.20; + public override double ChargeLevel => Battery.ChargeLevel; public override bool IsCharging => Battery.PowerSource != BatteryPowerSource.Battery; diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 48e28fa251..0c21c75290 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -21,7 +21,6 @@ using osu.Game.Updater; using osu.Desktop.Windows; using osu.Framework.Threading; using osu.Game.IO; -using osu.Game.Utils; namespace osu.Desktop { @@ -34,7 +33,6 @@ namespace osu.Desktop : base(args) { noVersionOverlay = args?.Any(a => a == "--no-version-overlay") ?? false; - PowerStatus = new DefaultPowerStatus(); } public override StableStorage GetStorageForStableInstall() diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index 1377459c62..36958b0741 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -49,8 +49,8 @@ namespace osu.Game.Tests.Visual.Gameplay [Cached] private readonly VolumeOverlay volumeOverlay; - [Resolved] - private PowerStatus powerStatus { get; set; } + [Cached(typeof(PowerStatus))] + private readonly LocalPowerStatus powerStatus = new LocalPowerStatus(); private readonly ChangelogOverlay changelogOverlay; @@ -292,22 +292,22 @@ namespace osu.Game.Tests.Visual.Gameplay } } - [TestCase(false, 1.0)] // not charging, full battery --> no warning + [TestCase(false, 1.0)] // not charging, above cutoff --> no warning [TestCase(false, 0.2)] // not charging, at cutoff --> warning - [TestCase(false, 0.1)] // charging, below cutoff --> warning + [TestCase(true, 0.1)] // charging, below cutoff --> no warning public void TestLowBatteryNotification(bool isCharging, double chargeLevel) { - AddStep("reset notification lock", () => sessionStatics.GetBindable(Static.BatteryLowNotificationShownOnce).Value = false); + AddStep("reset notification lock", () => sessionStatics.GetBindable(Static.LowBatteryNotificationShownOnce).Value = false); // set charge status and level AddStep("load player", () => resetPlayer(false, () => { - powerStatus.IsCharging = isCharging; - powerStatus.ChargeLevel = chargeLevel; + powerStatus.SetCharging(isCharging); + powerStatus.SetChargeLevel(chargeLevel); })); AddUntilStep("wait for player", () => player?.LoadState == LoadState.Ready); - int notificationCount = !isCharging && chargeLevel <= powerStatus.BatteryCutoff ? 1 : 0; - AddAssert("check for notification", () => notificationOverlay.UnreadCount.Value == notificationCount); + int warning = !isCharging && chargeLevel <= powerStatus.BatteryCutoff ? 1 : 0; + AddAssert($"notification {(warning == 1 ? "triggered" : "not triggered")}", () => notificationOverlay.UnreadCount.Value == warning); AddStep("click notification", () => { var scrollContainer = (OsuScrollContainer)notificationOverlay.Children.Last(); @@ -380,5 +380,31 @@ namespace osu.Game.Tests.Visual.Gameplay throw new TimeoutException(); } } + + /// + /// Mutable dummy PowerStatus class for + /// + /// + private class LocalPowerStatus : PowerStatus + { + private bool isCharging = true; + private double chargeLevel = 1; + + public override double BatteryCutoff => 0.2; + + public override bool IsCharging => isCharging; + + public override double ChargeLevel => chargeLevel; + + public void SetCharging(bool value) + { + isCharging = value; + } + + public void SetChargeLevel(double value) + { + chargeLevel = value; + } + } } } diff --git a/osu.Game/Configuration/SessionStatics.cs b/osu.Game/Configuration/SessionStatics.cs index e8ee04731c..71e1a1efcc 100644 --- a/osu.Game/Configuration/SessionStatics.cs +++ b/osu.Game/Configuration/SessionStatics.cs @@ -16,7 +16,7 @@ namespace osu.Game.Configuration { SetDefault(Static.LoginOverlayDisplayed, false); SetDefault(Static.MutedAudioNotificationShownOnce, false); - SetDefault(Static.BatteryLowNotificationShownOnce, false); + SetDefault(Static.LowBatteryNotificationShownOnce, false); SetDefault(Static.LastHoverSoundPlaybackTime, (double?)null); SetDefault(Static.SeasonalBackgrounds, null); } @@ -26,7 +26,7 @@ namespace osu.Game.Configuration { LoginOverlayDisplayed, MutedAudioNotificationShownOnce, - BatteryLowNotificationShownOnce, + LowBatteryNotificationShownOnce, /// /// Info about seasonal backgrounds available fetched from API - see . diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 809e5d3c1b..277455b7d3 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -694,6 +694,11 @@ namespace osu.Game loadComponentSingleFile(new AccountCreationOverlay(), topMostOverlayContent.Add, true); loadComponentSingleFile(new DialogOverlay(), topMostOverlayContent.Add, true); + if (CreatePowerStatus() != null) + { + dependencies.CacheAs(CreatePowerStatus()); + } + chatOverlay.State.ValueChanged += state => channelManager.HighPollRate.Value = state.NewValue == Visibility.Visible; Add(externalLinkOpener = new ExternalLinkOpener()); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index a907afd8af..1b10de614a 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -96,7 +96,7 @@ namespace osu.Game protected Storage Storage { get; set; } - protected PowerStatus PowerStatus; + protected virtual PowerStatus CreatePowerStatus() => null; [Cached] [Cached(typeof(IBindable))] @@ -332,8 +332,6 @@ namespace osu.Game dependencies.CacheAs(MusicController); Ruleset.BindValueChanged(onRulesetChanged); - - dependencies.CacheAs(PowerStatus); } private void onRulesetChanged(ValueChangedEvent r) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index e1ab0b8ef5..d342bf8273 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -113,7 +113,7 @@ namespace osu.Game.Screens.Play [Resolved] private AudioManager audioManager { get; set; } - [Resolved] + [Resolved(CanBeNull = true)] private PowerStatus powerStatus { get; set; } public PlayerLoader(Func createPlayer) @@ -125,7 +125,7 @@ namespace osu.Game.Screens.Play private void load(SessionStatics sessionStatics) { muteWarningShownOnce = sessionStatics.GetBindable(Static.MutedAudioNotificationShownOnce); - batteryWarningShownOnce = sessionStatics.GetBindable(Static.BatteryLowNotificationShownOnce); + batteryWarningShownOnce = sessionStatics.GetBindable(Static.LowBatteryNotificationShownOnce); InternalChild = (content = new LogoTrackingContainer { @@ -483,10 +483,11 @@ namespace osu.Game.Screens.Play private void showBatteryWarningIfNeeded() { + if (powerStatus == null) return; + if (!batteryWarningShownOnce.Value) { - // Checks if the notification has not been shown yet, device is unplugged and if device battery is at or below the cutoff - if (!powerStatus.IsCharging && powerStatus.ChargeLevel <= powerStatus.BatteryCutoff) + if (powerStatus.IsLowBattery) { notificationOverlay?.Post(new BatteryWarningNotification()); batteryWarningShownOnce.Value = true; @@ -500,7 +501,7 @@ namespace osu.Game.Screens.Play public BatteryWarningNotification() { - Text = "Your battery level is low! Charge your device to prevent interruptions."; + Text = "Your battery level is low! Charge your device to prevent interruptions during gameplay."; } [BackgroundDependencyLoader] diff --git a/osu.Game/Tests/OsuTestBrowser.cs b/osu.Game/Tests/OsuTestBrowser.cs index d4fa2a11d3..71b0b02fa6 100644 --- a/osu.Game/Tests/OsuTestBrowser.cs +++ b/osu.Game/Tests/OsuTestBrowser.cs @@ -7,17 +7,11 @@ using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Graphics; using osu.Game.Screens.Backgrounds; -using osu.Game.Utils; namespace osu.Game.Tests { public class OsuTestBrowser : OsuGameBase { - public OsuTestBrowser() - { - PowerStatus = new DefaultPowerStatus(); - } - protected override void LoadComplete() { base.LoadComplete(); diff --git a/osu.Game/Utils/PowerStatus.cs b/osu.Game/Utils/PowerStatus.cs index 1caed5b6fa..77d531710e 100644 --- a/osu.Game/Utils/PowerStatus.cs +++ b/osu.Game/Utils/PowerStatus.cs @@ -3,21 +3,27 @@ namespace osu.Game.Utils { + /// + /// Provides access to the system's power status. + /// Currently implemented on iOS and Android only. + /// public abstract class PowerStatus { /// - /// The maximum battery level before a warning notification - /// is sent. + /// The maximum battery level considered as low, from 0 to 1. /// - public virtual double BatteryCutoff { get; } = 0.2; + public virtual double BatteryCutoff { get; } = 0; - public virtual double ChargeLevel { get; set; } - public virtual bool IsCharging { get; set; } - } + /// + /// The charge level of the battery, from 0 to 1. + /// + public virtual double ChargeLevel { get; } = 0; - public class DefaultPowerStatus : PowerStatus - { - public override double ChargeLevel { get; set; } = 1; - public override bool IsCharging { get; set; } = true; + public virtual bool IsCharging { get; } = false; + + /// + /// Returns true if = false and <= . + /// + public bool IsLowBattery => !IsCharging && ChargeLevel <= BatteryCutoff; } } diff --git a/osu.iOS/OsuGameIOS.cs b/osu.iOS/OsuGameIOS.cs index d417fa8f8d..b53b594eae 100644 --- a/osu.iOS/OsuGameIOS.cs +++ b/osu.iOS/OsuGameIOS.cs @@ -12,20 +12,16 @@ namespace osu.iOS { public class OsuGameIOS : OsuGame { - public OsuGameIOS() - : base(null) - { - PowerStatus = new IOSPowerStatus(); - } public override Version AssemblyVersion => new Version(NSBundle.MainBundle.InfoDictionary["CFBundleVersion"].ToString()); protected override UpdateManager CreateUpdateManager() => new SimpleUpdateManager(); - public class IOSPowerStatus : PowerStatus + protected override PowerStatus CreatePowerStatus() => new IOSPowerStatus(); + + private class IOSPowerStatus : PowerStatus { - // The low battery alert appears at 20% on iOS - // https://github.com/ppy/osu/issues/12239 public override double BatteryCutoff => 0.25; + public override double ChargeLevel => Battery.ChargeLevel; public override bool IsCharging => Battery.PowerSource != BatteryPowerSource.Battery; From 43174b708c007e7bf8156fd3f6a01a6f8313a024 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 10 Apr 2021 12:58:40 +0200 Subject: [PATCH 1353/1791] Remove visibility settings Can look into this later, not really important for a first iteration. --- osu.Game/Screens/Edit/Verify/IssueSettings.cs | 1 - .../Screens/Edit/Verify/VisibilitySettings.cs | 52 ------------------- 2 files changed, 53 deletions(-) delete mode 100644 osu.Game/Screens/Edit/Verify/VisibilitySettings.cs diff --git a/osu.Game/Screens/Edit/Verify/IssueSettings.cs b/osu.Game/Screens/Edit/Verify/IssueSettings.cs index 608be877de..4519231cd2 100644 --- a/osu.Game/Screens/Edit/Verify/IssueSettings.cs +++ b/osu.Game/Screens/Edit/Verify/IssueSettings.cs @@ -41,7 +41,6 @@ namespace osu.Game.Screens.Edit.Verify private IReadOnlyList createSections() => new Drawable[] { - new VisibilitySettings() }; } } diff --git a/osu.Game/Screens/Edit/Verify/VisibilitySettings.cs b/osu.Game/Screens/Edit/Verify/VisibilitySettings.cs deleted file mode 100644 index 6488c616e4..0000000000 --- a/osu.Game/Screens/Edit/Verify/VisibilitySettings.cs +++ /dev/null @@ -1,52 +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.Graphics.UserInterfaceV2; -using osuTK; - -namespace osu.Game.Screens.Edit.Verify -{ - internal class VisibilitySettings : CompositeDrawable - { - [BackgroundDependencyLoader] - private void load() - { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - - Padding = new MarginPadding(10); - - InternalChildren = new Drawable[] - { - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(10), - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - new LabelledSwitchButton - { - Label = "Show problems", - Current = new Bindable(true) - }, - new LabelledSwitchButton - { - Label = "Show warnings", - Current = new Bindable(true) - }, - new LabelledSwitchButton - { - Label = "Show negligibles" - } - } - }, - }; - } - } -} From d1007ff26aa9b2f9827e666125d0161dba6fd35f Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 10 Apr 2021 13:02:22 +0200 Subject: [PATCH 1354/1791] Move components to more appropriate spot --- osu.Game.Rulesets.Osu/Edit/OsuChecker.cs | 2 +- osu.Game/Rulesets/Edit/Checker.cs | 4 +-- .../Edit/Checks}/Components/BeatmapCheck.cs | 2 +- .../Edit/Checks}/Components/Check.cs | 2 +- .../Edit/Checks}/Components/CheckMetadata.cs | 2 +- .../Edit/Checks}/Components/Issue.cs | 25 +++++++++++-------- .../Edit/Checks}/Components/IssueTemplate.cs | 2 +- osu.Game/Screens/Edit/Verify/IssueTable.cs | 2 +- 8 files changed, 22 insertions(+), 19 deletions(-) rename osu.Game/{Screens/Edit/Verify => Rulesets/Edit/Checks}/Components/BeatmapCheck.cs (92%) rename osu.Game/{Screens/Edit/Verify => Rulesets/Edit/Checks}/Components/Check.cs (96%) rename osu.Game/{Screens/Edit/Verify => Rulesets/Edit/Checks}/Components/CheckMetadata.cs (97%) rename osu.Game/{Screens/Edit/Verify => Rulesets/Edit/Checks}/Components/Issue.cs (79%) rename osu.Game/{Screens/Edit/Verify => Rulesets/Edit/Checks}/Components/IssueTemplate.cs (98%) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuChecker.cs b/osu.Game.Rulesets.Osu/Edit/OsuChecker.cs index 9918b53c85..df3847f2fe 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuChecker.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuChecker.cs @@ -5,8 +5,8 @@ using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Checks.Components; using osu.Game.Rulesets.Osu.Edit.Checks; -using osu.Game.Screens.Edit.Verify.Components; namespace osu.Game.Rulesets.Osu.Edit { diff --git a/osu.Game/Rulesets/Edit/Checker.cs b/osu.Game/Rulesets/Edit/Checker.cs index 1c267c3435..9bebfc0668 100644 --- a/osu.Game/Rulesets/Edit/Checker.cs +++ b/osu.Game/Rulesets/Edit/Checker.cs @@ -4,8 +4,8 @@ using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; -using osu.Game.Checks; -using osu.Game.Screens.Edit.Verify.Components; +using osu.Game.Rulesets.Edit.Checks; +using osu.Game.Rulesets.Edit.Checks.Components; namespace osu.Game.Rulesets.Edit { diff --git a/osu.Game/Screens/Edit/Verify/Components/BeatmapCheck.cs b/osu.Game/Rulesets/Edit/Checks/Components/BeatmapCheck.cs similarity index 92% rename from osu.Game/Screens/Edit/Verify/Components/BeatmapCheck.cs rename to osu.Game/Rulesets/Edit/Checks/Components/BeatmapCheck.cs index 7297dab60d..d0f93b5eef 100644 --- a/osu.Game/Screens/Edit/Verify/Components/BeatmapCheck.cs +++ b/osu.Game/Rulesets/Edit/Checks/Components/BeatmapCheck.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using osu.Game.Beatmaps; -namespace osu.Game.Screens.Edit.Verify.Components +namespace osu.Game.Rulesets.Edit.Checks.Components { public abstract class BeatmapCheck : Check { diff --git a/osu.Game/Screens/Edit/Verify/Components/Check.cs b/osu.Game/Rulesets/Edit/Checks/Components/Check.cs similarity index 96% rename from osu.Game/Screens/Edit/Verify/Components/Check.cs rename to osu.Game/Rulesets/Edit/Checks/Components/Check.cs index 2ae21fd350..228c11f0f3 100644 --- a/osu.Game/Screens/Edit/Verify/Components/Check.cs +++ b/osu.Game/Rulesets/Edit/Checks/Components/Check.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace osu.Game.Screens.Edit.Verify.Components +namespace osu.Game.Rulesets.Edit.Checks.Components { public abstract class Check { diff --git a/osu.Game/Screens/Edit/Verify/Components/CheckMetadata.cs b/osu.Game/Rulesets/Edit/Checks/Components/CheckMetadata.cs similarity index 97% rename from osu.Game/Screens/Edit/Verify/Components/CheckMetadata.cs rename to osu.Game/Rulesets/Edit/Checks/Components/CheckMetadata.cs index 1cac99d47d..5a226729ad 100644 --- a/osu.Game/Screens/Edit/Verify/Components/CheckMetadata.cs +++ b/osu.Game/Rulesets/Edit/Checks/Components/CheckMetadata.cs @@ -1,7 +1,7 @@ // 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.Edit.Verify +namespace osu.Game.Rulesets.Edit.Checks.Components { public class CheckMetadata { diff --git a/osu.Game/Screens/Edit/Verify/Components/Issue.cs b/osu.Game/Rulesets/Edit/Checks/Components/Issue.cs similarity index 79% rename from osu.Game/Screens/Edit/Verify/Components/Issue.cs rename to osu.Game/Rulesets/Edit/Checks/Components/Issue.cs index fe81cb9335..0f835202e7 100644 --- a/osu.Game/Screens/Edit/Verify/Components/Issue.cs +++ b/osu.Game/Rulesets/Edit/Checks/Components/Issue.cs @@ -7,7 +7,7 @@ using System.Linq; using osu.Game.Extensions; using osu.Game.Rulesets.Objects; -namespace osu.Game.Screens.Edit.Verify.Components +namespace osu.Game.Rulesets.Edit.Checks.Components { public class Issue { @@ -39,7 +39,7 @@ namespace osu.Game.Screens.Edit.Verify.Components public Issue(IssueTemplate template, params object[] args) { Time = null; - HitObjects = System.Array.Empty(); + HitObjects = Array.Empty(); Template = template; Arguments = args; @@ -57,11 +57,20 @@ namespace osu.Game.Screens.Edit.Verify.Components Time = time; } + public Issue(HitObject hitObject, IssueTemplate template, params object[] args) + : this(template, args) + { + Time = hitObject.StartTime; + HitObjects = new[] { hitObject }; + } + public Issue(IEnumerable hitObjects, IssueTemplate template, params object[] args) : this(template, args) { - Time = hitObjects.FirstOrDefault()?.StartTime; - HitObjects = hitObjects.ToArray(); + var hitObjectList = hitObjects.ToList(); + + Time = hitObjectList.FirstOrDefault()?.StartTime; + HitObjects = hitObjectList; } public override string ToString() @@ -71,13 +80,7 @@ namespace osu.Game.Screens.Edit.Verify.Components public string GetEditorTimestamp() { - // TODO: Editor timestamp formatting is handled in https://github.com/ppy/osu/pull/12030 - // We may be able to use that here too (if we decouple it from the HitObjectComposer class). - - if (Time == null) - return string.Empty; - - return Time.Value.ToEditorFormattedString(); + return Time == null ? string.Empty : Time.Value.ToEditorFormattedString(); } } } diff --git a/osu.Game/Screens/Edit/Verify/Components/IssueTemplate.cs b/osu.Game/Rulesets/Edit/Checks/Components/IssueTemplate.cs similarity index 98% rename from osu.Game/Screens/Edit/Verify/Components/IssueTemplate.cs rename to osu.Game/Rulesets/Edit/Checks/Components/IssueTemplate.cs index b178fa7122..7eaabdc59b 100644 --- a/osu.Game/Screens/Edit/Verify/Components/IssueTemplate.cs +++ b/osu.Game/Rulesets/Edit/Checks/Components/IssueTemplate.cs @@ -5,7 +5,7 @@ using Humanizer; using osu.Framework.Graphics; using osuTK.Graphics; -namespace osu.Game.Screens.Edit.Verify.Components +namespace osu.Game.Rulesets.Edit.Checks.Components { public class IssueTemplate { diff --git a/osu.Game/Screens/Edit/Verify/IssueTable.cs b/osu.Game/Screens/Edit/Verify/IssueTable.cs index c70695a849..20dceb5333 100644 --- a/osu.Game/Screens/Edit/Verify/IssueTable.cs +++ b/osu.Game/Screens/Edit/Verify/IssueTable.cs @@ -13,7 +13,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Input.Bindings; -using osu.Game.Screens.Edit.Verify.Components; +using osu.Game.Rulesets.Edit.Checks.Components; using osuTK.Graphics; namespace osu.Game.Screens.Edit.Verify From b30e41b805ffb7196851c42fc7c8b36c2828df0d Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 10 Apr 2021 13:02:36 +0200 Subject: [PATCH 1355/1791] Fix comment; mode -> ruleset --- osu.Game/Rulesets/Edit/Checker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/Checker.cs b/osu.Game/Rulesets/Edit/Checker.cs index 9bebfc0668..6687160b10 100644 --- a/osu.Game/Rulesets/Edit/Checker.cs +++ b/osu.Game/Rulesets/Edit/Checker.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Edit { public abstract class Checker { - // These are all mode-invariant, hence here instead of in e.g. `OsuChecker`. + // These are all ruleset-invariant, hence here instead of in e.g. `OsuChecker`. private readonly List beatmapChecks = new List { new CheckMetadataVowels() From bc4f3351f38bdf5d7e5812db554ceceeaa6ec2cc Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 10 Apr 2021 13:03:16 +0200 Subject: [PATCH 1356/1791] Replace checks with realistic ones --- .../Edit/Checks/CheckConsecutiveCircles.cs | 86 -------------- .../Edit/Checks/CheckOffscreenObjects.cs | 110 ++++++++++++++++++ osu.Game.Rulesets.Osu/Edit/OsuChecker.cs | 2 +- osu.Game/Checks/CheckMetadataVowels.cs | 65 ----------- osu.Game/Rulesets/Edit/Checker.cs | 2 +- .../Rulesets/Edit/Checks/CheckBackground.cs | 57 +++++++++ 6 files changed, 169 insertions(+), 153 deletions(-) delete mode 100644 osu.Game.Rulesets.Osu/Edit/Checks/CheckConsecutiveCircles.cs create mode 100644 osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs delete mode 100644 osu.Game/Checks/CheckMetadataVowels.cs create mode 100644 osu.Game/Rulesets/Edit/Checks/CheckBackground.cs diff --git a/osu.Game.Rulesets.Osu/Edit/Checks/CheckConsecutiveCircles.cs b/osu.Game.Rulesets.Osu/Edit/Checks/CheckConsecutiveCircles.cs deleted file mode 100644 index c41c0dac2b..0000000000 --- a/osu.Game.Rulesets.Osu/Edit/Checks/CheckConsecutiveCircles.cs +++ /dev/null @@ -1,86 +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 System.Linq; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Screens.Edit.Verify; -using osu.Game.Screens.Edit.Verify.Components; - -namespace osu.Game.Rulesets.Osu.Edit.Checks -{ - public class CheckConsecutiveCircles : BeatmapCheck - { - private const double consecutive_threshold = 3; - private const double delta_time_min_expected = 300; - private const double delta_time_min_threshold = 100; - - public override CheckMetadata Metadata() => new CheckMetadata - ( - category: CheckMetadata.CheckCategory.Spread, - description: "Too many or fast consecutive circles." - ); - - private IssueTemplate templateManyInARow = new IssueTemplate - ( - type: IssueTemplate.IssueType.Problem, - unformattedMessage: "There are {0} circles in a row here, expected at most {1}." - ); - - private IssueTemplate templateTooFast = new IssueTemplate - ( - type: IssueTemplate.IssueType.Warning, - unformattedMessage: "These circles are too fast ({0:0} ms), expected at least {1:0} ms." - ); - - private IssueTemplate templateAlmostTooFast = new IssueTemplate - ( - type: IssueTemplate.IssueType.Negligible, - unformattedMessage: "These circles are almost too fast ({0:0} ms), expected at least {1:0} ms." - ); - - public override IEnumerable Templates() => new[] - { - templateManyInARow, - templateTooFast, - templateAlmostTooFast - }; - - public override IEnumerable Run(IBeatmap beatmap) - { - List prevCircles = new List(); - - foreach (HitObject hitobject in beatmap.HitObjects) - { - if (!(hitobject is HitCircle circle) || hitobject == beatmap.HitObjects.Last()) - { - if (prevCircles.Count > consecutive_threshold) - { - yield return new Issue( - prevCircles, - templateManyInARow, - prevCircles.Count, consecutive_threshold - ); - } - - prevCircles.Clear(); - continue; - } - - double? prevDeltaTime = circle.StartTime - prevCircles.LastOrDefault()?.StartTime; - prevCircles.Add(circle); - - if (prevDeltaTime == null || prevDeltaTime >= delta_time_min_expected) - continue; - - yield return new Issue( - prevCircles.TakeLast(2), - prevDeltaTime < delta_time_min_threshold ? templateTooFast : templateAlmostTooFast, - prevDeltaTime, delta_time_min_expected - ); - } - } - } -} diff --git a/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs b/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs new file mode 100644 index 0000000000..c47864855b --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs @@ -0,0 +1,110 @@ +// 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.Rulesets.Edit.Checks.Components; +using osu.Game.Rulesets.Osu.Objects; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Edit.Checks +{ + public class CheckOffscreenObjects : BeatmapCheck + { + // These are close approximates to the edges of the screen + // in gameplay on a 4:3 aspect ratio for osu!stable. + private const int min_x = -67; + private const int min_y = -60; + private const int max_x = 579; + private const int max_y = 428; + + // The amount of milliseconds to step through a slider path at a time + // (higher = more performant, but higher false-negative chance). + private const int path_step_size = 5; + + public override CheckMetadata Metadata() => new CheckMetadata + ( + category: CheckMetadata.CheckCategory.Compose, + description: "Offscreen hitobjects." + ); + + public override IEnumerable Templates() => new[] + { + templateOffscreen, + templateOffscreenSliderPath + }; + + private readonly IssueTemplate templateOffscreen = new IssueTemplate + ( + type: IssueTemplate.IssueType.Problem, + unformattedMessage: "This object goes offscreen on a 4:3 aspect ratio." + ); + + private readonly IssueTemplate templateOffscreenSliderPath = new IssueTemplate + ( + type: IssueTemplate.IssueType.Problem, + unformattedMessage: "This slider goes offscreen here on a 4:3 aspect ratio." + ); + + public override IEnumerable Run(IBeatmap beatmap) + { + foreach (var hitobject in beatmap.HitObjects) + { + switch (hitobject) + { + case Slider slider: + { + foreach (var issue in sliderIssues(slider)) + yield return issue; + + break; + } + + case HitCircle circle: + { + if (isOffscreen(circle.StackedPosition, circle.Radius)) + yield return new Issue(circle, templateOffscreen); + + break; + } + } + } + } + + /// + /// Steps through points on the slider to ensure the entire path is on-screen. + /// Returns at most one issue. + /// + /// The slider whose path to check. + /// + private IEnumerable sliderIssues(Slider slider) + { + for (int i = 0; i < slider.Distance; i += path_step_size) + { + double progress = i / slider.Distance; + Vector2 position = slider.StackedPositionAt(progress); + + if (!isOffscreen(position, slider.Radius)) + continue; + + // `SpanDuration` ensures we don't include reverses. + double time = slider.StartTime + progress * slider.SpanDuration; + yield return new Issue(slider, templateOffscreenSliderPath) { Time = time }; + + yield break; + } + + // Above loop may skip the last position in the slider due to step size. + if (!isOffscreen(slider.StackedEndPosition, slider.Radius)) + yield break; + + yield return new Issue(slider, templateOffscreenSliderPath) { Time = slider.EndTime }; + } + + private bool isOffscreen(Vector2 position, double radius) + { + return position.X - radius < min_x || position.X + radius > max_x || + position.Y - radius < min_y || position.Y + radius > max_y; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/OsuChecker.cs b/osu.Game.Rulesets.Osu/Edit/OsuChecker.cs index df3847f2fe..ee3b230679 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuChecker.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuChecker.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Edit { public readonly List beatmapChecks = new List { - new CheckConsecutiveCircles() + new CheckOffscreenObjects() }; public override IEnumerable Run(IBeatmap beatmap) diff --git a/osu.Game/Checks/CheckMetadataVowels.cs b/osu.Game/Checks/CheckMetadataVowels.cs deleted file mode 100644 index 8bcfe89c3a..0000000000 --- a/osu.Game/Checks/CheckMetadataVowels.cs +++ /dev/null @@ -1,65 +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 System.Linq; -using osu.Game.Beatmaps; -using osu.Game.Screens.Edit.Verify; -using osu.Game.Screens.Edit.Verify.Components; - -namespace osu.Game.Checks -{ - public class CheckMetadataVowels : BeatmapCheck - { - private static readonly char[] vowels = { 'a', 'e', 'i', 'o', 'u' }; - - public override CheckMetadata Metadata() => new CheckMetadata - ( - category: CheckMetadata.CheckCategory.Metadata, - description: "Metadata fields contain vowels" - ); - - public override IEnumerable Templates() => new[] - { - templateArtistHasVowels - }; - - private IssueTemplate templateArtistHasVowels = new IssueTemplate - ( - type: IssueTemplate.IssueType.Warning, - unformattedMessage: "The {0} field \"{1}\" contains the vowel(s) {2}." - ); - - public override IEnumerable Run(IBeatmap beatmap) - { - foreach (var issue in GetVowelIssues("artist", beatmap.Metadata.Artist)) - yield return issue; - - foreach (var issue in GetVowelIssues("unicode artist", beatmap.Metadata.ArtistUnicode)) - yield return issue; - - foreach (var issue in GetVowelIssues("title", beatmap.Metadata.Title)) - yield return issue; - - foreach (var issue in GetVowelIssues("unicode title", beatmap.Metadata.TitleUnicode)) - yield return issue; - } - - private IEnumerable GetVowelIssues(string fieldName, string fieldValue) - { - if (fieldValue == null) - // Unicode fields can be null if same as respective romanized fields. - yield break; - - List matches = vowels.Where(c => fieldValue.ToLower().Contains(c)).ToList(); - - if (!matches.Any()) - yield break; - - yield return new Issue( - templateArtistHasVowels, - fieldName, fieldValue, string.Join(", ", matches) - ); - } - } -} diff --git a/osu.Game/Rulesets/Edit/Checker.cs b/osu.Game/Rulesets/Edit/Checker.cs index 6687160b10..65d7fc5913 100644 --- a/osu.Game/Rulesets/Edit/Checker.cs +++ b/osu.Game/Rulesets/Edit/Checker.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Edit // These are all ruleset-invariant, hence here instead of in e.g. `OsuChecker`. private readonly List beatmapChecks = new List { - new CheckMetadataVowels() + new CheckBackground() }; public virtual IEnumerable Run(IBeatmap beatmap) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs b/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs new file mode 100644 index 0000000000..9376f9568a --- /dev/null +++ b/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs @@ -0,0 +1,57 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit.Checks.Components; + +namespace osu.Game.Rulesets.Edit.Checks +{ + public class CheckBackground : BeatmapCheck + { + public override CheckMetadata Metadata() => new CheckMetadata + ( + category: CheckMetadata.CheckCategory.Resources, + description: "Missing background." + ); + + public override IEnumerable Templates() => new[] + { + templateNoneSet, + templateDoesNotExist + }; + + private readonly IssueTemplate templateNoneSet = new IssueTemplate + ( + type: IssueTemplate.IssueType.Problem, + unformattedMessage: "No background has been set." + ); + + private readonly IssueTemplate templateDoesNotExist = new IssueTemplate + ( + type: IssueTemplate.IssueType.Problem, + unformattedMessage: "The background file \"{0}\" is does not exist." + ); + + public override IEnumerable Run(IBeatmap beatmap) + { + if (beatmap.Metadata.BackgroundFile == null) + { + yield return new Issue(templateNoneSet); + + yield break; + } + + // If the background is set, also make sure it still exists. + + var set = beatmap.BeatmapInfo.BeatmapSet; + var file = set.Files.FirstOrDefault(f => f.Filename == beatmap.Metadata.BackgroundFile); + + if (file != null) + yield break; + + yield return new Issue(templateDoesNotExist, beatmap.Metadata.BackgroundFile); + } + } +} From 6d3cf78e4a4e50bd40c2b5f667d7e805e5bb93e5 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 10 Apr 2021 13:04:39 +0200 Subject: [PATCH 1357/1791] Add issue selection This mainly helps with keeping track of which issue was clicked, since doing so switches tab. --- osu.Game/Screens/Edit/Verify/IssueTable.cs | 42 ++++++++++++++++++-- osu.Game/Screens/Edit/Verify/VerifyScreen.cs | 7 ++++ 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Verify/IssueTable.cs b/osu.Game/Screens/Edit/Verify/IssueTable.cs index 20dceb5333..458b0184b6 100644 --- a/osu.Game/Screens/Edit/Verify/IssueTable.cs +++ b/osu.Game/Screens/Edit/Verify/IssueTable.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -26,6 +27,9 @@ namespace osu.Game.Screens.Edit.Verify private readonly FillFlowContainer backgroundFlow; + [Resolved] + private Bindable selectedIssue { get; set; } + public IssueTable() { RelativeSizeAxes = Axes.X; @@ -115,6 +119,7 @@ namespace osu.Game.Screens.Edit.Verify public class RowBackground : OsuClickableContainer { + private readonly Issue issue; private const int fade_duration = 100; private readonly Box hoveredBackground; @@ -128,13 +133,16 @@ namespace osu.Game.Screens.Edit.Verify [Resolved] private EditorBeatmap editorBeatmap { get; set; } + [Resolved] + private Bindable selectedIssue { get; set; } + public RowBackground(Issue issue) { + this.issue = issue; + RelativeSizeAxes = Axes.X; Height = row_height; - AlwaysPresent = true; - CornerRadius = 3; Masking = true; @@ -152,6 +160,8 @@ namespace osu.Game.Screens.Edit.Verify // Supposed to work like clicking timestamps outside of the game. // TODO: Is there already defined behaviour for this I may be able to call? + selectedIssue.Value = issue; + if (issue.Time != null) { clock.Seek(issue.Time.Value); @@ -167,11 +177,35 @@ namespace osu.Game.Screens.Edit.Verify } private Color4 colourHover; + private Color4 colourSelected; [BackgroundDependencyLoader] private void load(OsuColour colours) { hoveredBackground.Colour = colourHover = colours.BlueDarker; + colourSelected = colours.YellowDarker; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + selectedIssue.BindValueChanged(change => { Selected = issue == change.NewValue; }, true); + } + + private bool selected; + + protected bool Selected + { + get => selected; + set + { + if (value == selected) + return; + + selected = value; + updateState(); + } } protected override bool OnHover(HoverEvent e) @@ -188,9 +222,9 @@ namespace osu.Game.Screens.Edit.Verify private void updateState() { - hoveredBackground.FadeColour(colourHover, 450, Easing.OutQuint); + hoveredBackground.FadeColour(selected ? colourSelected : colourHover, 450, Easing.OutQuint); - if (IsHovered) + if (selected || IsHovered) hoveredBackground.FadeIn(fade_duration, Easing.OutQuint); else hoveredBackground.FadeOut(fade_duration, Easing.OutQuint); diff --git a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs index 88397fdbff..ea9f986eb6 100644 --- a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs +++ b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs @@ -13,6 +13,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Checks.Components; using osuTK; namespace osu.Game.Screens.Edit.Verify @@ -22,6 +23,9 @@ namespace osu.Game.Screens.Edit.Verify private Ruleset ruleset; private static Checker checker; // TODO: Should not be static, but apparently needs to be? + [Cached] + private Bindable selectedIssue = new Bindable(); + public VerifyScreen() : base(EditorScreenMode.Verify) { @@ -74,6 +78,9 @@ namespace osu.Game.Screens.Edit.Verify [Resolved] protected EditorBeatmap Beatmap { get; private set; } + [Resolved] + private Bindable selectedIssue { get; set; } + [BackgroundDependencyLoader] private void load(OsuColour colours) { From c995eca029f5f126e734507018b62fb3ea717bb5 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 10 Apr 2021 13:05:24 +0200 Subject: [PATCH 1358/1791] Remove todo Doesn't really matter in the end, as only one checker will run at a time in this case. --- osu.Game/Screens/Edit/Verify/VerifyScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs index ea9f986eb6..b9fd93ac19 100644 --- a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs +++ b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs @@ -21,7 +21,7 @@ namespace osu.Game.Screens.Edit.Verify public class VerifyScreen : EditorScreen { private Ruleset ruleset; - private static Checker checker; // TODO: Should not be static, but apparently needs to be? + private static Checker checker; [Cached] private Bindable selectedIssue = new Bindable(); From 3a4f2e3d7e393d29d1f0f4ad8dcbf989f4f19073 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 10 Apr 2021 13:09:16 +0200 Subject: [PATCH 1359/1791] Show table even if no issues --- osu.Game/Screens/Edit/Verify/IssueTable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Verify/IssueTable.cs b/osu.Game/Screens/Edit/Verify/IssueTable.cs index 458b0184b6..e4612927fd 100644 --- a/osu.Game/Screens/Edit/Verify/IssueTable.cs +++ b/osu.Game/Screens/Edit/Verify/IssueTable.cs @@ -57,7 +57,7 @@ namespace osu.Game.Screens.Edit.Verify Content = null; backgroundFlow.Clear(); - if (value?.Any() != true) + if (value == null) return; foreach (var issue in value) From 747e0f00dc63e060feb246f69a5ffe6c33249604 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 10 Apr 2021 13:10:05 +0200 Subject: [PATCH 1360/1791] Improve table formatting --- osu.Game/Screens/Edit/Verify/IssueTable.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Verify/IssueTable.cs b/osu.Game/Screens/Edit/Verify/IssueTable.cs index e4612927fd..40bd134551 100644 --- a/osu.Game/Screens/Edit/Verify/IssueTable.cs +++ b/osu.Game/Screens/Edit/Verify/IssueTable.cs @@ -74,9 +74,9 @@ namespace osu.Game.Screens.Edit.Verify { var columns = new List { - new TableColumn(string.Empty, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), - new TableColumn("Type", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), - new TableColumn("Time", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), + new TableColumn(string.Empty, Anchor.CentreLeft, new Dimension(GridSizeMode.AutoSize)), + new TableColumn("Type", Anchor.CentreLeft, new Dimension(GridSizeMode.AutoSize, minSize: 60)), + new TableColumn("Time", Anchor.CentreLeft, new Dimension(GridSizeMode.AutoSize, minSize: 60)), new TableColumn("Message", Anchor.CentreLeft), new TableColumn("Category", Anchor.CentreRight, new Dimension(GridSizeMode.AutoSize)), }; @@ -89,20 +89,21 @@ namespace osu.Game.Screens.Edit.Verify new OsuSpriteText { Text = $"#{index + 1}", - Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Medium) + Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Medium), + Margin = new MarginPadding { Right = 10 } }, new OsuSpriteText { Text = issue.Template.Type.ToString(), Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold), - Margin = new MarginPadding { Left = 10 }, + Margin = new MarginPadding { Right = 10 }, Colour = issue.Template.TypeColour() }, new OsuSpriteText { Text = issue.GetEditorTimestamp(), Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold), - Margin = new MarginPadding(10) + Margin = new MarginPadding { Right = 10 }, }, new OsuSpriteText { From 3289bb03791a11f0181d86a4c21ce13d1e214c2b Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 10 Apr 2021 14:56:30 +0200 Subject: [PATCH 1361/1791] Merge `Check` and `BeatmapCheck` We're probably not going to need GeneralChecks or BeatmapsetChecks. The verify tab is only available to a single difficulty at a time, and we already have access to the rest of the set through `IBeatmap`. --- .../Edit/Checks/CheckOffscreenObjects.cs | 2 +- osu.Game.Rulesets.Osu/Edit/OsuChecker.cs | 2 +- osu.Game/Rulesets/Edit/Checker.cs | 4 ++-- .../Rulesets/Edit/Checks/CheckBackground.cs | 2 +- .../Edit/Checks/Components/BeatmapCheck.cs | 19 ------------------ .../Rulesets/Edit/Checks/Components/Check.cs | 20 +++++++++---------- 6 files changed, 14 insertions(+), 35 deletions(-) delete mode 100644 osu.Game/Rulesets/Edit/Checks/Components/BeatmapCheck.cs diff --git a/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs b/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs index c47864855b..3d411d6e12 100644 --- a/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs +++ b/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs @@ -9,7 +9,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Edit.Checks { - public class CheckOffscreenObjects : BeatmapCheck + public class CheckOffscreenObjects : Check { // These are close approximates to the edges of the screen // in gameplay on a 4:3 aspect ratio for osu!stable. diff --git a/osu.Game.Rulesets.Osu/Edit/OsuChecker.cs b/osu.Game.Rulesets.Osu/Edit/OsuChecker.cs index ee3b230679..d0dbc043d4 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuChecker.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuChecker.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Edit { public class OsuChecker : Checker { - public readonly List beatmapChecks = new List + public readonly List beatmapChecks = new List { new CheckOffscreenObjects() }; diff --git a/osu.Game/Rulesets/Edit/Checker.cs b/osu.Game/Rulesets/Edit/Checker.cs index 65d7fc5913..6ab6ed75e8 100644 --- a/osu.Game/Rulesets/Edit/Checker.cs +++ b/osu.Game/Rulesets/Edit/Checker.cs @@ -12,14 +12,14 @@ namespace osu.Game.Rulesets.Edit public abstract class Checker { // These are all ruleset-invariant, hence here instead of in e.g. `OsuChecker`. - private readonly List beatmapChecks = new List + private readonly IReadOnlyList checks = new List { new CheckBackground() }; public virtual IEnumerable Run(IBeatmap beatmap) { - return beatmapChecks.SelectMany(check => check.Run(beatmap)); + return checks.SelectMany(check => check.Run(beatmap)); } } } diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs b/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs index 9376f9568a..aa10fad77b 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs @@ -8,7 +8,7 @@ using osu.Game.Rulesets.Edit.Checks.Components; namespace osu.Game.Rulesets.Edit.Checks { - public class CheckBackground : BeatmapCheck + public class CheckBackground : Check { public override CheckMetadata Metadata() => new CheckMetadata ( diff --git a/osu.Game/Rulesets/Edit/Checks/Components/BeatmapCheck.cs b/osu.Game/Rulesets/Edit/Checks/Components/BeatmapCheck.cs deleted file mode 100644 index d0f93b5eef..0000000000 --- a/osu.Game/Rulesets/Edit/Checks/Components/BeatmapCheck.cs +++ /dev/null @@ -1,19 +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; - -namespace osu.Game.Rulesets.Edit.Checks.Components -{ - public abstract class BeatmapCheck : Check - { - /// - /// Returns zero, one, or several issues detected by this - /// check on the given beatmap. - /// - /// The beatmap to run the check on. - /// - public abstract override IEnumerable Run(IBeatmap beatmap); - } -} diff --git a/osu.Game/Rulesets/Edit/Checks/Components/Check.cs b/osu.Game/Rulesets/Edit/Checks/Components/Check.cs index 228c11f0f3..4e444cacbf 100644 --- a/osu.Game/Rulesets/Edit/Checks/Components/Check.cs +++ b/osu.Game/Rulesets/Edit/Checks/Components/Check.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using osu.Game.Beatmaps; namespace osu.Game.Rulesets.Edit.Checks.Components { @@ -21,21 +22,18 @@ namespace osu.Game.Rulesets.Edit.Checks.Components /// public abstract IEnumerable Templates(); + /// + /// Returns zero, one, or several issues detected by this + /// check on the given beatmap. + /// + /// The beatmap to run the check on. + /// + public abstract IEnumerable Run(IBeatmap beatmap); + protected Check() { foreach (var template in Templates()) template.Origin = this; } } - - public abstract class Check : Check - { - /// - /// Returns zero, one, or several issues detected by - /// this check on the given object. - /// - /// The object to run the check on. - /// - public abstract IEnumerable Run(T obj); - } } From 7d40b017223a008e17cf53cf0b05006e8dfe288f Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 10 Apr 2021 15:18:15 +0200 Subject: [PATCH 1362/1791] Remove old todo --- osu.Game/Screens/Edit/Verify/IssueTable.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Verify/IssueTable.cs b/osu.Game/Screens/Edit/Verify/IssueTable.cs index 40bd134551..5f04c7c4e8 100644 --- a/osu.Game/Screens/Edit/Verify/IssueTable.cs +++ b/osu.Game/Screens/Edit/Verify/IssueTable.cs @@ -158,9 +158,6 @@ namespace osu.Game.Screens.Edit.Verify Action = () => { - // Supposed to work like clicking timestamps outside of the game. - // TODO: Is there already defined behaviour for this I may be able to call? - selectedIssue.Value = issue; if (issue.Time != null) From dac733cced62e30089f52fa6147f5210197b3e20 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 10 Apr 2021 15:49:57 +0200 Subject: [PATCH 1363/1791] Fix field name and accessibility --- osu.Game.Rulesets.Osu/Edit/OsuChecker.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuChecker.cs b/osu.Game.Rulesets.Osu/Edit/OsuChecker.cs index d0dbc043d4..10f0ecf0cf 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuChecker.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuChecker.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Edit { public class OsuChecker : Checker { - public readonly List beatmapChecks = new List + private readonly List checks = new List { new CheckOffscreenObjects() }; @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Edit foreach (var issue in base.Run(beatmap)) yield return issue; - foreach (var issue in beatmapChecks.SelectMany(check => check.Run(beatmap))) + foreach (var issue in checks.SelectMany(check => check.Run(beatmap))) yield return issue; } } From a42714540b6c24385042725540bd6b85a53ebdda Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 10 Apr 2021 23:04:15 -0700 Subject: [PATCH 1364/1791] Add follow delay setting to osu! flashlight mod --- .../Mods/OsuModFlashlight.cs | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index 20c0818d03..683b35f282 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -9,10 +9,12 @@ using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Framework.Utils; +using osu.Game.Configuration; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.UI; using osuTK; namespace osu.Game.Rulesets.Osu.Mods @@ -23,6 +25,8 @@ namespace osu.Game.Rulesets.Osu.Mods private const float default_flashlight_size = 180; + private const double default_follow_delay = 120; + private OsuFlashlight flashlight; public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(); @@ -35,8 +39,25 @@ namespace osu.Game.Rulesets.Osu.Mods } } + public override void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + { + base.ApplyToDrawableRuleset(drawableRuleset); + + flashlight.FollowDelay = FollowDelay.Value; + } + + [SettingSource("Follow delay", "Milliseconds until the flashlight reaches the cursor")] + public BindableNumber FollowDelay { get; } = new BindableDouble(default_follow_delay) + { + MinValue = default_follow_delay, + MaxValue = default_follow_delay * 10, + Precision = default_follow_delay, + }; + private class OsuFlashlight : Flashlight, IRequireHighFrequencyMousePosition { + public double FollowDelay { private get; set; } + public OsuFlashlight() { FlashlightSize = new Vector2(0, getSizeFor(0)); @@ -50,13 +71,11 @@ namespace osu.Game.Rulesets.Osu.Mods protected override bool OnMouseMove(MouseMoveEvent e) { - const double follow_delay = 120; - var position = FlashlightPosition; var destination = e.MousePosition; FlashlightPosition = Interpolation.ValueAt( - Math.Min(Math.Abs(Clock.ElapsedFrameTime), follow_delay), position, destination, 0, follow_delay, Easing.Out); + Math.Min(Math.Abs(Clock.ElapsedFrameTime), FollowDelay), position, destination, 0, FollowDelay, Easing.Out); return base.OnMouseMove(e); } From 2b947a44da2d320669f61d13d6c1ee8c7c660001 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 11 Apr 2021 09:00:37 +0300 Subject: [PATCH 1365/1791] Cache power status at base instead --- osu.Game/OsuGame.cs | 5 ----- osu.Game/OsuGameBase.cs | 5 +++++ 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 277455b7d3..809e5d3c1b 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -694,11 +694,6 @@ namespace osu.Game loadComponentSingleFile(new AccountCreationOverlay(), topMostOverlayContent.Add, true); loadComponentSingleFile(new DialogOverlay(), topMostOverlayContent.Add, true); - if (CreatePowerStatus() != null) - { - dependencies.CacheAs(CreatePowerStatus()); - } - chatOverlay.State.ValueChanged += state => channelManager.HighPollRate.Value = state.NewValue == Visibility.Visible; Add(externalLinkOpener = new ExternalLinkOpener()); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 41d790ea4a..fec8272076 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -284,6 +284,11 @@ namespace osu.Game dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore)); dependencies.Cache(SettingsStore = new SettingsStore(contextFactory)); dependencies.Cache(RulesetConfigCache = new RulesetConfigCache(SettingsStore)); + + var powerStatus = CreatePowerStatus(); + if (powerStatus != null) + dependencies.CacheAs(powerStatus); + dependencies.Cache(new SessionStatics()); dependencies.Cache(new OsuColour()); From 3d85dc11c62aedccda43b373326463c9d5be5162 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 11 Apr 2021 09:02:32 +0300 Subject: [PATCH 1366/1791] Adjust documentation --- osu.Game/Utils/PowerStatus.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Utils/PowerStatus.cs b/osu.Game/Utils/PowerStatus.cs index 77d531710e..a53f50b620 100644 --- a/osu.Game/Utils/PowerStatus.cs +++ b/osu.Game/Utils/PowerStatus.cs @@ -22,7 +22,8 @@ namespace osu.Game.Utils public virtual bool IsCharging { get; } = false; /// - /// Returns true if = false and <= . + /// Whether the battery is currently low in charge. + /// Returns true if not charging and current charge level is lower than or equal to . /// public bool IsLowBattery => !IsCharging && ChargeLevel <= BatteryCutoff; } From 07ee1b4d0b73edebaaffc632acd13dc993f0871c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 11 Apr 2021 09:11:28 +0300 Subject: [PATCH 1367/1791] Make power status properties abstract --- osu.Game/Utils/PowerStatus.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Utils/PowerStatus.cs b/osu.Game/Utils/PowerStatus.cs index a53f50b620..46f7e32b9e 100644 --- a/osu.Game/Utils/PowerStatus.cs +++ b/osu.Game/Utils/PowerStatus.cs @@ -12,14 +12,14 @@ namespace osu.Game.Utils /// /// The maximum battery level considered as low, from 0 to 1. /// - public virtual double BatteryCutoff { get; } = 0; + public abstract double BatteryCutoff { get; } /// /// The charge level of the battery, from 0 to 1. /// - public virtual double ChargeLevel { get; } = 0; + public abstract double ChargeLevel { get; } - public virtual bool IsCharging { get; } = false; + public abstract bool IsCharging { get; } /// /// Whether the battery is currently low in charge. From cb947a3b2710d1c22a55a1ac91262c83bc68591c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 11 Apr 2021 10:02:51 +0300 Subject: [PATCH 1368/1791] Add expected output in test case rather than determining internally --- .../Visual/Gameplay/TestScenePlayerLoader.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index 36958b0741..657c1dd47e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -292,10 +292,10 @@ namespace osu.Game.Tests.Visual.Gameplay } } - [TestCase(false, 1.0)] // not charging, above cutoff --> no warning - [TestCase(false, 0.2)] // not charging, at cutoff --> warning - [TestCase(true, 0.1)] // charging, below cutoff --> no warning - public void TestLowBatteryNotification(bool isCharging, double chargeLevel) + [TestCase(false, 1.0, false)] // not charging, above cutoff --> no warning + [TestCase(true, 0.1, false)] // charging, below cutoff --> no warning + [TestCase(false, 0.2, true)] // not charging, at cutoff --> warning + public void TestLowBatteryNotification(bool isCharging, double chargeLevel, bool shouldWarn) { AddStep("reset notification lock", () => sessionStatics.GetBindable(Static.LowBatteryNotificationShownOnce).Value = false); @@ -306,8 +306,7 @@ namespace osu.Game.Tests.Visual.Gameplay powerStatus.SetChargeLevel(chargeLevel); })); AddUntilStep("wait for player", () => player?.LoadState == LoadState.Ready); - int warning = !isCharging && chargeLevel <= powerStatus.BatteryCutoff ? 1 : 0; - AddAssert($"notification {(warning == 1 ? "triggered" : "not triggered")}", () => notificationOverlay.UnreadCount.Value == warning); + AddAssert($"notification {(shouldWarn ? "triggered" : "not triggered")}", () => notificationOverlay.UnreadCount.Value == (shouldWarn ? 1 : 0)); AddStep("click notification", () => { var scrollContainer = (OsuScrollContainer)notificationOverlay.Children.Last(); From 419fd4470cbb818298cd5fe324d8ff528b2f3901 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 11 Apr 2021 08:57:44 +0300 Subject: [PATCH 1369/1791] Reorder method declaration --- osu.Game/OsuGameBase.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index fec8272076..96aabf0024 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -96,8 +96,6 @@ namespace osu.Game protected Storage Storage { get; set; } - protected virtual PowerStatus CreatePowerStatus() => null; - [Cached] [Cached(typeof(IBindable))] protected readonly Bindable Ruleset = new Bindable(); @@ -159,6 +157,8 @@ namespace osu.Game protected override UserInputManager CreateUserInputManager() => new OsuUserInputManager(); + protected virtual PowerStatus CreatePowerStatus() => null; + /// /// The maximum volume at which audio tracks should playback. This can be set lower than 1 to create some head-room for sound effects. /// From b7e16c2fcc8cad36ab2f40cc59c3f254088624e9 Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Sun, 11 Apr 2021 15:47:38 -0400 Subject: [PATCH 1370/1791] Remove Xamarin.Essentials package from main project --- osu.Game/osu.Game.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index d0a918a8f5..471eced55a 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -35,6 +35,5 @@ - From fbd5195738bdd9843434ea7462a906e928285d5a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 12 Apr 2021 03:37:03 +0300 Subject: [PATCH 1371/1791] Extract mod setting value handling to utils class --- .../API/ModSettingsDictionaryFormatter.cs | 34 ++---------------- osu.Game/Utils/ModUtils.cs | 36 +++++++++++++++++++ 2 files changed, 38 insertions(+), 32 deletions(-) diff --git a/osu.Game/Online/API/ModSettingsDictionaryFormatter.cs b/osu.Game/Online/API/ModSettingsDictionaryFormatter.cs index dd854acc32..81ecc74ddb 100644 --- a/osu.Game/Online/API/ModSettingsDictionaryFormatter.cs +++ b/osu.Game/Online/API/ModSettingsDictionaryFormatter.cs @@ -3,11 +3,10 @@ using System.Buffers; using System.Collections.Generic; -using System.Diagnostics; using System.Text; using MessagePack; using MessagePack.Formatters; -using osu.Framework.Bindables; +using osu.Game.Utils; namespace osu.Game.Online.API { @@ -24,36 +23,7 @@ namespace osu.Game.Online.API var stringBytes = new ReadOnlySequence(Encoding.UTF8.GetBytes(kvp.Key)); writer.WriteString(in stringBytes); - switch (kvp.Value) - { - case Bindable d: - primitiveFormatter.Serialize(ref writer, d.Value, options); - break; - - case Bindable i: - primitiveFormatter.Serialize(ref writer, i.Value, options); - break; - - case Bindable f: - primitiveFormatter.Serialize(ref writer, f.Value, options); - break; - - case Bindable b: - primitiveFormatter.Serialize(ref writer, b.Value, options); - break; - - case IBindable u: - // A mod with unknown (e.g. enum) generic type. - var valueMethod = u.GetType().GetProperty(nameof(IBindable.Value)); - Debug.Assert(valueMethod != null); - primitiveFormatter.Serialize(ref writer, valueMethod.GetValue(u), options); - break; - - default: - // fall back for non-bindable cases. - primitiveFormatter.Serialize(ref writer, kvp.Value, options); - break; - } + primitiveFormatter.Serialize(ref writer, ModUtils.GetSettingUnderlyingValue(kvp.Value), options); } } diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs index c12b5a9fd4..596880f2e7 100644 --- a/osu.Game/Utils/ModUtils.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -3,8 +3,11 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; +using osu.Framework.Bindables; +using osu.Game.Online.API; using osu.Game.Rulesets.Mods; #nullable enable @@ -129,5 +132,38 @@ namespace osu.Game.Utils else yield return mod; } + + /// + /// Returns the underlying value of the given mod setting object. + /// Used in for serialization and equality comparison purposes. + /// + /// The mod setting. + public static object GetSettingUnderlyingValue(object setting) + { + switch (setting) + { + case Bindable d: + return d.Value; + + case Bindable i: + return i.Value; + + case Bindable f: + return f.Value; + + case Bindable b: + return b.Value; + + case IBindable u: + // A mod with unknown (e.g. enum) generic type. + var valueMethod = u.GetType().GetProperty(nameof(IBindable.Value)); + Debug.Assert(valueMethod != null); + return valueMethod.GetValue(u); + + default: + // fall back for non-bindable cases. + return setting; + } + } } } From d6d8ea5b6b5ce956d81863136f5eff3f62bf5cd6 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 12 Apr 2021 11:17:56 +0900 Subject: [PATCH 1372/1791] Throw when getting a frame of an empty replay --- .../Gameplay/TestSceneSpectatorPlayback.cs | 30 +++++++++---------- osu.Game/Input/Handlers/ReplayInputHandler.cs | 2 -- .../Replays/FramedReplayInputHandler.cs | 9 ++++-- osu.Game/Rulesets/UI/RulesetInputManager.cs | 10 +++++++ 4 files changed, 32 insertions(+), 19 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs index 35b3bfc1f8..9c763814f3 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs @@ -204,27 +204,27 @@ namespace osu.Game.Tests.Visual.Gameplay return; } - if (replayHandler.NextFrame != null) - { - var lastFrame = replay.Frames.LastOrDefault(); + if (!replayHandler.HasFrames) + return; - // this isn't perfect as we basically can't be aware of the rate-of-send here (the streamer is not sending data when not being moved). - // in gameplay playback, the case where NextFrame is null would pause gameplay and handle this correctly; it's strictly a test limitation / best effort implementation. - if (lastFrame != null) - latency = Math.Max(latency, Time.Current - lastFrame.Time); + var lastFrame = replay.Frames.LastOrDefault(); - latencyDisplay.Text = $"latency: {latency:N1}"; + // this isn't perfect as we basically can't be aware of the rate-of-send here (the streamer is not sending data when not being moved). + // in gameplay playback, the case where NextFrame is null would pause gameplay and handle this correctly; it's strictly a test limitation / best effort implementation. + if (lastFrame != null) + latency = Math.Max(latency, Time.Current - lastFrame.Time); - double proposedTime = Time.Current - latency + Time.Elapsed; + latencyDisplay.Text = $"latency: {latency:N1}"; - // this will either advance by one or zero frames. - double? time = replayHandler.SetFrameFromTime(proposedTime); + double proposedTime = Time.Current - latency + Time.Elapsed; - if (time == null) - return; + // this will either advance by one or zero frames. + double? time = replayHandler.SetFrameFromTime(proposedTime); - manualClock.CurrentTime = time.Value; - } + if (time == null) + return; + + manualClock.CurrentTime = time.Value; } [TearDownSteps] diff --git a/osu.Game/Input/Handlers/ReplayInputHandler.cs b/osu.Game/Input/Handlers/ReplayInputHandler.cs index fba1bee0b8..cd76000f98 100644 --- a/osu.Game/Input/Handlers/ReplayInputHandler.cs +++ b/osu.Game/Input/Handlers/ReplayInputHandler.cs @@ -32,8 +32,6 @@ namespace osu.Game.Input.Handlers public override bool Initialize(GameHost host) => true; - public override bool IsActive => true; - public class ReplayState : IInput where T : struct { diff --git a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs index 0b41ca31ea..5eaccc766e 100644 --- a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs +++ b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs @@ -17,6 +17,8 @@ namespace osu.Game.Rulesets.Replays public abstract class FramedReplayInputHandler : ReplayInputHandler where TFrame : ReplayFrame { + public override bool IsActive => HasFrames; + private readonly Replay replay; protected List Frames => replay.Frames; @@ -25,7 +27,10 @@ namespace osu.Game.Rulesets.Replays { get { - if (!HasFrames || !currentFrameIndex.HasValue) + if (!HasFrames) + throw new InvalidOperationException($"Cannot get {nameof(CurrentFrame)} of the empty replay"); + + if (!currentFrameIndex.HasValue) return null; return (TFrame)Frames[currentFrameIndex.Value]; @@ -37,7 +42,7 @@ namespace osu.Game.Rulesets.Replays get { if (!HasFrames) - return null; + throw new InvalidOperationException($"Cannot get {nameof(NextFrame)} of the empty replay"); if (!currentFrameIndex.HasValue) return currentDirection > 0 ? (TFrame)Frames[0] : null; diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index d6f002ea2c..fb56a5d93d 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -2,6 +2,7 @@ // 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.Bindables; @@ -10,6 +11,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Framework.Input.StateChanges; using osu.Framework.Input.StateChanges.Events; using osu.Framework.Input.States; using osu.Game.Configuration; @@ -100,6 +102,14 @@ namespace osu.Game.Rulesets.UI #endregion + protected override List GetPendingInputs() + { + if (replayInputHandler != null && !replayInputHandler.IsActive) + return new List(); + + return base.GetPendingInputs(); + } + #region Setting application (disables etc.) private Bindable mouseDisabled; From 4fcddfb44b7284dcf39720a685d2f34813e56ecd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 12 Apr 2021 13:42:14 +0900 Subject: [PATCH 1373/1791] Fix multiplayer test failure --- .../Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index 839118de2f..caa731f985 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -134,7 +134,7 @@ namespace osu.Game.Tests.Visual.Multiplayer InputManager.Click(MouseButton.Left); }); - AddAssert("match started", () => Client.Room?.State == MultiplayerRoomState.WaitingForLoad); + AddUntilStep("match started", () => Client.Room?.State == MultiplayerRoomState.WaitingForLoad); } } } From 995c244ceef985c3b381b6ec6af54f8d5d940b0d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Apr 2021 14:00:26 +0900 Subject: [PATCH 1374/1791] Remove alt-mousewheel bindings for volume adjustment With the recent changes to the order of processing key bindings (`GlobalAction`s are handled first), having the alt-wheel bindings in here causes a regression as they are handled before `OnScroll` events. Specifically, this means editor alt-scroll functionality no longer works with the default bindings. Removing the bindings fixes this, while also still allowing alt-wheel adjustment of the volume via `VolumeControlReceptor`: https://github.com/ppy/osu/blob/a2f50af4243dfde95ec556859666b65e34f3007b/osu.Game/Overlays/Volume/VolumeControlReceptor.cs#L21-L26 In conjunction with the special case in `OsuScrollContainer`: https://github.com/ppy/osu/blob/02d5b1352b6c9971ea83a131c0d2637e167b56b3/osu.Game/Graphics/Containers/OsuScrollContainer.cs#L103-L105 --- .../Input/Bindings/GlobalActionContainer.cs | 2 - ...700_RefreshVolumeBindingsAgain.Designer.cs | 506 ++++++++++++++++++ ...210412045700_RefreshVolumeBindingsAgain.cs | 16 + 3 files changed, 522 insertions(+), 2 deletions(-) create mode 100644 osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.Designer.cs create mode 100644 osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.cs diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 671c3bc8bc..ba4d757cd7 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -97,9 +97,7 @@ namespace osu.Game.Input.Bindings public IEnumerable AudioControlKeyBindings => new[] { new KeyBinding(new[] { InputKey.Alt, InputKey.Up }, GlobalAction.IncreaseVolume), - new KeyBinding(new[] { InputKey.Alt, InputKey.MouseWheelUp }, GlobalAction.IncreaseVolume), new KeyBinding(new[] { InputKey.Alt, InputKey.Down }, GlobalAction.DecreaseVolume), - new KeyBinding(new[] { InputKey.Alt, InputKey.MouseWheelDown }, GlobalAction.DecreaseVolume), new KeyBinding(new[] { InputKey.Control, InputKey.F4 }, GlobalAction.ToggleMute), diff --git a/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.Designer.cs b/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.Designer.cs new file mode 100644 index 0000000000..2c100d39b9 --- /dev/null +++ b/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.Designer.cs @@ -0,0 +1,506 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20210412045700_RefreshVolumeBindingsAgain")] + partial class RefreshVolumeBindingsAgain + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BPM"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("Length"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("Status"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.Property("VideoFile"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DateAdded"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Key") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("SkinInfoID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("SkinInfoID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("ScoreInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("ScoreInfoID"); + + b.ToTable("ScoreFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Accuracy") + .HasColumnType("DECIMAL(1,4)"); + + b.Property("BeatmapInfoID"); + + b.Property("Combo"); + + b.Property("Date"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MaxCombo"); + + b.Property("ModsJson") + .HasColumnName("Mods"); + + b.Property("OnlineScoreID"); + + b.Property("PP"); + + b.Property("Rank"); + + b.Property("RulesetID"); + + b.Property("StatisticsJson") + .HasColumnName("Statistics"); + + b.Property("TotalScore"); + + b.Property("UserID") + .HasColumnName("UserID"); + + b.Property("UserString") + .HasColumnName("User"); + + b.HasKey("ID"); + + b.HasIndex("BeatmapInfoID"); + + b.HasIndex("OnlineScoreID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("ScoreInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Settings") + .HasForeignKey("SkinInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Scoring.ScoreInfo") + .WithMany("Files") + .HasForeignKey("ScoreInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") + .WithMany("Scores") + .HasForeignKey("BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.cs b/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.cs new file mode 100644 index 0000000000..155d6670a8 --- /dev/null +++ b/osu.Game/Migrations/20210412045700_RefreshVolumeBindingsAgain.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class RefreshVolumeBindingsAgain : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql("DELETE FROM KeyBinding WHERE action in (6,7)"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + } + } +} From dd1925aaedc756a57e1aecadbe2e36992bd17e8a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Apr 2021 14:29:27 +0900 Subject: [PATCH 1375/1791] Remove temporary input ignore --- .../Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid_Cell.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid_Cell.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid_Cell.cs index 37d88693ee..2df05cb5ed 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid_Cell.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid_Cell.cs @@ -89,9 +89,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate private Vector2 getFinalSize() => facade.DrawSize; - // Todo: Temporary? - protected override bool ShouldBeConsideredForInput(Drawable child) => false; - protected override bool OnClick(ClickEvent e) { ToggleMaximisationState(this); From 1c553b5d481d21186eed0498160f90a6e51eedee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Apr 2021 15:14:53 +0900 Subject: [PATCH 1376/1791] Checker -> BeatmapVerifier --- .../Edit/{OsuChecker.cs => OsuBeatmapVerifier.cs} | 2 +- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- .../Rulesets/Edit/{Checker.cs => BeatmapVerifier.cs} | 2 +- osu.Game/Rulesets/Ruleset.cs | 2 +- osu.Game/Screens/Edit/Verify/VerifyScreen.cs | 10 +++++----- 5 files changed, 9 insertions(+), 9 deletions(-) rename osu.Game.Rulesets.Osu/Edit/{OsuChecker.cs => OsuBeatmapVerifier.cs} (94%) rename osu.Game/Rulesets/Edit/{Checker.cs => BeatmapVerifier.cs} (94%) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuChecker.cs b/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs similarity index 94% rename from osu.Game.Rulesets.Osu/Edit/OsuChecker.cs rename to osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs index 10f0ecf0cf..272612dbaf 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuChecker.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Osu.Edit.Checks; namespace osu.Game.Rulesets.Osu.Edit { - public class OsuChecker : Checker + public class OsuBeatmapVerifier : BeatmapVerifier { private readonly List checks = new List { diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 74679bd578..1658846e98 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -206,7 +206,7 @@ namespace osu.Game.Rulesets.Osu public override HitObjectComposer CreateHitObjectComposer() => new OsuHitObjectComposer(this); - public override Checker CreateChecker() => new OsuChecker(); + public override BeatmapVerifier CreateChecker() => new OsuBeatmapVerifier(); public override string Description => "osu!"; diff --git a/osu.Game/Rulesets/Edit/Checker.cs b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs similarity index 94% rename from osu.Game/Rulesets/Edit/Checker.cs rename to osu.Game/Rulesets/Edit/BeatmapVerifier.cs index 6ab6ed75e8..230b128cc1 100644 --- a/osu.Game/Rulesets/Edit/Checker.cs +++ b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs @@ -9,7 +9,7 @@ using osu.Game.Rulesets.Edit.Checks.Components; namespace osu.Game.Rulesets.Edit { - public abstract class Checker + public abstract class BeatmapVerifier { // These are all ruleset-invariant, hence here instead of in e.g. `OsuChecker`. private readonly IReadOnlyList checks = new List diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 71f80c9982..088bcc7712 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -202,7 +202,7 @@ namespace osu.Game.Rulesets public virtual HitObjectComposer CreateHitObjectComposer() => null; - public virtual Checker CreateChecker() => null; + public virtual BeatmapVerifier CreateChecker() => null; public virtual Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.Solid.QuestionCircle }; diff --git a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs index b9fd93ac19..7a520826f5 100644 --- a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs +++ b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs @@ -21,7 +21,7 @@ namespace osu.Game.Screens.Edit.Verify public class VerifyScreen : EditorScreen { private Ruleset ruleset; - private static Checker checker; + private static BeatmapVerifier beatmapVerifier; [Cached] private Bindable selectedIssue = new Bindable(); @@ -36,7 +36,7 @@ namespace osu.Game.Screens.Edit.Verify var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); ruleset = parent.Get>().Value.BeatmapInfo.Ruleset?.CreateInstance(); - checker = ruleset?.CreateChecker(); + beatmapVerifier = ruleset?.CreateChecker(); return dependencies; } @@ -128,9 +128,9 @@ namespace osu.Game.Screens.Edit.Verify private void refresh() { - table.Issues = checker.Run(Beatmap) - .OrderByDescending(issue => issue.Template.Type) - .ThenByDescending(issue => issue.Template.Origin.Metadata().Category); + table.Issues = beatmapVerifier.Run(Beatmap) + .OrderByDescending(issue => issue.Template.Type) + .ThenByDescending(issue => issue.Template.Origin.Metadata().Category); } } } From 136627b9ac59697129ccd1ae94a67e50442d3d61 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Apr 2021 15:27:12 +0900 Subject: [PATCH 1377/1791] Wrap xmldoc less and make a few fixes --- osu.Game/Rulesets/Edit/BeatmapVerifier.cs | 9 ++++++--- osu.Game/Rulesets/Edit/Checks/Components/Check.cs | 12 +++--------- .../Rulesets/Edit/Checks/Components/CheckMetadata.cs | 6 ++---- osu.Game/Rulesets/Edit/Checks/Components/Issue.cs | 10 +++------- .../Rulesets/Edit/Checks/Components/IssueTemplate.cs | 5 +---- 5 files changed, 15 insertions(+), 27 deletions(-) diff --git a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs index 230b128cc1..f67a068525 100644 --- a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs +++ b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs @@ -11,15 +11,18 @@ namespace osu.Game.Rulesets.Edit { public abstract class BeatmapVerifier { - // These are all ruleset-invariant, hence here instead of in e.g. `OsuChecker`. - private readonly IReadOnlyList checks = new List + /// + /// Checks which are performed regardless of ruleset. + /// These handle things like beatmap metadata, timing, and other ruleset agnostic elements. + /// + private readonly IReadOnlyList generalChecks = new List { new CheckBackground() }; public virtual IEnumerable Run(IBeatmap beatmap) { - return checks.SelectMany(check => check.Run(beatmap)); + return generalChecks.SelectMany(check => check.Run(beatmap)); } } } diff --git a/osu.Game/Rulesets/Edit/Checks/Components/Check.cs b/osu.Game/Rulesets/Edit/Checks/Components/Check.cs index 4e444cacbf..5a922fde56 100644 --- a/osu.Game/Rulesets/Edit/Checks/Components/Check.cs +++ b/osu.Game/Rulesets/Edit/Checks/Components/Check.cs @@ -9,25 +9,19 @@ namespace osu.Game.Rulesets.Edit.Checks.Components public abstract class Check { /// - /// Returns the for this check. - /// Basically, its information. + /// The metadata for this check. /// - /// public abstract CheckMetadata Metadata(); /// - /// The templates for issues that this check may use. - /// Basically, what issues this check can detect. + /// All possible templates for issues that this check may return. /// - /// public abstract IEnumerable Templates(); /// - /// Returns zero, one, or several issues detected by this - /// check on the given beatmap. + /// Runs this check and returns any issues detected for the provided beatmap. /// /// The beatmap to run the check on. - /// public abstract IEnumerable Run(IBeatmap beatmap); protected Check() diff --git a/osu.Game/Rulesets/Edit/Checks/Components/CheckMetadata.cs b/osu.Game/Rulesets/Edit/Checks/Components/CheckMetadata.cs index 5a226729ad..38a9a16325 100644 --- a/osu.Game/Rulesets/Edit/Checks/Components/CheckMetadata.cs +++ b/osu.Game/Rulesets/Edit/Checks/Components/CheckMetadata.cs @@ -42,14 +42,12 @@ namespace osu.Game.Rulesets.Edit.Checks.Components } /// - /// The category this check belongs to. E.g. , - /// , or . + /// The category this check belongs to. E.g. , , or . /// public readonly CheckCategory Category; /// - /// Describes the issue(s) that this check looks for. Keep this brief, such that - /// it fits into "No {description}". E.g. "Offscreen objects" / "Too short sliders". + /// Describes the issue(s) that this check looks for. Keep this brief, such that it fits into "No {description}". E.g. "Offscreen objects" / "Too short sliders". /// public readonly string Description; diff --git a/osu.Game/Rulesets/Edit/Checks/Components/Issue.cs b/osu.Game/Rulesets/Edit/Checks/Components/Issue.cs index 0f835202e7..6f6ba2a323 100644 --- a/osu.Game/Rulesets/Edit/Checks/Components/Issue.cs +++ b/osu.Game/Rulesets/Edit/Checks/Components/Issue.cs @@ -22,17 +22,13 @@ namespace osu.Game.Rulesets.Edit.Checks.Components public IReadOnlyList HitObjects; /// - /// The template which this issue is using. This provides properties - /// such as the , and the - /// . + /// The template which this issue is using. This provides properties such as the , and the . /// public IssueTemplate Template; /// - /// The arguments that give this issue its context, based on the - /// . These are then substituted into the - /// . - /// E.g. timestamps, which diff is being compared to, what some volume is, etc. + /// The arguments that give this issue its context, based on the . These are then substituted into the . + /// This could for instance include timestamps, which diff is being compared to, what some volume is, etc. /// public object[] Arguments; diff --git a/osu.Game/Rulesets/Edit/Checks/Components/IssueTemplate.cs b/osu.Game/Rulesets/Edit/Checks/Components/IssueTemplate.cs index 7eaabdc59b..69c421140b 100644 --- a/osu.Game/Rulesets/Edit/Checks/Components/IssueTemplate.cs +++ b/osu.Game/Rulesets/Edit/Checks/Components/IssueTemplate.cs @@ -35,8 +35,7 @@ namespace osu.Game.Rulesets.Edit.Checks.Components public Check Origin; /// - /// The type of the issue. E.g. , - /// , or . + /// The type of the issue. /// public readonly IssueType Type; @@ -57,7 +56,6 @@ namespace osu.Game.Rulesets.Edit.Checks.Components /// Returns the formatted message given the arguments used to format it. /// /// The arguments used to format the message. - /// public string Message(params object[] args) => UnformattedMessage.FormatWith(args); public static readonly Color4 PROBLEM_RED = new Colour4(1.0f, 0.4f, 0.4f, 1.0f); @@ -68,7 +66,6 @@ namespace osu.Game.Rulesets.Edit.Checks.Components /// /// Returns the colour corresponding to the type of this issue. /// - /// public Colour4 TypeColour() { return Type switch From 257acf9cd88f3826e6da336fe66e71d3a7834c60 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Apr 2021 15:28:13 +0900 Subject: [PATCH 1378/1791] Colour constants to private --- .../Edit/Checks/Components/IssueTemplate.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/Components/IssueTemplate.cs b/osu.Game/Rulesets/Edit/Checks/Components/IssueTemplate.cs index 69c421140b..5381d54600 100644 --- a/osu.Game/Rulesets/Edit/Checks/Components/IssueTemplate.cs +++ b/osu.Game/Rulesets/Edit/Checks/Components/IssueTemplate.cs @@ -9,6 +9,11 @@ namespace osu.Game.Rulesets.Edit.Checks.Components { public class IssueTemplate { + private static readonly Color4 problem_red = new Colour4(1.0f, 0.4f, 0.4f, 1.0f); + private static readonly Color4 warning_yellow = new Colour4(1.0f, 0.8f, 0.2f, 1.0f); + private static readonly Color4 negligible_green = new Colour4(0.33f, 0.8f, 0.5f, 1.0f); + private static readonly Color4 error_gray = new Colour4(0.5f, 0.5f, 0.5f, 1.0f); + /// /// The type, or severity, of an issue. This decides its priority. /// @@ -58,11 +63,6 @@ namespace osu.Game.Rulesets.Edit.Checks.Components /// The arguments used to format the message. public string Message(params object[] args) => UnformattedMessage.FormatWith(args); - public static readonly Color4 PROBLEM_RED = new Colour4(1.0f, 0.4f, 0.4f, 1.0f); - public static readonly Color4 WARNING_YELLOW = new Colour4(1.0f, 0.8f, 0.2f, 1.0f); - public static readonly Color4 NEGLIGIBLE_GREEN = new Colour4(0.33f, 0.8f, 0.5f, 1.0f); - public static readonly Color4 ERROR_GRAY = new Colour4(0.5f, 0.5f, 0.5f, 1.0f); - /// /// Returns the colour corresponding to the type of this issue. /// @@ -70,10 +70,10 @@ namespace osu.Game.Rulesets.Edit.Checks.Components { return Type switch { - IssueType.Problem => PROBLEM_RED, - IssueType.Warning => WARNING_YELLOW, - IssueType.Negligible => NEGLIGIBLE_GREEN, - IssueType.Error => ERROR_GRAY, + IssueType.Problem => problem_red, + IssueType.Warning => warning_yellow, + IssueType.Negligible => negligible_green, + IssueType.Error => error_gray, _ => Color4.White }; } From 3551322f1d95738e09c9e5f9c847abf0163d1d3b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Apr 2021 15:30:33 +0900 Subject: [PATCH 1379/1791] Fix formatting of colour getter --- .../Edit/Checks/Components/IssueTemplate.cs | 28 +++++++++++++------ osu.Game/Screens/Edit/Verify/IssueTable.cs | 2 +- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/Components/IssueTemplate.cs b/osu.Game/Rulesets/Edit/Checks/Components/IssueTemplate.cs index 5381d54600..6fe8b8de87 100644 --- a/osu.Game/Rulesets/Edit/Checks/Components/IssueTemplate.cs +++ b/osu.Game/Rulesets/Edit/Checks/Components/IssueTemplate.cs @@ -66,16 +66,28 @@ namespace osu.Game.Rulesets.Edit.Checks.Components /// /// Returns the colour corresponding to the type of this issue. /// - public Colour4 TypeColour() + public Colour4 Colour { - return Type switch + get { - IssueType.Problem => problem_red, - IssueType.Warning => warning_yellow, - IssueType.Negligible => negligible_green, - IssueType.Error => error_gray, - _ => Color4.White - }; + switch (Type) + { + case IssueType.Problem: + return problem_red; + + case IssueType.Warning: + return warning_yellow; + + case IssueType.Negligible: + return negligible_green; + + case IssueType.Error: + return error_gray; + + default: + return Color4.White; + } + } } } } diff --git a/osu.Game/Screens/Edit/Verify/IssueTable.cs b/osu.Game/Screens/Edit/Verify/IssueTable.cs index 5f04c7c4e8..1f275ca537 100644 --- a/osu.Game/Screens/Edit/Verify/IssueTable.cs +++ b/osu.Game/Screens/Edit/Verify/IssueTable.cs @@ -97,7 +97,7 @@ namespace osu.Game.Screens.Edit.Verify Text = issue.Template.Type.ToString(), Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold), Margin = new MarginPadding { Right = 10 }, - Colour = issue.Template.TypeColour() + Colour = issue.Template.Colour }, new OsuSpriteText { From f78239c7f238bd64a6272de1c3b5648511f8c581 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Apr 2021 15:32:52 +0900 Subject: [PATCH 1380/1791] Move enums out of nesting --- .../Edit/Checks/CheckOffscreenObjects.cs | 6 +-- .../Rulesets/Edit/Checks/CheckBackground.cs | 6 +-- .../Edit/Checks/Components/CheckCategory.cs | 41 +++++++++++++++++++ .../Edit/Checks/Components/CheckMetadata.cs | 36 ---------------- .../Rulesets/Edit/Checks/Components/Issue.cs | 2 +- .../Edit/Checks/Components/IssueTemplate.cs | 20 --------- .../Edit/Checks/Components/IssueType.cs | 25 +++++++++++ 7 files changed, 73 insertions(+), 63 deletions(-) create mode 100644 osu.Game/Rulesets/Edit/Checks/Components/CheckCategory.cs create mode 100644 osu.Game/Rulesets/Edit/Checks/Components/IssueType.cs diff --git a/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs b/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs index 3d411d6e12..910de76c5f 100644 --- a/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs +++ b/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Checks public override CheckMetadata Metadata() => new CheckMetadata ( - category: CheckMetadata.CheckCategory.Compose, + category: CheckCategory.Compose, description: "Offscreen hitobjects." ); @@ -36,13 +36,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Checks private readonly IssueTemplate templateOffscreen = new IssueTemplate ( - type: IssueTemplate.IssueType.Problem, + type: IssueType.Problem, unformattedMessage: "This object goes offscreen on a 4:3 aspect ratio." ); private readonly IssueTemplate templateOffscreenSliderPath = new IssueTemplate ( - type: IssueTemplate.IssueType.Problem, + type: IssueType.Problem, unformattedMessage: "This slider goes offscreen here on a 4:3 aspect ratio." ); diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs b/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs index aa10fad77b..2de9d9daae 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Edit.Checks { public override CheckMetadata Metadata() => new CheckMetadata ( - category: CheckMetadata.CheckCategory.Resources, + category: CheckCategory.Resources, description: "Missing background." ); @@ -24,13 +24,13 @@ namespace osu.Game.Rulesets.Edit.Checks private readonly IssueTemplate templateNoneSet = new IssueTemplate ( - type: IssueTemplate.IssueType.Problem, + type: IssueType.Problem, unformattedMessage: "No background has been set." ); private readonly IssueTemplate templateDoesNotExist = new IssueTemplate ( - type: IssueTemplate.IssueType.Problem, + type: IssueType.Problem, unformattedMessage: "The background file \"{0}\" is does not exist." ); diff --git a/osu.Game/Rulesets/Edit/Checks/Components/CheckCategory.cs b/osu.Game/Rulesets/Edit/Checks/Components/CheckCategory.cs new file mode 100644 index 0000000000..c37a580dd8 --- /dev/null +++ b/osu.Game/Rulesets/Edit/Checks/Components/CheckCategory.cs @@ -0,0 +1,41 @@ +// 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.Rulesets.Edit.Checks.Components +{ + /// + /// The category of an issue. + /// + public enum CheckCategory + { + /// Anything to do with control points. + Timing, + + /// Anything to do with artist, title, creator, etc. + Metadata, + + /// Anything to do with non-audio files, e.g. background, skin, sprites, and video. + Resources, + + /// Anything to do with audio files, e.g. song and hitsounds. + Audio, + + /// Anything to do with files that don't fit into the above, e.g. unused, osu, or osb. + Files, + + /// Anything to do with hitobjects unrelated to spread. + Compose, + + /// Anything to do with difficulty levels or their progression. + Spread, + + /// Anything to do with variables like CS, OD, AR, HP, and global SV. + Settings, + + /// Anything to do with hitobject feedback. + Hitsounds, + + /// Anything to do with storyboarding, breaks, video offset, etc. + Events + } +} diff --git a/osu.Game/Rulesets/Edit/Checks/Components/CheckMetadata.cs b/osu.Game/Rulesets/Edit/Checks/Components/CheckMetadata.cs index 38a9a16325..cebb2f5455 100644 --- a/osu.Game/Rulesets/Edit/Checks/Components/CheckMetadata.cs +++ b/osu.Game/Rulesets/Edit/Checks/Components/CheckMetadata.cs @@ -5,42 +5,6 @@ namespace osu.Game.Rulesets.Edit.Checks.Components { public class CheckMetadata { - /// - /// The category of an issue. - /// - public enum CheckCategory - { - /// Anything to do with control points. - Timing, - - /// Anything to do with artist, title, creator, etc. - Metadata, - - /// Anything to do with non-audio files, e.g. background, skin, sprites, and video. - Resources, - - /// Anything to do with audio files, e.g. song and hitsounds. - Audio, - - /// Anything to do with files that don't fit into the above, e.g. unused, osu, or osb. - Files, - - /// Anything to do with hitobjects unrelated to spread. - Compose, - - /// Anything to do with difficulty levels or their progression. - Spread, - - /// Anything to do with variables like CS, OD, AR, HP, and global SV. - Settings, - - /// Anything to do with hitobject feedback. - Hitsounds, - - /// Anything to do with storyboarding, breaks, video offset, etc. - Events - } - /// /// The category this check belongs to. E.g. , , or . /// diff --git a/osu.Game/Rulesets/Edit/Checks/Components/Issue.cs b/osu.Game/Rulesets/Edit/Checks/Components/Issue.cs index 6f6ba2a323..f392fa8ef4 100644 --- a/osu.Game/Rulesets/Edit/Checks/Components/Issue.cs +++ b/osu.Game/Rulesets/Edit/Checks/Components/Issue.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Edit.Checks.Components public IReadOnlyList HitObjects; /// - /// The template which this issue is using. This provides properties such as the , and the . + /// The template which this issue is using. This provides properties such as the , and the . /// public IssueTemplate Template; diff --git a/osu.Game/Rulesets/Edit/Checks/Components/IssueTemplate.cs b/osu.Game/Rulesets/Edit/Checks/Components/IssueTemplate.cs index 6fe8b8de87..976341e41c 100644 --- a/osu.Game/Rulesets/Edit/Checks/Components/IssueTemplate.cs +++ b/osu.Game/Rulesets/Edit/Checks/Components/IssueTemplate.cs @@ -14,26 +14,6 @@ namespace osu.Game.Rulesets.Edit.Checks.Components private static readonly Color4 negligible_green = new Colour4(0.33f, 0.8f, 0.5f, 1.0f); private static readonly Color4 error_gray = new Colour4(0.5f, 0.5f, 0.5f, 1.0f); - /// - /// The type, or severity, of an issue. This decides its priority. - /// - public enum IssueType - { - /// A must-fix in the vast majority of cases. - Problem = 3, - - /// A possible mistake. Often requires critical thinking. - Warning = 2, - - // TODO: Try/catch all checks run and return error templates if exceptions occur. - /// An error occurred and a complete check could not be made. - Error = 1, - - // TODO: Negligible issues should be hidden by default. - /// A possible mistake so minor/unlikely that it can often be safely ignored. - Negligible = 0, - } - /// /// The check that this template originates from. /// diff --git a/osu.Game/Rulesets/Edit/Checks/Components/IssueType.cs b/osu.Game/Rulesets/Edit/Checks/Components/IssueType.cs new file mode 100644 index 0000000000..be43060cfc --- /dev/null +++ b/osu.Game/Rulesets/Edit/Checks/Components/IssueType.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. + +namespace osu.Game.Rulesets.Edit.Checks.Components +{ + /// + /// The type, or severity, of an issue. This decides its priority. + /// + public enum IssueType + { + /// A must-fix in the vast majority of cases. + Problem = 3, + + /// A possible mistake. Often requires critical thinking. + Warning = 2, + + // TODO: Try/catch all checks run and return error templates if exceptions occur. + /// An error occurred and a complete check could not be made. + Error = 1, + + // TODO: Negligible issues should be hidden by default. + /// A possible mistake so minor/unlikely that it can often be safely ignored. + Negligible = 0, + } +} From 8c31e96cdfd657c537dce50a722438fdad7b63dc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Apr 2021 15:37:41 +0900 Subject: [PATCH 1381/1791] Change some methods to get properties --- osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs | 2 +- osu.Game/Rulesets/Edit/Checks/CheckBackground.cs | 2 +- osu.Game/Rulesets/Edit/Checks/Components/Check.cs | 4 ++-- osu.Game/Rulesets/Edit/Checks/Components/Issue.cs | 5 +---- osu.Game/Rulesets/Edit/Checks/Components/IssueTemplate.cs | 2 +- 5 files changed, 6 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs b/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs index 910de76c5f..b2a00da12a 100644 --- a/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs +++ b/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Checks description: "Offscreen hitobjects." ); - public override IEnumerable Templates() => new[] + public override IEnumerable PossibleTemplates => new[] { templateOffscreen, templateOffscreenSliderPath diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs b/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs index 2de9d9daae..89b2e8293c 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Edit.Checks description: "Missing background." ); - public override IEnumerable Templates() => new[] + public override IEnumerable PossibleTemplates => new[] { templateNoneSet, templateDoesNotExist diff --git a/osu.Game/Rulesets/Edit/Checks/Components/Check.cs b/osu.Game/Rulesets/Edit/Checks/Components/Check.cs index 5a922fde56..550d2ea0a2 100644 --- a/osu.Game/Rulesets/Edit/Checks/Components/Check.cs +++ b/osu.Game/Rulesets/Edit/Checks/Components/Check.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Edit.Checks.Components /// /// All possible templates for issues that this check may return. /// - public abstract IEnumerable Templates(); + public abstract IEnumerable PossibleTemplates { get; } /// /// Runs this check and returns any issues detected for the provided beatmap. @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Edit.Checks.Components protected Check() { - foreach (var template in Templates()) + foreach (var template in PossibleTemplates) template.Origin = this; } } diff --git a/osu.Game/Rulesets/Edit/Checks/Components/Issue.cs b/osu.Game/Rulesets/Edit/Checks/Components/Issue.cs index f392fa8ef4..0d5c5411fd 100644 --- a/osu.Game/Rulesets/Edit/Checks/Components/Issue.cs +++ b/osu.Game/Rulesets/Edit/Checks/Components/Issue.cs @@ -69,10 +69,7 @@ namespace osu.Game.Rulesets.Edit.Checks.Components HitObjects = hitObjectList; } - public override string ToString() - { - return Template.Message(Arguments); - } + public override string ToString() => Template.GetMessage(Arguments); public string GetEditorTimestamp() { diff --git a/osu.Game/Rulesets/Edit/Checks/Components/IssueTemplate.cs b/osu.Game/Rulesets/Edit/Checks/Components/IssueTemplate.cs index 976341e41c..a1156c7c72 100644 --- a/osu.Game/Rulesets/Edit/Checks/Components/IssueTemplate.cs +++ b/osu.Game/Rulesets/Edit/Checks/Components/IssueTemplate.cs @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Edit.Checks.Components /// Returns the formatted message given the arguments used to format it. /// /// The arguments used to format the message. - public string Message(params object[] args) => UnformattedMessage.FormatWith(args); + public string GetMessage(params object[] args) => UnformattedMessage.FormatWith(args); /// /// Returns the colour corresponding to the type of this issue. From 78bbc8f5c824ffd35a8dab92ca0082c4e7244d7c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Apr 2021 15:47:32 +0900 Subject: [PATCH 1382/1791] Tidy some remaining code --- .../Edit/Checks/Components/CheckCategory.cs | 42 ++++++++++++++----- .../Rulesets/Edit/Checks/Components/Issue.cs | 6 +-- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/Components/CheckCategory.cs b/osu.Game/Rulesets/Edit/Checks/Components/CheckCategory.cs index c37a580dd8..ae943cfda9 100644 --- a/osu.Game/Rulesets/Edit/Checks/Components/CheckCategory.cs +++ b/osu.Game/Rulesets/Edit/Checks/Components/CheckCategory.cs @@ -8,34 +8,54 @@ namespace osu.Game.Rulesets.Edit.Checks.Components /// public enum CheckCategory { - /// Anything to do with control points. + /// + /// Anything to do with control points. + /// Timing, - /// Anything to do with artist, title, creator, etc. + /// + /// Anything to do with artist, title, creator, etc. + /// Metadata, - /// Anything to do with non-audio files, e.g. background, skin, sprites, and video. + /// + /// Anything to do with non-audio files, e.g. background, skin, sprites, and video. + /// Resources, - /// Anything to do with audio files, e.g. song and hitsounds. + /// + /// Anything to do with audio files, e.g. song and hitsounds. + /// Audio, - /// Anything to do with files that don't fit into the above, e.g. unused, osu, or osb. + /// + /// Anything to do with files that don't fit into the above, e.g. unused, osu, or osb. + /// Files, - /// Anything to do with hitobjects unrelated to spread. + /// + /// Anything to do with hitobjects unrelated to spread. + /// Compose, - /// Anything to do with difficulty levels or their progression. + /// + /// Anything to do with difficulty levels or their progression. + /// Spread, - /// Anything to do with variables like CS, OD, AR, HP, and global SV. + /// + /// Anything to do with variables like CS, OD, AR, HP, and global SV. + /// Settings, - /// Anything to do with hitobject feedback. - Hitsounds, + /// + /// Anything to do with hitobject feedback. + /// + HitObjects, - /// Anything to do with storyboarding, breaks, video offset, etc. + /// + /// Anything to do with storyboarding, breaks, video offset, etc. + /// Events } } diff --git a/osu.Game/Rulesets/Edit/Checks/Components/Issue.cs b/osu.Game/Rulesets/Edit/Checks/Components/Issue.cs index 0d5c5411fd..7241fabf5b 100644 --- a/osu.Game/Rulesets/Edit/Checks/Components/Issue.cs +++ b/osu.Game/Rulesets/Edit/Checks/Components/Issue.cs @@ -40,11 +40,7 @@ namespace osu.Game.Rulesets.Edit.Checks.Components Arguments = args; if (template.Origin == null) - { - throw new ArgumentException( - "A template had no origin. Make sure the `Templates()` method contains all templates used." - ); - } + throw new ArgumentException("A template had no origin. Make sure the `Templates()` method contains all templates used."); } public Issue(double? time, IssueTemplate template, params object[] args) From 8bf85d737c75a71ba9e388842770cfc987f2bed7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Apr 2021 15:52:29 +0900 Subject: [PATCH 1383/1791] Change Metadata into a get property --- osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs | 2 +- osu.Game/Rulesets/Edit/Checks/CheckBackground.cs | 2 +- osu.Game/Rulesets/Edit/Checks/Components/Check.cs | 2 +- osu.Game/Screens/Edit/Verify/IssueTable.cs | 2 +- osu.Game/Screens/Edit/Verify/VerifyScreen.cs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs b/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs index b2a00da12a..252f65ab5a 100644 --- a/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs +++ b/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Checks // (higher = more performant, but higher false-negative chance). private const int path_step_size = 5; - public override CheckMetadata Metadata() => new CheckMetadata + public override CheckMetadata Metadata { get; } = new CheckMetadata ( category: CheckCategory.Compose, description: "Offscreen hitobjects." diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs b/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs index 89b2e8293c..22f98b6fd7 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs @@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Edit.Checks { public class CheckBackground : Check { - public override CheckMetadata Metadata() => new CheckMetadata + public override CheckMetadata Metadata { get; } = new CheckMetadata ( category: CheckCategory.Resources, description: "Missing background." diff --git a/osu.Game/Rulesets/Edit/Checks/Components/Check.cs b/osu.Game/Rulesets/Edit/Checks/Components/Check.cs index 550d2ea0a2..7c039d1572 100644 --- a/osu.Game/Rulesets/Edit/Checks/Components/Check.cs +++ b/osu.Game/Rulesets/Edit/Checks/Components/Check.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Edit.Checks.Components /// /// The metadata for this check. /// - public abstract CheckMetadata Metadata(); + public abstract CheckMetadata Metadata { get; } /// /// All possible templates for issues that this check may return. diff --git a/osu.Game/Screens/Edit/Verify/IssueTable.cs b/osu.Game/Screens/Edit/Verify/IssueTable.cs index 1f275ca537..84dbabf300 100644 --- a/osu.Game/Screens/Edit/Verify/IssueTable.cs +++ b/osu.Game/Screens/Edit/Verify/IssueTable.cs @@ -112,7 +112,7 @@ namespace osu.Game.Screens.Edit.Verify }, new OsuSpriteText { - Text = issue.Template.Origin.Metadata().Category.ToString(), + Text = issue.Template.Origin.Metadata.Category.ToString(), Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold), Margin = new MarginPadding(10) } diff --git a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs index 7a520826f5..0c4aa04ff3 100644 --- a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs +++ b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs @@ -130,7 +130,7 @@ namespace osu.Game.Screens.Edit.Verify { table.Issues = beatmapVerifier.Run(Beatmap) .OrderByDescending(issue => issue.Template.Type) - .ThenByDescending(issue => issue.Template.Origin.Metadata().Category); + .ThenByDescending(issue => issue.Template.Origin.Metadata.Category); } } } From 42604afcdcf9ed0ae922b07434b4587ad4b4c85d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Apr 2021 16:15:27 +0900 Subject: [PATCH 1384/1791] Add binding for verify mode (and move enum entry to end) --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 3ea7f5f5fa..36e465720a 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -70,6 +70,7 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.F2 }, GlobalAction.EditorDesignMode), new KeyBinding(new[] { InputKey.F3 }, GlobalAction.EditorTimingMode), new KeyBinding(new[] { InputKey.F4 }, GlobalAction.EditorSetupMode), + new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.A }, GlobalAction.EditorVerifyMode), }; public IEnumerable InGameKeyBindings => new[] @@ -225,9 +226,6 @@ namespace osu.Game.Input.Bindings [Description("Timing mode")] EditorTimingMode, - [Description("Verify mode")] - EditorVerifyMode, - [Description("Hold for HUD")] HoldForHUD, @@ -252,5 +250,8 @@ namespace osu.Game.Input.Bindings [Description("Beatmap Options")] ToggleBeatmapOptions, + + [Description("Verify mode")] + EditorVerifyMode, } } From e19e8ff2a3252b9adde46dae589ce8d2a134f4dc Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 12 Apr 2021 15:52:43 +0900 Subject: [PATCH 1385/1791] Rewrite FramedReplayInputHandler for robustness This commit changes the semantics of `CurrentFrame` and `NextFrame` of the class. The ordering of `NextFrame.Time` and `CurrentFrame.Time` was dependent on the current direction. Now, it should always satisfy `CurrentFrame.Time <= CurrentTime <= NextFrame.Time` except at the start/end. This change, however, doesn't break existing deriving classes if the template code pattern usage of interpolation is used. The deriving class code can be simplified due to the elimination of nullable types. I didn't include those changes in this commit. I removed `StreamingFramedReplayInputHandlerTest` for now, as it is almost-duplicate of `FramedReplayInputHandlerTest`. I'll include more tests in later commits. This commit fixes #6150. --- .../NonVisual/FramedReplayInputHandlerTest.cs | 81 ++--- .../StreamingFramedReplayInputHandlerTest.cs | 296 ------------------ .../Visual/Gameplay/TestSceneSpectator.cs | 4 +- .../Replays/FramedReplayInputHandler.cs | 167 +++++----- 4 files changed, 109 insertions(+), 439 deletions(-) delete mode 100644 osu.Game.Tests/NonVisual/StreamingFramedReplayInputHandlerTest.cs diff --git a/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs b/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs index 92a60663de..b4fc081a2a 100644 --- a/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs +++ b/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs @@ -37,11 +37,6 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestNormalPlayback() { - Assert.IsNull(handler.CurrentFrame); - - confirmCurrentFrame(null); - confirmNextFrame(0); - setTime(0, 0); confirmCurrentFrame(0); confirmNextFrame(1); @@ -97,22 +92,22 @@ namespace osu.Game.Tests.NonVisual // exited important section setTime(8200, 8000); confirmCurrentFrame(7); - confirmNextFrame(null); + confirmNextFrame(7); setTime(8200, 8200); confirmCurrentFrame(7); - confirmNextFrame(null); + confirmNextFrame(7); } [Test] public void TestIntroTime() { setTime(-1000, -1000); - confirmCurrentFrame(null); + confirmCurrentFrame(0); confirmNextFrame(0); setTime(-500, -500); - confirmCurrentFrame(null); + confirmCurrentFrame(0); confirmNextFrame(0); setTime(0, 0); @@ -133,29 +128,29 @@ namespace osu.Game.Tests.NonVisual // pivot without crossing a frame boundary setTime(2700, 2700); confirmCurrentFrame(2); - confirmNextFrame(1); + confirmNextFrame(3); - // cross current frame boundary; should not yet update frame - setTime(1980, 1980); + // cross current frame boundary + setTime(1980, 2000); confirmCurrentFrame(2); - confirmNextFrame(1); + confirmNextFrame(3); setTime(1200, 1200); - confirmCurrentFrame(2); - confirmNextFrame(1); + confirmCurrentFrame(1); + confirmNextFrame(2); // ensure each frame plays out until start setTime(-500, 1000); confirmCurrentFrame(1); - confirmNextFrame(0); + confirmNextFrame(2); setTime(-500, 0); confirmCurrentFrame(0); - confirmNextFrame(null); + confirmNextFrame(1); setTime(-500, -500); confirmCurrentFrame(0); - confirmNextFrame(null); + confirmNextFrame(0); } [Test] @@ -168,12 +163,12 @@ namespace osu.Game.Tests.NonVisual confirmNextFrame(5); setTime(3500, null); - confirmCurrentFrame(4); - confirmNextFrame(3); + confirmCurrentFrame(3); + confirmNextFrame(4); setTime(3000, 3000); confirmCurrentFrame(3); - confirmNextFrame(2); + confirmNextFrame(4); setTime(3500, null); confirmCurrentFrame(3); @@ -187,17 +182,17 @@ namespace osu.Game.Tests.NonVisual confirmCurrentFrame(4); confirmNextFrame(5); - setTime(4000, null); + setTime(4000, 4000); confirmCurrentFrame(4); confirmNextFrame(5); setTime(3500, null); - confirmCurrentFrame(4); - confirmNextFrame(3); + confirmCurrentFrame(3); + confirmNextFrame(4); setTime(3000, 3000); confirmCurrentFrame(3); - confirmNextFrame(2); + confirmNextFrame(4); } [Test] @@ -209,24 +204,24 @@ namespace osu.Game.Tests.NonVisual confirmNextFrame(4); setTime(3200, null); - // next frame doesn't change even though direction reversed, because of important section. confirmCurrentFrame(3); confirmNextFrame(4); - setTime(3000, null); + setTime(3000, 3000); confirmCurrentFrame(3); confirmNextFrame(4); setTime(2800, 2800); - confirmCurrentFrame(3); - confirmNextFrame(2); + confirmCurrentFrame(2); + confirmNextFrame(3); } private void fastForwardToPoint(double destination) { for (int i = 0; i < 1000; i++) { - if (handler.SetFrameFromTime(destination) == null) + var time = handler.SetFrameFromTime(destination); + if (time == null || time == destination) return; } @@ -235,33 +230,17 @@ namespace osu.Game.Tests.NonVisual private void setTime(double set, double? expect) { - Assert.AreEqual(expect, handler.SetFrameFromTime(set)); + Assert.AreEqual(expect, handler.SetFrameFromTime(set), "Unexpected return value"); } - private void confirmCurrentFrame(int? frame) + private void confirmCurrentFrame(int frame) { - if (frame.HasValue) - { - Assert.IsNotNull(handler.CurrentFrame); - Assert.AreEqual(replay.Frames[frame.Value].Time, handler.CurrentFrame.Time); - } - else - { - Assert.IsNull(handler.CurrentFrame); - } + Assert.AreEqual(replay.Frames[frame].Time, handler.CurrentFrame.Time, "Unexpected current frame"); } - private void confirmNextFrame(int? frame) + private void confirmNextFrame(int frame) { - if (frame.HasValue) - { - Assert.IsNotNull(handler.NextFrame); - Assert.AreEqual(replay.Frames[frame.Value].Time, handler.NextFrame.Time); - } - else - { - Assert.IsNull(handler.NextFrame); - } + Assert.AreEqual(replay.Frames[frame].Time, handler.NextFrame.Time, "Unexpected next frame"); } private class TestReplayFrame : ReplayFrame diff --git a/osu.Game.Tests/NonVisual/StreamingFramedReplayInputHandlerTest.cs b/osu.Game.Tests/NonVisual/StreamingFramedReplayInputHandlerTest.cs deleted file mode 100644 index 21ec29b10b..0000000000 --- a/osu.Game.Tests/NonVisual/StreamingFramedReplayInputHandlerTest.cs +++ /dev/null @@ -1,296 +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 NUnit.Framework; -using osu.Game.Replays; -using osu.Game.Rulesets.Replays; - -namespace osu.Game.Tests.NonVisual -{ - [TestFixture] - public class StreamingFramedReplayInputHandlerTest - { - private Replay replay; - private TestInputHandler handler; - - [SetUp] - public void SetUp() - { - handler = new TestInputHandler(replay = new Replay - { - HasReceivedAllFrames = false, - Frames = new List - { - new TestReplayFrame(0), - new TestReplayFrame(1000), - new TestReplayFrame(2000), - new TestReplayFrame(3000, true), - new TestReplayFrame(4000, true), - new TestReplayFrame(5000, true), - new TestReplayFrame(7000, true), - new TestReplayFrame(8000), - } - }); - } - - [Test] - public void TestNormalPlayback() - { - Assert.IsNull(handler.CurrentFrame); - - confirmCurrentFrame(null); - confirmNextFrame(0); - - setTime(0, 0); - confirmCurrentFrame(0); - confirmNextFrame(1); - - // if we hit the first frame perfectly, time should progress to it. - setTime(1000, 1000); - confirmCurrentFrame(1); - confirmNextFrame(2); - - // in between non-important frames should progress based on input. - setTime(1200, 1200); - confirmCurrentFrame(1); - - setTime(1400, 1400); - confirmCurrentFrame(1); - - // progressing beyond the next frame should force time to that frame once. - setTime(2200, 2000); - confirmCurrentFrame(2); - - // second attempt should progress to input time - setTime(2200, 2200); - confirmCurrentFrame(2); - - // entering important section - setTime(3000, 3000); - confirmCurrentFrame(3); - - // cannot progress within - setTime(3500, null); - confirmCurrentFrame(3); - - setTime(4000, 4000); - confirmCurrentFrame(4); - - // still cannot progress - setTime(4500, null); - confirmCurrentFrame(4); - - setTime(5200, 5000); - confirmCurrentFrame(5); - - // important section AllowedImportantTimeSpan allowance - setTime(5200, 5200); - confirmCurrentFrame(5); - - setTime(7200, 7000); - confirmCurrentFrame(6); - - setTime(7200, null); - confirmCurrentFrame(6); - - // exited important section - setTime(8200, 8000); - confirmCurrentFrame(7); - confirmNextFrame(null); - - setTime(8200, null); - confirmCurrentFrame(7); - confirmNextFrame(null); - - setTime(8400, null); - confirmCurrentFrame(7); - confirmNextFrame(null); - } - - [Test] - public void TestIntroTime() - { - setTime(-1000, -1000); - confirmCurrentFrame(null); - confirmNextFrame(0); - - setTime(-500, -500); - confirmCurrentFrame(null); - confirmNextFrame(0); - - setTime(0, 0); - confirmCurrentFrame(0); - confirmNextFrame(1); - } - - [Test] - public void TestBasicRewind() - { - setTime(2800, 0); - setTime(2800, 1000); - setTime(2800, 2000); - setTime(2800, 2800); - confirmCurrentFrame(2); - confirmNextFrame(3); - - // pivot without crossing a frame boundary - setTime(2700, 2700); - confirmCurrentFrame(2); - confirmNextFrame(1); - - // cross current frame boundary; should not yet update frame - setTime(1980, 1980); - confirmCurrentFrame(2); - confirmNextFrame(1); - - setTime(1200, 1200); - confirmCurrentFrame(2); - confirmNextFrame(1); - - // ensure each frame plays out until start - setTime(-500, 1000); - confirmCurrentFrame(1); - confirmNextFrame(0); - - setTime(-500, 0); - confirmCurrentFrame(0); - confirmNextFrame(null); - - setTime(-500, -500); - confirmCurrentFrame(0); - confirmNextFrame(null); - } - - [Test] - public void TestRewindInsideImportantSection() - { - fastForwardToPoint(3000); - - setTime(4000, 4000); - confirmCurrentFrame(4); - confirmNextFrame(5); - - setTime(3500, null); - confirmCurrentFrame(4); - confirmNextFrame(3); - - setTime(3000, 3000); - confirmCurrentFrame(3); - confirmNextFrame(2); - - setTime(3500, null); - confirmCurrentFrame(3); - confirmNextFrame(4); - - setTime(4000, 4000); - confirmCurrentFrame(4); - confirmNextFrame(5); - - setTime(4500, null); - confirmCurrentFrame(4); - confirmNextFrame(5); - - setTime(4000, null); - confirmCurrentFrame(4); - confirmNextFrame(5); - - setTime(3500, null); - confirmCurrentFrame(4); - confirmNextFrame(3); - - setTime(3000, 3000); - confirmCurrentFrame(3); - confirmNextFrame(2); - } - - [Test] - public void TestRewindOutOfImportantSection() - { - fastForwardToPoint(3500); - - confirmCurrentFrame(3); - confirmNextFrame(4); - - setTime(3200, null); - // next frame doesn't change even though direction reversed, because of important section. - confirmCurrentFrame(3); - confirmNextFrame(4); - - setTime(3000, null); - confirmCurrentFrame(3); - confirmNextFrame(4); - - setTime(2800, 2800); - confirmCurrentFrame(3); - confirmNextFrame(2); - } - - private void fastForwardToPoint(double destination) - { - for (int i = 0; i < 1000; i++) - { - if (handler.SetFrameFromTime(destination) == null) - return; - } - - throw new TimeoutException("Seek was never fulfilled"); - } - - private void setTime(double set, double? expect) - { - Assert.AreEqual(expect, handler.SetFrameFromTime(set)); - } - - private void confirmCurrentFrame(int? frame) - { - if (frame.HasValue) - { - Assert.IsNotNull(handler.CurrentFrame); - Assert.AreEqual(replay.Frames[frame.Value].Time, handler.CurrentFrame.Time); - } - else - { - Assert.IsNull(handler.CurrentFrame); - } - } - - private void confirmNextFrame(int? frame) - { - if (frame.HasValue) - { - Assert.IsNotNull(handler.NextFrame); - Assert.AreEqual(replay.Frames[frame.Value].Time, handler.NextFrame.Time); - } - else - { - Assert.IsNull(handler.NextFrame); - } - } - - private class TestReplayFrame : ReplayFrame - { - public readonly bool IsImportant; - - public TestReplayFrame(double time, bool isImportant = false) - : base(time) - { - IsImportant = isImportant; - } - } - - private class TestInputHandler : FramedReplayInputHandler - { - public TestInputHandler(Replay replay) - : base(replay) - { - FrameAccuratePlayback = true; - } - - protected override double AllowedImportantTimeSpan => 1000; - - protected override bool IsImportant(TestReplayFrame frame) => frame.IsImportant; - } - } -} diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 9d85a9995d..9f1faa8e26 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -83,7 +83,7 @@ namespace osu.Game.Tests.Visual.Gameplay waitForPlayer(); AddAssert("ensure frames arrived", () => replayHandler.HasFrames); - AddUntilStep("wait for frame starvation", () => replayHandler.NextFrame == null); + AddUntilStep("wait for frame starvation", () => replayHandler.WaitingNextFrame); checkPaused(true); double? pausedTime = null; @@ -92,7 +92,7 @@ namespace osu.Game.Tests.Visual.Gameplay sendFrames(); - AddUntilStep("wait for frame starvation", () => replayHandler.NextFrame == null); + AddUntilStep("wait for frame starvation", () => replayHandler.WaitingNextFrame); checkPaused(true); AddAssert("time advanced", () => currentFrameStableTime > pausedTime); diff --git a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs index 5eaccc766e..f527c0e105 100644 --- a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs +++ b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs @@ -1,9 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + using System; using System.Collections.Generic; -using System.Diagnostics; using JetBrains.Annotations; using osu.Game.Input.Handlers; using osu.Game.Replays; @@ -17,70 +18,80 @@ namespace osu.Game.Rulesets.Replays public abstract class FramedReplayInputHandler : ReplayInputHandler where TFrame : ReplayFrame { - public override bool IsActive => HasFrames; + /// + /// Whether we have at least one replay frame. + /// + public bool HasFrames => Frames.Count != 0; - private readonly Replay replay; - - protected List Frames => replay.Frames; + /// + /// Whether we are waiting for new frames to be received. + /// + public bool WaitingNextFrame => !replay.HasReceivedAllFrames && currentFrameIndex == Frames.Count - 1; + /// + /// The current frame of the replay. + /// The current time is always between the start and the end time of the current frame. + /// + /// The replay is empty. public TFrame CurrentFrame { get { if (!HasFrames) - throw new InvalidOperationException($"Cannot get {nameof(CurrentFrame)} of the empty replay"); + throw new InvalidOperationException($"Attempted to get {nameof(CurrentFrame)} of an empty replay"); - if (!currentFrameIndex.HasValue) - return null; - - return (TFrame)Frames[currentFrameIndex.Value]; + return (TFrame)Frames[Math.Max(0, currentFrameIndex)]; } } + /// + /// The next frame of the replay. + /// The start time is always greater or equals to the start time of regardless of the seeking direction. + /// If it is before the first frame of the replay or the after the last frame of the replay, and agree. + /// + /// The replay is empty. public TFrame NextFrame { get { if (!HasFrames) - throw new InvalidOperationException($"Cannot get {nameof(NextFrame)} of the empty replay"); + throw new InvalidOperationException($"Attempted to get {nameof(NextFrame)} of an empty replay"); - if (!currentFrameIndex.HasValue) - return currentDirection > 0 ? (TFrame)Frames[0] : null; - - int nextFrame = clampedNextFrameIndex; - - if (nextFrame == currentFrameIndex.Value) - return null; - - return (TFrame)Frames[clampedNextFrameIndex]; + return (TFrame)Frames[Math.Min(currentFrameIndex + 1, Frames.Count - 1)]; } } - private int? currentFrameIndex; - - private int clampedNextFrameIndex => - currentFrameIndex.HasValue ? Math.Clamp(currentFrameIndex.Value + currentDirection, 0, Frames.Count - 1) : 0; - - protected FramedReplayInputHandler(Replay replay) - { - this.replay = replay; - } - - private const double sixty_frame_time = 1000.0 / 60; - - protected virtual double AllowedImportantTimeSpan => sixty_frame_time * 1.2; - - protected double? CurrentTime { get; private set; } - - private int currentDirection = 1; - /// /// When set, we will ensure frames executed by nested drawables are frame-accurate to replay data. /// Disabling this can make replay playback smoother (useful for autoplay, currently). /// public bool FrameAccuratePlayback; - public bool HasFrames => Frames.Count > 0; + // This input handler should be enabled only if there is at least one replay frame. + public override bool IsActive => HasFrames; + + // Can make it non-null but that is a breaking change. + protected double? CurrentTime { get; private set; } + + protected virtual double AllowedImportantTimeSpan => sixty_frame_time * 1.2; + + protected List Frames => replay.Frames; + + private readonly Replay replay; + + private int currentFrameIndex; + + private const double sixty_frame_time = 1000.0 / 60; + + protected FramedReplayInputHandler(Replay replay) + { + // This replay frame ordering should be enforced on the Replay type + replay.Frames.Sort((x, y) => x.Time.CompareTo(y.Time)); + + this.replay = replay; + currentFrameIndex = -1; + CurrentTime = double.NegativeInfinity; + } private bool inImportantSection { @@ -89,13 +100,8 @@ namespace osu.Game.Rulesets.Replays if (!HasFrames || !FrameAccuratePlayback) return false; - var frame = currentDirection > 0 ? CurrentFrame : NextFrame; - - if (frame == null) - return false; - - return IsImportant(frame) && // a button is in a pressed state - Math.Abs(CurrentTime - NextFrame?.Time ?? 0) <= AllowedImportantTimeSpan; // the next frame is within an allowable time span + return IsImportant(CurrentFrame) && // a button is in a pressed state + Math.Abs(CurrentTime - NextFrame.Time ?? 0) <= AllowedImportantTimeSpan; // the next frame is within an allowable time span } } @@ -110,71 +116,52 @@ namespace osu.Game.Rulesets.Replays /// The usable time value. If null, we should not advance time as we do not have enough data. public override double? SetFrameFromTime(double time) { - updateDirection(time); - - Debug.Assert(currentDirection != 0); - if (!HasFrames) { - // in the case all frames are received, allow time to progress regardless. + // In the case all frames are received, allow time to progress regardless. if (replay.HasReceivedAllFrames) return CurrentTime = time; return null; } - TFrame next = NextFrame; + double frameStart = getFrameTime(currentFrameIndex); + double frameEnd = getFrameTime(currentFrameIndex + 1); - // if we have a next frame, check if it is before or at the current time in playback, and advance time to it if so. - if (next != null) + // If the proposed time is after the current frame end time, we progress forwards. + // If the proposed time is before the current frame start time, and we are at the frame boundary, we progress backwards. + if (frameEnd <= time) { - int compare = time.CompareTo(next.Time); - - if (compare == 0 || compare == currentDirection) - { - currentFrameIndex = clampedNextFrameIndex; - return CurrentTime = CurrentFrame.Time; - } + time = frameEnd; + currentFrameIndex++; } + else if (time < frameStart && CurrentTime == frameStart) + currentFrameIndex--; - // at this point, the frame index can't be advanced. - // even so, we may be able to propose the clock progresses forward due to being at an extent of the replay, - // or moving towards the next valid frame (ie. interpolating in a non-important section). + frameStart = getFrameTime(currentFrameIndex); + frameEnd = getFrameTime(currentFrameIndex + 1); - // the exception is if currently in an important section, which is respected above all. - if (inImportantSection) + // Pause until more frames are arrived. + if (WaitingNextFrame && frameStart < time) { - Debug.Assert(next != null || !replay.HasReceivedAllFrames); + CurrentTime = frameStart; return null; } - // if a next frame does exist, allow interpolation. - if (next != null) - return CurrentTime = time; + CurrentTime = Math.Clamp(time, frameStart, frameEnd); - // if all frames have been received, allow playing beyond extents. - if (replay.HasReceivedAllFrames) - return CurrentTime = time; - - // if not all frames are received but we are before the first frame, allow playing. - if (time < Frames[0].Time) - return CurrentTime = time; - - // in the case we have no next frames and haven't received enough frame data, block. - return null; + // In an important section, a mid-frame time cannot be used and a null is returned instead. + return inImportantSection && frameStart < time && time < frameEnd ? null : CurrentTime; } - private void updateDirection(double time) + private double getFrameTime(int index) { - if (!CurrentTime.HasValue) - { - currentDirection = 1; - } - else - { - currentDirection = time.CompareTo(CurrentTime); - if (currentDirection == 0) currentDirection = 1; - } + if (index < 0) + return double.NegativeInfinity; + if (index >= Frames.Count) + return double.PositiveInfinity; + + return Frames[index].Time; } } } From 3c28c09ab5073c20a8eaebb8ce043a6cdc16b77f Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 12 Apr 2021 16:18:35 +0900 Subject: [PATCH 1386/1791] Add more FramedReplayInputHandler tests --- .../NonVisual/FramedReplayInputHandlerTest.cs | 100 ++++++++++++++++-- 1 file changed, 89 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs b/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs index b4fc081a2a..64af4b6db6 100644 --- a/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs +++ b/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs @@ -20,23 +20,15 @@ namespace osu.Game.Tests.NonVisual { handler = new TestInputHandler(replay = new Replay { - Frames = new List - { - new TestReplayFrame(0), - new TestReplayFrame(1000), - new TestReplayFrame(2000), - new TestReplayFrame(3000, true), - new TestReplayFrame(4000, true), - new TestReplayFrame(5000, true), - new TestReplayFrame(7000, true), - new TestReplayFrame(8000), - } + HasReceivedAllFrames = false }); } [Test] public void TestNormalPlayback() { + setReplayFrames(); + setTime(0, 0); confirmCurrentFrame(0); confirmNextFrame(1); @@ -102,6 +94,8 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestIntroTime() { + setReplayFrames(); + setTime(-1000, -1000); confirmCurrentFrame(0); confirmNextFrame(0); @@ -118,6 +112,8 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestBasicRewind() { + setReplayFrames(); + setTime(2800, 0); setTime(2800, 1000); setTime(2800, 2000); @@ -156,6 +152,7 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestRewindInsideImportantSection() { + setReplayFrames(); fastForwardToPoint(3000); setTime(4000, 4000); @@ -198,6 +195,7 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestRewindOutOfImportantSection() { + setReplayFrames(); fastForwardToPoint(3500); confirmCurrentFrame(3); @@ -216,6 +214,86 @@ namespace osu.Game.Tests.NonVisual confirmNextFrame(3); } + [Test] + public void TestReplayStreaming() + { + // no frames are arrived yet + setTime(0, null); + setTime(1000, null); + Assert.IsTrue(handler.WaitingNextFrame, "Should be waiting for the first frame"); + + replay.Frames.Add(new TestReplayFrame(0)); + replay.Frames.Add(new TestReplayFrame(1000)); + + // should always play from beginning + setTime(1000, 0); + confirmCurrentFrame(0); + Assert.IsFalse(handler.WaitingNextFrame, "Should not be waiting yet"); + setTime(1000, 1000); + confirmCurrentFrame(1); + confirmNextFrame(1); + Assert.IsTrue(handler.WaitingNextFrame, "Should be waiting"); + + // cannot seek beyond the last frame + setTime(1500, null); + confirmCurrentFrame(1); + + setTime(-100, 0); + confirmCurrentFrame(0); + + // can seek to the point before the first frame, however + setTime(-100, -100); + confirmCurrentFrame(0); + confirmNextFrame(0); + + fastForwardToPoint(1000); + setTime(3000, null); + replay.Frames.Add(new TestReplayFrame(2000)); + confirmCurrentFrame(1); + setTime(1000, 1000); + setTime(3000, 2000); + } + + [Test] + public void TestMultipleFramesSameTime() + { + replay.Frames.Add(new TestReplayFrame(0)); + replay.Frames.Add(new TestReplayFrame(0)); + replay.Frames.Add(new TestReplayFrame(1000)); + replay.Frames.Add(new TestReplayFrame(1000)); + replay.Frames.Add(new TestReplayFrame(2000)); + + // forward direction is prioritized when multiple frames have the same time. + setTime(0, 0); + setTime(0, 0); + + setTime(2000, 1000); + setTime(2000, 1000); + + setTime(1000, 1000); + setTime(1000, 1000); + setTime(-100, 1000); + setTime(-100, 0); + setTime(-100, 0); + setTime(-100, -100); + } + + private void setReplayFrames() + { + replay.Frames = new List + { + new TestReplayFrame(0), + new TestReplayFrame(1000), + new TestReplayFrame(2000), + new TestReplayFrame(3000, true), + new TestReplayFrame(4000, true), + new TestReplayFrame(5000, true), + new TestReplayFrame(7000, true), + new TestReplayFrame(8000), + }; + replay.HasReceivedAllFrames = true; + } + private void fastForwardToPoint(double destination) { for (int i = 0; i < 1000; i++) From 0eab9daf13bb406d2b82c1f624dbb7e483f0ab40 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Apr 2021 16:41:36 +0900 Subject: [PATCH 1387/1791] Update existing overlay containers to not block scroll input --- osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs | 2 ++ .../Screens/OnlinePlay/Match/Components/MatchSettingsOverlay.cs | 2 ++ osu.Game/Screens/Play/GameplayMenuOverlay.cs | 2 ++ osu.Game/Screens/Play/SongProgress.cs | 2 ++ 4 files changed, 8 insertions(+) diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index fbf2ffd4bd..e168f265dd 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs @@ -23,6 +23,8 @@ namespace osu.Game.Graphics.Containers protected virtual string PopInSampleName => "UI/overlay-pop-in"; protected virtual string PopOutSampleName => "UI/overlay-pop-out"; + protected override bool BlockScrollInput => false; + protected override bool BlockNonPositionalInput => true; /// diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/MatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Match/Components/MatchSettingsOverlay.cs index ea3951fc3b..5699da740c 100644 --- a/osu.Game/Screens/OnlinePlay/Match/Components/MatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Match/Components/MatchSettingsOverlay.cs @@ -19,6 +19,8 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components protected OnlinePlayComposite Settings { get; set; } + protected override bool BlockScrollInput => false; + [BackgroundDependencyLoader] private void load() { diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index f938839be3..4a28da0dde 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -31,6 +31,8 @@ namespace osu.Game.Screens.Play protected override bool BlockNonPositionalInput => true; + protected override bool BlockScrollInput => false; + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; public Action OnRetry; diff --git a/osu.Game/Screens/Play/SongProgress.cs b/osu.Game/Screens/Play/SongProgress.cs index acf4640aa4..6c7cb9376c 100644 --- a/osu.Game/Screens/Play/SongProgress.cs +++ b/osu.Game/Screens/Play/SongProgress.cs @@ -45,6 +45,8 @@ namespace osu.Game.Screens.Play public override bool HandleNonPositionalInput => AllowSeeking.Value; public override bool HandlePositionalInput => AllowSeeking.Value; + protected override bool BlockScrollInput => false; + private double firstHitTime => objects.First().StartTime; private IEnumerable objects; From 65ebdd8f7a48c57f31e46acd21ac3369f8521be9 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Mon, 12 Apr 2021 10:08:08 +0200 Subject: [PATCH 1388/1791] Move check origin from `IssueTemplate` to `Issue` As a result we can also make check an interface, and need to provide the check itself when constructing an issue. --- .../Edit/Checks/CheckOffscreenObjects.cs | 14 +++++------ .../Edit/OsuBeatmapVerifier.cs | 2 +- osu.Game/Rulesets/Edit/BeatmapVerifier.cs | 2 +- .../Rulesets/Edit/Checks/CheckBackground.cs | 12 +++++----- .../Rulesets/Edit/Checks/Components/Check.cs | 14 ++++------- .../Rulesets/Edit/Checks/Components/Issue.cs | 23 +++++++++++-------- .../Edit/Checks/Components/IssueTemplate.cs | 5 ---- osu.Game/Screens/Edit/Verify/VerifyScreen.cs | 2 +- 8 files changed, 33 insertions(+), 41 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs b/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs index 252f65ab5a..b34c9966a4 100644 --- a/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs +++ b/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs @@ -9,7 +9,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Edit.Checks { - public class CheckOffscreenObjects : Check + public class CheckOffscreenObjects : ICheck { // These are close approximates to the edges of the screen // in gameplay on a 4:3 aspect ratio for osu!stable. @@ -22,13 +22,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Checks // (higher = more performant, but higher false-negative chance). private const int path_step_size = 5; - public override CheckMetadata Metadata { get; } = new CheckMetadata + public CheckMetadata Metadata { get; } = new CheckMetadata ( category: CheckCategory.Compose, description: "Offscreen hitobjects." ); - public override IEnumerable PossibleTemplates => new[] + public IEnumerable PossibleTemplates => new[] { templateOffscreen, templateOffscreenSliderPath @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Checks unformattedMessage: "This slider goes offscreen here on a 4:3 aspect ratio." ); - public override IEnumerable Run(IBeatmap beatmap) + public IEnumerable Run(IBeatmap beatmap) { foreach (var hitobject in beatmap.HitObjects) { @@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Checks case HitCircle circle: { if (isOffscreen(circle.StackedPosition, circle.Radius)) - yield return new Issue(circle, templateOffscreen); + yield return new Issue(this, circle, templateOffscreen); break; } @@ -89,7 +89,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Checks // `SpanDuration` ensures we don't include reverses. double time = slider.StartTime + progress * slider.SpanDuration; - yield return new Issue(slider, templateOffscreenSliderPath) { Time = time }; + yield return new Issue(this, slider, templateOffscreenSliderPath) { Time = time }; yield break; } @@ -98,7 +98,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Checks if (!isOffscreen(slider.StackedEndPosition, slider.Radius)) yield break; - yield return new Issue(slider, templateOffscreenSliderPath) { Time = slider.EndTime }; + yield return new Issue(this, slider, templateOffscreenSliderPath) { Time = slider.EndTime }; } private bool isOffscreen(Vector2 position, double radius) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs b/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs index 272612dbaf..2faa239720 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Edit { public class OsuBeatmapVerifier : BeatmapVerifier { - private readonly List checks = new List + private readonly List checks = new List { new CheckOffscreenObjects() }; diff --git a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs index f67a068525..1d0508705a 100644 --- a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs +++ b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Edit /// Checks which are performed regardless of ruleset. /// These handle things like beatmap metadata, timing, and other ruleset agnostic elements. /// - private readonly IReadOnlyList generalChecks = new List + private readonly IReadOnlyList generalChecks = new List { new CheckBackground() }; diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs b/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs index 22f98b6fd7..c922aa03c0 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs @@ -8,15 +8,15 @@ using osu.Game.Rulesets.Edit.Checks.Components; namespace osu.Game.Rulesets.Edit.Checks { - public class CheckBackground : Check + public class CheckBackground : ICheck { - public override CheckMetadata Metadata { get; } = new CheckMetadata + public CheckMetadata Metadata { get; } = new CheckMetadata ( category: CheckCategory.Resources, description: "Missing background." ); - public override IEnumerable PossibleTemplates => new[] + public IEnumerable PossibleTemplates => new[] { templateNoneSet, templateDoesNotExist @@ -34,11 +34,11 @@ namespace osu.Game.Rulesets.Edit.Checks unformattedMessage: "The background file \"{0}\" is does not exist." ); - public override IEnumerable Run(IBeatmap beatmap) + public IEnumerable Run(IBeatmap beatmap) { if (beatmap.Metadata.BackgroundFile == null) { - yield return new Issue(templateNoneSet); + yield return new Issue(this, templateNoneSet); yield break; } @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Edit.Checks if (file != null) yield break; - yield return new Issue(templateDoesNotExist, beatmap.Metadata.BackgroundFile); + yield return new Issue(this, templateDoesNotExist, beatmap.Metadata.BackgroundFile); } } } diff --git a/osu.Game/Rulesets/Edit/Checks/Components/Check.cs b/osu.Game/Rulesets/Edit/Checks/Components/Check.cs index 7c039d1572..f355ae734e 100644 --- a/osu.Game/Rulesets/Edit/Checks/Components/Check.cs +++ b/osu.Game/Rulesets/Edit/Checks/Components/Check.cs @@ -6,28 +6,22 @@ using osu.Game.Beatmaps; namespace osu.Game.Rulesets.Edit.Checks.Components { - public abstract class Check + public interface ICheck { /// /// The metadata for this check. /// - public abstract CheckMetadata Metadata { get; } + public CheckMetadata Metadata { get; } /// /// All possible templates for issues that this check may return. /// - public abstract IEnumerable PossibleTemplates { get; } + public IEnumerable PossibleTemplates { get; } /// /// Runs this check and returns any issues detected for the provided beatmap. /// /// The beatmap to run the check on. - public abstract IEnumerable Run(IBeatmap beatmap); - - protected Check() - { - foreach (var template in PossibleTemplates) - template.Origin = this; - } + public IEnumerable Run(IBeatmap beatmap); } } diff --git a/osu.Game/Rulesets/Edit/Checks/Components/Issue.cs b/osu.Game/Rulesets/Edit/Checks/Components/Issue.cs index 7241fabf5b..d0f7df857b 100644 --- a/osu.Game/Rulesets/Edit/Checks/Components/Issue.cs +++ b/osu.Game/Rulesets/Edit/Checks/Components/Issue.cs @@ -26,38 +26,41 @@ namespace osu.Game.Rulesets.Edit.Checks.Components /// public IssueTemplate Template; + /// + /// The check that this issue originates from. + /// + public ICheck Check; + /// /// The arguments that give this issue its context, based on the . These are then substituted into the . /// This could for instance include timestamps, which diff is being compared to, what some volume is, etc. /// public object[] Arguments; - public Issue(IssueTemplate template, params object[] args) + public Issue(ICheck check, IssueTemplate template, params object[] args) { + Check = check; Time = null; HitObjects = Array.Empty(); Template = template; Arguments = args; - - if (template.Origin == null) - throw new ArgumentException("A template had no origin. Make sure the `Templates()` method contains all templates used."); } - public Issue(double? time, IssueTemplate template, params object[] args) - : this(template, args) + public Issue(ICheck check, double? time, IssueTemplate template, params object[] args) + : this(check, template, args) { Time = time; } - public Issue(HitObject hitObject, IssueTemplate template, params object[] args) - : this(template, args) + public Issue(ICheck check, HitObject hitObject, IssueTemplate template, params object[] args) + : this(check, template, args) { Time = hitObject.StartTime; HitObjects = new[] { hitObject }; } - public Issue(IEnumerable hitObjects, IssueTemplate template, params object[] args) - : this(template, args) + public Issue(ICheck check, IEnumerable hitObjects, IssueTemplate template, params object[] args) + : this(check, template, args) { var hitObjectList = hitObjects.ToList(); diff --git a/osu.Game/Rulesets/Edit/Checks/Components/IssueTemplate.cs b/osu.Game/Rulesets/Edit/Checks/Components/IssueTemplate.cs index a1156c7c72..4a5f98ca5f 100644 --- a/osu.Game/Rulesets/Edit/Checks/Components/IssueTemplate.cs +++ b/osu.Game/Rulesets/Edit/Checks/Components/IssueTemplate.cs @@ -14,11 +14,6 @@ namespace osu.Game.Rulesets.Edit.Checks.Components private static readonly Color4 negligible_green = new Colour4(0.33f, 0.8f, 0.5f, 1.0f); private static readonly Color4 error_gray = new Colour4(0.5f, 0.5f, 0.5f, 1.0f); - /// - /// The check that this template originates from. - /// - public Check Origin; - /// /// The type of the issue. /// diff --git a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs index 0c4aa04ff3..c0c4a040f6 100644 --- a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs +++ b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs @@ -130,7 +130,7 @@ namespace osu.Game.Screens.Edit.Verify { table.Issues = beatmapVerifier.Run(Beatmap) .OrderByDescending(issue => issue.Template.Type) - .ThenByDescending(issue => issue.Template.Origin.Metadata.Category); + .ThenByDescending(issue => issue.Check.Metadata.Category); } } } From a2fc9c398fc731e36c3472bc37a5c153af7d5378 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Mon, 12 Apr 2021 10:08:30 +0200 Subject: [PATCH 1389/1791] Rename `CreateChecker` -> `CreateBeatmapVerifier` --- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- osu.Game/Rulesets/Ruleset.cs | 2 +- osu.Game/Screens/Edit/Verify/VerifyScreen.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 1658846e98..63da100a04 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -206,7 +206,7 @@ namespace osu.Game.Rulesets.Osu public override HitObjectComposer CreateHitObjectComposer() => new OsuHitObjectComposer(this); - public override BeatmapVerifier CreateChecker() => new OsuBeatmapVerifier(); + public override BeatmapVerifier CreateBeatmapVerifier() => new OsuBeatmapVerifier(); public override string Description => "osu!"; diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 088bcc7712..2a29d88c89 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -202,7 +202,7 @@ namespace osu.Game.Rulesets public virtual HitObjectComposer CreateHitObjectComposer() => null; - public virtual BeatmapVerifier CreateChecker() => null; + public virtual BeatmapVerifier CreateBeatmapVerifier() => null; public virtual Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.Solid.QuestionCircle }; diff --git a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs index c0c4a040f6..806029df4d 100644 --- a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs +++ b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs @@ -36,7 +36,7 @@ namespace osu.Game.Screens.Edit.Verify var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); ruleset = parent.Get>().Value.BeatmapInfo.Ruleset?.CreateInstance(); - beatmapVerifier = ruleset?.CreateChecker(); + beatmapVerifier = ruleset?.CreateBeatmapVerifier(); return dependencies; } From f1b8171e389e5df4eb67b567d4e48586a3001c73 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 12 Apr 2021 17:13:48 +0900 Subject: [PATCH 1390/1791] Remove `#nullable true` for now to suppress inspector --- osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs index f527c0e105..45bcd0bc19 100644 --- a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs +++ b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable enable - using System; using System.Collections.Generic; using JetBrains.Annotations; From b5954a55adc3af47c27ea4c2b354e320d9b6cf4c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Apr 2021 17:46:14 +0900 Subject: [PATCH 1391/1791] Remove empty xmldoc --- osu.Desktop/Program.cs | 1 - .../Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs | 1 - osu.Game.Tournament/TournamentGameBase.cs | 1 - osu.Game/Beatmaps/IBeatmap.cs | 1 - osu.Game/Configuration/SettingsStore.cs | 1 - osu.Game/Graphics/Backgrounds/Triangles.cs | 1 - osu.Game/IO/Serialization/IJsonSerializable.cs | 1 - osu.Game/Input/KeyBindingStore.cs | 1 - osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 1 - osu.Game/Rulesets/Objects/SliderPath.cs | 2 -- osu.Game/Rulesets/Ruleset.cs | 1 - osu.Game/Rulesets/Scoring/HitWindows.cs | 1 - osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs | 1 - osu.Game/Screens/Ranking/ScorePanelList.cs | 1 - osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs | 1 - osu.Game/Tests/Beatmaps/LegacyModConversionTest.cs | 1 - osu.Game/Utils/Optional.cs | 1 - 17 files changed, 18 deletions(-) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index d06c4b6746..5fb09c0cef 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -69,7 +69,6 @@ namespace osu.Desktop /// Allow a maximum of one unhandled exception, per second of execution. /// /// - /// private static bool handleException(Exception arg) { bool continueExecution = Interlocked.Decrement(ref allowableExceptions) >= 0; diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index c81710ed18..26e5d381e2 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -482,7 +482,6 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// Retrieves the sample info list at a point in time. /// /// The time to retrieve the sample info list from. - /// private IList sampleInfoListAt(int time) => nodeSamplesAt(time)?.First() ?? HitObject.Samples; /// diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 2ee52c35aa..92eb7ac713 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -141,7 +141,6 @@ namespace osu.Game.Tournament /// /// Add missing player info based on user IDs. /// - /// private bool addPlayers() { bool addedInfo = false; diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index 9847ea020a..769b33009a 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -44,7 +44,6 @@ namespace osu.Game.Beatmaps /// /// Returns statistics for the contained in this beatmap. /// - /// IEnumerable GetStatistics(); /// diff --git a/osu.Game/Configuration/SettingsStore.cs b/osu.Game/Configuration/SettingsStore.cs index f8c9bdeaf8..86e84b0732 100644 --- a/osu.Game/Configuration/SettingsStore.cs +++ b/osu.Game/Configuration/SettingsStore.cs @@ -22,7 +22,6 @@ namespace osu.Game.Configuration /// /// The ruleset's internal ID. /// An optional variant. - /// public List Query(int? rulesetId = null, int? variant = null) => ContextFactory.Get().DatabasedSetting.Where(b => b.RulesetID == rulesetId && b.Variant == variant).ToList(); diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs index 0e9382279a..67cee883c8 100644 --- a/osu.Game/Graphics/Backgrounds/Triangles.cs +++ b/osu.Game/Graphics/Backgrounds/Triangles.cs @@ -346,7 +346,6 @@ namespace osu.Game.Graphics.Backgrounds /// such that the smaller triangles appear on top. /// /// - /// public int CompareTo(TriangleParticle other) => other.Scale.CompareTo(Scale); } } diff --git a/osu.Game/IO/Serialization/IJsonSerializable.cs b/osu.Game/IO/Serialization/IJsonSerializable.cs index ba188963ea..c8d5ce39a6 100644 --- a/osu.Game/IO/Serialization/IJsonSerializable.cs +++ b/osu.Game/IO/Serialization/IJsonSerializable.cs @@ -22,7 +22,6 @@ namespace osu.Game.IO.Serialization /// /// Creates the default that should be used for all s. /// - /// public static JsonSerializerSettings CreateGlobalSettings() => new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore, diff --git a/osu.Game/Input/KeyBindingStore.cs b/osu.Game/Input/KeyBindingStore.cs index b25b00eb84..9d0cfedc03 100644 --- a/osu.Game/Input/KeyBindingStore.cs +++ b/osu.Game/Input/KeyBindingStore.cs @@ -85,7 +85,6 @@ namespace osu.Game.Input /// /// The ruleset's internal ID. /// An optional variant. - /// public List Query(int? rulesetId = null, int? variant = null) => ContextFactory.Get().DatabasedKeyBinding.Where(b => b.RulesetID == rulesetId && b.Variant == variant).ToList(); diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 6da9f12b50..d95b246c96 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -574,7 +574,6 @@ namespace osu.Game.Rulesets.Objects.Drawables /// Calculate the position to be used for sample playback at a specified X position (0..1). /// /// The lookup X position. Generally should be . - /// protected double CalculateSamplePlaybackBalance(double position) { const float balance_adjust_amount = 0.4f; diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index 61f5f94142..e64298f98d 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -147,7 +147,6 @@ namespace osu.Game.Rulesets.Objects /// to 1 (end of the path). /// /// Ranges from 0 (beginning of the path) to 1 (end of the path). - /// public Vector2 PositionAt(double progress) { ensureValid(); @@ -161,7 +160,6 @@ namespace osu.Game.Rulesets.Objects /// The first point has a PathType which all other points inherit. /// /// One of the control points in the segment. - /// public List PointsInSegment(PathControlPoint controlPoint) { bool found = false; diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 38d30a2e31..efc8b50e3c 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -146,7 +146,6 @@ namespace osu.Game.Rulesets /// The beatmap to create the hit renderer for. /// The s to apply. /// Unable to successfully load the beatmap to be usable with this ruleset. - /// public abstract DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null); /// diff --git a/osu.Game/Rulesets/Scoring/HitWindows.cs b/osu.Game/Rulesets/Scoring/HitWindows.cs index 018b50bd3d..410614de07 100644 --- a/osu.Game/Rulesets/Scoring/HitWindows.cs +++ b/osu.Game/Rulesets/Scoring/HitWindows.cs @@ -62,7 +62,6 @@ namespace osu.Game.Rulesets.Scoring /// /// Retrieves a mapping of s to their timing windows for all allowed s. /// - /// public IEnumerable<(HitResult result, double length)> GetAllAvailableWindows() { for (var result = HitResult.Meh; result <= HitResult.Perfect; ++result) diff --git a/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs b/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs index a7b0fb05e3..dcf5f8a788 100644 --- a/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs +++ b/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs @@ -12,7 +12,6 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons /// /// Whether this is selected. /// - /// public readonly BindableBool Selected; /// diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index 77b3d8fc3b..441c9e048a 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -226,7 +226,6 @@ namespace osu.Game.Screens.Ranking /// /// Enumerates all s contained in this . /// - /// public IEnumerable GetScorePanels() => flow.Select(t => t.Panel); /// diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs index fcf20a2eb2..5ef2458919 100644 --- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs +++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs @@ -189,7 +189,6 @@ namespace osu.Game.Tests.Beatmaps /// /// Creates the applicable to this . /// - /// protected abstract Ruleset CreateRuleset(); private class ConvertResult diff --git a/osu.Game/Tests/Beatmaps/LegacyModConversionTest.cs b/osu.Game/Tests/Beatmaps/LegacyModConversionTest.cs index 76f97db59f..54a83f4305 100644 --- a/osu.Game/Tests/Beatmaps/LegacyModConversionTest.cs +++ b/osu.Game/Tests/Beatmaps/LegacyModConversionTest.cs @@ -17,7 +17,6 @@ namespace osu.Game.Tests.Beatmaps /// /// Creates the whose legacy mod conversion is to be tested. /// - /// protected abstract Ruleset CreateRuleset(); protected void TestFromLegacy(LegacyMods legacyMods, Type[] expectedMods) diff --git a/osu.Game/Utils/Optional.cs b/osu.Game/Utils/Optional.cs index 9f8a1c2e62..fdb7623be5 100644 --- a/osu.Game/Utils/Optional.cs +++ b/osu.Game/Utils/Optional.cs @@ -37,7 +37,6 @@ namespace osu.Game.Utils /// Shortcase for: optional.HasValue ? optional.Value : fallback. /// /// The fallback value to return if is false. - /// public T GetOr(T fallback) => HasValue ? Value : fallback; public static implicit operator Optional(T value) => new Optional(value); From 6d18b3db00a5f3f045dc35cdc6a740bae7156f90 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 12 Apr 2021 18:49:38 +0900 Subject: [PATCH 1392/1791] Avoid empty list allocation --- osu.Game/Rulesets/UI/RulesetInputManager.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index fb56a5d93d..1c0d820a3d 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -102,10 +102,13 @@ namespace osu.Game.Rulesets.UI #endregion + // to avoid allocation + private readonly List emptyInputList = new List(); + protected override List GetPendingInputs() { if (replayInputHandler != null && !replayInputHandler.IsActive) - return new List(); + return emptyInputList; return base.GetPendingInputs(); } From 359fae895f843ff7b8775917d443a89556705d07 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 12 Apr 2021 18:50:25 +0900 Subject: [PATCH 1393/1791] Rename property --- osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs | 6 +++--- osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs | 4 ++-- osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs b/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs index 64af4b6db6..954871595e 100644 --- a/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs +++ b/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs @@ -220,7 +220,7 @@ namespace osu.Game.Tests.NonVisual // no frames are arrived yet setTime(0, null); setTime(1000, null); - Assert.IsTrue(handler.WaitingNextFrame, "Should be waiting for the first frame"); + Assert.IsTrue(handler.WaitingForFrame, "Should be waiting for the first frame"); replay.Frames.Add(new TestReplayFrame(0)); replay.Frames.Add(new TestReplayFrame(1000)); @@ -228,11 +228,11 @@ namespace osu.Game.Tests.NonVisual // should always play from beginning setTime(1000, 0); confirmCurrentFrame(0); - Assert.IsFalse(handler.WaitingNextFrame, "Should not be waiting yet"); + Assert.IsFalse(handler.WaitingForFrame, "Should not be waiting yet"); setTime(1000, 1000); confirmCurrentFrame(1); confirmNextFrame(1); - Assert.IsTrue(handler.WaitingNextFrame, "Should be waiting"); + Assert.IsTrue(handler.WaitingForFrame, "Should be waiting"); // cannot seek beyond the last frame setTime(1500, null); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 9f1faa8e26..397b37718d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -83,7 +83,7 @@ namespace osu.Game.Tests.Visual.Gameplay waitForPlayer(); AddAssert("ensure frames arrived", () => replayHandler.HasFrames); - AddUntilStep("wait for frame starvation", () => replayHandler.WaitingNextFrame); + AddUntilStep("wait for frame starvation", () => replayHandler.WaitingForFrame); checkPaused(true); double? pausedTime = null; @@ -92,7 +92,7 @@ namespace osu.Game.Tests.Visual.Gameplay sendFrames(); - AddUntilStep("wait for frame starvation", () => replayHandler.WaitingNextFrame); + AddUntilStep("wait for frame starvation", () => replayHandler.WaitingForFrame); checkPaused(true); AddAssert("time advanced", () => currentFrameStableTime > pausedTime); diff --git a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs index 45bcd0bc19..23cc311d79 100644 --- a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs +++ b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Replays /// /// Whether we are waiting for new frames to be received. /// - public bool WaitingNextFrame => !replay.HasReceivedAllFrames && currentFrameIndex == Frames.Count - 1; + public bool WaitingForFrame => !replay.HasReceivedAllFrames && currentFrameIndex == Frames.Count - 1; /// /// The current frame of the replay. @@ -140,7 +140,7 @@ namespace osu.Game.Rulesets.Replays frameEnd = getFrameTime(currentFrameIndex + 1); // Pause until more frames are arrived. - if (WaitingNextFrame && frameStart < time) + if (WaitingForFrame && frameStart < time) { CurrentTime = frameStart; return null; From 31d36071052bf72f9a82256242cbb6fa7c2b0e38 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 12 Apr 2021 18:50:54 +0900 Subject: [PATCH 1394/1791] Add TODO comment --- osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs index 23cc311d79..c3cd957f0d 100644 --- a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs +++ b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Replays /// /// The next frame of the replay. - /// The start time is always greater or equals to the start time of regardless of the seeking direction. + /// The start time is always greater or equal to the start time of regardless of the seeking direction. /// If it is before the first frame of the replay or the after the last frame of the replay, and agree. /// /// The replay is empty. @@ -83,7 +83,8 @@ namespace osu.Game.Rulesets.Replays protected FramedReplayInputHandler(Replay replay) { - // This replay frame ordering should be enforced on the Replay type + // TODO: This replay frame ordering should be enforced on the Replay type. + // Currently, the ordering can be broken if the frames are added after this construction. replay.Frames.Sort((x, y) => x.Time.CompareTo(y.Time)); this.replay = replay; From cc2acf5e548fb11873f1d20d480b27e35ced57fb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Apr 2021 19:05:23 +0900 Subject: [PATCH 1395/1791] Fix ctrl-dragging on an existing selection causing deselection of the hovered object --- .../Compose/Components/BlueprintContainer.cs | 38 ++++++++++++++----- .../Compose/Components/SelectionHandler.cs | 37 +++++++++++++++--- 2 files changed, 60 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 5699be4560..64cf0e7512 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -135,11 +135,12 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override bool OnMouseDown(MouseDownEvent e) { - if (!beginClickSelection(e)) return true; + bool selectionPerformed = beginClickSelection(e); + // even if a selection didn't occur, a drag event may still move the selection. prepareSelectionMovement(); - return e.Button == MouseButton.Left; + return selectionPerformed || e.Button == MouseButton.Left; } private SelectionBlueprint clickedBlueprint; @@ -154,7 +155,7 @@ namespace osu.Game.Screens.Edit.Compose.Components // Deselection should only occur if no selected blueprints are hovered // A special case for when a blueprint was selected via this click is added since OnClick() may occur outside the hitobject and should not trigger deselection - if (endClickSelection() || clickedBlueprint != null) + if (endClickSelection(e) || clickedBlueprint != null) return true; deselectAll(); @@ -177,7 +178,12 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override void OnMouseUp(MouseUpEvent e) { // Special case for when a drag happened instead of a click - Schedule(() => endClickSelection()); + Schedule(() => + { + endClickSelection(e); + clickSelectionBegan = false; + isDraggingBlueprint = false; + }); finishSelectionMovement(); } @@ -226,7 +232,6 @@ namespace osu.Game.Screens.Edit.Compose.Components Beatmap.Update(obj); changeHandler?.EndChange(); - isDraggingBlueprint = false; } if (DragBox.State == Visibility.Visible) @@ -355,13 +360,28 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// Finishes the current blueprint selection. /// + /// The mouse event which triggered end of selection. /// Whether a click selection was active. - private bool endClickSelection() + private bool endClickSelection(MouseButtonEvent e) { - if (!clickSelectionBegan) - return false; + if (!clickSelectionBegan && !isDraggingBlueprint) + { + // if a selection didn't occur, we may want to trigger a deselection. + if (e.ControlPressed && e.Button == MouseButton.Left) + { + // Iterate from the top of the input stack (blueprints closest to the front of the screen first). + // Priority is given to already-selected blueprints. + foreach (SelectionBlueprint blueprint in SelectionBlueprints.AliveChildren.Reverse().OrderByDescending(b => b.IsSelected)) + { + if (!blueprint.IsHovered) continue; + + return clickSelectionBegan = SelectionHandler.HandleDeselectionRequested(blueprint, e); + } + } + + return false; + } - clickSelectionBegan = false; return true; } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 018d4d081c..e5e1100797 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -228,12 +228,31 @@ namespace osu.Game.Screens.Edit.Compose.Components return false; } - if (e.ControlPressed && e.Button == MouseButton.Left) + // while holding control, we only want to add to selection, not replace an existing selection. + if (e.ControlPressed && e.Button == MouseButton.Left && !blueprint.IsSelected) + { blueprint.ToggleSelection(); - else - ensureSelected(blueprint); + return true; + } - return true; + return ensureSelected(blueprint); + } + + /// + /// Handle a blueprint requesting selection. + /// + /// The blueprint. + /// The mouse event responsible for deselection. + /// Whether a deselection was performed. + internal bool HandleDeselectionRequested(SelectionBlueprint blueprint, MouseButtonEvent e) + { + if (blueprint.IsSelected) + { + blueprint.ToggleSelection(); + return true; + } + + return false; } private void handleQuickDeletion(SelectionBlueprint blueprint) @@ -247,13 +266,19 @@ namespace osu.Game.Screens.Edit.Compose.Components deleteSelected(); } - private void ensureSelected(SelectionBlueprint blueprint) + /// + /// Ensure the blueprint is in a selected state. + /// + /// The blueprint to select. + /// Whether selection state was changed. + private bool ensureSelected(SelectionBlueprint blueprint) { if (blueprint.IsSelected) - return; + return false; DeselectAll?.Invoke(); blueprint.Select(); + return true; } private void deleteSelected() From b4c75ba3c6eb81a8cf88f36edc8738ee7e2e09df Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Apr 2021 19:19:25 +0900 Subject: [PATCH 1396/1791] Fix TestQuickDeleteRemovesObject failing on second run --- .../Visual/Editing/TestSceneEditorQuickDelete.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorQuickDelete.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorQuickDelete.cs index 9efd299fba..8a0f27b851 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorQuickDelete.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorQuickDelete.cs @@ -51,7 +51,7 @@ namespace osu.Game.Tests.Visual.Editing [Test] public void TestQuickDeleteRemovesSliderControlPoint() { - Slider slider = new Slider { StartTime = 1000 }; + Slider slider = null; PathControlPoint[] points = { @@ -62,7 +62,12 @@ namespace osu.Game.Tests.Visual.Editing AddStep("add slider", () => { - slider.Path = new SliderPath(points); + slider = new Slider + { + StartTime = 1000, + Path = new SliderPath(points) + }; + EditorBeatmap.Add(slider); }); From 905cd7c8eb9e7c3785ffa88e63cce5b4b7f2c024 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Apr 2021 19:22:07 +0900 Subject: [PATCH 1397/1791] Update resources --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 196d122a2a..c78dfb6a55 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 71a6f0e5cd..92e05cb4a6 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -30,7 +30,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index a389cc13dd..11124730c9 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + From d2d7f77430181ce55d76eb7e2ef24613cfe69ca4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 12 Apr 2021 19:50:24 +0900 Subject: [PATCH 1398/1791] Fix mods not being serialised correctly in ScoreInfo --- .../Online/TestAPIModJsonSerialization.cs | 33 +++++++++++++ osu.Game/Scoring/ScoreInfo.cs | 49 +++++++++---------- 2 files changed, 57 insertions(+), 25 deletions(-) diff --git a/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs b/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs index 77f910c144..3afb7481b1 100644 --- a/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs +++ b/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs @@ -11,7 +11,10 @@ using osu.Game.Online.API; using osu.Game.Rulesets; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.UI; +using osu.Game.Scoring; namespace osu.Game.Tests.Online { @@ -84,6 +87,36 @@ namespace osu.Game.Tests.Online Assert.That(converted?.OverallDifficulty.Value, Is.EqualTo(11)); } + [Test] + public void TestDeserialiseScoreInfoWithEmptyMods() + { + var score = new ScoreInfo { Ruleset = new OsuRuleset().RulesetInfo }; + + var deserialised = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(score)); + + if (deserialised != null) + deserialised.Ruleset = new OsuRuleset().RulesetInfo; + + Assert.That(deserialised?.Mods.Length, Is.Zero); + } + + [Test] + public void TestDeserialiseScoreInfoWithCustomModSetting() + { + var score = new ScoreInfo + { + Ruleset = new OsuRuleset().RulesetInfo, + Mods = new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 2 } } } + }; + + var deserialised = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(score)); + + if (deserialised != null) + deserialised.Ruleset = new OsuRuleset().RulesetInfo; + + Assert.That(((OsuModDoubleTime)deserialised?.Mods[0])?.SpeedChange.Value, Is.EqualTo(2)); + } + private class TestRuleset : Ruleset { public override IEnumerable GetModsFor(ModType type) => new Mod[] diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index ef11c19e3f..df8f309ee0 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -10,6 +10,7 @@ using Newtonsoft.Json.Converters; using osu.Framework.Extensions; using osu.Game.Beatmaps; using osu.Game.Database; +using osu.Game.Online.API; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; @@ -55,9 +56,10 @@ namespace osu.Game.Scoring [JsonIgnore] public virtual RulesetInfo Ruleset { get; set; } + private APIMod[] localAPIMods; private Mod[] mods; - [JsonProperty("mods")] + [JsonIgnore] [NotMapped] public Mod[] Mods { @@ -66,45 +68,50 @@ namespace osu.Game.Scoring if (mods != null) return mods; - if (modsJson == null) + if (apiMods == null) return Array.Empty(); - return getModsFromRuleset(JsonConvert.DeserializeObject(modsJson)); + var rulesetInstance = Ruleset.CreateInstance(); + return apiMods.Select(m => m.ToMod(rulesetInstance)).ToArray(); } set { - modsJson = null; + localAPIMods = null; mods = value; } } - private Mod[] getModsFromRuleset(DeserializedMod[] mods) => Ruleset.CreateInstance().GetAllMods().Where(mod => mods.Any(d => d.Acronym == mod.Acronym)).ToArray(); - - private string modsJson; - - [JsonIgnore] - [Column("Mods")] - public string ModsJson + // Used for API serialisation/deserialisation. + [JsonProperty("mods")] + private APIMod[] apiMods { get { - if (modsJson != null) - return modsJson; + if (localAPIMods != null) + return localAPIMods; if (mods == null) - return null; + return Array.Empty(); - return modsJson = JsonConvert.SerializeObject(mods.Select(m => new DeserializedMod { Acronym = m.Acronym })); + return localAPIMods = mods.Select(m => new APIMod(m)).ToArray(); } set { - modsJson = value; + localAPIMods = value; - // we potentially can't update this yet due to Ruleset being late-bound, so instead update on read as necessary. + // We potentially can't update this yet due to Ruleset being late-bound, so instead update on read as necessary. mods = null; } } + // Used for database serialisation/deserialisation. + [Column("Mods")] + private string modsString + { + get => JsonConvert.SerializeObject(apiMods); + set => apiMods = JsonConvert.DeserializeObject(value); + } + [NotMapped] [JsonProperty("user")] public User User { get; set; } @@ -251,14 +258,6 @@ namespace osu.Game.Scoring } } - [Serializable] - protected class DeserializedMod : IMod - { - public string Acronym { get; set; } - - public bool Equals(IMod other) => Acronym == other?.Acronym; - } - public override string ToString() => $"{User} playing {Beatmap}"; public bool Equals(ScoreInfo other) From 982d8fa8b1ef8ce69a4456b8a31192f948314f09 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 12 Apr 2021 20:49:26 +0900 Subject: [PATCH 1399/1791] Fix incorrect reference --- osu.Game/Scoring/ScoreInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index df8f309ee0..1e438edeea 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -68,7 +68,7 @@ namespace osu.Game.Scoring if (mods != null) return mods; - if (apiMods == null) + if (localAPIMods == null) return Array.Empty(); var rulesetInstance = Ruleset.CreateInstance(); From 625484468e4ba788e04b73b0c6b55614d8e30bdf Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 12 Apr 2021 20:49:37 +0900 Subject: [PATCH 1400/1791] Fix DB serialisation --- osu.Game/Scoring/ScoreInfo.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 1e438edeea..01b62f8b4f 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -105,8 +105,9 @@ namespace osu.Game.Scoring } // Used for database serialisation/deserialisation. + [JsonIgnore] [Column("Mods")] - private string modsString + public string ModsString { get => JsonConvert.SerializeObject(apiMods); set => apiMods = JsonConvert.DeserializeObject(value); From 8413b0a5d32a7aa159bfc971f10b2b11bd058c5c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 12 Apr 2021 20:49:44 +0900 Subject: [PATCH 1401/1791] Don't map api mods to DB --- osu.Game/Scoring/ScoreInfo.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 01b62f8b4f..312b5f46f4 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -83,6 +83,7 @@ namespace osu.Game.Scoring // Used for API serialisation/deserialisation. [JsonProperty("mods")] + [NotMapped] private APIMod[] apiMods { get From e9a114a15c37bf498ff7e1e8b24775964f01a4b8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 12 Apr 2021 20:50:18 +0900 Subject: [PATCH 1402/1791] Rename property back --- osu.Game/Scoring/ScoreInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 312b5f46f4..222f69b025 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -108,7 +108,7 @@ namespace osu.Game.Scoring // Used for database serialisation/deserialisation. [JsonIgnore] [Column("Mods")] - public string ModsString + public string ModsJson { get => JsonConvert.SerializeObject(apiMods); set => apiMods = JsonConvert.DeserializeObject(value); From c531e38a369452f3be8664e0c2b6ff4bb079e64c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 12 Apr 2021 22:00:27 +0900 Subject: [PATCH 1403/1791] Rework to create a derived tracked user data instead --- ...TestSceneMultiplayerGameplayLeaderboard.cs | 2 +- .../MultiplayerSpectatorLeaderboard.cs | 85 +++++------ .../HUD/MultiplayerGameplayLeaderboard.cs | 137 +++++++++--------- 3 files changed, 107 insertions(+), 117 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index 1ee848b902..272d547505 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -163,7 +163,7 @@ namespace osu.Game.Tests.Visual.Multiplayer break; } - ((ISpectatorClient)this).UserSentFrames(userId, new FrameDataBundle(header, Array.Empty())); + ((ISpectatorClient)this).UserSentFrames(userId, new FrameDataBundle(header, new[] { new LegacyReplayFrame(Time.Current, 0, 0, ReplayButtonState.None) })); } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorLeaderboard.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorLeaderboard.cs index aa9f162036..1b9e2bda2d 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorLeaderboard.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiplayerSpectatorLeaderboard.cs @@ -2,10 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; -using System.Linq; +using JetBrains.Annotations; using osu.Framework.Timing; -using osu.Game.Online.Spectator; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play.HUD; @@ -13,73 +11,62 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { public class MultiplayerSpectatorLeaderboard : MultiplayerGameplayLeaderboard { - private readonly Dictionary trackedData = new Dictionary(); - public MultiplayerSpectatorLeaderboard(ScoreProcessor scoreProcessor, int[] userIds) : base(scoreProcessor, userIds) { } - public void AddClock(int userId, IClock source) => trackedData[userId] = new TrackedUserData(source); - - public void RemoveClock(int userId) => trackedData.Remove(userId); - - protected override void OnIncomingFrames(int userId, FrameDataBundle bundle) + public void AddClock(int userId, IClock clock) { - if (!trackedData.TryGetValue(userId, out var data)) + if (!UserScores.TryGetValue(userId, out var data)) return; - data.Frames.Add(new TimedFrameHeader(bundle.Frames.First().Time, bundle.Header)); + ((SpectatingTrackedUserData)data).Clock = clock; } + public void RemoveClock(int userId) + { + if (!UserScores.TryGetValue(userId, out var data)) + return; + + ((SpectatingTrackedUserData)data).Clock = null; + } + + protected override TrackedUserData CreateUserData(int userId, ScoreProcessor scoreProcessor) => new SpectatingTrackedUserData(userId, scoreProcessor); + protected override void Update() { base.Update(); - foreach (var (userId, data) in trackedData) + foreach (var (_, data) in UserScores) + data.UpdateScore(); + } + + private class SpectatingTrackedUserData : TrackedUserData + { + [CanBeNull] + public IClock Clock; + + public SpectatingTrackedUserData(int userId, ScoreProcessor scoreProcessor) + : base(userId, scoreProcessor) { - var targetTime = data.Clock.CurrentTime; + } - if (data.Frames.Count == 0) - continue; + public override void UpdateScore() + { + if (Frames.Count == 0) + return; - int frameIndex = data.Frames.BinarySearch(new TimedFrameHeader(targetTime)); + if (Clock == null) + return; + + int frameIndex = Frames.BinarySearch(new TimedFrame(Clock.CurrentTime)); if (frameIndex < 0) frameIndex = ~frameIndex; - frameIndex = Math.Clamp(frameIndex - 1, 0, data.Frames.Count - 1); + frameIndex = Math.Clamp(frameIndex - 1, 0, Frames.Count - 1); - SetCurrentFrame(userId, data.Frames[frameIndex].Header); + SetFrame(Frames[frameIndex]); } } - - private class TrackedUserData - { - public readonly IClock Clock; - public readonly List Frames = new List(); - - public TrackedUserData(IClock clock) - { - Clock = clock; - } - } - - private class TimedFrameHeader : IComparable - { - public readonly double Time; - public readonly FrameHeader Header; - - public TimedFrameHeader(double time) - { - Time = time; - } - - public TimedFrameHeader(double time, FrameHeader header) - { - Time = time; - Header = header; - } - - public int CompareTo(TimedFrameHeader other) => Time.CompareTo(other.Time); - } } } diff --git a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs index f0d2ac4b7f..70de067784 100644 --- a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs @@ -1,10 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Configuration; @@ -19,8 +19,7 @@ namespace osu.Game.Screens.Play.HUD [LongRunningLoad] public class MultiplayerGameplayLeaderboard : GameplayLeaderboard { - private readonly ScoreProcessor scoreProcessor; - private readonly Dictionary userScores = new Dictionary(); + protected readonly Dictionary UserScores = new Dictionary(); [Resolved] private SpectatorStreamingClient streamingClient { get; set; } @@ -31,9 +30,9 @@ namespace osu.Game.Screens.Play.HUD [Resolved] private UserLookupCache userLookupCache { get; set; } - private Bindable scoringMode; - + private readonly ScoreProcessor scoreProcessor; private readonly BindableList playingUsers; + private Bindable scoringMode; /// /// Construct a new leaderboard. @@ -52,6 +51,8 @@ namespace osu.Game.Screens.Play.HUD [BackgroundDependencyLoader] private void load(OsuConfigManager config, IAPIProvider api) { + scoringMode = config.GetBindable(OsuSetting.ScoreDisplayMode); + foreach (var userId in playingUsers) { streamingClient.WatchUser(userId); @@ -59,19 +60,17 @@ namespace osu.Game.Screens.Play.HUD // probably won't be required in the final implementation. var resolvedUser = userLookupCache.GetUserAsync(userId).Result; - var trackedUser = new TrackedUserData(); + var trackedUser = CreateUserData(userId, scoreProcessor); + trackedUser.ScoringMode.BindTo(scoringMode); - userScores[userId] = trackedUser; var leaderboardScore = AddPlayer(resolvedUser, resolvedUser?.Id == api.LocalUser.Value.Id); + leaderboardScore.Accuracy.BindTo(trackedUser.Accuracy); + leaderboardScore.TotalScore.BindTo(trackedUser.Score); + leaderboardScore.Combo.BindTo(trackedUser.CurrentCombo); + leaderboardScore.HasQuit.BindTo(trackedUser.UserQuit); - ((IBindable)leaderboardScore.Accuracy).BindTo(trackedUser.Accuracy); - ((IBindable)leaderboardScore.TotalScore).BindTo(trackedUser.Score); - ((IBindable)leaderboardScore.Combo).BindTo(trackedUser.CurrentCombo); - ((IBindable)leaderboardScore.HasQuit).BindTo(trackedUser.UserQuit); + UserScores[userId] = trackedUser; } - - scoringMode = config.GetBindable(OsuSetting.ScoreDisplayMode); - scoringMode.BindValueChanged(updateAllScores, true); } protected override void LoadComplete() @@ -101,7 +100,7 @@ namespace osu.Game.Screens.Play.HUD { streamingClient.StopWatchingUser(userId); - if (userScores.TryGetValue(userId, out var trackedData)) + if (UserScores.TryGetValue(userId, out var trackedData)) trackedData.MarkUserQuit(); } @@ -109,39 +108,16 @@ namespace osu.Game.Screens.Play.HUD } } - private void updateAllScores(ValueChangedEvent mode) - { - foreach (var trackedData in userScores.Values) - trackedData.UpdateScore(scoreProcessor, mode.NewValue); - } - private void handleIncomingFrames(int userId, FrameDataBundle bundle) => Schedule(() => { - if (userScores.ContainsKey(userId)) - OnIncomingFrames(userId, bundle); + if (!UserScores.TryGetValue(userId, out var trackedData)) + return; + + trackedData.Frames.Add(new TimedFrame(bundle.Frames.First().Time, bundle.Header)); + trackedData.UpdateScore(); }); - /// - /// Invoked when new frames have arrived for a user. - /// - /// - /// By default, this immediately sets the current frame to be displayed for the user. - /// - /// The user which the frames arrived for. - /// The bundle of frames. - protected virtual void OnIncomingFrames(int userId, FrameDataBundle bundle) => SetCurrentFrame(userId, bundle.Header); - - /// - /// Sets the current frame to be displayed for a user. - /// - /// The user to set the frame of. - /// The frame to set. - protected void SetCurrentFrame(int userId, FrameHeader header) - { - var trackedScore = userScores[userId]; - trackedScore.LastHeader = header; - trackedScore.UpdateScore(scoreProcessor, scoringMode.Value); - } + protected virtual TrackedUserData CreateUserData(int userId, ScoreProcessor scoreProcessor) => new TrackedUserData(userId, scoreProcessor); protected override void Dispose(bool isDisposing) { @@ -158,38 +134,65 @@ namespace osu.Game.Screens.Play.HUD } } - private class TrackedUserData + protected class TrackedUserData { - public IBindableNumber Score => score; + public readonly int UserId; + public readonly ScoreProcessor ScoreProcessor; - private readonly BindableDouble score = new BindableDouble(); + public readonly BindableDouble Score = new BindableDouble(); + public readonly BindableDouble Accuracy = new BindableDouble(1); + public readonly BindableInt CurrentCombo = new BindableInt(); + public readonly BindableBool UserQuit = new BindableBool(); - public IBindableNumber Accuracy => accuracy; + public readonly IBindable ScoringMode = new Bindable(); - private readonly BindableDouble accuracy = new BindableDouble(1); + public readonly List Frames = new List(); - public IBindableNumber CurrentCombo => currentCombo; - - private readonly BindableInt currentCombo = new BindableInt(); - - public IBindable UserQuit => userQuit; - - private readonly BindableBool userQuit = new BindableBool(); - - [CanBeNull] - public FrameHeader LastHeader; - - public void MarkUserQuit() => userQuit.Value = true; - - public void UpdateScore(ScoreProcessor processor, ScoringMode mode) + public TrackedUserData(int userId, ScoreProcessor scoreProcessor) { - if (LastHeader == null) + UserId = userId; + ScoreProcessor = scoreProcessor; + + ScoringMode.BindValueChanged(_ => UpdateScore()); + } + + public void MarkUserQuit() => UserQuit.Value = true; + + public virtual void UpdateScore() + { + if (Frames.Count == 0) return; - score.Value = processor.GetImmediateScore(mode, LastHeader.MaxCombo, LastHeader.Statistics); - accuracy.Value = LastHeader.Accuracy; - currentCombo.Value = LastHeader.Combo; + SetFrame(Frames.Last()); } + + protected void SetFrame(TimedFrame frame) + { + var header = frame.Header; + + Score.Value = ScoreProcessor.GetImmediateScore(ScoringMode.Value, header.MaxCombo, header.Statistics); + Accuracy.Value = header.Accuracy; + CurrentCombo.Value = header.Combo; + } + } + + protected class TimedFrame : IComparable + { + public readonly double Time; + public readonly FrameHeader Header; + + public TimedFrame(double time) + { + Time = time; + } + + public TimedFrame(double time, FrameHeader header) + { + Time = time; + Header = header; + } + + public int CompareTo(TimedFrame other) => Time.CompareTo(other.Time); } } } From 1e002841cff4ffeac18a58c57c6a0769e33b05c9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 12 Apr 2021 22:03:45 +0900 Subject: [PATCH 1404/1791] Add test for scoring mode changes --- .../TestSceneMultiplayerGameplayLeaderboard.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index 272d547505..b6c06bb149 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Framework.Utils; +using osu.Game.Configuration; using osu.Game.Database; using osu.Game.Online; using osu.Game.Online.API; @@ -38,6 +39,8 @@ namespace osu.Game.Tests.Visual.Multiplayer protected override Container Content { get; } = new Container { RelativeSizeAxes = Axes.Both }; + private OsuConfigManager config; + public TestSceneMultiplayerGameplayLeaderboard() { base.Content.Children = new Drawable[] @@ -48,6 +51,12 @@ namespace osu.Game.Tests.Visual.Multiplayer }; } + [BackgroundDependencyLoader] + private void load() + { + Dependencies.Cache(config = new OsuConfigManager(LocalStorage)); + } + [SetUpSteps] public override void SetUpSteps() { @@ -97,6 +106,14 @@ namespace osu.Game.Tests.Visual.Multiplayer AddRepeatStep("mark user quit", () => Client.CurrentMatchPlayingUserIds.RemoveAt(0), users); } + [Test] + public void TestChangeScoringMode() + { + AddRepeatStep("update state", () => streamingClient.RandomlyUpdateState(), 5); + AddStep("change to classic", () => config.SetValue(OsuSetting.ScoreDisplayMode, ScoringMode.Classic)); + AddStep("change to standardised", () => config.SetValue(OsuSetting.ScoreDisplayMode, ScoringMode.Standardised)); + } + public class TestMultiplayerStreaming : SpectatorStreamingClient { public new BindableList PlayingUsers => (BindableList)base.PlayingUsers; From 7c4f6d2b62635ef38c95754484f7bb82b2d2122d Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Mon, 12 Apr 2021 15:47:26 +0200 Subject: [PATCH 1405/1791] Rework template usage Includes moving the origin check back to templates, constructing nested template classes in each check, and making parameterized template usage. --- .../Edit/Checks/CheckOffscreenObjects.cs | 57 ++++++++++++------- .../Rulesets/Edit/Checks/CheckBackground.cs | 55 +++++++++++------- .../Rulesets/Edit/Checks/Components/Issue.cs | 17 +++--- .../Edit/Checks/Components/IssueTemplate.cs | 8 ++- 4 files changed, 88 insertions(+), 49 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs b/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs index b34c9966a4..c4f38e6d09 100644 --- a/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs +++ b/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs @@ -22,29 +22,46 @@ namespace osu.Game.Rulesets.Osu.Edit.Checks // (higher = more performant, but higher false-negative chance). private const int path_step_size = 5; + private readonly IssueTemplateOffscreenCircle templateOffscreenCircle; + private readonly IssueTemplateOffscreenSlider templateOffscreenSlider; + private readonly IssueTemplate[] templates; + + private class IssueTemplateOffscreenCircle : IssueTemplate + { + public IssueTemplateOffscreenCircle(ICheck checkOrigin) + : base(checkOrigin, IssueType.Problem, "This circle goes offscreen on a 4:3 aspect ratio.") + { + } + + public Issue Create(HitCircle circle) => new Issue(circle, this); + } + + private class IssueTemplateOffscreenSlider : IssueTemplate + { + public IssueTemplateOffscreenSlider(ICheck checkOrigin) + : base(checkOrigin, IssueType.Problem, "This slider goes offscreen here on a 4:3 aspect ratio.") + { + } + + public Issue Create(Slider slider, double offscreenTime) => new Issue(slider, this) { Time = offscreenTime }; + } + + public CheckOffscreenObjects() + { + templates = new IssueTemplate[] + { + templateOffscreenCircle = new IssueTemplateOffscreenCircle(this), + templateOffscreenSlider = new IssueTemplateOffscreenSlider(this) + }; + } + public CheckMetadata Metadata { get; } = new CheckMetadata ( category: CheckCategory.Compose, description: "Offscreen hitobjects." ); - public IEnumerable PossibleTemplates => new[] - { - templateOffscreen, - templateOffscreenSliderPath - }; - - private readonly IssueTemplate templateOffscreen = new IssueTemplate - ( - type: IssueType.Problem, - unformattedMessage: "This object goes offscreen on a 4:3 aspect ratio." - ); - - private readonly IssueTemplate templateOffscreenSliderPath = new IssueTemplate - ( - type: IssueType.Problem, - unformattedMessage: "This slider goes offscreen here on a 4:3 aspect ratio." - ); + public IEnumerable PossibleTemplates => templates; public IEnumerable Run(IBeatmap beatmap) { @@ -63,7 +80,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Checks case HitCircle circle: { if (isOffscreen(circle.StackedPosition, circle.Radius)) - yield return new Issue(this, circle, templateOffscreen); + yield return templateOffscreenCircle.Create(circle); break; } @@ -89,7 +106,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Checks // `SpanDuration` ensures we don't include reverses. double time = slider.StartTime + progress * slider.SpanDuration; - yield return new Issue(this, slider, templateOffscreenSliderPath) { Time = time }; + yield return templateOffscreenSlider.Create(slider, time); yield break; } @@ -98,7 +115,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Checks if (!isOffscreen(slider.StackedEndPosition, slider.Radius)) yield break; - yield return new Issue(this, slider, templateOffscreenSliderPath) { Time = slider.EndTime }; + yield return templateOffscreenSlider.Create(slider, slider.EndTime); } private bool isOffscreen(Vector2 position, double radius) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs b/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs index c922aa03c0..1e45ea261c 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs @@ -10,35 +10,52 @@ namespace osu.Game.Rulesets.Edit.Checks { public class CheckBackground : ICheck { + private readonly IssueTemplateNoneSet templateNoneSet; + private readonly IssueTemplateDoesNotExist templateDoesNotExist; + private readonly IssueTemplate[] templates; + + private class IssueTemplateNoneSet : IssueTemplate + { + public IssueTemplateNoneSet(ICheck checkOrigin) + : base(checkOrigin, IssueType.Problem, "No background has been set") + { + } + + public Issue Create() => new Issue(this); + } + + private class IssueTemplateDoesNotExist : IssueTemplate + { + public IssueTemplateDoesNotExist(ICheck checkOrigin) + : base(checkOrigin, IssueType.Problem, "The background file \"{0}\" does not exist.") + { + } + + public Issue Create(string filename) => new Issue(this, filename); + } + + public CheckBackground() + { + templates = new IssueTemplate[] + { + templateNoneSet = new IssueTemplateNoneSet(this), + templateDoesNotExist = new IssueTemplateDoesNotExist(this) + }; + } + public CheckMetadata Metadata { get; } = new CheckMetadata ( category: CheckCategory.Resources, description: "Missing background." ); - public IEnumerable PossibleTemplates => new[] - { - templateNoneSet, - templateDoesNotExist - }; - - private readonly IssueTemplate templateNoneSet = new IssueTemplate - ( - type: IssueType.Problem, - unformattedMessage: "No background has been set." - ); - - private readonly IssueTemplate templateDoesNotExist = new IssueTemplate - ( - type: IssueType.Problem, - unformattedMessage: "The background file \"{0}\" is does not exist." - ); + public IEnumerable PossibleTemplates => templates; public IEnumerable Run(IBeatmap beatmap) { if (beatmap.Metadata.BackgroundFile == null) { - yield return new Issue(this, templateNoneSet); + yield return templateNoneSet.Create(); yield break; } @@ -51,7 +68,7 @@ namespace osu.Game.Rulesets.Edit.Checks if (file != null) yield break; - yield return new Issue(this, templateDoesNotExist, beatmap.Metadata.BackgroundFile); + yield return templateDoesNotExist.Create(beatmap.Metadata.BackgroundFile); } } } diff --git a/osu.Game/Rulesets/Edit/Checks/Components/Issue.cs b/osu.Game/Rulesets/Edit/Checks/Components/Issue.cs index d0f7df857b..2bc9930e8f 100644 --- a/osu.Game/Rulesets/Edit/Checks/Components/Issue.cs +++ b/osu.Game/Rulesets/Edit/Checks/Components/Issue.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Edit.Checks.Components /// /// The check that this issue originates from. /// - public ICheck Check; + public ICheck Check => Template.Check; /// /// The arguments that give this issue its context, based on the . These are then substituted into the . @@ -37,30 +37,29 @@ namespace osu.Game.Rulesets.Edit.Checks.Components /// public object[] Arguments; - public Issue(ICheck check, IssueTemplate template, params object[] args) + public Issue(IssueTemplate template, params object[] args) { - Check = check; Time = null; HitObjects = Array.Empty(); Template = template; Arguments = args; } - public Issue(ICheck check, double? time, IssueTemplate template, params object[] args) - : this(check, template, args) + public Issue(double? time, IssueTemplate template, params object[] args) + : this(template, args) { Time = time; } - public Issue(ICheck check, HitObject hitObject, IssueTemplate template, params object[] args) - : this(check, template, args) + public Issue(HitObject hitObject, IssueTemplate template, params object[] args) + : this(template, args) { Time = hitObject.StartTime; HitObjects = new[] { hitObject }; } - public Issue(ICheck check, IEnumerable hitObjects, IssueTemplate template, params object[] args) - : this(check, template, args) + public Issue(IEnumerable hitObjects, IssueTemplate template, params object[] args) + : this(template, args) { var hitObjectList = hitObjects.ToList(); diff --git a/osu.Game/Rulesets/Edit/Checks/Components/IssueTemplate.cs b/osu.Game/Rulesets/Edit/Checks/Components/IssueTemplate.cs index 4a5f98ca5f..e746844879 100644 --- a/osu.Game/Rulesets/Edit/Checks/Components/IssueTemplate.cs +++ b/osu.Game/Rulesets/Edit/Checks/Components/IssueTemplate.cs @@ -14,6 +14,11 @@ namespace osu.Game.Rulesets.Edit.Checks.Components private static readonly Color4 negligible_green = new Colour4(0.33f, 0.8f, 0.5f, 1.0f); private static readonly Color4 error_gray = new Colour4(0.5f, 0.5f, 0.5f, 1.0f); + /// + /// The check that this template originates from. + /// + public ICheck Check; + /// /// The type of the issue. /// @@ -26,8 +31,9 @@ namespace osu.Game.Rulesets.Edit.Checks.Components /// public readonly string UnformattedMessage; - public IssueTemplate(IssueType type, string unformattedMessage) + public IssueTemplate(ICheck check, IssueType type, string unformattedMessage) { + Check = check; Type = type; UnformattedMessage = unformattedMessage; } From 1c69829ad488edf10b2aaaa347c5b3be28aeabc0 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Mon, 12 Apr 2021 15:47:58 +0200 Subject: [PATCH 1406/1791] Fix `Template.Origin` -> `Check` --- osu.Game/Screens/Edit/Verify/IssueTable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Verify/IssueTable.cs b/osu.Game/Screens/Edit/Verify/IssueTable.cs index 84dbabf300..516e1adf44 100644 --- a/osu.Game/Screens/Edit/Verify/IssueTable.cs +++ b/osu.Game/Screens/Edit/Verify/IssueTable.cs @@ -112,7 +112,7 @@ namespace osu.Game.Screens.Edit.Verify }, new OsuSpriteText { - Text = issue.Template.Origin.Metadata.Category.ToString(), + Text = issue.Check.Metadata.Category.ToString(), Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold), Margin = new MarginPadding(10) } From 008dbc7dd6f841ea245ec8b732696ebbbb2a73b4 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Mon, 12 Apr 2021 15:49:13 +0200 Subject: [PATCH 1407/1791] Reverse `IssueType` ordering Reversed both in the enum and where it's displayed, so ends up the same in the end. --- osu.Game/Rulesets/Edit/Checks/Components/IssueType.cs | 10 +++++----- osu.Game/Screens/Edit/Verify/VerifyScreen.cs | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/Components/IssueType.cs b/osu.Game/Rulesets/Edit/Checks/Components/IssueType.cs index be43060cfc..1241e058ad 100644 --- a/osu.Game/Rulesets/Edit/Checks/Components/IssueType.cs +++ b/osu.Game/Rulesets/Edit/Checks/Components/IssueType.cs @@ -4,22 +4,22 @@ namespace osu.Game.Rulesets.Edit.Checks.Components { /// - /// The type, or severity, of an issue. This decides its priority. + /// The type, or severity, of an issue. /// public enum IssueType { /// A must-fix in the vast majority of cases. - Problem = 3, + Problem, /// A possible mistake. Often requires critical thinking. - Warning = 2, + Warning, // TODO: Try/catch all checks run and return error templates if exceptions occur. /// An error occurred and a complete check could not be made. - Error = 1, + Error, // TODO: Negligible issues should be hidden by default. /// A possible mistake so minor/unlikely that it can often be safely ignored. - Negligible = 0, + Negligible, } } diff --git a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs index 806029df4d..a3d808ce62 100644 --- a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs +++ b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs @@ -129,8 +129,8 @@ namespace osu.Game.Screens.Edit.Verify private void refresh() { table.Issues = beatmapVerifier.Run(Beatmap) - .OrderByDescending(issue => issue.Template.Type) - .ThenByDescending(issue => issue.Check.Metadata.Category); + .OrderBy(issue => issue.Template.Type) + .ThenBy(issue => issue.Check.Metadata.Category); } } } From caaaba59505f19f999a593a3c35f460c598c9065 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Mon, 12 Apr 2021 16:20:53 +0200 Subject: [PATCH 1408/1791] Rename `Check` -> `ICheck` --- osu.Game/Rulesets/Edit/Checks/Components/{Check.cs => ICheck.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename osu.Game/Rulesets/Edit/Checks/Components/{Check.cs => ICheck.cs} (100%) diff --git a/osu.Game/Rulesets/Edit/Checks/Components/Check.cs b/osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs similarity index 100% rename from osu.Game/Rulesets/Edit/Checks/Components/Check.cs rename to osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs From 6d50d01186f24c342b0fc406803b2486cebbbf4b Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Mon, 12 Apr 2021 16:23:05 +0200 Subject: [PATCH 1409/1791] Make `IssueTemplate.Check` readonly --- osu.Game/Rulesets/Edit/Checks/Components/IssueTemplate.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/Checks/Components/IssueTemplate.cs b/osu.Game/Rulesets/Edit/Checks/Components/IssueTemplate.cs index e746844879..97df79ecd8 100644 --- a/osu.Game/Rulesets/Edit/Checks/Components/IssueTemplate.cs +++ b/osu.Game/Rulesets/Edit/Checks/Components/IssueTemplate.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Edit.Checks.Components /// /// The check that this template originates from. /// - public ICheck Check; + public readonly ICheck Check; /// /// The type of the issue. From 36bd235021d08202dd0c489b1bc664b91a31aca1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Apr 2021 23:36:10 +0900 Subject: [PATCH 1410/1791] Move nested classes to bottom of file --- .../Edit/Checks/CheckOffscreenObjects.cs | 40 +++++++++---------- .../Rulesets/Edit/Checks/CheckBackground.cs | 40 +++++++++---------- 2 files changed, 40 insertions(+), 40 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs b/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs index c4f38e6d09..f2a062b6d7 100644 --- a/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs +++ b/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs @@ -26,26 +26,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Checks private readonly IssueTemplateOffscreenSlider templateOffscreenSlider; private readonly IssueTemplate[] templates; - private class IssueTemplateOffscreenCircle : IssueTemplate - { - public IssueTemplateOffscreenCircle(ICheck checkOrigin) - : base(checkOrigin, IssueType.Problem, "This circle goes offscreen on a 4:3 aspect ratio.") - { - } - - public Issue Create(HitCircle circle) => new Issue(circle, this); - } - - private class IssueTemplateOffscreenSlider : IssueTemplate - { - public IssueTemplateOffscreenSlider(ICheck checkOrigin) - : base(checkOrigin, IssueType.Problem, "This slider goes offscreen here on a 4:3 aspect ratio.") - { - } - - public Issue Create(Slider slider, double offscreenTime) => new Issue(slider, this) { Time = offscreenTime }; - } - public CheckOffscreenObjects() { templates = new IssueTemplate[] @@ -123,5 +103,25 @@ namespace osu.Game.Rulesets.Osu.Edit.Checks return position.X - radius < min_x || position.X + radius > max_x || position.Y - radius < min_y || position.Y + radius > max_y; } + + private class IssueTemplateOffscreenCircle : IssueTemplate + { + public IssueTemplateOffscreenCircle(ICheck checkOrigin) + : base(checkOrigin, IssueType.Problem, "This circle goes offscreen on a 4:3 aspect ratio.") + { + } + + public Issue Create(HitCircle circle) => new Issue(circle, this); + } + + private class IssueTemplateOffscreenSlider : IssueTemplate + { + public IssueTemplateOffscreenSlider(ICheck checkOrigin) + : base(checkOrigin, IssueType.Problem, "This slider goes offscreen here on a 4:3 aspect ratio.") + { + } + + public Issue Create(Slider slider, double offscreenTime) => new Issue(slider, this) { Time = offscreenTime }; + } } } diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs b/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs index 1e45ea261c..b48d19ae29 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs @@ -14,26 +14,6 @@ namespace osu.Game.Rulesets.Edit.Checks private readonly IssueTemplateDoesNotExist templateDoesNotExist; private readonly IssueTemplate[] templates; - private class IssueTemplateNoneSet : IssueTemplate - { - public IssueTemplateNoneSet(ICheck checkOrigin) - : base(checkOrigin, IssueType.Problem, "No background has been set") - { - } - - public Issue Create() => new Issue(this); - } - - private class IssueTemplateDoesNotExist : IssueTemplate - { - public IssueTemplateDoesNotExist(ICheck checkOrigin) - : base(checkOrigin, IssueType.Problem, "The background file \"{0}\" does not exist.") - { - } - - public Issue Create(string filename) => new Issue(this, filename); - } - public CheckBackground() { templates = new IssueTemplate[] @@ -70,5 +50,25 @@ namespace osu.Game.Rulesets.Edit.Checks yield return templateDoesNotExist.Create(beatmap.Metadata.BackgroundFile); } + + private class IssueTemplateNoneSet : IssueTemplate + { + public IssueTemplateNoneSet(ICheck checkOrigin) + : base(checkOrigin, IssueType.Problem, "No background has been set") + { + } + + public Issue Create() => new Issue(this); + } + + private class IssueTemplateDoesNotExist : IssueTemplate + { + public IssueTemplateDoesNotExist(ICheck checkOrigin) + : base(checkOrigin, IssueType.Problem, "The background file \"{0}\" does not exist.") + { + } + + public Issue Create(string filename) => new Issue(this, filename); + } } } From 62c181228284f034168b87c27879fa72b87369b9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Apr 2021 23:37:47 +0900 Subject: [PATCH 1411/1791] Remove redundant parameter naming --- osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs | 6 +----- osu.Game/Rulesets/Edit/Checks/CheckBackground.cs | 6 +----- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs b/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs index f2a062b6d7..8d4cf2f4f0 100644 --- a/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs +++ b/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs @@ -35,11 +35,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Checks }; } - public CheckMetadata Metadata { get; } = new CheckMetadata - ( - category: CheckCategory.Compose, - description: "Offscreen hitobjects." - ); + public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Compose, "Offscreen hitobjects"); public IEnumerable PossibleTemplates => templates; diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs b/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs index b48d19ae29..1a766798cb 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs @@ -23,11 +23,7 @@ namespace osu.Game.Rulesets.Edit.Checks }; } - public CheckMetadata Metadata { get; } = new CheckMetadata - ( - category: CheckCategory.Resources, - description: "Missing background." - ); + public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Resources, "Missing background"); public IEnumerable PossibleTemplates => templates; From 43b97fe0ad738854f1ef795bfd386db615c75458 Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Mon, 12 Apr 2021 10:52:12 -0400 Subject: [PATCH 1412/1791] Refactor PowerStatus (now called BatteryInfo) --- osu.Android/OsuGameAndroid.cs | 6 ++---- osu.Android/osu.Android.csproj | 1 - .../Visual/Gameplay/TestScenePlayerLoader.cs | 14 ++++++-------- osu.Game/OsuGameBase.cs | 4 ++-- osu.Game/Screens/Play/PlayerLoader.cs | 6 +++--- osu.Game/Utils/{PowerStatus.cs => BatteryInfo.cs} | 12 +++--------- osu.iOS/OsuGameIOS.cs | 6 ++---- osu.iOS/osu.iOS.csproj | 1 - 8 files changed, 18 insertions(+), 32 deletions(-) rename osu.Game/Utils/{PowerStatus.cs => BatteryInfo.cs} (68%) diff --git a/osu.Android/OsuGameAndroid.cs b/osu.Android/OsuGameAndroid.cs index 02f4fa1ad6..050bf2b787 100644 --- a/osu.Android/OsuGameAndroid.cs +++ b/osu.Android/OsuGameAndroid.cs @@ -75,12 +75,10 @@ namespace osu.Android protected override UpdateManager CreateUpdateManager() => new SimpleUpdateManager(); - protected override PowerStatus CreatePowerStatus() => new AndroidPowerStatus(); + protected override BatteryInfo CreateBatteryInfo() => new AndroidBatteryInfo(); - private class AndroidPowerStatus : PowerStatus + private class AndroidBatteryInfo : BatteryInfo { - public override double BatteryCutoff => 0.20; - public override double ChargeLevel => Battery.ChargeLevel; public override bool IsCharging => Battery.PowerSource != BatteryPowerSource.Battery; diff --git a/osu.Android/osu.Android.csproj b/osu.Android/osu.Android.csproj index 64d5e5b1c8..582c856a47 100644 --- a/osu.Android/osu.Android.csproj +++ b/osu.Android/osu.Android.csproj @@ -64,7 +64,6 @@ - diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index 657c1dd47e..311448a284 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -49,8 +49,8 @@ namespace osu.Game.Tests.Visual.Gameplay [Cached] private readonly VolumeOverlay volumeOverlay; - [Cached(typeof(PowerStatus))] - private readonly LocalPowerStatus powerStatus = new LocalPowerStatus(); + [Cached(typeof(BatteryInfo))] + private readonly LocalBatteryInfo batteryInfo = new LocalBatteryInfo(); private readonly ChangelogOverlay changelogOverlay; @@ -302,8 +302,8 @@ namespace osu.Game.Tests.Visual.Gameplay // set charge status and level AddStep("load player", () => resetPlayer(false, () => { - powerStatus.SetCharging(isCharging); - powerStatus.SetChargeLevel(chargeLevel); + batteryInfo.SetCharging(isCharging); + batteryInfo.SetChargeLevel(chargeLevel); })); AddUntilStep("wait for player", () => player?.LoadState == LoadState.Ready); AddAssert($"notification {(shouldWarn ? "triggered" : "not triggered")}", () => notificationOverlay.UnreadCount.Value == (shouldWarn ? 1 : 0)); @@ -381,16 +381,14 @@ namespace osu.Game.Tests.Visual.Gameplay } /// - /// Mutable dummy PowerStatus class for + /// Mutable dummy BatteryInfo class for /// /// - private class LocalPowerStatus : PowerStatus + private class LocalBatteryInfo : BatteryInfo { private bool isCharging = true; private double chargeLevel = 1; - public override double BatteryCutoff => 0.2; - public override bool IsCharging => isCharging; public override double ChargeLevel => chargeLevel; diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 96aabf0024..de8ba93106 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -157,7 +157,7 @@ namespace osu.Game protected override UserInputManager CreateUserInputManager() => new OsuUserInputManager(); - protected virtual PowerStatus CreatePowerStatus() => null; + protected virtual BatteryInfo CreateBatteryInfo() => null; /// /// The maximum volume at which audio tracks should playback. This can be set lower than 1 to create some head-room for sound effects. @@ -285,7 +285,7 @@ namespace osu.Game dependencies.Cache(SettingsStore = new SettingsStore(contextFactory)); dependencies.Cache(RulesetConfigCache = new RulesetConfigCache(SettingsStore)); - var powerStatus = CreatePowerStatus(); + var powerStatus = CreateBatteryInfo(); if (powerStatus != null) dependencies.CacheAs(powerStatus); diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index d342bf8273..964410f838 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -114,7 +114,7 @@ namespace osu.Game.Screens.Play private AudioManager audioManager { get; set; } [Resolved(CanBeNull = true)] - private PowerStatus powerStatus { get; set; } + private BatteryInfo batteryInfo { get; set; } public PlayerLoader(Func createPlayer) { @@ -483,11 +483,11 @@ namespace osu.Game.Screens.Play private void showBatteryWarningIfNeeded() { - if (powerStatus == null) return; + if (batteryInfo == null) return; if (!batteryWarningShownOnce.Value) { - if (powerStatus.IsLowBattery) + if (batteryInfo.IsLowBattery) { notificationOverlay?.Post(new BatteryWarningNotification()); batteryWarningShownOnce.Value = true; diff --git a/osu.Game/Utils/PowerStatus.cs b/osu.Game/Utils/BatteryInfo.cs similarity index 68% rename from osu.Game/Utils/PowerStatus.cs rename to osu.Game/Utils/BatteryInfo.cs index 46f7e32b9e..1a64213d8e 100644 --- a/osu.Game/Utils/PowerStatus.cs +++ b/osu.Game/Utils/BatteryInfo.cs @@ -5,15 +5,9 @@ namespace osu.Game.Utils { /// /// Provides access to the system's power status. - /// Currently implemented on iOS and Android only. /// - public abstract class PowerStatus + public abstract class BatteryInfo { - /// - /// The maximum battery level considered as low, from 0 to 1. - /// - public abstract double BatteryCutoff { get; } - /// /// The charge level of the battery, from 0 to 1. /// @@ -23,8 +17,8 @@ namespace osu.Game.Utils /// /// Whether the battery is currently low in charge. - /// Returns true if not charging and current charge level is lower than or equal to . + /// Returns true if not charging and current charge level is lower than or equal to 25%. /// - public bool IsLowBattery => !IsCharging && ChargeLevel <= BatteryCutoff; + public bool IsLowBattery => !IsCharging && ChargeLevel <= 0.25; } } diff --git a/osu.iOS/OsuGameIOS.cs b/osu.iOS/OsuGameIOS.cs index b53b594eae..702aef45f5 100644 --- a/osu.iOS/OsuGameIOS.cs +++ b/osu.iOS/OsuGameIOS.cs @@ -16,12 +16,10 @@ namespace osu.iOS protected override UpdateManager CreateUpdateManager() => new SimpleUpdateManager(); - protected override PowerStatus CreatePowerStatus() => new IOSPowerStatus(); + protected override BatteryInfo CreateBatteryInfo() => new IOSBatteryInfo(); - private class IOSPowerStatus : PowerStatus + private class IOSBatteryInfo : BatteryInfo { - public override double BatteryCutoff => 0.25; - public override double ChargeLevel => Battery.ChargeLevel; public override bool IsCharging => Battery.PowerSource != BatteryPowerSource.Battery; diff --git a/osu.iOS/osu.iOS.csproj b/osu.iOS/osu.iOS.csproj index ed6f52c60e..1cbe4422cc 100644 --- a/osu.iOS/osu.iOS.csproj +++ b/osu.iOS/osu.iOS.csproj @@ -117,7 +117,6 @@ - From bb720c23a01e6f27b376491abf29e578f4db5bc6 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Mon, 12 Apr 2021 17:12:37 +0200 Subject: [PATCH 1413/1791] Remove check ctors and locals --- .../Edit/Checks/CheckOffscreenObjects.cs | 25 ++++++------------- .../Rulesets/Edit/Checks/CheckBackground.cs | 23 ++++++----------- 2 files changed, 15 insertions(+), 33 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs b/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs index 8d4cf2f4f0..adaabc0a1d 100644 --- a/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs +++ b/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs @@ -22,22 +22,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Checks // (higher = more performant, but higher false-negative chance). private const int path_step_size = 5; - private readonly IssueTemplateOffscreenCircle templateOffscreenCircle; - private readonly IssueTemplateOffscreenSlider templateOffscreenSlider; - private readonly IssueTemplate[] templates; - - public CheckOffscreenObjects() - { - templates = new IssueTemplate[] - { - templateOffscreenCircle = new IssueTemplateOffscreenCircle(this), - templateOffscreenSlider = new IssueTemplateOffscreenSlider(this) - }; - } - public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Compose, "Offscreen hitobjects"); - public IEnumerable PossibleTemplates => templates; + public IEnumerable PossibleTemplates => new IssueTemplate[] + { + new IssueTemplateOffscreenCircle(this), + new IssueTemplateOffscreenSlider(this) + }; public IEnumerable Run(IBeatmap beatmap) { @@ -56,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Checks case HitCircle circle: { if (isOffscreen(circle.StackedPosition, circle.Radius)) - yield return templateOffscreenCircle.Create(circle); + yield return new IssueTemplateOffscreenCircle(this).Create(circle); break; } @@ -82,7 +73,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Checks // `SpanDuration` ensures we don't include reverses. double time = slider.StartTime + progress * slider.SpanDuration; - yield return templateOffscreenSlider.Create(slider, time); + yield return new IssueTemplateOffscreenSlider(this).Create(slider, time); yield break; } @@ -91,7 +82,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Checks if (!isOffscreen(slider.StackedEndPosition, slider.Radius)) yield break; - yield return templateOffscreenSlider.Create(slider, slider.EndTime); + yield return new IssueTemplateOffscreenSlider(this).Create(slider, slider.EndTime); } private bool isOffscreen(Vector2 position, double radius) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs b/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs index 1a766798cb..b0c1d6eb4b 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs @@ -10,28 +10,19 @@ namespace osu.Game.Rulesets.Edit.Checks { public class CheckBackground : ICheck { - private readonly IssueTemplateNoneSet templateNoneSet; - private readonly IssueTemplateDoesNotExist templateDoesNotExist; - private readonly IssueTemplate[] templates; - - public CheckBackground() - { - templates = new IssueTemplate[] - { - templateNoneSet = new IssueTemplateNoneSet(this), - templateDoesNotExist = new IssueTemplateDoesNotExist(this) - }; - } - public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Resources, "Missing background"); - public IEnumerable PossibleTemplates => templates; + public IEnumerable PossibleTemplates => new IssueTemplate[] + { + new IssueTemplateNoneSet(this), + new IssueTemplateDoesNotExist(this) + }; public IEnumerable Run(IBeatmap beatmap) { if (beatmap.Metadata.BackgroundFile == null) { - yield return templateNoneSet.Create(); + yield return new IssueTemplateNoneSet(this).Create(); yield break; } @@ -44,7 +35,7 @@ namespace osu.Game.Rulesets.Edit.Checks if (file != null) yield break; - yield return templateDoesNotExist.Create(beatmap.Metadata.BackgroundFile); + yield return new IssueTemplateDoesNotExist(this).Create(beatmap.Metadata.BackgroundFile); } private class IssueTemplateNoneSet : IssueTemplate From f66306a81ad70868c29bc2a56d471a08f6bebbe5 Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Mon, 12 Apr 2021 11:11:22 -0400 Subject: [PATCH 1414/1791] Remove IsLowBattery --- osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs | 2 +- osu.Game/Screens/Play/PlayerLoader.cs | 2 +- osu.Game/Utils/BatteryInfo.cs | 6 ------ 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index 311448a284..c56f2db0d0 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -294,7 +294,7 @@ namespace osu.Game.Tests.Visual.Gameplay [TestCase(false, 1.0, false)] // not charging, above cutoff --> no warning [TestCase(true, 0.1, false)] // charging, below cutoff --> no warning - [TestCase(false, 0.2, true)] // not charging, at cutoff --> warning + [TestCase(false, 0.25, true)] // not charging, at cutoff --> warning public void TestLowBatteryNotification(bool isCharging, double chargeLevel, bool shouldWarn) { AddStep("reset notification lock", () => sessionStatics.GetBindable(Static.LowBatteryNotificationShownOnce).Value = false); diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 964410f838..fc4659da97 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -487,7 +487,7 @@ namespace osu.Game.Screens.Play if (!batteryWarningShownOnce.Value) { - if (batteryInfo.IsLowBattery) + if (!batteryInfo.IsCharging && batteryInfo.ChargeLevel <= 0.25) { notificationOverlay?.Post(new BatteryWarningNotification()); batteryWarningShownOnce.Value = true; diff --git a/osu.Game/Utils/BatteryInfo.cs b/osu.Game/Utils/BatteryInfo.cs index 1a64213d8e..dd9b695e1f 100644 --- a/osu.Game/Utils/BatteryInfo.cs +++ b/osu.Game/Utils/BatteryInfo.cs @@ -14,11 +14,5 @@ namespace osu.Game.Utils public abstract double ChargeLevel { get; } public abstract bool IsCharging { get; } - - /// - /// Whether the battery is currently low in charge. - /// Returns true if not charging and current charge level is lower than or equal to 25%. - /// - public bool IsLowBattery => !IsCharging && ChargeLevel <= 0.25; } } From 19a154ddf15684f8a3429a642e3f71e555e0d27f Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Mon, 12 Apr 2021 17:28:12 +0200 Subject: [PATCH 1415/1791] Rename `checkOrigin` -> `check` More consistent with `Issue.ctor`'s "template". --- .../Edit/Checks/CheckOffscreenObjects.cs | 8 ++++---- osu.Game/Rulesets/Edit/Checks/CheckBackground.cs | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs b/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs index adaabc0a1d..0a682c4a83 100644 --- a/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs +++ b/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs @@ -93,8 +93,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Checks private class IssueTemplateOffscreenCircle : IssueTemplate { - public IssueTemplateOffscreenCircle(ICheck checkOrigin) - : base(checkOrigin, IssueType.Problem, "This circle goes offscreen on a 4:3 aspect ratio.") + public IssueTemplateOffscreenCircle(ICheck check) + : base(check, IssueType.Problem, "This circle goes offscreen on a 4:3 aspect ratio.") { } @@ -103,8 +103,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Checks private class IssueTemplateOffscreenSlider : IssueTemplate { - public IssueTemplateOffscreenSlider(ICheck checkOrigin) - : base(checkOrigin, IssueType.Problem, "This slider goes offscreen here on a 4:3 aspect ratio.") + public IssueTemplateOffscreenSlider(ICheck check) + : base(check, IssueType.Problem, "This slider goes offscreen here on a 4:3 aspect ratio.") { } diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs b/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs index b0c1d6eb4b..463b596120 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs @@ -40,8 +40,8 @@ namespace osu.Game.Rulesets.Edit.Checks private class IssueTemplateNoneSet : IssueTemplate { - public IssueTemplateNoneSet(ICheck checkOrigin) - : base(checkOrigin, IssueType.Problem, "No background has been set") + public IssueTemplateNoneSet(ICheck check) + : base(check, IssueType.Problem, "No background has been set") { } @@ -50,8 +50,8 @@ namespace osu.Game.Rulesets.Edit.Checks private class IssueTemplateDoesNotExist : IssueTemplate { - public IssueTemplateDoesNotExist(ICheck checkOrigin) - : base(checkOrigin, IssueType.Problem, "The background file \"{0}\" does not exist.") + public IssueTemplateDoesNotExist(ICheck check) + : base(check, IssueType.Problem, "The background file \"{0}\" does not exist.") { } From d9e3276d0edb2f163efd5dcd5894a2881b89ab78 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Mon, 12 Apr 2021 19:18:22 +0200 Subject: [PATCH 1416/1791] Don't update path type once immediately --- .../Blueprints/Sliders/Components/PathControlPointPiece.cs | 3 ++- 1 file changed, 2 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 6b78cff33e..7686043c43 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -58,12 +58,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { this.slider = slider; ControlPoint = controlPoint; + PointsInSegment = slider.Path.PointsInSegment(ControlPoint); slider.Path.Version.BindValueChanged(_ => { PointsInSegment = slider.Path.PointsInSegment(ControlPoint); updatePathType(); - }, runOnceImmediately: true); + }); controlPoint.Type.BindValueChanged(_ => updateMarkerDisplay()); From 92fab653e175b594327cbce5a7702a6068cf6a58 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 12 Apr 2021 20:10:22 +0300 Subject: [PATCH 1417/1791] Take current mod settings value into account on equality comparsion --- osu.Game/Online/API/APIMod.cs | 26 +++++++++++++++++++++++++- osu.Game/Rulesets/Mods/Mod.cs | 14 +++++++++++++- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/API/APIMod.cs b/osu.Game/Online/API/APIMod.cs index bad7e0af38..4427c82a8b 100644 --- a/osu.Game/Online/API/APIMod.cs +++ b/osu.Game/Online/API/APIMod.cs @@ -11,6 +11,7 @@ using osu.Framework.Bindables; using osu.Game.Configuration; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; +using osu.Game.Utils; namespace osu.Game.Online.API { @@ -64,7 +65,15 @@ namespace osu.Game.Online.API } public bool Equals(IMod other) => other is APIMod them && Equals(them); - public bool Equals(APIMod other) => Acronym == other?.Acronym; + + public bool Equals(APIMod other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + + return Acronym == other.Acronym && + Settings.SequenceEqual(other.Settings, ModSettingsEqualityComparer.Default); + } public override string ToString() { @@ -73,5 +82,20 @@ namespace osu.Game.Online.API return $"{Acronym}"; } + + private class ModSettingsEqualityComparer : IEqualityComparer> + { + public static ModSettingsEqualityComparer Default { get; } = new ModSettingsEqualityComparer(); + + public bool Equals(KeyValuePair x, KeyValuePair y) + { + object xValue = ModUtils.GetSettingUnderlyingValue(x.Value); + object yValue = ModUtils.GetSettingUnderlyingValue(y.Value); + + return x.Key == y.Key && EqualityComparer.Default.Equals(xValue, yValue); + } + + public int GetHashCode(KeyValuePair obj) => HashCode.Combine(obj.Key, ModUtils.GetSettingUnderlyingValue(obj.Value)); + } } } diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 93a11e70f8..832a14ee1e 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -12,6 +12,7 @@ using osu.Framework.Testing; using osu.Game.Configuration; using osu.Game.IO.Serialization; using osu.Game.Rulesets.UI; +using osu.Game.Utils; namespace osu.Game.Rulesets.Mods { @@ -173,7 +174,18 @@ namespace osu.Game.Rulesets.Mods } public bool Equals(IMod other) => other is Mod them && Equals(them); - public bool Equals(Mod other) => GetType() == other?.GetType(); + + public bool Equals(Mod other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + + return GetType() == other.GetType() && + this.GetSettingsSourceProperties().All(pair => + EqualityComparer.Default.Equals( + ModUtils.GetSettingUnderlyingValue(pair.Item2.GetValue(this)), + ModUtils.GetSettingUnderlyingValue(pair.Item2.GetValue(other)))); + } /// /// Reset all custom settings for this mod back to their defaults. From 589e1a2a471ef69c49d24d42d5b56df9362f7b6c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 12 Apr 2021 20:51:24 +0300 Subject: [PATCH 1418/1791] Add mod settings equality test --- .../Mods/ModSettingsEqualityComparsion.cs | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 osu.Game.Tests/Mods/ModSettingsEqualityComparsion.cs diff --git a/osu.Game.Tests/Mods/ModSettingsEqualityComparsion.cs b/osu.Game.Tests/Mods/ModSettingsEqualityComparsion.cs new file mode 100644 index 0000000000..7a5789f01a --- /dev/null +++ b/osu.Game.Tests/Mods/ModSettingsEqualityComparsion.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 NUnit.Framework; +using osu.Game.Online.API; +using osu.Game.Rulesets.Osu.Mods; + +namespace osu.Game.Tests.Mods +{ + [TestFixture] + public class ModSettingsEqualityComparison + { + [Test] + public void Test() + { + var mod1 = new OsuModDoubleTime { SpeedChange = { Value = 1.25 } }; + var mod2 = new OsuModDoubleTime { SpeedChange = { Value = 1.26 } }; + var mod3 = new OsuModDoubleTime { SpeedChange = { Value = 1.26 } }; + var apiMod1 = new APIMod(mod1); + var apiMod2 = new APIMod(mod2); + var apiMod3 = new APIMod(mod3); + + Assert.That(mod1, Is.Not.EqualTo(mod2)); + Assert.That(apiMod1, Is.Not.EqualTo(apiMod2)); + + Assert.That(mod2, Is.EqualTo(mod2)); + Assert.That(apiMod2, Is.EqualTo(apiMod2)); + + Assert.That(mod2, Is.EqualTo(mod3)); + Assert.That(apiMod2, Is.EqualTo(apiMod3)); + + Assert.That(mod3, Is.EqualTo(mod2)); + Assert.That(apiMod3, Is.EqualTo(apiMod2)); + } + } +} From 8f84abf34807bae9679b2f70cdfd8c082eb25e03 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 12 Apr 2021 21:51:04 +0300 Subject: [PATCH 1419/1791] Display "replays watched" tooltip for replays subsection --- .../Visual/Online/TestSceneUserHistoryGraph.cs | 3 +-- .../Sections/Historical/ChartProfileSubsection.cs | 7 ++++++- .../Sections/Historical/PlayHistorySubsection.cs | 2 ++ .../Profile/Sections/Historical/ProfileLineChart.cs | 4 ++-- .../Profile/Sections/Historical/ReplaysSubsection.cs | 2 ++ .../Profile/Sections/Historical/UserHistoryGraph.cs | 12 +++++++----- 6 files changed, 20 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserHistoryGraph.cs b/osu.Game.Tests/Visual/Online/TestSceneUserHistoryGraph.cs index 57ce4c41e7..484c59695e 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserHistoryGraph.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserHistoryGraph.cs @@ -19,13 +19,12 @@ namespace osu.Game.Tests.Visual.Online { UserHistoryGraph graph; - Add(graph = new UserHistoryGraph + Add(graph = new UserHistoryGraph("Test") { RelativeSizeAxes = Axes.X, Height = 200, Anchor = Anchor.Centre, Origin = Anchor.Centre, - TooltipCounterName = "Test" }); var values = new[] diff --git a/osu.Game/Overlays/Profile/Sections/Historical/ChartProfileSubsection.cs b/osu.Game/Overlays/Profile/Sections/Historical/ChartProfileSubsection.cs index b82773155d..a48036dcbb 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/ChartProfileSubsection.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/ChartProfileSubsection.cs @@ -15,6 +15,11 @@ namespace osu.Game.Overlays.Profile.Sections.Historical { private ProfileLineChart chart; + /// + /// Text describing the value being plotted on the graph, which will be displayed as a prefix to the value in the history graph tooltip. + /// + protected abstract string GraphCounterName { get; } + protected ChartProfileSubsection(Bindable user, string headerText) : base(user, headerText) { @@ -30,7 +35,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical Left = 20, Right = 40 }, - Child = chart = new ProfileLineChart() + Child = chart = new ProfileLineChart(GraphCounterName) }; protected override void LoadComplete() diff --git a/osu.Game/Overlays/Profile/Sections/Historical/PlayHistorySubsection.cs b/osu.Game/Overlays/Profile/Sections/Historical/PlayHistorySubsection.cs index 2f15886c3a..dfd29db693 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/PlayHistorySubsection.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/PlayHistorySubsection.cs @@ -9,6 +9,8 @@ namespace osu.Game.Overlays.Profile.Sections.Historical { public class PlayHistorySubsection : ChartProfileSubsection { + protected override string GraphCounterName => "Plays"; + public PlayHistorySubsection(Bindable user) : base(user, "Play History") { diff --git a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs index f02aa36b6c..eb5deb2802 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs @@ -42,7 +42,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical private readonly Container rowLinesContainer; private readonly Container columnLinesContainer; - public ProfileLineChart() + public ProfileLineChart(string graphCounterName) { RelativeSizeAxes = Axes.X; Height = 250; @@ -88,7 +88,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical } } }, - graph = new UserHistoryGraph + graph = new UserHistoryGraph(graphCounterName) { RelativeSizeAxes = Axes.Both } diff --git a/osu.Game/Overlays/Profile/Sections/Historical/ReplaysSubsection.cs b/osu.Game/Overlays/Profile/Sections/Historical/ReplaysSubsection.cs index e594e8d020..1c28306f17 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/ReplaysSubsection.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/ReplaysSubsection.cs @@ -9,6 +9,8 @@ namespace osu.Game.Overlays.Profile.Sections.Historical { public class ReplaysSubsection : ChartProfileSubsection { + protected override string GraphCounterName => "Replays Watched"; + public ReplaysSubsection(Bindable user) : base(user, "Replays Watched History") { diff --git a/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs b/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs index b1e8c8f0ca..f9e5ccf618 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs @@ -11,20 +11,22 @@ namespace osu.Game.Overlays.Profile.Sections.Historical { public class UserHistoryGraph : UserGraph { + private readonly string tooltipCounterName; + [CanBeNull] public UserHistoryCount[] Values { set => Data = value?.Select(v => new KeyValuePair(v.Date, v.Count)).ToArray(); } - /// - /// Text describing the value being plotted on the graph, which will be displayed as a prefix to the value in the . - /// - public string TooltipCounterName { get; set; } = "Plays"; + public UserHistoryGraph(string tooltipCounterName) + { + this.tooltipCounterName = tooltipCounterName; + } protected override float GetDataPointHeight(long playCount) => playCount; - protected override UserGraphTooltip GetTooltip() => new HistoryGraphTooltip(TooltipCounterName); + protected override UserGraphTooltip GetTooltip() => new HistoryGraphTooltip(tooltipCounterName); protected override object GetTooltipContent(DateTime date, long playCount) { From d8088777ea0a1177fb3e205530fdab7e04165458 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 13 Apr 2021 01:21:34 +0200 Subject: [PATCH 1420/1791] Add `Equals` method to `IssueTemplate` This will be useful in tests. --- osu.Game/Rulesets/Edit/Checks/Components/IssueTemplate.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Rulesets/Edit/Checks/Components/IssueTemplate.cs b/osu.Game/Rulesets/Edit/Checks/Components/IssueTemplate.cs index 97df79ecd8..e21f14f4bc 100644 --- a/osu.Game/Rulesets/Edit/Checks/Components/IssueTemplate.cs +++ b/osu.Game/Rulesets/Edit/Checks/Components/IssueTemplate.cs @@ -70,5 +70,7 @@ namespace osu.Game.Rulesets.Edit.Checks.Components } } } + + public bool Equals(IssueTemplate other) => other.Type == Type && other.UnformattedMessage == UnformattedMessage; } } From 47cf4bcf2595c672c0f70a85da4c55a36beea182 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 13 Apr 2021 01:22:24 +0200 Subject: [PATCH 1421/1791] Add `CheckBackground` tests --- .../Editing/Checks/CheckBackgroundTest.cs | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 osu.Game.Tests/Editing/Checks/CheckBackgroundTest.cs diff --git a/osu.Game.Tests/Editing/Checks/CheckBackgroundTest.cs b/osu.Game.Tests/Editing/Checks/CheckBackgroundTest.cs new file mode 100644 index 0000000000..54d1a116c7 --- /dev/null +++ b/osu.Game.Tests/Editing/Checks/CheckBackgroundTest.cs @@ -0,0 +1,73 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit.Checks; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Tests.Editing.Checks +{ + [TestFixture] + public class CheckBackgroundTest + { + private CheckBackground check; + private IBeatmap beatmap; + + [SetUp] + public void Setup() + { + check = new CheckBackground(); + beatmap = new Beatmap + { + BeatmapInfo = new BeatmapInfo + { + Metadata = new BeatmapMetadata { BackgroundFile = "abc123.jpg" }, + BeatmapSet = new BeatmapSetInfo + { + Files = new List(new [] + { + new BeatmapSetFileInfo { Filename = "abc123.jpg" } + }) + } + } + }; + } + + [Test] + public void TestBackgroundSetAndInFiles() + { + var issues = check.Run(beatmap); + + Assert.That(!issues.Any()); + } + + [Test] + public void TestBackgroundSetAndNotInFiles() + { + beatmap.BeatmapInfo.BeatmapSet.Files.Clear(); + + var issues = check.Run(beatmap).ToList(); + var issue = issues.FirstOrDefault(); + + Assert.That(issues.Count == 1); + Assert.That(issue != null); + Assert.That(issue.Template.Equals(check.PossibleTemplates.ElementAt(1))); + } + + [Test] + public void TestBackgroundNotSet() + { + beatmap.Metadata.BackgroundFile = null; + + var issues = check.Run(beatmap).ToList(); + var issue = issues.FirstOrDefault(); + + Assert.That(issues.Count == 1); + Assert.That(issue != null); + Assert.That(issue.Template.Equals(check.PossibleTemplates.ElementAt(0))); + } + } +} From 8a6dfcfae1cb9c8b50e04777648098ef271a8d56 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 13 Apr 2021 01:22:36 +0200 Subject: [PATCH 1422/1791] Add `CheckOffscreenObjects` tests --- .../Checks/CheckOffscreenObjectsTest.cs | 263 ++++++++++++++++++ 1 file changed, 263 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs new file mode 100644 index 0000000000..fa7854765f --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs @@ -0,0 +1,263 @@ +// 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.Game.Beatmaps; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Edit.Checks; +using osu.Game.Rulesets.Osu.Objects; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks +{ + [TestFixture] + public class CheckOffscreenObjectsTest + { + private CheckOffscreenObjects check; + + [SetUp] + public void Setup() + { + check = new CheckOffscreenObjects(); + } + + [Test] + public void TestCircleInCenter() + { + var beatmap = new Beatmap + { + HitObjects = new List + { + new HitCircle + { + StartTime = 3000, + Position = new Vector2(320, 240) // Playfield is 640 x 480. + } + } + }; + + var issues = check.Run(beatmap); + + Assert.That(!issues.Any()); + } + + [Test] + public void TestCircleNearEdge() + { + var beatmap = new Beatmap + { + HitObjects = new List + { + new HitCircle + { + StartTime = 3000, + Position = new Vector2(5, 5) + } + } + }; + + var issues = check.Run(beatmap); + + Assert.That(!issues.Any()); + } + + [Test] + public void TestCircleNearEdgeStackedOffscreen() + { + var beatmap = new Beatmap + { + HitObjects = new List + { + new HitCircle + { + StartTime = 3000, + Position = new Vector2(5, 5), + StackHeight = 5 + } + } + }; + + assertOffscreenCircle(beatmap); + } + + [Test] + public void TestCircleOffscreen() + { + var beatmap = new Beatmap + { + HitObjects = new List + { + new HitCircle + { + StartTime = 3000, + Position = new Vector2(0, 0) + } + } + }; + + assertOffscreenCircle(beatmap); + } + + [Test] + public void TestSliderInCenter() + { + var beatmap = new Beatmap + { + HitObjects = new List + { + new Slider + { + StartTime = 3000, + Position = new Vector2(420, 240), + Path = new SliderPath(new[] + { + new PathControlPoint(new Vector2(0, 0), PathType.Linear), + new PathControlPoint(new Vector2(-100, 0)) + }), + } + } + }; + + var issues = check.Run(beatmap); + + Assert.That(!issues.Any()); + } + + [Test] + public void TestSliderNearEdge() + { + var beatmap = new Beatmap + { + HitObjects = new List + { + new Slider + { + StartTime = 3000, + Position = new Vector2(320, 240), + Path = new SliderPath(new[] + { + new PathControlPoint(new Vector2(0, 0), PathType.Linear), + new PathControlPoint(new Vector2(0, -235)) + }), + } + } + }; + + var issues = check.Run(beatmap); + + Assert.That(!issues.Any()); + } + + [Test] + public void TestSliderNearEdgeStackedOffscreen() + { + var beatmap = new Beatmap + { + HitObjects = new List + { + new Slider + { + StartTime = 3000, + Position = new Vector2(320, 240), + Path = new SliderPath(new[] + { + new PathControlPoint(new Vector2(0, 0), PathType.Linear), + new PathControlPoint(new Vector2(0, -235)) + }), + StackHeight = 5 + } + } + }; + + assertOffscreenSlider(beatmap); + } + + [Test] + public void TestSliderOffscreenStart() + { + var beatmap = new Beatmap + { + HitObjects = new List + { + new Slider + { + StartTime = 3000, + Position = new Vector2(0, 0), + Path = new SliderPath(new[] + { + new PathControlPoint(new Vector2(0, 0), PathType.Linear), + new PathControlPoint(new Vector2(320, 240)) + }), + } + } + }; + + assertOffscreenSlider(beatmap); + } + + [Test] + public void TestSliderOffscreenEnd() + { + var beatmap = new Beatmap + { + HitObjects = new List + { + new Slider + { + StartTime = 3000, + Position = new Vector2(320, 240), + Path = new SliderPath(new[] + { + new PathControlPoint(new Vector2(0, 0), PathType.Linear), + new PathControlPoint(new Vector2(-320, -240)) + }), + } + } + }; + + assertOffscreenSlider(beatmap); + } + + [Test] + public void TestSliderOffscreenPath() + { + var beatmap = new Beatmap + { + HitObjects = new List + { + new Slider + { + StartTime = 3000, + Position = new Vector2(320, 240), + Path = new SliderPath(new[] + { + // Circular arc shoots over the top of the screen. + new PathControlPoint(new Vector2(0, 0), PathType.PerfectCurve), + new PathControlPoint(new Vector2(-100, -200)), + new PathControlPoint(new Vector2(100, -200)) + }), + } + } + }; + + assertOffscreenSlider(beatmap); + } + + private void assertOneIssue(IBeatmap beatmap, int templateIndex) + { + var issues = check.Run(beatmap).ToList(); + var issue = issues.FirstOrDefault(); + + Assert.That(issues.Count == 1); + Assert.That(issue != null); + Assert.That(issue.Template.Equals(check.PossibleTemplates.ElementAt(templateIndex))); + } + + private void assertOffscreenCircle(IBeatmap beatmap) => assertOneIssue(beatmap, 0); + + private void assertOffscreenSlider(IBeatmap beatmap) => assertOneIssue(beatmap, 1); + } +} From 0bcc39bd36a9a5893f57fcaa8b94f9d3e31356e9 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 13 Apr 2021 02:17:35 +0200 Subject: [PATCH 1423/1791] Remove redundant space --- osu.Game.Tests/Editing/Checks/CheckBackgroundTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Editing/Checks/CheckBackgroundTest.cs b/osu.Game.Tests/Editing/Checks/CheckBackgroundTest.cs index 54d1a116c7..2f309afb6c 100644 --- a/osu.Game.Tests/Editing/Checks/CheckBackgroundTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckBackgroundTest.cs @@ -27,7 +27,7 @@ namespace osu.Game.Tests.Editing.Checks Metadata = new BeatmapMetadata { BackgroundFile = "abc123.jpg" }, BeatmapSet = new BeatmapSetInfo { - Files = new List(new [] + Files = new List(new[] { new BeatmapSetFileInfo { Filename = "abc123.jpg" } }) From 6d3f9fa9cefdd776d937e0c6b809df8624457e50 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 13 Apr 2021 02:29:25 +0200 Subject: [PATCH 1424/1791] Use `is` class instead of `Equals` with template index Ensures ordering of `PossibleTemplates` does not affect tests. --- .../Editor/Checks/CheckOffscreenObjectsTest.cs | 14 ++++++++++---- .../Edit/Checks/CheckOffscreenObjects.cs | 4 ++-- .../Editing/Checks/CheckBackgroundTest.cs | 4 ++-- osu.Game/Rulesets/Edit/Checks/CheckBackground.cs | 4 ++-- .../Edit/Checks/Components/IssueTemplate.cs | 2 -- 5 files changed, 16 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs index fa7854765f..33b839650f 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs @@ -246,18 +246,24 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks assertOffscreenSlider(beatmap); } - private void assertOneIssue(IBeatmap beatmap, int templateIndex) + private void assertOffscreenCircle(IBeatmap beatmap) { var issues = check.Run(beatmap).ToList(); var issue = issues.FirstOrDefault(); Assert.That(issues.Count == 1); Assert.That(issue != null); - Assert.That(issue.Template.Equals(check.PossibleTemplates.ElementAt(templateIndex))); + Assert.That(issue.Template is CheckOffscreenObjects.IssueTemplateOffscreenCircle); } - private void assertOffscreenCircle(IBeatmap beatmap) => assertOneIssue(beatmap, 0); + private void assertOffscreenSlider(IBeatmap beatmap) + { + var issues = check.Run(beatmap).ToList(); + var issue = issues.FirstOrDefault(); - private void assertOffscreenSlider(IBeatmap beatmap) => assertOneIssue(beatmap, 1); + Assert.That(issues.Count == 1); + Assert.That(issue != null); + Assert.That(issue.Template is CheckOffscreenObjects.IssueTemplateOffscreenSlider); + } } } diff --git a/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs b/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs index 0a682c4a83..c241db7e06 100644 --- a/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs +++ b/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs @@ -91,7 +91,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Checks position.Y - radius < min_y || position.Y + radius > max_y; } - private class IssueTemplateOffscreenCircle : IssueTemplate + public class IssueTemplateOffscreenCircle : IssueTemplate { public IssueTemplateOffscreenCircle(ICheck check) : base(check, IssueType.Problem, "This circle goes offscreen on a 4:3 aspect ratio.") @@ -101,7 +101,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Checks public Issue Create(HitCircle circle) => new Issue(circle, this); } - private class IssueTemplateOffscreenSlider : IssueTemplate + public class IssueTemplateOffscreenSlider : IssueTemplate { public IssueTemplateOffscreenSlider(ICheck check) : base(check, IssueType.Problem, "This slider goes offscreen here on a 4:3 aspect ratio.") diff --git a/osu.Game.Tests/Editing/Checks/CheckBackgroundTest.cs b/osu.Game.Tests/Editing/Checks/CheckBackgroundTest.cs index 2f309afb6c..327abcab6c 100644 --- a/osu.Game.Tests/Editing/Checks/CheckBackgroundTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckBackgroundTest.cs @@ -54,7 +54,7 @@ namespace osu.Game.Tests.Editing.Checks Assert.That(issues.Count == 1); Assert.That(issue != null); - Assert.That(issue.Template.Equals(check.PossibleTemplates.ElementAt(1))); + Assert.That(issue.Template is CheckBackground.IssueTemplateDoesNotExist); } [Test] @@ -67,7 +67,7 @@ namespace osu.Game.Tests.Editing.Checks Assert.That(issues.Count == 1); Assert.That(issue != null); - Assert.That(issue.Template.Equals(check.PossibleTemplates.ElementAt(0))); + Assert.That(issue.Template is CheckBackground.IssueTemplateNoneSet); } } } diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs b/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs index 463b596120..4d5069f446 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Edit.Checks yield return new IssueTemplateDoesNotExist(this).Create(beatmap.Metadata.BackgroundFile); } - private class IssueTemplateNoneSet : IssueTemplate + public class IssueTemplateNoneSet : IssueTemplate { public IssueTemplateNoneSet(ICheck check) : base(check, IssueType.Problem, "No background has been set") @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Edit.Checks public Issue Create() => new Issue(this); } - private class IssueTemplateDoesNotExist : IssueTemplate + public class IssueTemplateDoesNotExist : IssueTemplate { public IssueTemplateDoesNotExist(ICheck check) : base(check, IssueType.Problem, "The background file \"{0}\" does not exist.") diff --git a/osu.Game/Rulesets/Edit/Checks/Components/IssueTemplate.cs b/osu.Game/Rulesets/Edit/Checks/Components/IssueTemplate.cs index e21f14f4bc..97df79ecd8 100644 --- a/osu.Game/Rulesets/Edit/Checks/Components/IssueTemplate.cs +++ b/osu.Game/Rulesets/Edit/Checks/Components/IssueTemplate.cs @@ -70,7 +70,5 @@ namespace osu.Game.Rulesets.Edit.Checks.Components } } } - - public bool Equals(IssueTemplate other) => other.Type == Type && other.UnformattedMessage == UnformattedMessage; } } From 17c2c4e885b63761fa179ddc2ee193b6078a4b03 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 13 Apr 2021 05:31:56 +0300 Subject: [PATCH 1425/1791] Fix test case filename not matching --- ...ingsEqualityComparsion.cs => ModSettingsEqualityComparison.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename osu.Game.Tests/Mods/{ModSettingsEqualityComparsion.cs => ModSettingsEqualityComparison.cs} (100%) diff --git a/osu.Game.Tests/Mods/ModSettingsEqualityComparsion.cs b/osu.Game.Tests/Mods/ModSettingsEqualityComparison.cs similarity index 100% rename from osu.Game.Tests/Mods/ModSettingsEqualityComparsion.cs rename to osu.Game.Tests/Mods/ModSettingsEqualityComparison.cs From 66e74da2b74a10ed695b073bf1a92c5c1b7b7d32 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Apr 2021 13:03:14 +0900 Subject: [PATCH 1426/1791] Fix regression in quick delete mouse action blocking --- .../Visual/Editing/TestSceneEditorQuickDelete.cs | 3 ++- .../Screens/Edit/Compose/Components/BlueprintContainer.cs | 8 ++++---- .../Screens/Edit/Compose/Components/SelectionHandler.cs | 6 +++--- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorQuickDelete.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorQuickDelete.cs index 8a0f27b851..25e12b7a88 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorQuickDelete.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorQuickDelete.cs @@ -83,8 +83,9 @@ namespace osu.Game.Tests.Visual.Editing AddStep("right click", () => InputManager.Click(MouseButton.Right)); AddAssert("slider has 2 points", () => slider.Path.ControlPoints.Count == 2); - // second click should nuke the object completely. AddStep("right click", () => InputManager.Click(MouseButton.Right)); + + // second click should nuke the object completely. AddAssert("no hitobjects in beatmap", () => EditorBeatmap.HitObjects.Count == 0); AddStep("release shift", () => InputManager.ReleaseKey(Key.ShiftLeft)); diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 64cf0e7512..b5a28dc022 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -135,7 +135,7 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override bool OnMouseDown(MouseDownEvent e) { - bool selectionPerformed = beginClickSelection(e); + bool selectionPerformed = performMouseDownActions(e); // even if a selection didn't occur, a drag event may still move the selection. prepareSelectionMovement(); @@ -343,7 +343,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// The input event that triggered this selection. /// Whether a selection was performed. - private bool beginClickSelection(MouseButtonEvent e) + private bool performMouseDownActions(MouseButtonEvent e) { // Iterate from the top of the input stack (blueprints closest to the front of the screen first). // Priority is given to already-selected blueprints. @@ -351,7 +351,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { if (!blueprint.IsHovered) continue; - return clickSelectionBegan = SelectionHandler.HandleSelectionRequested(blueprint, e); + return clickSelectionBegan = SelectionHandler.MouseDownSelectionRequested(blueprint, e); } return false; @@ -375,7 +375,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { if (!blueprint.IsHovered) continue; - return clickSelectionBegan = SelectionHandler.HandleDeselectionRequested(blueprint, e); + return clickSelectionBegan = SelectionHandler.MouseUpSelectionRequested(blueprint, e); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index e5e1100797..389ef78ed5 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -220,12 +220,12 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The blueprint. /// The mouse event responsible for selection. /// Whether a selection was performed. - internal bool HandleSelectionRequested(SelectionBlueprint blueprint, MouseButtonEvent e) + internal bool MouseDownSelectionRequested(SelectionBlueprint blueprint, MouseButtonEvent e) { if (e.ShiftPressed && e.Button == MouseButton.Right) { handleQuickDeletion(blueprint); - return false; + return true; } // while holding control, we only want to add to selection, not replace an existing selection. @@ -244,7 +244,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The blueprint. /// The mouse event responsible for deselection. /// Whether a deselection was performed. - internal bool HandleDeselectionRequested(SelectionBlueprint blueprint, MouseButtonEvent e) + internal bool MouseUpSelectionRequested(SelectionBlueprint blueprint, MouseButtonEvent e) { if (blueprint.IsSelected) { From 05d7fe289f3e6a48b855edc629e163fe7ea26fff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Apr 2021 13:09:18 +0900 Subject: [PATCH 1427/1791] Rename test scene in preparation for increasing scope --- ...estSceneEditorQuickDelete.cs => TestSceneEditorSelection.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename osu.Game.Tests/Visual/Editing/{TestSceneEditorQuickDelete.cs => TestSceneEditorSelection.cs} (98%) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorQuickDelete.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSelection.cs similarity index 98% rename from osu.Game.Tests/Visual/Editing/TestSceneEditorQuickDelete.cs rename to osu.Game.Tests/Visual/Editing/TestSceneEditorSelection.cs index 25e12b7a88..36c4357432 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorQuickDelete.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSelection.cs @@ -18,7 +18,7 @@ using osuTK.Input; namespace osu.Game.Tests.Visual.Editing { - public class TestSceneEditorQuickDelete : EditorTestScene + public class TestSceneEditorSelection : EditorTestScene { protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); From 7c975359d9b6b06a797a7121c38700e09c2f08b8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Apr 2021 13:29:37 +0900 Subject: [PATCH 1428/1791] Add basic select/deselect tests --- .../Editing/TestSceneEditorSelection.cs | 98 ++++++++++++++++++- 1 file changed, 93 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSelection.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSelection.cs index 36c4357432..4f386972fa 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSelection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSelection.cs @@ -27,6 +27,97 @@ namespace osu.Game.Tests.Visual.Editing private BlueprintContainer blueprintContainer => Editor.ChildrenOfType().First(); + private void moveMouseToObject(HitObject obj) + { + AddStep("move mouse to object", () => + { + var pos = blueprintContainer.SelectionBlueprints + .First(s => s.HitObject == obj) + .ChildrenOfType() + .First().ScreenSpaceDrawQuad.Centre; + + InputManager.MoveMouseTo(pos); + }); + } + + [Test] + public void TestBasicSelect() + { + var addedObject = new HitCircle { StartTime = 100 }; + AddStep("add hitobject", () => EditorBeatmap.Add(addedObject)); + + moveMouseToObject(addedObject); + AddStep("left click", () => InputManager.Click(MouseButton.Left)); + + AddAssert("hitobject selected", () => EditorBeatmap.SelectedHitObjects.Single() == addedObject); + + var addedObject2 = new HitCircle + { + StartTime = 100, + Position = new Vector2(100), + }; + + AddStep("add one more hitobject", () => EditorBeatmap.Add(addedObject2)); + AddAssert("selection unchanged", () => EditorBeatmap.SelectedHitObjects.Single() == addedObject); + + moveMouseToObject(addedObject2); + AddStep("left click", () => InputManager.Click(MouseButton.Left)); + AddAssert("hitobject selected", () => EditorBeatmap.SelectedHitObjects.Single() == addedObject2); + } + + [Test] + public void TestMultiSelect() + { + var addedObjects = new[] + { + new HitCircle { StartTime = 100 }, + new HitCircle { StartTime = 200, Position = new Vector2(50) }, + new HitCircle { StartTime = 300, Position = new Vector2(100) }, + new HitCircle { StartTime = 400, Position = new Vector2(150) }, + }; + + AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects)); + + moveMouseToObject(addedObjects[0]); + AddStep("click first", () => InputManager.Click(MouseButton.Left)); + + AddAssert("hitobject selected", () => EditorBeatmap.SelectedHitObjects.Single() == addedObjects[0]); + + AddStep("hold control", () => InputManager.PressKey(Key.ControlLeft)); + + moveMouseToObject(addedObjects[1]); + AddStep("click second", () => InputManager.Click(MouseButton.Left)); + AddAssert("2 hitobjects selected", () => EditorBeatmap.SelectedHitObjects.Count == 2 && EditorBeatmap.SelectedHitObjects.Contains(addedObjects[1])); + + moveMouseToObject(addedObjects[2]); + AddStep("click third", () => InputManager.Click(MouseButton.Left)); + AddAssert("3 hitobjects selected", () => EditorBeatmap.SelectedHitObjects.Count == 3 && EditorBeatmap.SelectedHitObjects.Contains(addedObjects[2])); + + moveMouseToObject(addedObjects[1]); + AddStep("click second", () => InputManager.Click(MouseButton.Left)); + AddAssert("2 hitobjects selected", () => EditorBeatmap.SelectedHitObjects.Count == 2 && !EditorBeatmap.SelectedHitObjects.Contains(addedObjects[1])); + } + + [Test] + public void TestBasicDeselect() + { + var addedObject = new HitCircle { StartTime = 100 }; + AddStep("add hitobject", () => EditorBeatmap.Add(addedObject)); + + moveMouseToObject(addedObject); + AddStep("left click", () => InputManager.Click(MouseButton.Left)); + + AddAssert("hitobject selected", () => EditorBeatmap.SelectedHitObjects.Single() == addedObject); + + AddStep("click away", () => + { + InputManager.MoveMouseTo(blueprintContainer.ScreenSpaceDrawQuad.Centre); + InputManager.Click(MouseButton.Left); + }); + + AddAssert("selection lost", () => EditorBeatmap.SelectedHitObjects.Count == 0); + } + [Test] public void TestQuickDeleteRemovesObject() { @@ -36,11 +127,8 @@ namespace osu.Game.Tests.Visual.Editing AddStep("select added object", () => EditorBeatmap.SelectedHitObjects.Add(addedObject)); - AddStep("move mouse to object", () => - { - var pos = blueprintContainer.ChildrenOfType().First().ScreenSpaceDrawQuad.Centre; - InputManager.MoveMouseTo(pos); - }); + moveMouseToObject(addedObject); + AddStep("hold shift", () => InputManager.PressKey(Key.ShiftLeft)); AddStep("right click", () => InputManager.Click(MouseButton.Right)); AddStep("release shift", () => InputManager.ReleaseKey(Key.ShiftLeft)); From 516bd138e32ba1bd3e4ae35a150d4ae1e9b0d8ab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Apr 2021 13:46:38 +0900 Subject: [PATCH 1429/1791] Add (previously failing) test coverage of drag from selection --- .../Editing/TestSceneEditorSelection.cs | 57 +++++++++++++++---- 1 file changed, 47 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSelection.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSelection.cs index 4f386972fa..99f31b0c2a 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSelection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSelection.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.Linq; using NUnit.Framework; using osu.Framework.Testing; @@ -27,12 +28,12 @@ namespace osu.Game.Tests.Visual.Editing private BlueprintContainer blueprintContainer => Editor.ChildrenOfType().First(); - private void moveMouseToObject(HitObject obj) + private void moveMouseToObject(Func targetFunc) { AddStep("move mouse to object", () => { var pos = blueprintContainer.SelectionBlueprints - .First(s => s.HitObject == obj) + .First(s => s.HitObject == targetFunc()) .ChildrenOfType() .First().ScreenSpaceDrawQuad.Centre; @@ -46,7 +47,7 @@ namespace osu.Game.Tests.Visual.Editing var addedObject = new HitCircle { StartTime = 100 }; AddStep("add hitobject", () => EditorBeatmap.Add(addedObject)); - moveMouseToObject(addedObject); + moveMouseToObject(() => addedObject); AddStep("left click", () => InputManager.Click(MouseButton.Left)); AddAssert("hitobject selected", () => EditorBeatmap.SelectedHitObjects.Single() == addedObject); @@ -60,7 +61,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("add one more hitobject", () => EditorBeatmap.Add(addedObject2)); AddAssert("selection unchanged", () => EditorBeatmap.SelectedHitObjects.Single() == addedObject); - moveMouseToObject(addedObject2); + moveMouseToObject(() => addedObject2); AddStep("left click", () => InputManager.Click(MouseButton.Left)); AddAssert("hitobject selected", () => EditorBeatmap.SelectedHitObjects.Single() == addedObject2); } @@ -78,33 +79,69 @@ namespace osu.Game.Tests.Visual.Editing AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects)); - moveMouseToObject(addedObjects[0]); + moveMouseToObject(() => addedObjects[0]); AddStep("click first", () => InputManager.Click(MouseButton.Left)); AddAssert("hitobject selected", () => EditorBeatmap.SelectedHitObjects.Single() == addedObjects[0]); AddStep("hold control", () => InputManager.PressKey(Key.ControlLeft)); - moveMouseToObject(addedObjects[1]); + moveMouseToObject(() => addedObjects[1]); AddStep("click second", () => InputManager.Click(MouseButton.Left)); AddAssert("2 hitobjects selected", () => EditorBeatmap.SelectedHitObjects.Count == 2 && EditorBeatmap.SelectedHitObjects.Contains(addedObjects[1])); - moveMouseToObject(addedObjects[2]); + moveMouseToObject(() => addedObjects[2]); AddStep("click third", () => InputManager.Click(MouseButton.Left)); AddAssert("3 hitobjects selected", () => EditorBeatmap.SelectedHitObjects.Count == 3 && EditorBeatmap.SelectedHitObjects.Contains(addedObjects[2])); - moveMouseToObject(addedObjects[1]); + moveMouseToObject(() => addedObjects[1]); AddStep("click second", () => InputManager.Click(MouseButton.Left)); AddAssert("2 hitobjects selected", () => EditorBeatmap.SelectedHitObjects.Count == 2 && !EditorBeatmap.SelectedHitObjects.Contains(addedObjects[1])); } + [TestCase(false)] + [TestCase(true)] + public void TestMultiSelectFromDrag(bool alreadySelectedBeforeDrag) + { + HitCircle[] addedObjects = null; + + AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects = new[] + { + new HitCircle { StartTime = 100 }, + new HitCircle { StartTime = 200, Position = new Vector2(50) }, + new HitCircle { StartTime = 300, Position = new Vector2(100) }, + new HitCircle { StartTime = 400, Position = new Vector2(150) }, + })); + + moveMouseToObject(() => addedObjects[0]); + AddStep("click first", () => InputManager.Click(MouseButton.Left)); + + AddStep("hold control", () => InputManager.PressKey(Key.ControlLeft)); + + moveMouseToObject(() => addedObjects[1]); + + if (alreadySelectedBeforeDrag) + AddStep("click second", () => InputManager.Click(MouseButton.Left)); + + AddStep("mouse down on second", () => InputManager.PressButton(MouseButton.Left)); + + AddAssert("2 hitobjects selected", () => EditorBeatmap.SelectedHitObjects.Count == 2 && EditorBeatmap.SelectedHitObjects.Contains(addedObjects[1])); + + AddStep("drag to centre", () => InputManager.MoveMouseTo(blueprintContainer.ScreenSpaceDrawQuad.Centre)); + + AddAssert("positions changed", () => addedObjects[0].Position != Vector2.Zero && addedObjects[1].Position != new Vector2(50)); + + AddStep("release control", () => InputManager.ReleaseKey(Key.ControlLeft)); + AddStep("mouse up", () => InputManager.ReleaseButton(MouseButton.Left)); + } + [Test] public void TestBasicDeselect() { var addedObject = new HitCircle { StartTime = 100 }; AddStep("add hitobject", () => EditorBeatmap.Add(addedObject)); - moveMouseToObject(addedObject); + moveMouseToObject(() => addedObject); AddStep("left click", () => InputManager.Click(MouseButton.Left)); AddAssert("hitobject selected", () => EditorBeatmap.SelectedHitObjects.Single() == addedObject); @@ -127,7 +164,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("select added object", () => EditorBeatmap.SelectedHitObjects.Add(addedObject)); - moveMouseToObject(addedObject); + moveMouseToObject(() => addedObject); AddStep("hold shift", () => InputManager.PressKey(Key.ShiftLeft)); AddStep("right click", () => InputManager.Click(MouseButton.Right)); From a664efe12bc4e0fb38d71a43b25e59335cb8c7c1 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 13 Apr 2021 07:59:13 +0300 Subject: [PATCH 1430/1791] Fix history graph tooltips leaking to others Since there was no check about which tooltip content came from which graph, all history graphs use the "Replays Watched" tooltip, as it is the latest created one. --- .../Profile/Sections/Historical/UserHistoryGraph.cs | 7 ++++++- osu.Game/Overlays/Profile/UserGraph.cs | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs b/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs index f9e5ccf618..52831b4243 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs @@ -32,6 +32,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical { return new TooltipDisplayContent { + Name = tooltipCounterName, Count = playCount.ToString("N0"), Date = date.ToString("MMMM yyyy") }; @@ -39,14 +40,17 @@ namespace osu.Game.Overlays.Profile.Sections.Historical protected class HistoryGraphTooltip : UserGraphTooltip { + private readonly string tooltipCounterName; + public HistoryGraphTooltip(string tooltipCounterName) : base(tooltipCounterName) { + this.tooltipCounterName = tooltipCounterName; } public override bool SetContent(object content) { - if (!(content is TooltipDisplayContent info)) + if (!(content is TooltipDisplayContent info) || info.Name != tooltipCounterName) return false; Counter.Text = info.Count; @@ -57,6 +61,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical private class TooltipDisplayContent { + public string Name; public string Count; public string Date; } diff --git a/osu.Game/Overlays/Profile/UserGraph.cs b/osu.Game/Overlays/Profile/UserGraph.cs index cdfd722d68..e2eb5d5180 100644 --- a/osu.Game/Overlays/Profile/UserGraph.cs +++ b/osu.Game/Overlays/Profile/UserGraph.cs @@ -208,6 +208,7 @@ namespace osu.Game.Overlays.Profile protected abstract class UserGraphTooltip : VisibilityContainer, ITooltip { + protected new readonly OsuSpriteText Name; protected readonly OsuSpriteText Counter, BottomText; private readonly Box background; @@ -237,7 +238,7 @@ namespace osu.Game.Overlays.Profile Spacing = new Vector2(3, 0), Children = new Drawable[] { - new OsuSpriteText + Name = new OsuSpriteText { Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), Text = tooltipCounterName From 4852630c93564dd7af90ab18ea67c55b416e2655 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Apr 2021 14:03:42 +0900 Subject: [PATCH 1431/1791] Fix import multiple file types via drag potentially reaching the wrong importer --- osu.Game/OsuGameBase.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index ca132df552..e285faab11 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -433,12 +433,15 @@ namespace osu.Game if (paths.Length == 0) return; - var extension = Path.GetExtension(paths.First())?.ToLowerInvariant(); + var filesPerExtension = paths.GroupBy(p => Path.GetExtension(p).ToLowerInvariant()); - foreach (var importer in fileImporters) + foreach (var groups in filesPerExtension) { - if (importer.HandledExtensions.Contains(extension)) - await importer.Import(paths).ConfigureAwait(false); + foreach (var importer in fileImporters) + { + if (importer.HandledExtensions.Contains(groups.Key)) + await importer.Import(groups.ToArray()).ConfigureAwait(false); + } } } From d0f30b7b422afe385fd1fc0de7a28684bf0ab7ef Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 13 Apr 2021 14:29:47 +0900 Subject: [PATCH 1432/1791] Delay map completion one frame after the last judgment This is a workaround of a timing issue. KeyCounter is disabled while break time (`HasCompleted == true`). When the last keypress is exactly at the same time the map ends, the last frame was considered in a break time while forward play but considered not in a break time while rewinding. This inconsistency made the last keypress not decremented in the key counter when a replay is rewound. The situation regularly happens in osu!standard because the map ends right after the player hits the last hit circle. It was caught by `TestSceneGameplayRewinding`. This commit makes the update of the map completion delayed one frame. The problematic keypress frame is now processed strictly before the map completion, and the map completion status is correctly rewound before the keypress frame. --- osu.Game/Rulesets/Scoring/JudgementProcessor.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/JudgementProcessor.cs b/osu.Game/Rulesets/Scoring/JudgementProcessor.cs index 8aef615b5f..201a05e569 100644 --- a/osu.Game/Rulesets/Scoring/JudgementProcessor.cs +++ b/osu.Game/Rulesets/Scoring/JudgementProcessor.cs @@ -28,6 +28,8 @@ namespace osu.Game.Rulesets.Scoring /// public int JudgedHits { get; private set; } + private JudgementResult lastAppliedResult; + private readonly BindableBool hasCompleted = new BindableBool(); /// @@ -53,12 +55,11 @@ namespace osu.Game.Rulesets.Scoring public void ApplyResult(JudgementResult result) { JudgedHits++; + lastAppliedResult = result; ApplyResultInternal(result); NewJudgement?.Invoke(result); - - updateHasCompleted(); } /// @@ -69,8 +70,6 @@ namespace osu.Game.Rulesets.Scoring { JudgedHits--; - updateHasCompleted(); - RevertResultInternal(result); } @@ -134,6 +133,10 @@ namespace osu.Game.Rulesets.Scoring } } - private void updateHasCompleted() => hasCompleted.Value = JudgedHits == MaxHits; + protected override void Update() + { + base.Update(); + hasCompleted.Value = JudgedHits == MaxHits && (JudgedHits == 0 || lastAppliedResult.TimeAbsolute < Clock.CurrentTime); + } } } From 273099d53c6b0dfab4a694055fc12bf45d5611f2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Apr 2021 14:31:41 +0900 Subject: [PATCH 1433/1791] Don't store online IDs from score submission responses for now Closes remaining portion of https://github.com/ppy/osu/issues/12372. --- osu.Game/Screens/Play/SubmittingPlayer.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index d22199447d..b64d835c58 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -109,7 +109,12 @@ namespace osu.Game.Screens.Play request.Success += s => { - score.ScoreInfo.OnlineScoreID = s.ID; + // For the time being, online ID responses are not really useful for anything. + // In addition, the IDs provided via new (lazer) endpoints are based on a different autoincrement from legacy (stable) scores. + // + // Until we better define the server-side logic behind this, let's not store the online ID to avoid potential unique constraint + // conflicts across various systems (ie. solo and multiplayer). + // score.ScoreInfo.OnlineScoreID = s.ID; tcs.SetResult(true); }; From 4837cef095558c2ed301881471f3eaf76d6eb209 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Apr 2021 14:44:52 +0900 Subject: [PATCH 1434/1791] Use static for playfield centre positioning --- .../Checks/CheckOffscreenObjectsTest.cs | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs index 33b839650f..4a5e4e18c0 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs @@ -9,6 +9,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Edit.Checks; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.UI; using osuTK; namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks @@ -16,6 +17,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks [TestFixture] public class CheckOffscreenObjectsTest { + private static readonly Vector2 playfield_centre = OsuPlayfield.BASE_SIZE * 0.5f; + private CheckOffscreenObjects check; [SetUp] @@ -34,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks new HitCircle { StartTime = 3000, - Position = new Vector2(320, 240) // Playfield is 640 x 480. + Position = playfield_centre // Playfield is 640 x 480. } } }; @@ -136,11 +139,11 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks new Slider { StartTime = 3000, - Position = new Vector2(320, 240), + Position = playfield_centre, Path = new SliderPath(new[] { new PathControlPoint(new Vector2(0, 0), PathType.Linear), - new PathControlPoint(new Vector2(0, -235)) + new PathControlPoint(new Vector2(0, -playfield_centre.Y + 5)) }), } } @@ -161,11 +164,11 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks new Slider { StartTime = 3000, - Position = new Vector2(320, 240), + Position = playfield_centre, Path = new SliderPath(new[] { new PathControlPoint(new Vector2(0, 0), PathType.Linear), - new PathControlPoint(new Vector2(0, -235)) + new PathControlPoint(new Vector2(0, -playfield_centre.Y + 5)) }), StackHeight = 5 } @@ -189,7 +192,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks Path = new SliderPath(new[] { new PathControlPoint(new Vector2(0, 0), PathType.Linear), - new PathControlPoint(new Vector2(320, 240)) + new PathControlPoint(playfield_centre) }), } } @@ -208,11 +211,11 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks new Slider { StartTime = 3000, - Position = new Vector2(320, 240), + Position = playfield_centre, Path = new SliderPath(new[] { new PathControlPoint(new Vector2(0, 0), PathType.Linear), - new PathControlPoint(new Vector2(-320, -240)) + new PathControlPoint(-playfield_centre) }), } } @@ -231,7 +234,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks new Slider { StartTime = 3000, - Position = new Vector2(320, 240), + Position = playfield_centre, Path = new SliderPath(new[] { // Circular arc shoots over the top of the screen. From b45d7de4eccbd9aecbf621afefc6661ca986fa9e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Apr 2021 15:04:01 +0900 Subject: [PATCH 1435/1791] Update asserts to use better nunit specifications --- .../Checks/CheckOffscreenObjectsTest.cs | 29 ++++++------------- .../Editing/Checks/CheckBackgroundTest.cs | 16 ++++------ 2 files changed, 14 insertions(+), 31 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs index 4a5e4e18c0..9f9ba0e8db 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit.Checks.Components; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Edit.Checks; @@ -42,9 +43,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks } }; - var issues = check.Run(beatmap); - - Assert.That(!issues.Any()); + Assert.That(check.Run(beatmap), Is.Empty); } [Test] @@ -62,9 +61,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks } }; - var issues = check.Run(beatmap); - - Assert.That(!issues.Any()); + Assert.That(check.Run(beatmap), Is.Empty); } [Test] @@ -124,9 +121,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks } }; - var issues = check.Run(beatmap); - - Assert.That(!issues.Any()); + Assert.That(check.Run(beatmap), Is.Empty); } [Test] @@ -149,9 +144,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks } }; - var issues = check.Run(beatmap); - - Assert.That(!issues.Any()); + Assert.That(check.Run(beatmap), Is.Empty); } [Test] @@ -252,21 +245,17 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks private void assertOffscreenCircle(IBeatmap beatmap) { var issues = check.Run(beatmap).ToList(); - var issue = issues.FirstOrDefault(); - Assert.That(issues.Count == 1); - Assert.That(issue != null); - Assert.That(issue.Template is CheckOffscreenObjects.IssueTemplateOffscreenCircle); + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckOffscreenObjects.IssueTemplateOffscreenCircle); } private void assertOffscreenSlider(IBeatmap beatmap) { var issues = check.Run(beatmap).ToList(); - var issue = issues.FirstOrDefault(); - Assert.That(issues.Count == 1); - Assert.That(issue != null); - Assert.That(issue.Template is CheckOffscreenObjects.IssueTemplateOffscreenSlider); + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckOffscreenObjects.IssueTemplateOffscreenSlider); } } } diff --git a/osu.Game.Tests/Editing/Checks/CheckBackgroundTest.cs b/osu.Game.Tests/Editing/Checks/CheckBackgroundTest.cs index 327abcab6c..635e3bb0f3 100644 --- a/osu.Game.Tests/Editing/Checks/CheckBackgroundTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckBackgroundTest.cs @@ -39,9 +39,7 @@ namespace osu.Game.Tests.Editing.Checks [Test] public void TestBackgroundSetAndInFiles() { - var issues = check.Run(beatmap); - - Assert.That(!issues.Any()); + Assert.That(check.Run(beatmap), Is.Empty); } [Test] @@ -50,11 +48,9 @@ namespace osu.Game.Tests.Editing.Checks beatmap.BeatmapInfo.BeatmapSet.Files.Clear(); var issues = check.Run(beatmap).ToList(); - var issue = issues.FirstOrDefault(); - Assert.That(issues.Count == 1); - Assert.That(issue != null); - Assert.That(issue.Template is CheckBackground.IssueTemplateDoesNotExist); + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckBackground.IssueTemplateDoesNotExist); } [Test] @@ -63,11 +59,9 @@ namespace osu.Game.Tests.Editing.Checks beatmap.Metadata.BackgroundFile = null; var issues = check.Run(beatmap).ToList(); - var issue = issues.FirstOrDefault(); - Assert.That(issues.Count == 1); - Assert.That(issue != null); - Assert.That(issue.Template is CheckBackground.IssueTemplateNoneSet); + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckBackground.IssueTemplateNoneSet); } } } From 9f8af03a70633d32fa11842c69ea73b1ccf72f11 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 13 Apr 2021 09:28:58 +0300 Subject: [PATCH 1436/1791] Remove irrelevant change --- osu.Game/Overlays/Profile/UserGraph.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Profile/UserGraph.cs b/osu.Game/Overlays/Profile/UserGraph.cs index e2eb5d5180..cdfd722d68 100644 --- a/osu.Game/Overlays/Profile/UserGraph.cs +++ b/osu.Game/Overlays/Profile/UserGraph.cs @@ -208,7 +208,6 @@ namespace osu.Game.Overlays.Profile protected abstract class UserGraphTooltip : VisibilityContainer, ITooltip { - protected new readonly OsuSpriteText Name; protected readonly OsuSpriteText Counter, BottomText; private readonly Box background; @@ -238,7 +237,7 @@ namespace osu.Game.Overlays.Profile Spacing = new Vector2(3, 0), Children = new Drawable[] { - Name = new OsuSpriteText + new OsuSpriteText { Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), Text = tooltipCounterName From fbc6fb8fc55ad431173ad61072a84e26629469e7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Apr 2021 15:35:57 +0900 Subject: [PATCH 1437/1791] Split out common logic into private method and add inline comment for future visitors --- .../Sliders/Components/PathControlPointPiece.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) 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 7686043c43..ce9580d0f4 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -58,11 +58,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { this.slider = slider; ControlPoint = controlPoint; - PointsInSegment = slider.Path.PointsInSegment(ControlPoint); + + // we don't want to run the path type update on construction as it may inadvertently change the slider. + cachePoints(slider); slider.Path.Version.BindValueChanged(_ => { - PointsInSegment = slider.Path.PointsInSegment(ControlPoint); + cachePoints(slider); updatePathType(); }); @@ -206,6 +208,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components protected override void OnDragEnd(DragEndEvent e) => changeHandler?.EndChange(); + private void cachePoints(Slider slider) => PointsInSegment = slider.Path.PointsInSegment(ControlPoint); + /// /// Handles correction of invalid path types. /// From 57ba7b7cbbbd93870978d047b6edb957cd49d8cf Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 13 Apr 2021 15:55:23 +0900 Subject: [PATCH 1438/1791] Partially revert the changes of `CurrentFrame` and `NextFrame` for compatibility Making those always non-null is postponed as when a replay's frame contains keypress the behavior is changed. Previously, the key is pressed at the time of the first frame. But using non-null frames means the key is pressed at negative infinity. However, I think the new way of always using non-null frames makes the client code so I plan to bundle the change to more breaking changes. --- .../NonVisual/FramedReplayInputHandlerTest.cs | 22 +++++++++---------- .../Replays/FramedReplayInputHandler.cs | 9 ++++---- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs b/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs index 954871595e..a42b7d54ee 100644 --- a/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs +++ b/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs @@ -84,11 +84,11 @@ namespace osu.Game.Tests.NonVisual // exited important section setTime(8200, 8000); confirmCurrentFrame(7); - confirmNextFrame(7); + confirmNextFrame(null); setTime(8200, 8200); confirmCurrentFrame(7); - confirmNextFrame(7); + confirmNextFrame(null); } [Test] @@ -97,11 +97,11 @@ namespace osu.Game.Tests.NonVisual setReplayFrames(); setTime(-1000, -1000); - confirmCurrentFrame(0); + confirmCurrentFrame(null); confirmNextFrame(0); setTime(-500, -500); - confirmCurrentFrame(0); + confirmCurrentFrame(null); confirmNextFrame(0); setTime(0, 0); @@ -145,7 +145,7 @@ namespace osu.Game.Tests.NonVisual confirmNextFrame(1); setTime(-500, -500); - confirmCurrentFrame(0); + confirmCurrentFrame(null); confirmNextFrame(0); } @@ -231,7 +231,7 @@ namespace osu.Game.Tests.NonVisual Assert.IsFalse(handler.WaitingForFrame, "Should not be waiting yet"); setTime(1000, 1000); confirmCurrentFrame(1); - confirmNextFrame(1); + confirmNextFrame(null); Assert.IsTrue(handler.WaitingForFrame, "Should be waiting"); // cannot seek beyond the last frame @@ -243,7 +243,7 @@ namespace osu.Game.Tests.NonVisual // can seek to the point before the first frame, however setTime(-100, -100); - confirmCurrentFrame(0); + confirmCurrentFrame(null); confirmNextFrame(0); fastForwardToPoint(1000); @@ -311,14 +311,14 @@ namespace osu.Game.Tests.NonVisual Assert.AreEqual(expect, handler.SetFrameFromTime(set), "Unexpected return value"); } - private void confirmCurrentFrame(int frame) + private void confirmCurrentFrame(int? frame) { - Assert.AreEqual(replay.Frames[frame].Time, handler.CurrentFrame.Time, "Unexpected current frame"); + Assert.AreEqual(frame is int x ? replay.Frames[x].Time : (double?)null, handler.CurrentFrame?.Time, "Unexpected current frame"); } - private void confirmNextFrame(int frame) + private void confirmNextFrame(int? frame) { - Assert.AreEqual(replay.Frames[frame].Time, handler.NextFrame.Time, "Unexpected next frame"); + Assert.AreEqual(frame is int x ? replay.Frames[x].Time : (double?)null, handler.NextFrame?.Time, "Unexpected next frame"); } private class TestReplayFrame : ReplayFrame diff --git a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs index c3cd957f0d..a7f11b1e6f 100644 --- a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs +++ b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs @@ -30,6 +30,7 @@ namespace osu.Game.Rulesets.Replays /// The current frame of the replay. /// The current time is always between the start and the end time of the current frame. /// + /// Returns null if the current time is strictly before the first frame. /// The replay is empty. public TFrame CurrentFrame { @@ -38,15 +39,15 @@ namespace osu.Game.Rulesets.Replays if (!HasFrames) throw new InvalidOperationException($"Attempted to get {nameof(CurrentFrame)} of an empty replay"); - return (TFrame)Frames[Math.Max(0, currentFrameIndex)]; + return currentFrameIndex == -1 ? null : (TFrame)Frames[currentFrameIndex]; } } /// /// The next frame of the replay. /// The start time is always greater or equal to the start time of regardless of the seeking direction. - /// If it is before the first frame of the replay or the after the last frame of the replay, and agree. /// + /// Returns null if the current frame is the last frame. /// The replay is empty. public TFrame NextFrame { @@ -55,7 +56,7 @@ namespace osu.Game.Rulesets.Replays if (!HasFrames) throw new InvalidOperationException($"Attempted to get {nameof(NextFrame)} of an empty replay"); - return (TFrame)Frames[Math.Min(currentFrameIndex + 1, Frames.Count - 1)]; + return currentFrameIndex == Frames.Count - 1 ? null : (TFrame)Frames[currentFrameIndex + 1]; } } @@ -96,7 +97,7 @@ namespace osu.Game.Rulesets.Replays { get { - if (!HasFrames || !FrameAccuratePlayback) + if (!HasFrames || !FrameAccuratePlayback || CurrentFrame == null) return false; return IsImportant(CurrentFrame) && // a button is in a pressed state From a9652b7b259b2921648c220e1d7f2ccf98276afa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Apr 2021 16:05:12 +0900 Subject: [PATCH 1439/1791] Start TimelineTestScene in a more visible place --- osu.Game.Tests/Visual/Editing/TimelineTestScene.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs index 1da6433707..88b4614791 100644 --- a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs +++ b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs @@ -64,6 +64,13 @@ namespace osu.Game.Tests.Visual.Editing }); } + protected override void LoadComplete() + { + base.LoadComplete(); + + Clock.Seek(2500); + } + public abstract Drawable CreateTestComponent(); private class AudioVisualiser : CompositeDrawable From ebf97ff48ff43a5879b19fcb9365c84c8369fe21 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Apr 2021 16:25:43 +0900 Subject: [PATCH 1440/1791] Update timeline ticks to use width as a differentiation method, rather than height --- .../Visualisations/PointVisualisation.cs | 10 ++-- .../Timeline/TimelineTickDisplay.cs | 56 +++++++++++-------- 2 files changed, 39 insertions(+), 27 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs index b0ecffdd24..b5c4cd6dda 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs @@ -3,7 +3,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; -using osuTK; namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations { @@ -12,7 +11,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations /// public class PointVisualisation : Box { - public const float WIDTH = 1; + public const float MAX_WIDTH = 5; public PointVisualisation(double startTime) : this() @@ -27,8 +26,11 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations RelativePositionAxes = Axes.X; RelativeSizeAxes = Axes.Y; - Width = WIDTH; - EdgeSmoothness = new Vector2(WIDTH, 0); + Anchor = Anchor.CentreLeft; + Origin = Anchor.Centre; + + Width = MAX_WIDTH; + Height = 0.75f; } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs index c070c833f8..7ec5bb7197 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs @@ -6,9 +6,7 @@ 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; @@ -33,6 +31,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [Resolved] private OsuColour colours { get; set; } + private static readonly int highest_divisor = BindableBeatDivisor.VALID_DIVISORS.Last(); + public TimelineTickDisplay() { RelativeSizeAxes = Axes.Both; @@ -80,8 +80,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (timeline != null) { var newRange = ( - (ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopLeft).X - PointVisualisation.WIDTH * 2) / DrawWidth * Content.RelativeChildSize.X, - (ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopRight).X + PointVisualisation.WIDTH * 2) / DrawWidth * Content.RelativeChildSize.X); + (ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopLeft).X - PointVisualisation.MAX_WIDTH * 2) / DrawWidth * Content.RelativeChildSize.X, + (ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopRight).X + PointVisualisation.MAX_WIDTH * 2) / DrawWidth * Content.RelativeChildSize.X); if (visibleRange != newRange) { @@ -100,7 +100,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void createTicks() { int drawableIndex = 0; - int highestDivisor = BindableBeatDivisor.VALID_DIVISORS.Last(); nextMinTick = null; nextMaxTick = null; @@ -131,25 +130,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline 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. - float height = isMainBeat ? 0.5f : 0.4f - (float)divisor / highestDivisor * 0.2f; - float gradientOpacity = isMainBeat ? 1 : 0; - var topPoint = getNextUsablePoint(); - topPoint.X = xPos; - 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.Anchor = Anchor.BottomLeft; - bottomPoint.Colour = ColourInfo.GradientVertical(colour.Opacity(gradientOpacity), colour); - bottomPoint.Origin = Anchor.BottomCentre; - bottomPoint.Height = height; + var line = getNextUsableLine(); + line.X = xPos; + line.Width = PointVisualisation.MAX_WIDTH * getWidth(indexInBar, divisor); + line.Colour = colour; } beat++; @@ -168,7 +154,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline tickCache.Validate(); - Drawable getNextUsablePoint() + Drawable getNextUsableLine() { PointVisualisation point; if (drawableIndex >= Count) @@ -183,6 +169,30 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } } + private static float getWidth(int indexInBar, int divisor) + { + if (indexInBar == 0) + return 1; + + switch (divisor) + { + case 1: + case 2: + return 0.6f; + + case 3: + case 4: + return 0.5f; + + case 6: + case 8: + return 0.4f; + + default: + return 0.3f; + } + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From 27e851c2eed2b0edc1833a620e7ec4e9e85973f0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Apr 2021 16:41:29 +0900 Subject: [PATCH 1441/1791] Also adjust height --- .../Visualisations/PointVisualisation.cs | 2 +- .../Timeline/TimelineTickDisplay.cs | 25 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs index b5c4cd6dda..53a1f94731 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs @@ -11,7 +11,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations /// public class PointVisualisation : Box { - public const float MAX_WIDTH = 5; + public const float MAX_WIDTH = 4; public PointVisualisation(double startTime) : this() diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs index 7ec5bb7197..3aaf0451c8 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs @@ -135,6 +135,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline var line = getNextUsableLine(); line.X = xPos; line.Width = PointVisualisation.MAX_WIDTH * getWidth(indexInBar, divisor); + line.Height = 0.9f * getHeight(indexInBar, divisor); line.Colour = colour; } @@ -193,6 +194,30 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } } + private static float getHeight(int indexInBar, int divisor) + { + if (indexInBar == 0) + return 1; + + switch (divisor) + { + case 1: + case 2: + return 0.9f; + + case 3: + case 4: + return 0.8f; + + case 6: + case 8: + return 0.7f; + + default: + return 0.6f; + } + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From 5a06db8a113dfd012a46b304f9967508f0ca609e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Apr 2021 16:48:05 +0900 Subject: [PATCH 1442/1791] Change default editor waveform opacity to 25% The previous setting felt way too high. --- osu.Game/Configuration/OsuConfigManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 387cfbb193..0525e84077 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -143,7 +143,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.DiscordRichPresence, DiscordRichPresenceMode.Full); - SetDefault(OsuSetting.EditorWaveformOpacity, 1f); + SetDefault(OsuSetting.EditorWaveformOpacity, 0.25f); } public OsuConfigManager(Storage storage) From 0932daeaa8564fe9fd9e25560abe1bfa5a8b20b3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Apr 2021 16:50:03 +0900 Subject: [PATCH 1443/1791] Force the new default on update --- osu.Game/Configuration/OsuConfigManager.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 0525e84077..21a1a1d430 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -169,14 +169,9 @@ namespace osu.Game.Configuration int combined = (year * 10000) + monthDay; - if (combined < 20200305) + if (combined < 20210413) { - // the maximum value of this setting was changed. - // if we don't manually increase this, it causes song select to filter out beatmaps the user expects to see. - var maxStars = (BindableDouble)GetOriginalBindable(OsuSetting.DisplayStarsMaximum); - - if (maxStars.Value == 10) - maxStars.Value = maxStars.MaxValue; + SetValue(OsuSetting.EditorWaveformOpacity, 0.25f); } } From 36510309d10105ddcdbea2b45f36e941f6be5415 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 13 Apr 2021 09:24:35 +0300 Subject: [PATCH 1444/1791] Merge `EnableUserDim` and `IgnoreUserSettings` to one bindable --- .../Visual/Background/TestSceneUserDimBackgrounds.cs | 12 ++++++------ osu.Game/Graphics/Containers/UserDimContainer.cs | 8 +------- osu.Game/Rulesets/Mods/ModCinema.cs | 3 +-- .../Screens/Backgrounds/BackgroundScreenBeatmap.cs | 8 ++++---- osu.Game/Screens/Edit/Editor.cs | 2 +- osu.Game/Screens/Play/Player.cs | 4 ++-- osu.Game/Screens/Play/PlayerLoader.cs | 6 +++--- 7 files changed, 18 insertions(+), 25 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index ba4d12b19f..a4c28651df 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -142,9 +142,9 @@ namespace osu.Game.Tests.Visual.Background { performFullSetup(); AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); - AddStep("Enable user dim", () => songSelect.DimEnabled.Value = false); + AddStep("Disable user dim", () => songSelect.IgnoreUserSettings.Value = true); AddUntilStep("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsUserBlurDisabled()); - AddStep("Disable user dim", () => songSelect.DimEnabled.Value = true); + AddStep("Enable user dim", () => songSelect.IgnoreUserSettings.Value = false); AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); } @@ -161,10 +161,10 @@ namespace osu.Game.Tests.Visual.Background player.ReplacesBackground.Value = true; player.StoryboardEnabled.Value = true; }); - AddStep("Enable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = true); + AddStep("Enable user dim", () => player.DimmableStoryboard.IgnoreUserSettings.Value = false); AddStep("Set dim level to 1", () => songSelect.DimLevel.Value = 1f); AddUntilStep("Storyboard is invisible", () => !player.IsStoryboardVisible); - AddStep("Disable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = false); + AddStep("Disable user dim", () => player.DimmableStoryboard.IgnoreUserSettings.Value = true); AddUntilStep("Storyboard is visible", () => player.IsStoryboardVisible); } @@ -281,11 +281,11 @@ namespace osu.Game.Tests.Visual.Background protected override BackgroundScreen CreateBackground() { background = new FadeAccessibleBackground(Beatmap.Value); - DimEnabled.BindTo(background.EnableUserDim); + IgnoreUserSettings.BindTo(background.IgnoreUserSettings); return background; } - public readonly Bindable DimEnabled = new Bindable(); + public readonly Bindable IgnoreUserSettings = new Bindable(); public readonly Bindable DimLevel = new BindableDouble(); public readonly Bindable BlurLevel = new BindableDouble(); diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs index 39c1fdad52..4e555ac1eb 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -23,11 +23,6 @@ namespace osu.Game.Graphics.Containers protected const double BACKGROUND_FADE_DURATION = 800; - /// - /// Whether or not user-configured dim levels should be applied to the container. - /// - public readonly Bindable EnableUserDim = new Bindable(true); - /// /// Whether or not user-configured settings relating to brightness of elements should be ignored /// @@ -57,7 +52,7 @@ namespace osu.Game.Graphics.Containers private float breakLightening => LightenDuringBreaks.Value && IsBreakTime.Value ? BREAK_LIGHTEN_AMOUNT : 0; - protected float DimLevel => Math.Max(EnableUserDim.Value && !IgnoreUserSettings.Value ? (float)UserDimLevel.Value - breakLightening : 0, 0); + protected float DimLevel => Math.Max(!IgnoreUserSettings.Value ? (float)UserDimLevel.Value - breakLightening : 0, 0); protected override Container Content => dimContent; @@ -78,7 +73,6 @@ namespace osu.Game.Graphics.Containers LightenDuringBreaks = config.GetBindable(OsuSetting.LightenDuringBreaks); ShowStoryboard = config.GetBindable(OsuSetting.ShowStoryboard); - EnableUserDim.ValueChanged += _ => UpdateVisuals(); UserDimLevel.ValueChanged += _ => UpdateVisuals(); LightenDuringBreaks.ValueChanged += _ => UpdateVisuals(); IsBreakTime.ValueChanged += _ => UpdateVisuals(); diff --git a/osu.Game/Rulesets/Mods/ModCinema.cs b/osu.Game/Rulesets/Mods/ModCinema.cs index eb0473016a..c78088ba2d 100644 --- a/osu.Game/Rulesets/Mods/ModCinema.cs +++ b/osu.Game/Rulesets/Mods/ModCinema.cs @@ -37,8 +37,7 @@ namespace osu.Game.Rulesets.Mods public void ApplyToPlayer(Player player) { - player.ApplyToBackground(b => b.EnableUserDim.Value = false); - + player.ApplyToBackground(b => b.IgnoreUserSettings.Value = true); player.DimmableStoryboard.IgnoreUserSettings.Value = true; player.BreakOverlay.Hide(); diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs index b08455be95..d27211144e 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs @@ -27,9 +27,9 @@ namespace osu.Game.Screens.Backgrounds private WorkingBeatmap beatmap; /// - /// Whether or not user dim settings should be applied to this Background. + /// Whether or not user-configured settings relating to brightness of elements should be ignored /// - public readonly Bindable EnableUserDim = new Bindable(); + public readonly Bindable IgnoreUserSettings = new Bindable(); public readonly Bindable StoryboardReplacesBackground = new Bindable(); @@ -50,7 +50,7 @@ namespace osu.Game.Screens.Backgrounds InternalChild = dimmable = CreateFadeContainer(); - dimmable.EnableUserDim.BindTo(EnableUserDim); + dimmable.IgnoreUserSettings.BindTo(IgnoreUserSettings); dimmable.IsBreakTime.BindTo(IsBreakTime); dimmable.BlurAmount.BindTo(BlurAmount); @@ -148,7 +148,7 @@ namespace osu.Game.Screens.Backgrounds /// /// As an optimisation, we add the two blur portions to be applied rather than actually applying two separate blurs. /// - private Vector2 blurTarget => EnableUserDim.Value + private Vector2 blurTarget => !IgnoreUserSettings.Value ? new Vector2(BlurAmount.Value + (float)userBlurLevel.Value * USER_BLUR_FACTOR) : new Vector2(BlurAmount.Value); diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 0759e21382..64350fb56e 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -462,7 +462,7 @@ namespace osu.Game.Screens.Edit // todo: temporary. we want to be applying dim using the UserDimContainer eventually. b.FadeColour(Color4.DarkGray, 500); - b.EnableUserDim.Value = false; + b.IgnoreUserSettings.Value = true; b.BlurAmount.Value = 0; }); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index efe5d26409..dd3f58439b 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -764,7 +764,7 @@ namespace osu.Game.Screens.Play ApplyToBackground(b => { - b.EnableUserDim.Value = true; + b.IgnoreUserSettings.Value = false; b.BlurAmount.Value = 0; // bind component bindables. @@ -913,7 +913,7 @@ namespace osu.Game.Screens.Play float fadeOutDuration = instant ? 0 : 250; this.FadeOut(fadeOutDuration); - ApplyToBackground(b => b.EnableUserDim.Value = false); + ApplyToBackground(b => b.IgnoreUserSettings.Value = true); storyboardReplacesBackground.Value = false; } diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 679b3c7313..cf15104809 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -229,7 +229,7 @@ namespace osu.Game.Screens.Play content.ScaleTo(0.7f, 150, Easing.InQuint); this.FadeOut(150); - ApplyToBackground(b => b.EnableUserDim.Value = false); + ApplyToBackground(b => b.IgnoreUserSettings.Value = true); BackgroundBrightnessReduction = false; Beatmap.Value.Track.RemoveAdjustment(AdjustableProperty.Volume, volumeAdjustment); @@ -277,7 +277,7 @@ namespace osu.Game.Screens.Play // Preview user-defined background dim and blur when hovered on the visual settings panel. ApplyToBackground(b => { - b.EnableUserDim.Value = true; + b.IgnoreUserSettings.Value = false; b.BlurAmount.Value = 0; }); @@ -288,7 +288,7 @@ namespace osu.Game.Screens.Play ApplyToBackground(b => { // Returns background dim and blur to the values specified by PlayerLoader. - b.EnableUserDim.Value = false; + b.IgnoreUserSettings.Value = true; b.BlurAmount.Value = BACKGROUND_BLUR; }); From 98c25b2e71c1aef8ad2973c0cb7ce96b33faff42 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 13 Apr 2021 10:33:08 +0200 Subject: [PATCH 1445/1791] Remove unused import --- .../Editor/Checks/CheckOffscreenObjectsTest.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs index 9f9ba0e8db..f9445a9a96 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Game.Beatmaps; -using osu.Game.Rulesets.Edit.Checks.Components; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Edit.Checks; From c8cb4286f6e61eeba14b4b0256d5a17d781f6bd9 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 13 Apr 2021 10:35:06 +0200 Subject: [PATCH 1446/1791] Add reference for screen bounding box numbers --- osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs b/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs index c241db7e06..27cae2ecc1 100644 --- a/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs +++ b/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs @@ -11,8 +11,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Checks { public class CheckOffscreenObjects : ICheck { - // These are close approximates to the edges of the screen - // in gameplay on a 4:3 aspect ratio for osu!stable. + // A close approximation for the bounding box of the screen in gameplay on 4:3 aspect ratio. + // Uses gameplay space coordinates (512 x 384 playfield / 640 x 480 screen area). + // See https://github.com/ppy/osu/pull/12361#discussion_r612199777 for reference. private const int min_x = -67; private const int min_y = -60; private const int max_x = 579; From b41e3a2e7a84046981007ebc6a6d2ecd85d1b42b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Apr 2021 17:38:13 +0900 Subject: [PATCH 1447/1791] Remove unused using statement --- osu.Game/Configuration/OsuConfigManager.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 21a1a1d430..f9b1c9618b 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -3,7 +3,6 @@ using System; using System.Diagnostics; -using osu.Framework.Bindables; using osu.Framework.Configuration; using osu.Framework.Configuration.Tracking; using osu.Framework.Extensions; From 60c2494b316827c691327a55793750871753a190 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 13 Apr 2021 10:40:56 +0200 Subject: [PATCH 1448/1791] Make `BeatmapVerifier` an interface --- .../Edit/OsuBeatmapVerifier.cs | 12 ++---------- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- osu.Game/Rulesets/Edit/BeatmapVerifier.cs | 18 ++---------------- osu.Game/Rulesets/Ruleset.cs | 2 +- osu.Game/Screens/Edit/Verify/VerifyScreen.cs | 2 +- 5 files changed, 7 insertions(+), 29 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs b/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs index 2faa239720..1c7ab00bbb 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs @@ -10,21 +10,13 @@ using osu.Game.Rulesets.Osu.Edit.Checks; namespace osu.Game.Rulesets.Osu.Edit { - public class OsuBeatmapVerifier : BeatmapVerifier + public class OsuBeatmapVerifier : IBeatmapVerifier { private readonly List checks = new List { new CheckOffscreenObjects() }; - public override IEnumerable Run(IBeatmap beatmap) - { - // Also run mode-invariant checks. - foreach (var issue in base.Run(beatmap)) - yield return issue; - - foreach (var issue in checks.SelectMany(check => check.Run(beatmap))) - yield return issue; - } + public IEnumerable Run(IBeatmap beatmap) => checks.SelectMany(check => check.Run(beatmap)); } } diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 63da100a04..d6375fa6e3 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -206,7 +206,7 @@ namespace osu.Game.Rulesets.Osu public override HitObjectComposer CreateHitObjectComposer() => new OsuHitObjectComposer(this); - public override BeatmapVerifier CreateBeatmapVerifier() => new OsuBeatmapVerifier(); + public override IBeatmapVerifier CreateBeatmapVerifier() => new OsuBeatmapVerifier(); public override string Description => "osu!"; diff --git a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs index 1d0508705a..2bafacefa3 100644 --- a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs +++ b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs @@ -2,27 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.Linq; using osu.Game.Beatmaps; -using osu.Game.Rulesets.Edit.Checks; using osu.Game.Rulesets.Edit.Checks.Components; namespace osu.Game.Rulesets.Edit { - public abstract class BeatmapVerifier + public interface IBeatmapVerifier { - /// - /// Checks which are performed regardless of ruleset. - /// These handle things like beatmap metadata, timing, and other ruleset agnostic elements. - /// - private readonly IReadOnlyList generalChecks = new List - { - new CheckBackground() - }; - - public virtual IEnumerable Run(IBeatmap beatmap) - { - return generalChecks.SelectMany(check => check.Run(beatmap)); - } + public IEnumerable Run(IBeatmap beatmap); } } diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 2a29d88c89..b501c55aef 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -202,7 +202,7 @@ namespace osu.Game.Rulesets public virtual HitObjectComposer CreateHitObjectComposer() => null; - public virtual BeatmapVerifier CreateBeatmapVerifier() => null; + public virtual IBeatmapVerifier CreateBeatmapVerifier() => null; public virtual Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.Solid.QuestionCircle }; diff --git a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs index a3d808ce62..a733c9c176 100644 --- a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs +++ b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs @@ -21,7 +21,7 @@ namespace osu.Game.Screens.Edit.Verify public class VerifyScreen : EditorScreen { private Ruleset ruleset; - private static BeatmapVerifier beatmapVerifier; + private static IBeatmapVerifier beatmapVerifier; [Cached] private Bindable selectedIssue = new Bindable(); From 304fe5cd341027b19da18b82f394e5fc444a2883 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 13 Apr 2021 10:41:02 +0200 Subject: [PATCH 1449/1791] Add `CheckBackground` to `OsuBeatmapVerifier` --- osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs b/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs index 1c7ab00bbb..66ef74ab08 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Checks; using osu.Game.Rulesets.Edit.Checks.Components; using osu.Game.Rulesets.Osu.Edit.Checks; @@ -14,6 +15,10 @@ namespace osu.Game.Rulesets.Osu.Edit { private readonly List checks = new List { + // General checks + new CheckBackground(), + + // Ruleset-specific checks new CheckOffscreenObjects() }; From 15658eda554f0574dd85f8d9e4b557ace3229c2d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 13 Apr 2021 09:56:43 +0300 Subject: [PATCH 1450/1791] Add failing test case --- .../Background/TestSceneUserDimBackgrounds.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index a4c28651df..655b426e43 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -168,6 +168,29 @@ namespace osu.Game.Tests.Visual.Background AddUntilStep("Storyboard is visible", () => player.IsStoryboardVisible); } + [Test] + public void TestStoryboardIgnoreUserSettings() + { + performFullSetup(); + createFakeStoryboard(); + AddStep("Enable replacing background", () => player.ReplacesBackground.Value = true); + + AddUntilStep("Storyboard is invisible", () => !player.IsStoryboardVisible); + AddUntilStep("Background is visible", () => songSelect.IsBackgroundVisible()); + + AddStep("Ignore user settings", () => + { + player.ApplyToBackground(b => b.IgnoreUserSettings.Value = true); + player.DimmableStoryboard.IgnoreUserSettings.Value = true; + }); + AddUntilStep("Storyboard is visible", () => player.IsStoryboardVisible); + AddUntilStep("Background is invisible", () => songSelect.IsBackgroundInvisible()); + + AddStep("Disable background replacement", () => player.ReplacesBackground.Value = false); + AddUntilStep("Storyboard is visible", () => player.IsStoryboardVisible); + AddUntilStep("Background is visible", () => songSelect.IsBackgroundVisible()); + } + /// /// Check if the visual settings container retains dim and blur when pausing /// From 7c53bebfd4a5e1ac99b62ec12f0521616b6994dd Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 13 Apr 2021 09:25:08 +0300 Subject: [PATCH 1451/1791] Fix beatmap background not hiding when user settings ignored and storyboard replaces background --- osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs index d27211144e..10d381b8b7 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs @@ -166,7 +166,9 @@ namespace osu.Game.Screens.Backgrounds BlurAmount.ValueChanged += _ => UpdateVisuals(); } - protected override bool ShowDimContent => !ShowStoryboard.Value || !StoryboardReplacesBackground.Value; // The background needs to be hidden in the case of it being replaced by the storyboard + protected override bool ShowDimContent + // The background needs to be hidden in the case of it being replaced by the storyboard + => (!ShowStoryboard.Value && !IgnoreUserSettings.Value) || !StoryboardReplacesBackground.Value; protected override void UpdateVisuals() { From aa5fe2e9fcdbe3f2dd6ac1d63328c4f5f4af477d Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 13 Apr 2021 11:02:01 +0200 Subject: [PATCH 1452/1791] Rename `BeatmapVerifier` -> `IBeatmapVerifier` --- .../Rulesets/Edit/{BeatmapVerifier.cs => IBeatmapVerifier.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename osu.Game/Rulesets/Edit/{BeatmapVerifier.cs => IBeatmapVerifier.cs} (100%) diff --git a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs b/osu.Game/Rulesets/Edit/IBeatmapVerifier.cs similarity index 100% rename from osu.Game/Rulesets/Edit/BeatmapVerifier.cs rename to osu.Game/Rulesets/Edit/IBeatmapVerifier.cs From 4618728bf016fc3253d0cce337c230a2ed0f2fac Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 13 Apr 2021 11:35:12 +0200 Subject: [PATCH 1453/1791] Add test case --- .../TestSceneOsuEditorSelectInvalidPath.cs | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorSelectInvalidPath.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorSelectInvalidPath.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorSelectInvalidPath.cs new file mode 100644 index 0000000000..d0348c1b6b --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorSelectInvalidPath.cs @@ -0,0 +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 osu.Game.Beatmaps; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Tests.Beatmaps; +using osu.Game.Tests.Visual; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests.Editor +{ + public class TestSceneOsuEditorSelectInvalidPath : EditorTestScene + { + protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false); + + [Test] + public void TestSelectDoesNotModify() + { + Slider slider = new Slider { StartTime = 0, Position = new Vector2(320, 40) }; + + PathControlPoint[] points = + { + new PathControlPoint(new Vector2(0), PathType.PerfectCurve), + new PathControlPoint(new Vector2(-100, 0)), + new PathControlPoint(new Vector2(100, 20)) + }; + + int preSelectVersion = -1; + AddStep("add slider", () => + { + slider.Path = new SliderPath(points); + EditorBeatmap.Add(slider); + preSelectVersion = slider.Path.Version.Value; + }); + + AddStep("select added slider", () => EditorBeatmap.SelectedHitObjects.Add(slider)); + + AddAssert("slider same path", () => slider.Path.Version.Value == preSelectVersion); + } + } +} From fca9c70c1b47bd986fe089e675f85ea44d9b6690 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Apr 2021 17:39:12 +0900 Subject: [PATCH 1454/1791] Move timeline hit object test to immediately viewable area --- .../Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs index 35f394fe1d..88246381bf 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs @@ -29,7 +29,7 @@ namespace osu.Game.Tests.Visual.Editing EditorBeatmap.Add(new Spinner { Position = new Vector2(256, 256), - StartTime = 150, + StartTime = 2700, Duration = 500 }); }); From 00f235760d237e6902162c4f1675cbedbc0063ba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Apr 2021 18:12:46 +0900 Subject: [PATCH 1455/1791] Update visual appearance of timeline blueprints to close match new designs --- .../TestSceneTimelineHitObjectBlueprint.cs | 6 +- .../Timeline/TimelineBlueprintContainer.cs | 3 +- .../Timeline/TimelineHitObjectBlueprint.cs | 166 +++++++++--------- 3 files changed, 92 insertions(+), 83 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs index 88246381bf..e6fad33a51 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs @@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual.Editing [Test] public void TestDisallowZeroDurationObjects() { - DragBar dragBar; + DragArea dragArea; AddStep("add spinner", () => { @@ -37,8 +37,8 @@ namespace osu.Game.Tests.Visual.Editing AddStep("hold down drag bar", () => { // distinguishes between the actual drag bar and its "underlay shadow". - dragBar = this.ChildrenOfType().Single(bar => bar.HandlePositionalInput); - InputManager.MoveMouseTo(dragBar); + dragArea = this.ChildrenOfType().Single(bar => bar.HandlePositionalInput); + InputManager.MoveMouseTo(dragArea); InputManager.PressButton(MouseButton.Left); }); diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index be34c8d57e..7427473a35 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -63,7 +63,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { AddInternal(backgroundBox = new SelectableAreaBackground { - Colour = Color4.Black + Colour = Color4.Black, + Depth = float.MaxValue, }); } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index d24614299c..179abd0a26 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; @@ -28,9 +29,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { public class TimelineHitObjectBlueprint : SelectionBlueprint { - private const float thickness = 5; private const float shadow_radius = 5; - private const float circle_size = 34; + private const float circle_size = 38; public Action OnDragHandled; @@ -40,8 +40,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private Bindable indexInCurrentComboBindable; private Bindable comboIndexBindable; - private readonly Circle circle; - private readonly DragBar dragBar; + private readonly Container circle; + private readonly DragArea dragArea; private readonly List shadowComponents = new List(); private readonly Container mainComponents; private readonly OsuSpriteText comboIndexText; @@ -76,71 +76,27 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { Anchor = Anchor.CentreLeft, Origin = Anchor.Centre, - Font = OsuFont.Numeric.With(size: circle_size / 2, weight: FontWeight.Black), + Y = -1, + Font = OsuFont.Default.With(size: circle_size * 0.5f, weight: FontWeight.Regular), }, }); - circle = new Circle + circle = new ExtendableCircle { - Size = new Vector2(circle_size), + RelativeSizeAxes = Axes.X, + Size = new Vector2(1, circle_size), Anchor = Anchor.CentreLeft, - Origin = Anchor.Centre, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Radius = shadow_radius, - Colour = Color4.Black - }, + Origin = Anchor.CentreLeft, }; - shadowComponents.Add(circle); + mainComponents.Add(circle); if (hitObject is IHasDuration) { - DragBar dragBarUnderlay; - Container extensionBar; - - mainComponents.AddRange(new Drawable[] + mainComponents.Add(dragArea = new DragArea(hitObject) { - extensionBar = new Container - { - Masking = true, - Size = new Vector2(1, thickness), - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RelativePositionAxes = Axes.X, - RelativeSizeAxes = Axes.X, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Radius = shadow_radius, - Colour = Color4.Black - }, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - } - }, - circle, - // only used for drawing the shadow - dragBarUnderlay = new DragBar(null), - // cover up the shadow on the join - new Box - { - Height = thickness, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.X, - }, - dragBar = new DragBar(hitObject) { OnDragHandled = e => OnDragHandled?.Invoke(e) }, + OnDragHandled = e => OnDragHandled?.Invoke(e) }); - - shadowComponents.Add(dragBarUnderlay); - shadowComponents.Add(extensionBar); - } - else - { - mainComponents.Add(circle); } updateShadows(); @@ -173,7 +129,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline var comboColour = combo.GetComboColour(comboColours); if (HitObject is IHasDuration) - mainComponents.Colour = ColourInfo.GradientHorizontal(comboColour, Color4.White); + mainComponents.Colour = ColourInfo.GradientHorizontal(comboColour, comboColour.Lighten(0.4f)); else mainComponents.Colour = comboColour; @@ -227,10 +183,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline protected override bool ShouldBeConsideredForInput(Drawable child) => true; - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => - base.ReceivePositionalInputAt(screenSpacePos) || - circle.ReceivePositionalInputAt(screenSpacePos) || - dragBar?.ReceivePositionalInputAt(screenSpacePos) == true; + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => circle.Contains(screenSpacePos); protected override void OnSelected() { @@ -256,7 +209,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { Type = EdgeEffectType.Shadow, Radius = shadow_radius, - Colour = State == SelectionState.Selected ? Color4.Orange : Color4.Black + Colour = Color4.Black.Opacity(0.4f) }; } } @@ -267,22 +220,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline updateShadows(); } - public override Quad SelectionQuad - { - get - { - // correctly include the circle in the selection quad region, as it is usually outside the blueprint itself. - var leftQuad = circle.ScreenSpaceDrawQuad; - var rightQuad = dragBar?.ScreenSpaceDrawQuad ?? ScreenSpaceDrawQuad; - - return new Quad(leftQuad.TopLeft, Vector2.ComponentMax(rightQuad.TopRight, leftQuad.TopRight), - leftQuad.BottomLeft, Vector2.ComponentMax(rightQuad.BottomRight, leftQuad.BottomRight)); - } - } + public override Quad SelectionQuad => circle.ScreenSpaceDrawQuad; public override Vector2 ScreenSpaceSelectionPoint => ScreenSpaceDrawQuad.TopLeft; - public class DragBar : Container + public class DragArea : Container { private readonly HitObject hitObject; @@ -293,13 +235,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline public override bool HandlePositionalInput => hitObject != null; - public DragBar(HitObject hitObject) + public DragArea(HitObject hitObject) { this.hitObject = hitObject; - CornerRadius = 2; + CornerRadius = circle_size / 2; Masking = true; - Size = new Vector2(5, 1); + Size = new Vector2(circle_size, 1); Anchor = Anchor.CentreRight; Origin = Anchor.Centre; RelativePositionAxes = Axes.X; @@ -406,5 +348,71 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline changeHandler?.EndChange(); } } + + /// + /// A circle with externalised end caps so it can take up the full width of a relative width area. + /// + public class ExtendableCircle : Container + { + private readonly Circle rightCircle; + private readonly Circle leftCircle; + + public override Quad ScreenSpaceDrawQuad + { + get + { + var leftQuad = leftCircle.ScreenSpaceDrawQuad; + + if (Width == 0) + { + return leftQuad; + } + + var rightQuad = rightCircle.ScreenSpaceDrawQuad; + + return new Quad(leftQuad.TopLeft, rightQuad.TopRight, leftQuad.BottomLeft, rightQuad.BottomRight); + } + } + + public ExtendableCircle() + { + var effect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Radius = shadow_radius, + Colour = Color4.Black.Opacity(0.4f) + }; + + InternalChildren = new Drawable[] + { + new Container + { + Height = circle_size, + RelativeSizeAxes = Axes.X, + Masking = true, + AlwaysPresent = true, + EdgeEffect = effect, + }, + leftCircle = new Circle + { + EdgeEffect = effect, + Origin = Anchor.TopCentre, + Size = new Vector2(circle_size) + }, + rightCircle = new Circle + { + EdgeEffect = effect, + Anchor = Anchor.TopRight, + Origin = Anchor.TopCentre, + Size = new Vector2(circle_size) + }, + new Box + { + Height = circle_size, + RelativeSizeAxes = Axes.X, + }, + }; + } + } } } From 109ee395bf397fc73b13e10f70bf8dedae67b20f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Apr 2021 18:47:07 +0900 Subject: [PATCH 1456/1791] Fix input and remove outdated hover logic --- .../Timeline/TimelineHitObjectBlueprint.cs | 42 +++++++------------ 1 file changed, 14 insertions(+), 28 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 179abd0a26..89a9095d22 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -17,7 +17,6 @@ using osu.Framework.Input.Events; using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; @@ -40,9 +39,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private Bindable indexInCurrentComboBindable; private Bindable comboIndexBindable; - private readonly Container circle; - private readonly DragArea dragArea; - private readonly List shadowComponents = new List(); + private readonly Drawable circle; + private readonly Container mainComponents; private readonly OsuSpriteText comboIndexText; @@ -93,7 +91,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (hitObject is IHasDuration) { - mainComponents.Add(dragArea = new DragArea(hitObject) + mainComponents.Add(new DragArea(hitObject) { OnDragHandled = e => OnDragHandled?.Invoke(e) }); @@ -183,8 +181,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline protected override bool ShouldBeConsideredForInput(Drawable child) => true; - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => circle.Contains(screenSpacePos); - protected override void OnSelected() { updateShadows(); @@ -192,27 +188,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void updateShadows() { - foreach (var s in shadowComponents) - { - if (State == SelectionState.Selected) - { - s.EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Radius = shadow_radius / 2, - Colour = Color4.Orange, - }; - } - else - { - s.EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Radius = shadow_radius, - Colour = Color4.Black.Opacity(0.4f) - }; - } - } } protected override void OnDeselected() @@ -220,6 +195,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline updateShadows(); } + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => + circle.ReceivePositionalInputAt(screenSpacePos); + public override Quad SelectionQuad => circle.ScreenSpaceDrawQuad; public override Vector2 ScreenSpaceSelectionPoint => ScreenSpaceDrawQuad.TopLeft; @@ -351,12 +329,20 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline /// /// A circle with externalised end caps so it can take up the full width of a relative width area. + /// TODO: figure how to do this with a single circle to avoid pixel-misaligned edges. /// public class ExtendableCircle : Container { private readonly Circle rightCircle; private readonly Circle leftCircle; + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) + { + return base.ReceivePositionalInputAt(screenSpacePos) + || leftCircle.ReceivePositionalInputAt(screenSpacePos) + || rightCircle.ReceivePositionalInputAt(screenSpacePos); + } + public override Quad ScreenSpaceDrawQuad { get From 495fdd8d65d99d7818726e29829fcd6ff8fed660 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Apr 2021 18:58:51 +0900 Subject: [PATCH 1457/1791] Update drag area display to match new design logic --- .../Timeline/TimelineHitObjectBlueprint.cs | 40 ++++++++++++++++--- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 89a9095d22..493f62921b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -202,7 +202,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline public override Vector2 ScreenSpaceSelectionPoint => ScreenSpaceDrawQuad.TopLeft; - public class DragArea : Container + public class DragArea : Circle { private readonly HitObject hitObject; @@ -224,6 +224,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Origin = Anchor.Centre; RelativePositionAxes = Axes.X; RelativeSizeAxes = Axes.Y; + Colour = OsuColour.Gray(0.2f); InternalChildren = new Drawable[] { @@ -234,6 +235,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline }; } + protected override void LoadComplete() + { + base.LoadComplete(); + + updateState(); + FinishTransforms(); + } + protected override bool OnHover(HoverEvent e) { updateState(); @@ -265,7 +274,20 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void updateState() { - Colour = IsHovered || hasMouseDown ? Color4.OrangeRed : Color4.White; + if (hasMouseDown) + { + this.ScaleTo(0.7f, 200, Easing.OutQuint); + } + else if (IsHovered) + { + this.ScaleTo(0.8f, 200, Easing.OutQuint); + } + else + { + this.ScaleTo(0.6f, 200, Easing.OutQuint); + } + + this.FadeTo(IsHovered || hasMouseDown ? 0.8f : 0.2f, 200, Easing.OutQuint); } [Resolved] @@ -369,12 +391,16 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Colour = Color4.Black.Opacity(0.4f) }; + const float fudge = 0.97f; + InternalChildren = new Drawable[] { new Container { - Height = circle_size, - RelativeSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Height = fudge, Masking = true, AlwaysPresent = true, EdgeEffect = effect, @@ -394,8 +420,10 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline }, new Box { - Height = circle_size, - RelativeSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Height = fudge, }, }; } From b2c17979defca0bd018244129c9b9e1d029fdc84 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Apr 2021 19:24:20 +0900 Subject: [PATCH 1458/1791] Update colours of all overlay components in one swoop (based off combo colour) --- .../Timeline/TimelineHitObjectBlueprint.cs | 79 ++++++++++--------- 1 file changed, 41 insertions(+), 38 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 493f62921b..6463f1a200 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -31,6 +31,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private const float shadow_radius = 5; private const float circle_size = 38; + private Container repeatsContainer; + public Action OnDragHandled; [UsedImplicitly] @@ -41,7 +43,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private readonly Drawable circle; - private readonly Container mainComponents; + private readonly Container colouredComponents; private readonly OsuSpriteText comboIndexText; [Resolved] @@ -59,39 +61,37 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline RelativePositionAxes = Axes.X; RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; + Height = circle_size; - AddRangeInternal(new Drawable[] + AddRangeInternal(new[] { - mainComponents = new Container + circle = new ExtendableCircle + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + colouredComponents = new Container { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - }, - comboIndexText = new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.Centre, - Y = -1, - Font = OsuFont.Default.With(size: circle_size * 0.5f, weight: FontWeight.Regular), + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + comboIndexText = new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.Centre, + Y = -1, + Font = OsuFont.Default.With(size: circle_size * 0.5f, weight: FontWeight.Regular), + }, + } }, }); - circle = new ExtendableCircle - { - RelativeSizeAxes = Axes.X, - Size = new Vector2(1, circle_size), - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - }; - - mainComponents.Add(circle); - if (hitObject is IHasDuration) { - mainComponents.Add(new DragArea(hitObject) + colouredComponents.Add(new DragArea(hitObject) { OnDragHandled = e => OnDragHandled?.Invoke(e) }); @@ -127,15 +127,15 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline var comboColour = combo.GetComboColour(comboColours); if (HitObject is IHasDuration) - mainComponents.Colour = ColourInfo.GradientHorizontal(comboColour, comboColour.Lighten(0.4f)); + circle.Colour = ColourInfo.GradientHorizontal(comboColour, comboColour.Lighten(0.4f)); else - mainComponents.Colour = comboColour; + circle.Colour = comboColour; - var col = mainComponents.Colour.TopLeft.Linear; + var col = circle.Colour.TopLeft.Linear; float brightness = col.R + col.G + col.B; // decide the combo index colour based on brightness? - comboIndexText.Colour = brightness > 0.5f ? Color4.Black : Color4.White; + colouredComponents.Colour = OsuColour.Gray(brightness > 0.5f ? 0.2f : 0.9f); } protected override void Update() @@ -155,13 +155,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } } - private Container repeatsContainer; - private void updateRepeats(IHasRepeats repeats) { repeatsContainer?.Expire(); - mainComponents.Add(repeatsContainer = new Container + colouredComponents.Add(repeatsContainer = new Container { RelativeSizeAxes = Axes.Both, }); @@ -170,7 +168,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { repeatsContainer.Add(new Circle { - Size = new Vector2(circle_size / 2), + Size = new Vector2(circle_size / 3), + Alpha = 0.2f, Anchor = Anchor.CentreLeft, Origin = Anchor.Centre, RelativePositionAxes = Axes.X, @@ -224,7 +223,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Origin = Anchor.Centre; RelativePositionAxes = Axes.X; RelativeSizeAxes = Axes.Y; - Colour = OsuColour.Gray(0.2f); InternalChildren = new Drawable[] { @@ -351,7 +349,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline /// /// A circle with externalised end caps so it can take up the full width of a relative width area. - /// TODO: figure how to do this with a single circle to avoid pixel-misaligned edges. /// public class ExtendableCircle : Container { @@ -391,7 +388,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Colour = Color4.Black.Opacity(0.4f) }; - const float fudge = 0.97f; + // TODO: figure how to do this whole thing with a single circle to avoid pixel-misaligned edges. + // just working with what i can make work for the time being.. + const float fudge = 0.4f; InternalChildren = new Drawable[] { @@ -400,7 +399,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline RelativeSizeAxes = Axes.Both, Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Height = fudge, + Padding = new MarginPadding { Vertical = fudge }, Masking = true, AlwaysPresent = true, EdgeEffect = effect, @@ -418,12 +417,16 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Origin = Anchor.TopCentre, Size = new Vector2(circle_size) }, - new Box + new Container { RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Vertical = fudge }, Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Height = fudge, + Children = new Drawable[] + { + new Box { RelativeSizeAxes = Axes.Both, } + } }, }; } From e7b0042a608d09e1f6a01e53860a01595905177f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Apr 2021 19:25:03 +0900 Subject: [PATCH 1459/1791] Remove unnecessary hover / shadow logic --- .../Timeline/TimelineHitObjectBlueprint.cs | 29 +++++++------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 6463f1a200..67be298567 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -28,7 +28,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { public class TimelineHitObjectBlueprint : SelectionBlueprint { - private const float shadow_radius = 5; private const float circle_size = 38; private Container repeatsContainer; @@ -96,8 +95,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline OnDragHandled = e => OnDragHandled?.Invoke(e) }); } - - updateShadows(); } protected override void LoadComplete() @@ -116,6 +113,16 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } } + protected override void OnSelected() + { + // base logic hides selected blueprints when not selected, but timeline doesn't do that. + } + + protected override void OnDeselected() + { + // base logic hides selected blueprints when not selected, but timeline doesn't do that. + } + private void updateComboIndex() => comboIndexText.Text = (indexInCurrentComboBindable.Value + 1).ToString(); private void updateComboColour() @@ -180,20 +187,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline protected override bool ShouldBeConsideredForInput(Drawable child) => true; - protected override void OnSelected() - { - updateShadows(); - } - - private void updateShadows() - { - } - - protected override void OnDeselected() - { - updateShadows(); - } - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => circle.ReceivePositionalInputAt(screenSpacePos); @@ -384,7 +377,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline var effect = new EdgeEffectParameters { Type = EdgeEffectType.Shadow, - Radius = shadow_radius, + Radius = 5, Colour = Color4.Black.Opacity(0.4f) }; From bcd41417b3cb7701985d8db8614ded82cc9c7034 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Apr 2021 19:36:29 +0900 Subject: [PATCH 1460/1791] 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 c78dfb6a55..b5315c3616 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 92e05cb4a6..45b3d5c161 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 11124730c9..105a6e59c2 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 03ba04e8cec2b335355916d477712e84305ce00e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Apr 2021 19:50:22 +0900 Subject: [PATCH 1461/1791] Split out general checks into its own verifier class (and remove `static` usage) --- .../Edit/OsuBeatmapVerifier.cs | 5 --- osu.Game/Rulesets/Edit/BeatmapVerifier.cs | 24 ++++++++++++++ osu.Game/Screens/Edit/Verify/VerifyScreen.cs | 32 ++++++++----------- 3 files changed, 38 insertions(+), 23 deletions(-) create mode 100644 osu.Game/Rulesets/Edit/BeatmapVerifier.cs diff --git a/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs b/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs index 66ef74ab08..1c7ab00bbb 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Edit.Checks; using osu.Game.Rulesets.Edit.Checks.Components; using osu.Game.Rulesets.Osu.Edit.Checks; @@ -15,10 +14,6 @@ namespace osu.Game.Rulesets.Osu.Edit { private readonly List checks = new List { - // General checks - new CheckBackground(), - - // Ruleset-specific checks new CheckOffscreenObjects() }; diff --git a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs new file mode 100644 index 0000000000..b1d538bf04 --- /dev/null +++ b/osu.Game/Rulesets/Edit/BeatmapVerifier.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 System.Collections.Generic; +using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit.Checks; +using osu.Game.Rulesets.Edit.Checks.Components; + +namespace osu.Game.Rulesets.Edit +{ + /// + /// A ruleset-agnostic beatmap converter that identifies issues in common metadata or mapping standards. + /// + public class BeatmapVerifier : IBeatmapVerifier + { + private readonly List checks = new List + { + new CheckBackground(), + }; + + public IEnumerable Run(IBeatmap beatmap) => checks.SelectMany(check => check.Run(beatmap)); + } +} diff --git a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs index a733c9c176..550fbe2950 100644 --- a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs +++ b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs @@ -7,11 +7,9 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; -using osu.Game.Rulesets; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Checks.Components; using osuTK; @@ -20,9 +18,6 @@ namespace osu.Game.Screens.Edit.Verify { public class VerifyScreen : EditorScreen { - private Ruleset ruleset; - private static IBeatmapVerifier beatmapVerifier; - [Cached] private Bindable selectedIssue = new Bindable(); @@ -31,16 +26,6 @@ namespace osu.Game.Screens.Edit.Verify { } - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - - ruleset = parent.Get>().Value.BeatmapInfo.Ruleset?.CreateInstance(); - beatmapVerifier = ruleset?.CreateBeatmapVerifier(); - - return dependencies; - } - [BackgroundDependencyLoader] private void load() { @@ -81,9 +66,15 @@ namespace osu.Game.Screens.Edit.Verify [Resolved] private Bindable selectedIssue { get; set; } + private IBeatmapVerifier rulesetVerifier; + private BeatmapVerifier generalVerifier; + [BackgroundDependencyLoader] private void load(OsuColour colours) { + generalVerifier = new BeatmapVerifier(); + rulesetVerifier = Beatmap.BeatmapInfo.Ruleset?.CreateInstance()?.CreateBeatmapVerifier(); + RelativeSizeAxes = Axes.Both; InternalChildren = new Drawable[] @@ -128,9 +119,14 @@ namespace osu.Game.Screens.Edit.Verify private void refresh() { - table.Issues = beatmapVerifier.Run(Beatmap) - .OrderBy(issue => issue.Template.Type) - .ThenBy(issue => issue.Check.Metadata.Category); + var issues = generalVerifier.Run(Beatmap); + + if (rulesetVerifier != null) + issues = issues.Concat(rulesetVerifier.Run(Beatmap)); + + table.Issues = issues + .OrderBy(issue => issue.Template.Type) + .ThenBy(issue => issue.Check.Metadata.Category); } } } From 464fc02875f067aef4bd55f3244aa1df97cc774b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Apr 2021 19:55:17 +0900 Subject: [PATCH 1462/1791] Fix some styling issues with the verify screen layout --- osu.Game/Screens/Edit/Verify/IssueTable.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Verify/IssueTable.cs b/osu.Game/Screens/Edit/Verify/IssueTable.cs index 516e1adf44..042d6d84e3 100644 --- a/osu.Game/Screens/Edit/Verify/IssueTable.cs +++ b/osu.Game/Screens/Edit/Verify/IssueTable.cs @@ -38,9 +38,6 @@ namespace osu.Game.Screens.Edit.Verify Padding = new MarginPadding { Horizontal = horizontal_inset }; RowSize = new Dimension(GridSizeMode.Absolute, row_height); - Masking = true; - CornerRadius = 6; - AddInternal(backgroundFlow = new FillFlowContainer { RelativeSizeAxes = Axes.Both, @@ -118,6 +115,17 @@ namespace osu.Game.Screens.Edit.Verify } }; + protected override Drawable CreateHeader(int index, TableColumn column) => new HeaderText(column?.Header ?? string.Empty); + + private class HeaderText : OsuSpriteText + { + public HeaderText(string text) + { + Text = text.ToUpper(); + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold); + } + } + public class RowBackground : OsuClickableContainer { private readonly Issue issue; From 0d6890243fc7b6308ffdd87e7f87c1551d4ae2fd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Apr 2021 20:18:18 +0900 Subject: [PATCH 1463/1791] Fix typo in xmldoc --- osu.Game/Rulesets/Edit/BeatmapVerifier.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs index b1d538bf04..f9bced7beb 100644 --- a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs +++ b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Edit.Checks.Components; namespace osu.Game.Rulesets.Edit { /// - /// A ruleset-agnostic beatmap converter that identifies issues in common metadata or mapping standards. + /// A ruleset-agnostic beatmap verifier that identifies issues in common metadata or mapping standards. /// public class BeatmapVerifier : IBeatmapVerifier { From e601141be2a70e1a1f004c205b360212a9fe2605 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 13 Apr 2021 14:57:02 +0300 Subject: [PATCH 1464/1791] Simplify ExtendableCircle component --- .../Timeline/TimelineHitObjectBlueprint.cs | 83 ++++--------------- 1 file changed, 15 insertions(+), 68 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 67be298567..1c0d6e5ab6 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -343,84 +343,31 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline /// /// A circle with externalised end caps so it can take up the full width of a relative width area. /// - public class ExtendableCircle : Container + public class ExtendableCircle : CompositeDrawable { - private readonly Circle rightCircle; - private readonly Circle leftCircle; + private readonly CircularContainer content; - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) - { - return base.ReceivePositionalInputAt(screenSpacePos) - || leftCircle.ReceivePositionalInputAt(screenSpacePos) - || rightCircle.ReceivePositionalInputAt(screenSpacePos); - } + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => content.ReceivePositionalInputAt(screenSpacePos); - public override Quad ScreenSpaceDrawQuad - { - get - { - var leftQuad = leftCircle.ScreenSpaceDrawQuad; - - if (Width == 0) - { - return leftQuad; - } - - var rightQuad = rightCircle.ScreenSpaceDrawQuad; - - return new Quad(leftQuad.TopLeft, rightQuad.TopRight, leftQuad.BottomLeft, rightQuad.BottomRight); - } - } + public override Quad ScreenSpaceDrawQuad => content.ScreenSpaceDrawQuad; public ExtendableCircle() { - var effect = new EdgeEffectParameters + Padding = new MarginPadding { Horizontal = -circle_size / 2f }; + InternalChild = content = new CircularContainer { - Type = EdgeEffectType.Shadow, - Radius = 5, - Colour = Color4.Black.Opacity(0.4f) - }; - - // TODO: figure how to do this whole thing with a single circle to avoid pixel-misaligned edges. - // just working with what i can make work for the time being.. - const float fudge = 0.4f; - - InternalChildren = new Drawable[] - { - new Container + RelativeSizeAxes = Axes.Both, + Masking = true, + EdgeEffect = new EdgeEffectParameters { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Padding = new MarginPadding { Vertical = fudge }, - Masking = true, - AlwaysPresent = true, - EdgeEffect = effect, + Type = EdgeEffectType.Shadow, + Radius = 5, + Colour = Color4.Black.Opacity(0.4f) }, - leftCircle = new Circle + Child = new Box { - EdgeEffect = effect, - Origin = Anchor.TopCentre, - Size = new Vector2(circle_size) - }, - rightCircle = new Circle - { - EdgeEffect = effect, - Anchor = Anchor.TopRight, - Origin = Anchor.TopCentre, - Size = new Vector2(circle_size) - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Vertical = fudge }, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Children = new Drawable[] - { - new Box { RelativeSizeAxes = Axes.Both, } - } - }, + RelativeSizeAxes = Axes.Both + } }; } } From 69da804f817dac304b7bc36587070d5f129b5eae Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 13 Apr 2021 13:57:56 +0200 Subject: [PATCH 1465/1791] Add missing period --- osu.Game/Rulesets/Edit/Checks/CheckBackground.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs b/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs index 4d5069f446..93da42425c 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Edit.Checks public class IssueTemplateNoneSet : IssueTemplate { public IssueTemplateNoneSet(ICheck check) - : base(check, IssueType.Problem, "No background has been set") + : base(check, IssueType.Problem, "No background has been set.") { } From 0edc1a850d95c1c8bd94c873f12be8b40bcb33d5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Apr 2021 23:05:58 +0900 Subject: [PATCH 1466/1791] Split out common EditorTable base class --- osu.Game/Screens/Edit/EditorTable.cs | 49 +++++++++++++++++ .../Screens/Edit/Timing/ControlPointTable.cs | 44 ++-------------- osu.Game/Screens/Edit/Verify/IssueTable.cs | 52 ++++--------------- 3 files changed, 63 insertions(+), 82 deletions(-) create mode 100644 osu.Game/Screens/Edit/EditorTable.cs diff --git a/osu.Game/Screens/Edit/EditorTable.cs b/osu.Game/Screens/Edit/EditorTable.cs new file mode 100644 index 0000000000..e5e2add384 --- /dev/null +++ b/osu.Game/Screens/Edit/EditorTable.cs @@ -0,0 +1,49 @@ +// 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; + +namespace osu.Game.Screens.Edit +{ + public abstract class EditorTable : TableContainer + { + private const float horizontal_inset = 20; + + protected const float ROW_HEIGHT = 25; + + protected const int TEXT_SIZE = 14; + + protected readonly FillFlowContainer BackgroundFlow; + + protected EditorTable() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + Padding = new MarginPadding { Horizontal = horizontal_inset }; + RowSize = new Dimension(GridSizeMode.Absolute, ROW_HEIGHT); + + AddInternal(BackgroundFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Depth = 1f, + Padding = new MarginPadding { Horizontal = -horizontal_inset }, + Margin = new MarginPadding { Top = ROW_HEIGHT } + }); + } + + protected override Drawable CreateHeader(int index, TableColumn column) => new HeaderText(column?.Header ?? string.Empty); + + private class HeaderText : OsuSpriteText + { + public HeaderText(string text) + { + Text = text.ToUpper(); + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold); + } + } + } +} diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index a17b431fcc..75e2bb1f5c 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -20,47 +20,24 @@ using osuTK.Graphics; namespace osu.Game.Screens.Edit.Timing { - public class ControlPointTable : TableContainer + public class ControlPointTable : EditorTable { - private const float horizontal_inset = 20; - private const float row_height = 25; - private const int text_size = 14; - - private readonly FillFlowContainer backgroundFlow; - [Resolved] private Bindable selectedGroup { get; set; } - public ControlPointTable() - { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - - Padding = new MarginPadding { Horizontal = horizontal_inset }; - RowSize = new Dimension(GridSizeMode.Absolute, row_height); - - AddInternal(backgroundFlow = new FillFlowContainer - { - RelativeSizeAxes = Axes.Both, - Depth = 1f, - Padding = new MarginPadding { Horizontal = -horizontal_inset }, - Margin = new MarginPadding { Top = row_height } - }); - } - public IEnumerable ControlGroups { set { Content = null; - backgroundFlow.Clear(); + BackgroundFlow.Clear(); if (value?.Any() != true) return; foreach (var group in value) { - backgroundFlow.Add(new RowBackground(group)); + BackgroundFlow.Add(new RowBackground(group)); } Columns = createHeaders(); @@ -86,13 +63,13 @@ namespace osu.Game.Screens.Edit.Timing new OsuSpriteText { Text = $"#{index + 1}", - Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold), + Font = OsuFont.GetFont(size: TEXT_SIZE, weight: FontWeight.Bold), Margin = new MarginPadding(10) }, new OsuSpriteText { Text = group.Time.ToEditorFormattedString(), - Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) + Font = OsuFont.GetFont(size: TEXT_SIZE, weight: FontWeight.Bold) }, null, new ControlGroupAttributes(group), @@ -164,17 +141,6 @@ namespace osu.Game.Screens.Edit.Timing } } - protected override Drawable CreateHeader(int index, TableColumn column) => new HeaderText(column?.Header ?? string.Empty); - - private class HeaderText : OsuSpriteText - { - public HeaderText(string text) - { - Text = text.ToUpper(); - Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold); - } - } - public class RowBackground : OsuClickableContainer { private readonly ControlPointGroup controlGroup; diff --git a/osu.Game/Screens/Edit/Verify/IssueTable.cs b/osu.Game/Screens/Edit/Verify/IssueTable.cs index 042d6d84e3..00570d363b 100644 --- a/osu.Game/Screens/Edit/Verify/IssueTable.cs +++ b/osu.Game/Screens/Edit/Verify/IssueTable.cs @@ -19,47 +19,24 @@ using osuTK.Graphics; namespace osu.Game.Screens.Edit.Verify { - public class IssueTable : TableContainer + public class IssueTable : EditorTable { - private const float horizontal_inset = 20; - private const float row_height = 25; - private const int text_size = 14; - - private readonly FillFlowContainer backgroundFlow; - [Resolved] private Bindable selectedIssue { get; set; } - public IssueTable() - { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - - Padding = new MarginPadding { Horizontal = horizontal_inset }; - RowSize = new Dimension(GridSizeMode.Absolute, row_height); - - AddInternal(backgroundFlow = new FillFlowContainer - { - RelativeSizeAxes = Axes.Both, - Depth = 1f, - Padding = new MarginPadding { Horizontal = -horizontal_inset }, - Margin = new MarginPadding { Top = row_height } - }); - } - public IEnumerable Issues { set { Content = null; - backgroundFlow.Clear(); + BackgroundFlow.Clear(); if (value == null) return; foreach (var issue in value) { - backgroundFlow.Add(new RowBackground(issue)); + BackgroundFlow.Add(new RowBackground(issue)); } Columns = createHeaders(); @@ -86,46 +63,35 @@ namespace osu.Game.Screens.Edit.Verify new OsuSpriteText { Text = $"#{index + 1}", - Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Medium), + Font = OsuFont.GetFont(size: TEXT_SIZE, weight: FontWeight.Medium), Margin = new MarginPadding { Right = 10 } }, new OsuSpriteText { Text = issue.Template.Type.ToString(), - Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold), + Font = OsuFont.GetFont(size: TEXT_SIZE, weight: FontWeight.Bold), Margin = new MarginPadding { Right = 10 }, Colour = issue.Template.Colour }, new OsuSpriteText { Text = issue.GetEditorTimestamp(), - Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold), + Font = OsuFont.GetFont(size: TEXT_SIZE, weight: FontWeight.Bold), Margin = new MarginPadding { Right = 10 }, }, new OsuSpriteText { Text = issue.ToString(), - Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Medium) + Font = OsuFont.GetFont(size: TEXT_SIZE, weight: FontWeight.Medium) }, new OsuSpriteText { Text = issue.Check.Metadata.Category.ToString(), - Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold), + Font = OsuFont.GetFont(size: TEXT_SIZE, weight: FontWeight.Bold), Margin = new MarginPadding(10) } }; - protected override Drawable CreateHeader(int index, TableColumn column) => new HeaderText(column?.Header ?? string.Empty); - - private class HeaderText : OsuSpriteText - { - public HeaderText(string text) - { - Text = text.ToUpper(); - Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold); - } - } - public class RowBackground : OsuClickableContainer { private readonly Issue issue; @@ -150,7 +116,7 @@ namespace osu.Game.Screens.Edit.Verify this.issue = issue; RelativeSizeAxes = Axes.X; - Height = row_height; + Height = ROW_HEIGHT; AlwaysPresent = true; CornerRadius = 3; Masking = true; From 21e8e5fbcacf799b0485ab0203e66473a5d6e881 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Apr 2021 23:26:19 +0900 Subject: [PATCH 1467/1791] Move common table layout logic into `EditorTable` abstract class --- osu.Game/Screens/Edit/EditorTable.cs | 95 ++++++++++- .../Screens/Edit/Timing/ControlPointTable.cs | 120 +++----------- osu.Game/Screens/Edit/Verify/IssueTable.cs | 154 +++++------------- 3 files changed, 152 insertions(+), 217 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorTable.cs b/osu.Game/Screens/Edit/EditorTable.cs index e5e2add384..ef1c88db9a 100644 --- a/osu.Game/Screens/Edit/EditorTable.cs +++ b/osu.Game/Screens/Edit/EditorTable.cs @@ -1,10 +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.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osuTK.Graphics; namespace osu.Game.Screens.Edit { @@ -16,7 +21,7 @@ namespace osu.Game.Screens.Edit protected const int TEXT_SIZE = 14; - protected readonly FillFlowContainer BackgroundFlow; + protected readonly FillFlowContainer BackgroundFlow; protected EditorTable() { @@ -26,7 +31,7 @@ namespace osu.Game.Screens.Edit Padding = new MarginPadding { Horizontal = horizontal_inset }; RowSize = new Dimension(GridSizeMode.Absolute, ROW_HEIGHT); - AddInternal(BackgroundFlow = new FillFlowContainer + AddInternal(BackgroundFlow = new FillFlowContainer { RelativeSizeAxes = Axes.Both, Depth = 1f, @@ -45,5 +50,91 @@ namespace osu.Game.Screens.Edit Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold); } } + + public class RowBackground : OsuClickableContainer + { + public readonly object Item; + + private const int fade_duration = 100; + + private readonly Box hoveredBackground; + + [Resolved] + private EditorClock clock { get; set; } + + public RowBackground(object item) + { + Item = item; + + RelativeSizeAxes = Axes.X; + Height = 25; + + AlwaysPresent = true; + + CornerRadius = 3; + Masking = true; + + Children = new Drawable[] + { + hoveredBackground = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + }, + }; + + // todo delete + Action = () => + { + }; + } + + private Color4 colourHover; + private Color4 colourSelected; + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + hoveredBackground.Colour = colourHover = colours.BlueDarker; + colourSelected = colours.YellowDarker; + } + + private bool selected; + + public bool Selected + { + get => selected; + set + { + if (value == selected) + return; + + selected = value; + updateState(); + } + } + + protected override bool OnHover(HoverEvent e) + { + updateState(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + updateState(); + base.OnHoverLost(e); + } + + private void updateState() + { + hoveredBackground.FadeColour(selected ? colourSelected : colourHover, 450, Easing.OutQuint); + + if (selected || IsHovered) + hoveredBackground.FadeIn(fade_duration, Easing.OutQuint); + else + hoveredBackground.FadeOut(fade_duration, Easing.OutQuint); + } + } } } diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 75e2bb1f5c..dd51056bf1 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -8,12 +8,9 @@ using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.Events; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Extensions; using osu.Game.Graphics; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osuTK; using osuTK.Graphics; @@ -25,6 +22,9 @@ namespace osu.Game.Screens.Edit.Timing [Resolved] private Bindable selectedGroup { get; set; } + [Resolved] + private EditorClock clock { get; set; } + public IEnumerable ControlGroups { set @@ -37,7 +37,14 @@ namespace osu.Game.Screens.Edit.Timing foreach (var group in value) { - BackgroundFlow.Add(new RowBackground(group)); + BackgroundFlow.Add(new RowBackground(group) + { + Action = () => + { + selectedGroup.Value = group; + clock.SeekSmoothlyTo(group.Time); + } + }); } Columns = createHeaders(); @@ -45,6 +52,16 @@ namespace osu.Game.Screens.Edit.Timing } } + protected override void LoadComplete() + { + base.LoadComplete(); + + selectedGroup.BindValueChanged(group => + { + foreach (var b in BackgroundFlow) b.Selected = b.Item == group.NewValue; + }, true); + } + private TableColumn[] createHeaders() { var columns = new List @@ -140,100 +157,5 @@ namespace osu.Game.Screens.Edit.Timing return null; } } - - public class RowBackground : OsuClickableContainer - { - private readonly ControlPointGroup controlGroup; - private const int fade_duration = 100; - - private readonly Box hoveredBackground; - - [Resolved] - private EditorClock clock { get; set; } - - [Resolved] - private Bindable selectedGroup { get; set; } - - public RowBackground(ControlPointGroup controlGroup) - { - this.controlGroup = controlGroup; - RelativeSizeAxes = Axes.X; - Height = 25; - - AlwaysPresent = true; - - CornerRadius = 3; - Masking = true; - - Children = new Drawable[] - { - hoveredBackground = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - }, - }; - - Action = () => - { - selectedGroup.Value = controlGroup; - clock.SeekSmoothlyTo(controlGroup.Time); - }; - } - - private Color4 colourHover; - private Color4 colourSelected; - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - hoveredBackground.Colour = colourHover = colours.BlueDarker; - colourSelected = colours.YellowDarker; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - selectedGroup.BindValueChanged(group => { Selected = controlGroup == group.NewValue; }, true); - } - - private bool selected; - - protected bool Selected - { - get => selected; - set - { - if (value == selected) - return; - - selected = value; - updateState(); - } - } - - protected override bool OnHover(HoverEvent e) - { - updateState(); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - updateState(); - base.OnHoverLost(e); - } - - private void updateState() - { - hoveredBackground.FadeColour(selected ? colourSelected : colourHover, 450, Easing.OutQuint); - - if (selected || IsHovered) - hoveredBackground.FadeIn(fade_duration, Easing.OutQuint); - else - hoveredBackground.FadeOut(fade_duration, Easing.OutQuint); - } - } } } diff --git a/osu.Game/Screens/Edit/Verify/IssueTable.cs b/osu.Game/Screens/Edit/Verify/IssueTable.cs index 00570d363b..44244028c9 100644 --- a/osu.Game/Screens/Edit/Verify/IssueTable.cs +++ b/osu.Game/Screens/Edit/Verify/IssueTable.cs @@ -8,14 +8,10 @@ using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.Events; using osu.Game.Graphics; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Input.Bindings; using osu.Game.Rulesets.Edit.Checks.Components; -using osuTK.Graphics; namespace osu.Game.Screens.Edit.Verify { @@ -24,6 +20,15 @@ namespace osu.Game.Screens.Edit.Verify [Resolved] private Bindable selectedIssue { get; set; } + [Resolved] + private EditorClock clock { get; set; } + + [Resolved] + private EditorBeatmap editorBeatmap { get; set; } + + [Resolved] + private Editor editor { get; set; } + public IEnumerable Issues { set @@ -36,7 +41,25 @@ namespace osu.Game.Screens.Edit.Verify foreach (var issue in value) { - BackgroundFlow.Add(new RowBackground(issue)); + BackgroundFlow.Add(new RowBackground(issue) + { + Action = () => + { + selectedIssue.Value = issue; + + if (issue.Time != null) + { + clock.Seek(issue.Time.Value); + editor.OnPressed(GlobalAction.EditorComposeMode); + } + + if (!issue.HitObjects.Any()) + return; + + editorBeatmap.SelectedHitObjects.Clear(); + editorBeatmap.SelectedHitObjects.AddRange(issue.HitObjects); + }, + }); } Columns = createHeaders(); @@ -44,6 +67,16 @@ namespace osu.Game.Screens.Edit.Verify } } + protected override void LoadComplete() + { + base.LoadComplete(); + + selectedIssue.BindValueChanged(issue => + { + foreach (var b in BackgroundFlow) b.Selected = b.Item == issue.NewValue; + }, true); + } + private TableColumn[] createHeaders() { var columns = new List @@ -91,116 +124,5 @@ namespace osu.Game.Screens.Edit.Verify Margin = new MarginPadding(10) } }; - - public class RowBackground : OsuClickableContainer - { - private readonly Issue issue; - private const int fade_duration = 100; - - private readonly Box hoveredBackground; - - [Resolved] - private EditorClock clock { get; set; } - - [Resolved] - private Editor editor { get; set; } - - [Resolved] - private EditorBeatmap editorBeatmap { get; set; } - - [Resolved] - private Bindable selectedIssue { get; set; } - - public RowBackground(Issue issue) - { - this.issue = issue; - - RelativeSizeAxes = Axes.X; - Height = ROW_HEIGHT; - AlwaysPresent = true; - CornerRadius = 3; - Masking = true; - - Children = new Drawable[] - { - hoveredBackground = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - }, - }; - - Action = () => - { - selectedIssue.Value = issue; - - if (issue.Time != null) - { - clock.Seek(issue.Time.Value); - editor.OnPressed(GlobalAction.EditorComposeMode); - } - - if (!issue.HitObjects.Any()) - return; - - editorBeatmap.SelectedHitObjects.Clear(); - editorBeatmap.SelectedHitObjects.AddRange(issue.HitObjects); - }; - } - - private Color4 colourHover; - private Color4 colourSelected; - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - hoveredBackground.Colour = colourHover = colours.BlueDarker; - colourSelected = colours.YellowDarker; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - selectedIssue.BindValueChanged(change => { Selected = issue == change.NewValue; }, true); - } - - private bool selected; - - protected bool Selected - { - get => selected; - set - { - if (value == selected) - return; - - selected = value; - updateState(); - } - } - - protected override bool OnHover(HoverEvent e) - { - updateState(); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - updateState(); - base.OnHoverLost(e); - } - - private void updateState() - { - hoveredBackground.FadeColour(selected ? colourSelected : colourHover, 450, Easing.OutQuint); - - if (selected || IsHovered) - hoveredBackground.FadeIn(fade_duration, Easing.OutQuint); - else - hoveredBackground.FadeOut(fade_duration, Easing.OutQuint); - } - } } } From cb4f64133eedde15134e64b9b0a0c584256df3aa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Apr 2021 23:30:20 +0900 Subject: [PATCH 1468/1791] Add xmldoc to interfaces --- osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs | 3 +++ osu.Game/Rulesets/Edit/IBeatmapVerifier.cs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs b/osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs index f355ae734e..f284240092 100644 --- a/osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs +++ b/osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs @@ -6,6 +6,9 @@ using osu.Game.Beatmaps; namespace osu.Game.Rulesets.Edit.Checks.Components { + /// + /// A specific check that can be run on a beatmap to verify or find issues. + /// public interface ICheck { /// diff --git a/osu.Game/Rulesets/Edit/IBeatmapVerifier.cs b/osu.Game/Rulesets/Edit/IBeatmapVerifier.cs index 2bafacefa3..61d8119635 100644 --- a/osu.Game/Rulesets/Edit/IBeatmapVerifier.cs +++ b/osu.Game/Rulesets/Edit/IBeatmapVerifier.cs @@ -7,6 +7,9 @@ using osu.Game.Rulesets.Edit.Checks.Components; namespace osu.Game.Rulesets.Edit { + /// + /// A class which can run against a beatmap and surface issues to the user which could go against known criteria or hinder gameplay. + /// public interface IBeatmapVerifier { public IEnumerable Run(IBeatmap beatmap); From bf5ed12b757663216d7e5c1a92aa4e26e8371f2a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 14 Apr 2021 06:33:53 +0300 Subject: [PATCH 1469/1791] Add support for legacy skin `CursorCentre` setting --- osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursor.cs | 5 +++-- osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursor.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursor.cs index 314139d02a..8fe40f801b 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursor.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursor.cs @@ -24,6 +24,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy [BackgroundDependencyLoader] private void load(ISkinSource skin) { + bool centre = skin.GetConfig(OsuSkinConfiguration.CursorCentre)?.Value ?? false; spin = skin.GetConfig(OsuSkinConfiguration.CursorRotate)?.Value ?? true; InternalChildren = new[] @@ -32,13 +33,13 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { Texture = skin.GetTexture("cursor"), Anchor = Anchor.Centre, - Origin = Anchor.Centre, + Origin = centre ? Anchor.Centre : Anchor.TopLeft, }, new NonPlayfieldSprite { Texture = skin.GetTexture("cursormiddle"), Anchor = Anchor.Centre, - Origin = Anchor.Centre, + Origin = centre ? Anchor.Centre : Anchor.TopLeft, }, }; } diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs index 75a62a6f8e..6953e66b5c 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs @@ -8,6 +8,7 @@ namespace osu.Game.Rulesets.Osu.Skinning SliderBorderSize, SliderPathRadius, AllowSliderBallTint, + CursorCentre, CursorExpand, CursorRotate, HitCircleOverlayAboveNumber, From df991bc0affa64d7a0deaf680e57b24cb03cbc26 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 14 Apr 2021 06:36:31 +0300 Subject: [PATCH 1470/1791] Refactor gameplay cursor test scene and add visual coverage --- .../TestSceneGameplayCursor.cs | 62 +++++++++++++++---- 1 file changed, 49 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs index e3ccf83715..ed44bc7309 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs @@ -4,13 +4,22 @@ using System; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Textures; +using osu.Framework.Input; using osu.Framework.Testing.Input; using osu.Framework.Utils; +using osu.Game.Audio; using osu.Game.Configuration; +using osu.Game.Rulesets.Osu.Skinning; using osu.Game.Rulesets.Osu.UI.Cursor; using osu.Game.Screens.Play; +using osu.Game.Skinning; using osuTK; namespace osu.Game.Rulesets.Osu.Tests @@ -21,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Tests [Cached] private GameplayBeatmap gameplayBeatmap; - private ClickingCursorContainer lastContainer; + private OsuCursorContainer lastContainer; [Resolved] private OsuConfigManager config { get; set; } @@ -48,12 +57,10 @@ namespace osu.Game.Rulesets.Osu.Tests { config.SetValue(OsuSetting.AutoCursorSize, true); gameplayBeatmap.BeatmapInfo.BaseDifficulty.CircleSize = val; - Scheduler.AddOnce(recreate); + Scheduler.AddOnce(() => loadContent(false)); }); - AddStep("test cursor container", recreate); - - void recreate() => SetContents(() => new OsuInputManager(new OsuRuleset().RulesetInfo) { Child = new OsuCursorContainer() }); + AddStep("test cursor container", () => loadContent(false)); } [TestCase(1, 1)] @@ -68,7 +75,7 @@ namespace osu.Game.Rulesets.Osu.Tests AddStep($"adjust cs to {circleSize}", () => gameplayBeatmap.BeatmapInfo.BaseDifficulty.CircleSize = circleSize); AddStep("turn on autosizing", () => config.SetValue(OsuSetting.AutoCursorSize, true)); - AddStep("load content", loadContent); + AddStep("load content", () => loadContent()); AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == OsuCursorContainer.GetScaleForCircleSize(circleSize) * userScale); @@ -82,18 +89,47 @@ namespace osu.Game.Rulesets.Osu.Tests AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == userScale); } - private void loadContent() + [Test] + public void TestTopLeftOrigin() { - SetContents(() => new MovingCursorInputManager + AddStep("load content", () => loadContent(false, () => new SkinProvidingContainer(new TopLeftCursorSkin()))); + AddAssert("cursor top left", () => lastContainer.ActiveCursor.Origin == Anchor.TopLeft); + } + + private void loadContent(bool automated = true, Func skinProvider = null) + { + SetContents(() => { - Child = lastContainer = new ClickingCursorContainer - { - RelativeSizeAxes = Axes.Both, - Masking = true, - } + var inputManager = automated ? (InputManager)new MovingCursorInputManager() : new OsuInputManager(new OsuRuleset().RulesetInfo); + var skinContainer = skinProvider?.Invoke() ?? new SkinProvidingContainer(null); + + lastContainer = automated ? new ClickingCursorContainer() : new OsuCursorContainer(); + + return inputManager.WithChild(skinContainer.WithChild(lastContainer)); }); } + private class TopLeftCursorSkin : ISkin + { + public Drawable GetDrawableComponent(ISkinComponent component) => null; + public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => null; + public ISample GetSample(ISampleInfo sampleInfo) => null; + + public IBindable GetConfig(TLookup lookup) + { + switch (lookup) + { + case OsuSkinConfiguration osuLookup: + if (osuLookup == OsuSkinConfiguration.CursorCentre) + return SkinUtils.As(new BindableBool(false)); + + break; + } + + return null; + } + } + private class ClickingCursorContainer : OsuCursorContainer { private bool pressed; From 89ce8f290f3e074d6b762cbd6e30aeae5d35cf9c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Apr 2021 12:28:37 +0900 Subject: [PATCH 1471/1791] Add simple acceleration to volume metre adjustments --- osu.Game/Overlays/Volume/VolumeMeter.cs | 34 +++++++++++++++++++++---- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index 5b997bbd05..57485946c1 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Framework.Threading; using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -225,7 +226,7 @@ namespace osu.Game.Overlays.Volume private set => Bindable.Value = value; } - private const double adjust_step = 0.05; + private const double adjust_step = 0.01; public void Increase(double amount = 1, bool isPrecise = false) => adjust(amount, isPrecise); public void Decrease(double amount = 1, bool isPrecise = false) => adjust(-amount, isPrecise); @@ -233,16 +234,39 @@ namespace osu.Game.Overlays.Volume // because volume precision is set to 0.01, this local is required to keep track of more precise adjustments and only apply when possible. private double scrollAccumulation; + private double accelerationModifier = 1; + + private const double max_acceleration = 5; + + private ScheduledDelegate accelerationDebounce; + + private void resetAcceleration() => accelerationModifier = 1; + private void adjust(double delta, bool isPrecise) { - scrollAccumulation += delta * adjust_step * (isPrecise ? 0.1 : 1); + // every adjust increment increases the rate at which adjustments happen up to a cutoff. + // this debounce will reset on inactivity. + accelerationDebounce?.Cancel(); + accelerationDebounce = Scheduler.AddDelayed(resetAcceleration, 150); + + delta *= accelerationModifier; + accelerationModifier = Math.Min(max_acceleration, accelerationModifier * 1.2f); var precision = Bindable.Precision; - while (Precision.AlmostBigger(Math.Abs(scrollAccumulation), precision)) + if (isPrecise) { - Volume += Math.Sign(scrollAccumulation) * precision; - scrollAccumulation = scrollAccumulation < 0 ? Math.Min(0, scrollAccumulation + precision) : Math.Max(0, scrollAccumulation - precision); + scrollAccumulation += delta * adjust_step * 0.1; + + while (Precision.AlmostBigger(Math.Abs(scrollAccumulation), precision)) + { + Volume += Math.Sign(scrollAccumulation) * precision; + scrollAccumulation = scrollAccumulation < 0 ? Math.Min(0, scrollAccumulation + precision) : Math.Max(0, scrollAccumulation - precision); + } + } + else + { + Volume += Math.Sign(delta) * Math.Max(precision, Math.Abs(delta * adjust_step)); } } From 8282f38eb708884e765e14148aa254b21db5f4fd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Apr 2021 13:10:45 +0900 Subject: [PATCH 1472/1791] Fix volume controls not supporting key repeat --- osu.Game/Extensions/DrawableExtensions.cs | 8 +++-- .../Overlays/Volume/VolumeControlReceptor.cs | 32 +++++++++++++++---- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/osu.Game/Extensions/DrawableExtensions.cs b/osu.Game/Extensions/DrawableExtensions.cs index 1790eb608e..67b9e727a5 100644 --- a/osu.Game/Extensions/DrawableExtensions.cs +++ b/osu.Game/Extensions/DrawableExtensions.cs @@ -9,6 +9,9 @@ namespace osu.Game.Extensions { public static class DrawableExtensions { + public const double REPEAT_INTERVAL = 70; + public const double INITIAL_DELAY = 250; + /// /// Helper method that is used while doesn't support repetitions of . /// Simulates repetitions by continually invoking a delegate according to the default key repeat rate. @@ -19,12 +22,13 @@ namespace osu.Game.Extensions /// The which is handling the repeat. /// The to schedule repetitions on. /// The to be invoked once immediately and with every repetition. + /// The delay imposed on the first repeat. Defaults to . /// A which can be cancelled to stop the repeat events from firing. - public static ScheduledDelegate BeginKeyRepeat(this IKeyBindingHandler handler, Scheduler scheduler, Action action) + public static ScheduledDelegate BeginKeyRepeat(this IKeyBindingHandler handler, Scheduler scheduler, Action action, double initialRepeatDelay = INITIAL_DELAY) { action(); - ScheduledDelegate repeatDelegate = new ScheduledDelegate(action, handler.Time.Current + 250, 70); + ScheduledDelegate repeatDelegate = new ScheduledDelegate(action, handler.Time.Current + initialRepeatDelay, REPEAT_INTERVAL); scheduler.Add(repeatDelegate); return repeatDelegate; } diff --git a/osu.Game/Overlays/Volume/VolumeControlReceptor.cs b/osu.Game/Overlays/Volume/VolumeControlReceptor.cs index 3b39b74e00..34b86b2f81 100644 --- a/osu.Game/Overlays/Volume/VolumeControlReceptor.cs +++ b/osu.Game/Overlays/Volume/VolumeControlReceptor.cs @@ -6,6 +6,8 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Framework.Threading; +using osu.Game.Extensions; using osu.Game.Input.Bindings; namespace osu.Game.Overlays.Volume @@ -15,8 +17,30 @@ namespace osu.Game.Overlays.Volume public Func ActionRequested; public Func ScrollActionRequested; - public bool OnPressed(GlobalAction action) => - ActionRequested?.Invoke(action) ?? false; + private ScheduledDelegate keyRepeat; + + public bool OnPressed(GlobalAction action) + { + switch (action) + { + case GlobalAction.DecreaseVolume: + case GlobalAction.IncreaseVolume: + keyRepeat?.Cancel(); + keyRepeat = this.BeginKeyRepeat(Scheduler, () => ActionRequested?.Invoke(action), 150); + return true; + + case GlobalAction.ToggleMute: + ActionRequested?.Invoke(action); + return true; + } + + return false; + } + + public void OnReleased(GlobalAction action) + { + keyRepeat?.Cancel(); + } protected override bool OnScroll(ScrollEvent e) { @@ -27,9 +51,5 @@ namespace osu.Game.Overlays.Volume public bool OnScroll(GlobalAction action, float amount, bool isPrecise) => ScrollActionRequested?.Invoke(action, amount, isPrecise) ?? false; - - public void OnReleased(GlobalAction action) - { - } } } From 65a1270f9a4dc5d6ba1680dad869736e8714f31f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Apr 2021 14:16:18 +0900 Subject: [PATCH 1473/1791] Hide top-right HUD overlay elements as part of HUD visibility --- osu.Game/Screens/Play/HUDOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 3dffab8102..31b49dfbe9 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -74,7 +74,7 @@ namespace osu.Game.Screens.Play private bool holdingForHUD; - private IEnumerable hideTargets => new Drawable[] { visibilityContainer, KeyCounter }; + private IEnumerable hideTargets => new Drawable[] { visibilityContainer, KeyCounter, topRightElements }; public HUDOverlay(ScoreProcessor scoreProcessor, HealthProcessor healthProcessor, DrawableRuleset drawableRuleset, IReadOnlyList mods) { From ad53ababe89f49a2650a4bcd4eb5368052f9413f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 14 Apr 2021 08:16:45 +0300 Subject: [PATCH 1474/1791] Fix wrong default Ah, soz --- osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursor.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursor.cs index 8fe40f801b..7a8555d991 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursor.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursor.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy [BackgroundDependencyLoader] private void load(ISkinSource skin) { - bool centre = skin.GetConfig(OsuSkinConfiguration.CursorCentre)?.Value ?? false; + bool centre = skin.GetConfig(OsuSkinConfiguration.CursorCentre)?.Value ?? true; spin = skin.GetConfig(OsuSkinConfiguration.CursorRotate)?.Value ?? true; InternalChildren = new[] From b060b59dcf0c2f0bf73979b398aa2000007d6fd7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 14 Apr 2021 08:17:35 +0300 Subject: [PATCH 1475/1791] Return null values instead of throwing NIE --- osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs index 8fd13c7417..0ba97fac54 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs @@ -78,7 +78,7 @@ namespace osu.Game.Rulesets.Osu.Tests RelativeSizeAxes = Axes.Both; } - public Drawable GetDrawableComponent(ISkinComponent component) => throw new NotImplementedException(); + public Drawable GetDrawableComponent(ISkinComponent component) => null; public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) { @@ -98,9 +98,9 @@ namespace osu.Game.Rulesets.Osu.Tests return null; } - public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); + public ISample GetSample(ISampleInfo sampleInfo) => null; - public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException(); + public IBindable GetConfig(TLookup lookup) => null; public event Action SourceChanged { From daf198fa77f18a6872213477c37abcc66f933ad2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 14 Apr 2021 08:18:24 +0300 Subject: [PATCH 1476/1791] Add osu! 2007 skin cursor for testing purposes --- .../Resources/old-skin/cursor.png | Bin 0 -> 10496 bytes .../Resources/old-skin/cursortrail.png | Bin 0 -> 3763 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100755 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/cursor.png create mode 100755 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/cursortrail.png diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/cursor.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/cursor.png new file mode 100755 index 0000000000000000000000000000000000000000..fe305468fe9efc47be6e9e793baabdab04aab4da GIT binary patch literal 10496 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 Date: Wed, 14 Apr 2021 08:20:18 +0300 Subject: [PATCH 1477/1791] Apply `CursorCentre` to old-style legacy cursor trail --- .../Skinning/Legacy/LegacyCursorTrail.cs | 12 ++++++++- .../UI/Cursor/CursorTrail.cs | 25 ++++++++++++++++--- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorTrail.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorTrail.cs index af9ea99232..0025576325 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorTrail.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorTrail.cs @@ -26,7 +26,17 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy Texture = skin.GetTexture("cursortrail"); disjointTrail = skin.GetTexture("cursormiddle") == null; - Blending = !disjointTrail ? BlendingParameters.Additive : BlendingParameters.Inherit; + if (disjointTrail) + { + bool centre = skin.GetConfig(OsuSkinConfiguration.CursorCentre)?.Value ?? true; + + TrailOrigin = centre ? Anchor.Centre : Anchor.TopLeft; + Blending = BlendingParameters.Inherit; + } + else + { + Blending = BlendingParameters.Additive; + } if (Texture != null) { diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index 0b30c28b8d..b55575696e 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -5,6 +5,7 @@ using System; using System.Diagnostics; using System.Runtime.InteropServices; using osu.Framework.Allocation; +using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Batches; using osu.Framework.Graphics.OpenGL.Vertices; @@ -31,6 +32,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private double timeOffset; private float time; + protected Anchor TrailOrigin = Anchor.Centre; + public CursorTrail() { // as we are currently very dependent on having a running clock, let's make our own clock for the time being. @@ -197,6 +200,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private readonly TrailPart[] parts = new TrailPart[max_sprites]; private Vector2 size; + private Vector2 originPosition; + private readonly QuadBatch vertexBatch = new QuadBatch(max_sprites, 1); public TrailDrawNode(CursorTrail source) @@ -213,6 +218,18 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor size = Source.partSize; time = Source.time; + originPosition = Vector2.Zero; + + if (Source.TrailOrigin.HasFlagFast(Anchor.x1)) + originPosition.X = 0.5f; + else if (Source.TrailOrigin.HasFlagFast(Anchor.x2)) + originPosition.X = 1f; + + if (Source.TrailOrigin.HasFlagFast(Anchor.y1)) + originPosition.Y = 0.5f; + else if (Source.TrailOrigin.HasFlagFast(Anchor.y2)) + originPosition.Y = 1f; + Source.parts.CopyTo(parts, 0); } @@ -237,7 +254,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor vertexBatch.Add(new TexturedTrailVertex { - Position = new Vector2(part.Position.X - size.X / 2, part.Position.Y + size.Y / 2), + Position = new Vector2(part.Position.X - size.X * originPosition.X, part.Position.Y + size.Y * (1 - originPosition.Y)), TexturePosition = textureRect.BottomLeft, TextureRect = new Vector4(0, 0, 1, 1), Colour = DrawColourInfo.Colour.BottomLeft.Linear, @@ -246,7 +263,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor vertexBatch.Add(new TexturedTrailVertex { - Position = new Vector2(part.Position.X + size.X / 2, part.Position.Y + size.Y / 2), + Position = new Vector2(part.Position.X + size.X * (1 - originPosition.X), part.Position.Y + size.Y * (1 - originPosition.Y)), TexturePosition = textureRect.BottomRight, TextureRect = new Vector4(0, 0, 1, 1), Colour = DrawColourInfo.Colour.BottomRight.Linear, @@ -255,7 +272,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor vertexBatch.Add(new TexturedTrailVertex { - Position = new Vector2(part.Position.X + size.X / 2, part.Position.Y - size.Y / 2), + Position = new Vector2(part.Position.X + size.X * (1 - originPosition.X), part.Position.Y - size.Y * originPosition.Y), TexturePosition = textureRect.TopRight, TextureRect = new Vector4(0, 0, 1, 1), Colour = DrawColourInfo.Colour.TopRight.Linear, @@ -264,7 +281,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor vertexBatch.Add(new TexturedTrailVertex { - Position = new Vector2(part.Position.X - size.X / 2, part.Position.Y - size.Y / 2), + Position = new Vector2(part.Position.X - size.X * originPosition.X, part.Position.Y - size.Y * originPosition.Y), TexturePosition = textureRect.TopLeft, TextureRect = new Vector4(0, 0, 1, 1), Colour = DrawColourInfo.Colour.TopLeft.Linear, From 6044083cf71405f9fa0a67b0d105e32ef15861d9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Apr 2021 14:25:16 +0900 Subject: [PATCH 1478/1791] Speed up the fade of the HUD a touch --- osu.Game/Screens/Play/HUDOverlay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 31b49dfbe9..669c920017 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -24,9 +24,9 @@ namespace osu.Game.Screens.Play [Cached] public class HUDOverlay : Container, IKeyBindingHandler { - public const float FADE_DURATION = 400; + public const float FADE_DURATION = 300; - public const Easing FADE_EASING = Easing.Out; + public const Easing FADE_EASING = Easing.OutQuint; /// /// The total height of all the top of screen scoring elements. From b7d2821b5514f0b3e4b76064696354a0f56ae48a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Apr 2021 14:51:52 +0900 Subject: [PATCH 1479/1791] Display the centre marker above the waveform Gives it a bit more visibility. This is where it was meant to sit, but didn't consider using a proxy drawable to make it work previously. --- .../Edit/Compose/Components/Timeline/Timeline.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 86a30b7e2d..aa5cc46e37 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -74,13 +74,18 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [BackgroundDependencyLoader] private void load(IBindable beatmap, OsuColour colours, OsuConfigManager config) { + CentreMarker centreMarker; + + // We don't want the centre marker to scroll + AddInternal(centreMarker = new CentreMarker()); + AddRange(new Drawable[] { new Container { RelativeSizeAxes = Axes.Both, Depth = float.MaxValue, - Children = new Drawable[] + Children = new[] { waveform = new WaveformGraph { @@ -90,6 +95,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline MidColour = colours.BlueDark, HighColour = colours.BlueDarker, }, + centreMarker.CreateProxy(), ticks = new TimelineTickDisplay(), controlPoints = new TimelineControlPointDisplay(), new Box @@ -104,9 +110,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline }, }); - // We don't want the centre marker to scroll - AddInternal(new CentreMarker { Depth = float.MaxValue }); - waveformOpacity = config.GetBindable(OsuSetting.EditorWaveformOpacity); waveformOpacity.BindValueChanged(_ => updateWaveformOpacity(), true); From e543db9bee68f8d733b62f24a4c4ad8f1c046e8f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Apr 2021 14:56:27 +0900 Subject: [PATCH 1480/1791] Use additive blending for background box Doesn't make a huge difference but this was intended. --- .../Compose/Components/Timeline/TimelineBlueprintContainer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index 7427473a35..7a3781a981 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -65,6 +65,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { Colour = Color4.Black, Depth = float.MaxValue, + Blending = BlendingParameters.Additive, }); } From 4538e4b50357ea2b0d4f113e55b6fa3d491c32f2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 14 Apr 2021 08:58:25 +0300 Subject: [PATCH 1481/1791] Remove wrong assert --- osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs index ed44bc7309..9a77292aff 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs @@ -93,7 +93,6 @@ namespace osu.Game.Rulesets.Osu.Tests public void TestTopLeftOrigin() { AddStep("load content", () => loadContent(false, () => new SkinProvidingContainer(new TopLeftCursorSkin()))); - AddAssert("cursor top left", () => lastContainer.ActiveCursor.Origin == Anchor.TopLeft); } private void loadContent(bool automated = true, Func skinProvider = null) From a2094159421cc98d47b4af23a53ae8c4358091fe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Apr 2021 16:52:29 +0900 Subject: [PATCH 1482/1791] Add "Barrel Roll" mod --- .../Mods/OsuModBarrelRoll.cs | 30 +++++++++++++++++++ osu.Game.Rulesets.Osu/OsuRuleset.cs | 1 + osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 3 ++ 3 files changed, 34 insertions(+) create mode 100644 osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs new file mode 100644 index 0000000000..1ba6c47f4c --- /dev/null +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs @@ -0,0 +1,30 @@ +// 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.Bindables; +using osu.Game.Configuration; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Osu.Mods +{ + public class OsuModBarrelRoll : Mod, IUpdatableByPlayfield + { + [SettingSource("Roll speed", "Speed at which things rotate")] + public BindableNumber SpinSpeed { get; } = new BindableDouble(1) + { + MinValue = 0.1, + MaxValue = 20, + Precision = 0.1, + }; + + public override string Name => "Barrel Roll"; + public override string Acronym => "BR"; + public override double ScoreMultiplier => 1; + + public void Update(Playfield playfield) + { + playfield.Rotation = (float)(playfield.Time.Current / 1000 * SpinSpeed.Value); + } + } +} diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index d6375fa6e3..465d6d7155 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -185,6 +185,7 @@ namespace osu.Game.Rulesets.Osu new MultiMod(new OsuModGrow(), new OsuModDeflate()), new MultiMod(new ModWindUp(), new ModWindDown()), new OsuModTraceable(), + new OsuModBarrelRoll(), }; case ModType.System: diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index b1069149f3..ea3eb5eb5c 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -42,6 +42,9 @@ namespace osu.Game.Rulesets.Osu.UI public OsuPlayfield() { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + InternalChildren = new Drawable[] { playfieldBorder = new PlayfieldBorder { RelativeSizeAxes = Axes.Both }, From 1aa36818df11577979863233dd0a149de582504b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 14 Apr 2021 17:47:11 +0900 Subject: [PATCH 1483/1791] Abstractify GameplayClockContainer --- .../TestSceneSpinnerRotation.cs | 3 +- .../TestSceneGameplayClockContainer.cs | 2 +- .../Gameplay/TestSceneStoryboardSamples.cs | 4 +- .../Visual/Gameplay/TestSceneSkipOverlay.cs | 4 +- .../Screens/Play/GameplayClockContainer.cs | 277 +++++++++--------- osu.Game/Screens/Play/Player.cs | 11 +- osu.Game/Screens/Play/SkipOverlay.cs | 2 +- osu.Game/Screens/Play/SpectatorPlayer.cs | 2 +- 8 files changed, 156 insertions(+), 149 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs index 14c709cae1..8ff21057b5 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs @@ -21,6 +21,7 @@ using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; +using osu.Game.Screens.Play; using osu.Game.Storyboards; using osu.Game.Tests.Visual; using osuTK; @@ -193,7 +194,7 @@ namespace osu.Game.Rulesets.Osu.Tests addSeekStep(0); - AddStep("adjust track rate", () => Player.GameplayClockContainer.UserPlaybackRate.Value = rate); + AddStep("adjust track rate", () => ((MasterGameplayClockContainer)Player.GameplayClockContainer).UserPlaybackRate.Value = rate); addSeekStep(1000); AddAssert("progress almost same", () => Precision.AlmostEquals(expectedProgress, drawableSpinner.Progress, 0.05)); diff --git a/osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs b/osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs index 891537c4ad..4d5dcabbba 100644 --- a/osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs +++ b/osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs @@ -22,7 +22,7 @@ namespace osu.Game.Tests.Gameplay var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); working.LoadTrack(); - Add(gcc = new GameplayClockContainer(working, 0)); + Add(gcc = new MasterGameplayClockContainer(working, 0)); }); AddStep("start track", () => gcc.Start()); diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs index cae5f20332..7394458482 100644 --- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs @@ -67,7 +67,7 @@ namespace osu.Game.Tests.Gameplay var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); working.LoadTrack(); - Add(gameplayContainer = new GameplayClockContainer(working, 0)); + Add(gameplayContainer = new MasterGameplayClockContainer(working, 0)); gameplayContainer.Add(sample = new DrawableStoryboardSample(new StoryboardSampleInfo(string.Empty, 0, 1)) { @@ -114,7 +114,7 @@ namespace osu.Game.Tests.Gameplay var beatmapSkinSourceContainer = new BeatmapSkinProvidingContainer(Beatmap.Value.Skin); - Add(gameplayContainer = new GameplayClockContainer(Beatmap.Value, 0) + Add(gameplayContainer = new MasterGameplayClockContainer(Beatmap.Value, 0) { Child = beatmapSkinSourceContainer }); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs index 841722a8f1..e08e03b789 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs @@ -33,7 +33,7 @@ namespace osu.Game.Tests.Visual.Gameplay var working = CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)); working.LoadTrack(); - Child = gameplayClockContainer = new GameplayClockContainer(working, 0) + Child = gameplayClockContainer = new MasterGameplayClockContainer(working, 0) { RelativeSizeAxes = Axes.Both, Children = new Drawable[] @@ -80,7 +80,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre)); AddStep("click", () => { - increment = skip_time - gameplayClock.CurrentTime - GameplayClockContainer.MINIMUM_SKIP_TIME / 2; + increment = skip_time - gameplayClock.CurrentTime - MasterGameplayClockContainer.MINIMUM_SKIP_TIME / 2; InputManager.Click(MouseButton.Left); }); AddStep("click", () => InputManager.Click(MouseButton.Left)); diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index ddbb087962..163ed9444f 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -3,9 +3,7 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Linq; -using System.Threading.Tasks; using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -19,27 +17,90 @@ using osu.Game.Configuration; namespace osu.Game.Screens.Play { - /// - /// Encapsulates gameplay timing logic and provides a for children. - /// - public class GameplayClockContainer : Container + public abstract class GameplayClockContainer : Container { - private readonly WorkingBeatmap beatmap; - - [NotNull] - private ITrack track; + /// + /// The final clock which is exposed to underlying components. + /// + public GameplayClock GameplayClock { get; private set; } public readonly BindableBool IsPaused = new BindableBool(); /// /// The decoupled clock used for gameplay. Should be used for seeks and clock control. /// - private readonly DecoupleableInterpolatingFramedClock adjustableClock; + protected readonly DecoupleableInterpolatingFramedClock AdjustableClock; - private readonly double gameplayStartTime; - private readonly bool startAtGameplayStart; + protected readonly IClock SourceClock; - private readonly double firstHitObjectTime; + protected GameplayClockContainer(IClock sourceClock) + { + SourceClock = sourceClock; + + RelativeSizeAxes = Axes.Both; + + AdjustableClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; + AdjustableClock.ChangeSource(SourceClock); + + IsPaused.BindValueChanged(OnPauseChanged); + } + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + + dependencies.CacheAs(GameplayClock = CreateGameplayClock(AdjustableClock)); + + return dependencies; + } + + public virtual void Start() + { + if (!AdjustableClock.IsRunning) + { + // Seeking the decoupled clock to its current time ensures that its source clock will be seeked to the same time + // This accounts for the clock source potentially taking time to enter a completely stopped state + Seek(GameplayClock.CurrentTime); + + AdjustableClock.Start(); + } + + IsPaused.Value = false; + } + + /// + /// Seek to a specific time in gameplay. + /// + /// Adjusts for any offsets which have been applied (so the seek may not be the expected point in time on the underlying audio track). + /// + /// + /// The destination time to seek to. + public virtual void Seek(double time) => AdjustableClock.Seek(time); + + public virtual void Stop() => IsPaused.Value = true; + + public virtual void Restart() + { + AdjustableClock.Seek(0); + AdjustableClock.Stop(); + + if (!IsPaused.Value) + Start(); + } + + protected abstract void OnPauseChanged(ValueChangedEvent isPaused); + + protected abstract GameplayClock CreateGameplayClock(IClock source); + } + + public class MasterGameplayClockContainer : GameplayClockContainer + { + /// + /// Duration before gameplay start time required before skip button displays. + /// + public const double MINIMUM_SKIP_TIME = 1000; + + protected new DecoupleableInterpolatingFramedClock SourceClock => (DecoupleableInterpolatingFramedClock)base.SourceClock; public readonly BindableNumber UserPlaybackRate = new BindableDouble(1) { @@ -49,73 +110,32 @@ namespace osu.Game.Screens.Play Precision = 0.1, }; - /// - /// The final clock which is exposed to underlying components. - /// - public GameplayClock GameplayClock => localGameplayClock; + private double totalOffset => userOffsetClock.Offset + platformOffsetClock.Offset; - [Cached(typeof(GameplayClock))] - private readonly LocalGameplayClock localGameplayClock; + private readonly BindableDouble pauseFreqAdjust = new BindableDouble(1); + private readonly WorkingBeatmap beatmap; + private readonly double gameplayStartTime; + private readonly bool startAtGameplayStart; + private readonly double firstHitObjectTime; + + private FramedOffsetClock userOffsetClock; + private FramedOffsetClock platformOffsetClock; + private LocalGameplayClock localGameplayClock; private Bindable userAudioOffset; - private readonly FramedOffsetClock userOffsetClock; - - private readonly FramedOffsetClock platformOffsetClock; - - /// - /// Creates a new . - /// - /// The beatmap being played. - /// The suggested time to start gameplay at. - /// - /// Whether should be used regardless of when storyboard events and hitobjects are supposed to start. - /// - public GameplayClockContainer(WorkingBeatmap beatmap, double gameplayStartTime, bool startAtGameplayStart = false) + public MasterGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStartTime, bool startAtGameplayStart = false) + : base(new DecoupleableInterpolatingFramedClock()) { this.beatmap = beatmap; this.gameplayStartTime = gameplayStartTime; this.startAtGameplayStart = startAtGameplayStart; - track = beatmap.Track; firstHitObjectTime = beatmap.Beatmap.HitObjects.First().StartTime; - RelativeSizeAxes = Axes.Both; - - adjustableClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; - - // Lazer's audio timings in general doesn't match stable. This is the result of user testing, albeit limited. - // This only seems to be required on windows. We need to eventually figure out why, with a bit of luck. - platformOffsetClock = new HardwareCorrectionOffsetClock(adjustableClock) { Offset = RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? 15 : 0 }; - - // the final usable gameplay clock with user-set offsets applied. - userOffsetClock = new HardwareCorrectionOffsetClock(platformOffsetClock); - - // the clock to be exposed via DI to children. - localGameplayClock = new LocalGameplayClock(userOffsetClock); - - GameplayClock.IsPaused.BindTo(IsPaused); - - IsPaused.BindValueChanged(onPauseChanged); + SourceClock.ChangeSource(beatmap.Track); } - private void onPauseChanged(ValueChangedEvent isPaused) - { - if (isPaused.NewValue) - this.TransformBindableTo(pauseFreqAdjust, 0, 200, Easing.Out).OnComplete(_ => adjustableClock.Stop()); - else - this.TransformBindableTo(pauseFreqAdjust, 1, 200, Easing.In); - } - - private double totalOffset => userOffsetClock.Offset + platformOffsetClock.Offset; - - /// - /// Duration before gameplay start time required before skip button displays. - /// - public const double MINIMUM_SKIP_TIME = 1000; - - private readonly BindableDouble pauseFreqAdjust = new BindableDouble(1); - [BackgroundDependencyLoader] private void load(OsuConfigManager config) { @@ -143,39 +163,31 @@ namespace osu.Game.Screens.Play Seek(startTime); - adjustableClock.ProcessFrame(); + AdjustableClock.ProcessFrame(); } - public void Restart() + protected override void OnPauseChanged(ValueChangedEvent isPaused) { - Task.Run(() => - { - track.Seek(0); - track.Stop(); - - Schedule(() => - { - adjustableClock.ChangeSource(track); - updateRate(); - - if (!IsPaused.Value) - Start(); - }); - }); + if (isPaused.NewValue) + this.TransformBindableTo(pauseFreqAdjust, 0, 200, Easing.Out).OnComplete(_ => AdjustableClock.Stop()); + else + this.TransformBindableTo(pauseFreqAdjust, 1, 200, Easing.In); } - public void Start() + public override void Seek(double time) { - if (!adjustableClock.IsRunning) - { - // Seeking the decoupled clock to its current time ensures that its source clock will be seeked to the same time - // This accounts for the audio clock source potentially taking time to enter a completely stopped state - Seek(GameplayClock.CurrentTime); + // remove the offset component here because most of the time we want the seek to be aligned to gameplay, not the audio track. + // we may want to consider reversing the application of offsets in the future as it may feel more correct. + base.Seek(time - totalOffset); - adjustableClock.Start(); - } + // manually process frame to ensure GameplayClock is correctly updated after a seek. + userOffsetClock.ProcessFrame(); + } - IsPaused.Value = false; + public override void Restart() + { + updateRate(); + base.Restart(); } /// @@ -195,26 +207,24 @@ namespace osu.Game.Screens.Play Seek(skipTarget); } - /// - /// Seek to a specific time in gameplay. - /// - /// Adjusts for any offsets which have been applied (so the seek may not be the expected point in time on the underlying audio track). - /// - /// - /// The destination time to seek to. - public void Seek(double time) + protected override GameplayClock CreateGameplayClock(IClock source) { - // remove the offset component here because most of the time we want the seek to be aligned to gameplay, not the audio track. - // we may want to consider reversing the application of offsets in the future as it may feel more correct. - adjustableClock.Seek(time - totalOffset); + // Lazer's audio timings in general doesn't match stable. This is the result of user testing, albeit limited. + // This only seems to be required on windows. We need to eventually figure out why, with a bit of luck. + platformOffsetClock = new HardwareCorrectionOffsetClock(source) { Offset = RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? 15 : 0 }; - // manually process frame to ensure GameplayClock is correctly updated after a seek. - userOffsetClock.ProcessFrame(); + // the final usable gameplay clock with user-set offsets applied. + userOffsetClock = new HardwareCorrectionOffsetClock(platformOffsetClock); + + return localGameplayClock = new LocalGameplayClock(userOffsetClock); } - public void Stop() + protected override void Update() { - IsPaused.Value = true; + if (!IsPaused.Value) + userOffsetClock.ProcessFrame(); + + base.Update(); } /// @@ -223,19 +233,7 @@ namespace osu.Game.Screens.Play public void StopUsingBeatmapClock() { removeSourceClockAdjustments(); - - track = new TrackVirtual(track.Length); - adjustableClock.ChangeSource(track); - } - - protected override void Update() - { - if (!IsPaused.Value) - { - userOffsetClock.ProcessFrame(); - } - - base.Update(); + SourceClock.ChangeSource(new TrackVirtual(beatmap.Track.Length)); } private bool speedAdjustmentsApplied; @@ -245,6 +243,8 @@ namespace osu.Game.Screens.Play if (speedAdjustmentsApplied) return; + var track = (Track)SourceClock.Source; + track.AddAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); track.AddAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); @@ -254,15 +254,12 @@ namespace osu.Game.Screens.Play speedAdjustmentsApplied = true; } - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - removeSourceClockAdjustments(); - } - private void removeSourceClockAdjustments() { - if (!speedAdjustmentsApplied) return; + if (!speedAdjustmentsApplied) + return; + + var track = (Track)SourceClock.Source; track.RemoveAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); track.RemoveAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); @@ -273,16 +270,10 @@ namespace osu.Game.Screens.Play speedAdjustmentsApplied = false; } - private class LocalGameplayClock : GameplayClock + protected override void Dispose(bool isDisposing) { - public readonly List> MutableNonGameplayAdjustments = new List>(); - - public override IEnumerable> NonGameplayAdjustments => MutableNonGameplayAdjustments; - - public LocalGameplayClock(FramedOffsetClock underlyingClock) - : base(underlyingClock) - { - } + base.Dispose(isDisposing); + removeSourceClockAdjustments(); } private class HardwareCorrectionOffsetClock : FramedOffsetClock @@ -296,5 +287,17 @@ namespace osu.Game.Screens.Play { } } + + private class LocalGameplayClock : GameplayClock + { + public readonly List> MutableNonGameplayAdjustments = new List>(); + + public override IEnumerable> NonGameplayAdjustments => MutableNonGameplayAdjustments; + + public LocalGameplayClock(FramedOffsetClock underlyingClock) + : base(underlyingClock) + { + } + } } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index efe5d26409..e07c40e8ff 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -295,7 +295,7 @@ namespace osu.Game.Screens.Play IsBreakTime.BindValueChanged(onBreakTimeChanged, true); } - protected virtual GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) => new GameplayClockContainer(beatmap, gameplayStart); + protected virtual GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) => new MasterGameplayClockContainer(beatmap, gameplayStart); private Drawable createUnderlayComponents() => DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard) { RelativeSizeAxes = Axes.Both }; @@ -342,7 +342,6 @@ namespace osu.Game.Screens.Play Action = () => PerformExit(true), IsPaused = { BindTarget = GameplayClockContainer.IsPaused } }, - PlayerSettingsOverlay = { PlaybackSettings = { UserPlaybackRate = { BindTarget = GameplayClockContainer.UserPlaybackRate } } }, KeyCounter = { AlwaysVisible = { BindTarget = DrawableRuleset.HasReplayLoaded }, @@ -386,6 +385,9 @@ namespace osu.Game.Screens.Play } }; + if (GameplayClockContainer is MasterGameplayClockContainer master) + HUDOverlay.PlayerSettingsOverlay.PlaybackSettings.UserPlaybackRate.BindTarget = master.UserPlaybackRate; + if (!Configuration.AllowSkippingIntro) skipOverlay.Expire(); @@ -533,7 +535,8 @@ namespace osu.Game.Screens.Play // user requested skip // disable sample playback to stop currently playing samples and perform skip samplePlaybackDisabled.Value = true; - GameplayClockContainer.Skip(); + + (GameplayClockContainer as MasterGameplayClockContainer)?.Skip(); // return samplePlaybackDisabled.Value to what is defined by the beatmap's current state updateSampleDisabledState(); @@ -832,7 +835,7 @@ namespace osu.Game.Screens.Play // GameplayClockContainer performs seeks / start / stop operations on the beatmap's track. // as we are no longer the current screen, we cannot guarantee the track is still usable. - GameplayClockContainer?.StopUsingBeatmapClock(); + (GameplayClockContainer as MasterGameplayClockContainer)?.StopUsingBeatmapClock(); musicController.ResetTrackAdjustments(); diff --git a/osu.Game/Screens/Play/SkipOverlay.cs b/osu.Game/Screens/Play/SkipOverlay.cs index 3f214e49d9..ddb78dfb67 100644 --- a/osu.Game/Screens/Play/SkipOverlay.cs +++ b/osu.Game/Screens/Play/SkipOverlay.cs @@ -90,7 +90,7 @@ namespace osu.Game.Screens.Play private const double fade_time = 300; - private double fadeOutBeginTime => startTime - GameplayClockContainer.MINIMUM_SKIP_TIME; + private double fadeOutBeginTime => startTime - MasterGameplayClockContainer.MINIMUM_SKIP_TIME; protected override void LoadComplete() { diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index fdf996150f..9822f62dd8 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -61,7 +61,7 @@ namespace osu.Game.Screens.Play if (firstFrameTime == null || firstFrameTime <= gameplayStart + 5000) return base.CreateGameplayClockContainer(beatmap, gameplayStart); - return new GameplayClockContainer(beatmap, firstFrameTime.Value, true); + return new MasterGameplayClockContainer(beatmap, firstFrameTime.Value, true); } public override bool OnExiting(IScreen next) From a314f90d3732a0852edcd89fc8c1970012a6071f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Apr 2021 16:00:49 +0900 Subject: [PATCH 1484/1791] Allow timeline to govern the size of the rest of the editor content --- .../Compose/Components/Timeline/Timeline.cs | 5 + .../Components/Timeline/TimelineArea.cs | 11 +- .../Screens/Edit/EditorScreenWithTimeline.cs | 117 +++++++++++------- 3 files changed, 82 insertions(+), 51 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index aa5cc46e37..d06f977fc9 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -56,8 +56,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private Track track; + private const float timeline_height = 90; + public Timeline() { + RelativeSizeAxes = Axes.X; + Height = timeline_height; + ZoomDuration = 200; ZoomEasing = Easing.OutQuint; ScrollbarVisible = false; diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs index 0ec48e04c6..f144fd3a65 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs @@ -14,7 +14,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { public class TimelineArea : Container { - public readonly Timeline Timeline = new Timeline { RelativeSizeAxes = Axes.Both }; + public readonly Timeline Timeline = new Timeline(); protected override Container Content => Timeline; @@ -37,7 +37,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline }, new GridContainer { - RelativeSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, Content = new[] { new Drawable[] @@ -126,11 +127,15 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Timeline }, }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + }, ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize), new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.Distributed), + new Dimension(), } } }; diff --git a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs index 2d623a200c..b4b3aafc68 100644 --- a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs +++ b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs @@ -19,8 +19,6 @@ namespace osu.Game.Screens.Edit private const float vertical_margins = 10; private const float horizontal_margins = 20; - private const float timeline_height = 110; - private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor(); private Container timelineContainer; @@ -40,64 +38,86 @@ namespace osu.Game.Screens.Edit if (beatDivisor != null) this.beatDivisor.BindTo(beatDivisor); - Children = new Drawable[] + Child = new GridContainer { - mainContent = new Container + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] { - Name = "Main content", - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding - { - Horizontal = horizontal_margins, - Top = vertical_margins + timeline_height, - Bottom = vertical_margins - }, - Child = spinner = new LoadingSpinner(true) - { - State = { Value = Visibility.Visible }, - }, + new Dimension(GridSizeMode.AutoSize), + new Dimension(), }, - new Container + Content = new[] { - Name = "Timeline", - RelativeSizeAxes = Axes.X, - Height = timeline_height, - Children = new Drawable[] + new Drawable[] { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(0.5f) - }, new Container { - Name = "Timeline content", - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Horizontal = horizontal_margins, Vertical = vertical_margins }, - Child = new GridContainer + Name = "Timeline", + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Content = new[] + new Box { - new Drawable[] - { - timelineContainer = new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Right = 5 }, - }, - new BeatDivisorControl(beatDivisor) { RelativeSizeAxes = Axes.Both } - }, + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(0.5f) }, - ColumnDimensions = new[] + new Container { - new Dimension(), - new Dimension(GridSizeMode.Absolute, 90), + Name = "Timeline content", + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Horizontal = horizontal_margins, Vertical = vertical_margins }, + Child = new GridContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Content = new[] + { + new Drawable[] + { + timelineContainer = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Right = 5 }, + }, + new BeatDivisorControl(beatDivisor) { RelativeSizeAxes = Axes.Both } + }, + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + }, + ColumnDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.Absolute, 90), + } + }, } + } + }, + }, + new Drawable[] + { + mainContent = new Container + { + Name = "Main content", + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding + { + Horizontal = horizontal_margins, + Top = vertical_margins, + Bottom = vertical_margins }, - } - } - }, + Child = spinner = new LoadingSpinner(true) + { + State = { Value = Visibility.Visible }, + }, + }, + }, + } }; } @@ -114,7 +134,8 @@ namespace osu.Game.Screens.Edit LoadComponentAsync(new TimelineArea { - RelativeSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, Children = new[] { CreateTimelineContent(), From 26110cd777e3c4880adc2a10c2bb49e1b64c7947 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Apr 2021 18:15:11 +0900 Subject: [PATCH 1485/1791] Fix timeline not receiving input (being eaten by composer) --- osu.Game/Screens/Edit/EditorScreenWithTimeline.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs index b4b3aafc68..26083e6a82 100644 --- a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs +++ b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs @@ -105,6 +105,7 @@ namespace osu.Game.Screens.Edit { Name = "Main content", RelativeSizeAxes = Axes.Both, + Depth = float.MaxValue, Padding = new MarginPadding { Horizontal = horizontal_margins, From 2935f87e7082bd603c9a09aecda6ba51628a184b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 14 Apr 2021 18:29:34 +0900 Subject: [PATCH 1486/1791] Fix IsPaused not being bound --- osu.Game/Screens/Play/GameplayClockContainer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 163ed9444f..a97a87d73f 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -50,6 +50,7 @@ namespace osu.Game.Screens.Play var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); dependencies.CacheAs(GameplayClock = CreateGameplayClock(AdjustableClock)); + GameplayClock.IsPaused.BindTo(IsPaused); return dependencies; } From b53b30c1a97a71822359016e87d1334ce8097476 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 14 Apr 2021 19:32:48 +0900 Subject: [PATCH 1487/1791] Fix incorrect offset due to another intermediate Decoupleable clock --- .../Screens/Play/GameplayClockContainer.cs | 26 ++++++------------- 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index a97a87d73f..36ca7415d0 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -31,16 +31,12 @@ namespace osu.Game.Screens.Play /// protected readonly DecoupleableInterpolatingFramedClock AdjustableClock; - protected readonly IClock SourceClock; - protected GameplayClockContainer(IClock sourceClock) { - SourceClock = sourceClock; - RelativeSizeAxes = Axes.Both; AdjustableClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; - AdjustableClock.ChangeSource(SourceClock); + AdjustableClock.ChangeSource(sourceClock); IsPaused.BindValueChanged(OnPauseChanged); } @@ -101,7 +97,7 @@ namespace osu.Game.Screens.Play /// public const double MINIMUM_SKIP_TIME = 1000; - protected new DecoupleableInterpolatingFramedClock SourceClock => (DecoupleableInterpolatingFramedClock)base.SourceClock; + protected Track Track => (Track)AdjustableClock.Source; public readonly BindableNumber UserPlaybackRate = new BindableDouble(1) { @@ -126,15 +122,13 @@ namespace osu.Game.Screens.Play private Bindable userAudioOffset; public MasterGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStartTime, bool startAtGameplayStart = false) - : base(new DecoupleableInterpolatingFramedClock()) + : base(beatmap.Track) { this.beatmap = beatmap; this.gameplayStartTime = gameplayStartTime; this.startAtGameplayStart = startAtGameplayStart; firstHitObjectTime = beatmap.Beatmap.HitObjects.First().StartTime; - - SourceClock.ChangeSource(beatmap.Track); } [BackgroundDependencyLoader] @@ -234,7 +228,7 @@ namespace osu.Game.Screens.Play public void StopUsingBeatmapClock() { removeSourceClockAdjustments(); - SourceClock.ChangeSource(new TrackVirtual(beatmap.Track.Length)); + AdjustableClock.ChangeSource(new TrackVirtual(beatmap.Track.Length)); } private bool speedAdjustmentsApplied; @@ -244,10 +238,8 @@ namespace osu.Game.Screens.Play if (speedAdjustmentsApplied) return; - var track = (Track)SourceClock.Source; - - track.AddAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); - track.AddAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); + Track.AddAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); + Track.AddAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); localGameplayClock.MutableNonGameplayAdjustments.Add(pauseFreqAdjust); localGameplayClock.MutableNonGameplayAdjustments.Add(UserPlaybackRate); @@ -260,10 +252,8 @@ namespace osu.Game.Screens.Play if (!speedAdjustmentsApplied) return; - var track = (Track)SourceClock.Source; - - track.RemoveAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); - track.RemoveAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); + Track.RemoveAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); + Track.RemoveAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); localGameplayClock.MutableNonGameplayAdjustments.Remove(pauseFreqAdjust); localGameplayClock.MutableNonGameplayAdjustments.Remove(UserPlaybackRate); From 18c69cdaf7bb536d22c704171ba9bd03c00689cd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 14 Apr 2021 19:50:22 +0900 Subject: [PATCH 1488/1791] Split out files --- .../Screens/Play/GameplayClockContainer.cs | 214 +---------------- .../Play/MasterGameplayClockContainer.cs | 220 ++++++++++++++++++ 2 files changed, 222 insertions(+), 212 deletions(-) create mode 100644 osu.Game/Screens/Play/MasterGameplayClockContainer.cs diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 36ca7415d0..d8e6fda87e 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -1,19 +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 System; -using System.Collections.Generic; -using System.Linq; -using osu.Framework; using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; -using osu.Game.Beatmaps; -using osu.Game.Configuration; namespace osu.Game.Screens.Play { @@ -38,7 +30,7 @@ namespace osu.Game.Screens.Play AdjustableClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; AdjustableClock.ChangeSource(sourceClock); - IsPaused.BindValueChanged(OnPauseChanged); + IsPaused.BindValueChanged(OnIsPausedChanged); } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) @@ -85,210 +77,8 @@ namespace osu.Game.Screens.Play Start(); } - protected abstract void OnPauseChanged(ValueChangedEvent isPaused); + protected abstract void OnIsPausedChanged(ValueChangedEvent isPaused); protected abstract GameplayClock CreateGameplayClock(IClock source); } - - public class MasterGameplayClockContainer : GameplayClockContainer - { - /// - /// Duration before gameplay start time required before skip button displays. - /// - public const double MINIMUM_SKIP_TIME = 1000; - - protected Track Track => (Track)AdjustableClock.Source; - - public readonly BindableNumber UserPlaybackRate = new BindableDouble(1) - { - Default = 1, - MinValue = 0.5, - MaxValue = 2, - Precision = 0.1, - }; - - private double totalOffset => userOffsetClock.Offset + platformOffsetClock.Offset; - - private readonly BindableDouble pauseFreqAdjust = new BindableDouble(1); - - private readonly WorkingBeatmap beatmap; - private readonly double gameplayStartTime; - private readonly bool startAtGameplayStart; - private readonly double firstHitObjectTime; - - private FramedOffsetClock userOffsetClock; - private FramedOffsetClock platformOffsetClock; - private LocalGameplayClock localGameplayClock; - private Bindable userAudioOffset; - - public MasterGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStartTime, bool startAtGameplayStart = false) - : base(beatmap.Track) - { - this.beatmap = beatmap; - this.gameplayStartTime = gameplayStartTime; - this.startAtGameplayStart = startAtGameplayStart; - - firstHitObjectTime = beatmap.Beatmap.HitObjects.First().StartTime; - } - - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) - { - userAudioOffset = config.GetBindable(OsuSetting.AudioOffset); - userAudioOffset.BindValueChanged(offset => userOffsetClock.Offset = offset.NewValue, true); - - // sane default provided by ruleset. - double startTime = gameplayStartTime; - - if (!startAtGameplayStart) - { - startTime = Math.Min(0, startTime); - - // if a storyboard is present, it may dictate the appropriate start time by having events in negative time space. - // this is commonly used to display an intro before the audio track start. - double? firstStoryboardEvent = beatmap.Storyboard.EarliestEventTime; - if (firstStoryboardEvent != null) - startTime = Math.Min(startTime, firstStoryboardEvent.Value); - - // some beatmaps specify a current lead-in time which should be used instead of the ruleset-provided value when available. - // this is not available as an option in the live editor but can still be applied via .osu editing. - if (beatmap.BeatmapInfo.AudioLeadIn > 0) - startTime = Math.Min(startTime, firstHitObjectTime - beatmap.BeatmapInfo.AudioLeadIn); - } - - Seek(startTime); - - AdjustableClock.ProcessFrame(); - } - - protected override void OnPauseChanged(ValueChangedEvent isPaused) - { - if (isPaused.NewValue) - this.TransformBindableTo(pauseFreqAdjust, 0, 200, Easing.Out).OnComplete(_ => AdjustableClock.Stop()); - else - this.TransformBindableTo(pauseFreqAdjust, 1, 200, Easing.In); - } - - public override void Seek(double time) - { - // remove the offset component here because most of the time we want the seek to be aligned to gameplay, not the audio track. - // we may want to consider reversing the application of offsets in the future as it may feel more correct. - base.Seek(time - totalOffset); - - // manually process frame to ensure GameplayClock is correctly updated after a seek. - userOffsetClock.ProcessFrame(); - } - - public override void Restart() - { - updateRate(); - base.Restart(); - } - - /// - /// Skip forward to the next valid skip point. - /// - public void Skip() - { - if (GameplayClock.CurrentTime > gameplayStartTime - MINIMUM_SKIP_TIME) - return; - - double skipTarget = gameplayStartTime - MINIMUM_SKIP_TIME; - - if (GameplayClock.CurrentTime < 0 && skipTarget > 6000) - // double skip exception for storyboards with very long intros - skipTarget = 0; - - Seek(skipTarget); - } - - protected override GameplayClock CreateGameplayClock(IClock source) - { - // Lazer's audio timings in general doesn't match stable. This is the result of user testing, albeit limited. - // This only seems to be required on windows. We need to eventually figure out why, with a bit of luck. - platformOffsetClock = new HardwareCorrectionOffsetClock(source) { Offset = RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? 15 : 0 }; - - // the final usable gameplay clock with user-set offsets applied. - userOffsetClock = new HardwareCorrectionOffsetClock(platformOffsetClock); - - return localGameplayClock = new LocalGameplayClock(userOffsetClock); - } - - protected override void Update() - { - if (!IsPaused.Value) - userOffsetClock.ProcessFrame(); - - base.Update(); - } - - /// - /// Changes the backing clock to avoid using the originally provided track. - /// - public void StopUsingBeatmapClock() - { - removeSourceClockAdjustments(); - AdjustableClock.ChangeSource(new TrackVirtual(beatmap.Track.Length)); - } - - private bool speedAdjustmentsApplied; - - private void updateRate() - { - if (speedAdjustmentsApplied) - return; - - Track.AddAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); - Track.AddAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); - - localGameplayClock.MutableNonGameplayAdjustments.Add(pauseFreqAdjust); - localGameplayClock.MutableNonGameplayAdjustments.Add(UserPlaybackRate); - - speedAdjustmentsApplied = true; - } - - private void removeSourceClockAdjustments() - { - if (!speedAdjustmentsApplied) - return; - - Track.RemoveAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); - Track.RemoveAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); - - localGameplayClock.MutableNonGameplayAdjustments.Remove(pauseFreqAdjust); - localGameplayClock.MutableNonGameplayAdjustments.Remove(UserPlaybackRate); - - speedAdjustmentsApplied = false; - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - removeSourceClockAdjustments(); - } - - private class HardwareCorrectionOffsetClock : FramedOffsetClock - { - // we always want to apply the same real-time offset, so it should be adjusted by the difference in playback rate (from realtime) to achieve this. - // base implementation already adds offset at 1.0 rate, so we only add the difference from that here. - public override double CurrentTime => base.CurrentTime + Offset * (Rate - 1); - - public HardwareCorrectionOffsetClock(IClock source, bool processSource = true) - : base(source, processSource) - { - } - } - - private class LocalGameplayClock : GameplayClock - { - public readonly List> MutableNonGameplayAdjustments = new List>(); - - public override IEnumerable> NonGameplayAdjustments => MutableNonGameplayAdjustments; - - public LocalGameplayClock(FramedOffsetClock underlyingClock) - : base(underlyingClock) - { - } - } - } } diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs new file mode 100644 index 0000000000..efc8ca732e --- /dev/null +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -0,0 +1,220 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Track; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Configuration; + +namespace osu.Game.Screens.Play +{ + public class MasterGameplayClockContainer : GameplayClockContainer + { + /// + /// Duration before gameplay start time required before skip button displays. + /// + public const double MINIMUM_SKIP_TIME = 1000; + + protected Track Track => (Track)AdjustableClock.Source; + + public readonly BindableNumber UserPlaybackRate = new BindableDouble(1) + { + Default = 1, + MinValue = 0.5, + MaxValue = 2, + Precision = 0.1, + }; + + private double totalOffset => userOffsetClock.Offset + platformOffsetClock.Offset; + + private readonly BindableDouble pauseFreqAdjust = new BindableDouble(1); + + private readonly WorkingBeatmap beatmap; + private readonly double gameplayStartTime; + private readonly bool startAtGameplayStart; + private readonly double firstHitObjectTime; + + private FramedOffsetClock userOffsetClock; + private FramedOffsetClock platformOffsetClock; + private LocalGameplayClock localGameplayClock; + private Bindable userAudioOffset; + + public MasterGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStartTime, bool startAtGameplayStart = false) + : base(beatmap.Track) + { + this.beatmap = beatmap; + this.gameplayStartTime = gameplayStartTime; + this.startAtGameplayStart = startAtGameplayStart; + + firstHitObjectTime = beatmap.Beatmap.HitObjects.First().StartTime; + } + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + userAudioOffset = config.GetBindable(OsuSetting.AudioOffset); + userAudioOffset.BindValueChanged(offset => userOffsetClock.Offset = offset.NewValue, true); + + // sane default provided by ruleset. + double startTime = gameplayStartTime; + + if (!startAtGameplayStart) + { + startTime = Math.Min(0, startTime); + + // if a storyboard is present, it may dictate the appropriate start time by having events in negative time space. + // this is commonly used to display an intro before the audio track start. + double? firstStoryboardEvent = beatmap.Storyboard.EarliestEventTime; + if (firstStoryboardEvent != null) + startTime = Math.Min(startTime, firstStoryboardEvent.Value); + + // some beatmaps specify a current lead-in time which should be used instead of the ruleset-provided value when available. + // this is not available as an option in the live editor but can still be applied via .osu editing. + if (beatmap.BeatmapInfo.AudioLeadIn > 0) + startTime = Math.Min(startTime, firstHitObjectTime - beatmap.BeatmapInfo.AudioLeadIn); + } + + Seek(startTime); + + AdjustableClock.ProcessFrame(); + } + + protected override void OnIsPausedChanged(ValueChangedEvent isPaused) + { + if (isPaused.NewValue) + this.TransformBindableTo(pauseFreqAdjust, 0, 200, Easing.Out).OnComplete(_ => AdjustableClock.Stop()); + else + this.TransformBindableTo(pauseFreqAdjust, 1, 200, Easing.In); + } + + public override void Seek(double time) + { + // remove the offset component here because most of the time we want the seek to be aligned to gameplay, not the audio track. + // we may want to consider reversing the application of offsets in the future as it may feel more correct. + base.Seek(time - totalOffset); + + // manually process frame to ensure GameplayClock is correctly updated after a seek. + userOffsetClock.ProcessFrame(); + } + + public override void Restart() + { + updateRate(); + base.Restart(); + } + + /// + /// Skip forward to the next valid skip point. + /// + public void Skip() + { + if (GameplayClock.CurrentTime > gameplayStartTime - MINIMUM_SKIP_TIME) + return; + + double skipTarget = gameplayStartTime - MINIMUM_SKIP_TIME; + + if (GameplayClock.CurrentTime < 0 && skipTarget > 6000) + // double skip exception for storyboards with very long intros + skipTarget = 0; + + Seek(skipTarget); + } + + protected override GameplayClock CreateGameplayClock(IClock source) + { + // Lazer's audio timings in general doesn't match stable. This is the result of user testing, albeit limited. + // This only seems to be required on windows. We need to eventually figure out why, with a bit of luck. + platformOffsetClock = new HardwareCorrectionOffsetClock(source) { Offset = RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? 15 : 0 }; + + // the final usable gameplay clock with user-set offsets applied. + userOffsetClock = new HardwareCorrectionOffsetClock(platformOffsetClock); + + return localGameplayClock = new LocalGameplayClock(userOffsetClock); + } + + protected override void Update() + { + if (!IsPaused.Value) + userOffsetClock.ProcessFrame(); + + base.Update(); + } + + /// + /// Changes the backing clock to avoid using the originally provided track. + /// + public void StopUsingBeatmapClock() + { + removeSourceClockAdjustments(); + AdjustableClock.ChangeSource(new TrackVirtual(beatmap.Track.Length)); + } + + private bool speedAdjustmentsApplied; + + private void updateRate() + { + if (speedAdjustmentsApplied) + return; + + Track.AddAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); + Track.AddAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); + + localGameplayClock.MutableNonGameplayAdjustments.Add(pauseFreqAdjust); + localGameplayClock.MutableNonGameplayAdjustments.Add(UserPlaybackRate); + + speedAdjustmentsApplied = true; + } + + private void removeSourceClockAdjustments() + { + if (!speedAdjustmentsApplied) + return; + + Track.RemoveAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); + Track.RemoveAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); + + localGameplayClock.MutableNonGameplayAdjustments.Remove(pauseFreqAdjust); + localGameplayClock.MutableNonGameplayAdjustments.Remove(UserPlaybackRate); + + speedAdjustmentsApplied = false; + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + removeSourceClockAdjustments(); + } + + private class HardwareCorrectionOffsetClock : FramedOffsetClock + { + // we always want to apply the same real-time offset, so it should be adjusted by the difference in playback rate (from realtime) to achieve this. + // base implementation already adds offset at 1.0 rate, so we only add the difference from that here. + public override double CurrentTime => base.CurrentTime + Offset * (Rate - 1); + + public HardwareCorrectionOffsetClock(IClock source, bool processSource = true) + : base(source, processSource) + { + } + } + + private class LocalGameplayClock : GameplayClock + { + public readonly List> MutableNonGameplayAdjustments = new List>(); + + public override IEnumerable> NonGameplayAdjustments => MutableNonGameplayAdjustments; + + public LocalGameplayClock(FramedOffsetClock underlyingClock) + : base(underlyingClock) + { + } + } + } +} From ff2a37b7f492b16f135c026865e410b55c1095c7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Apr 2021 19:38:25 +0900 Subject: [PATCH 1489/1791] Add new colours for editor designs --- osu.Game/Graphics/OsuColour.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index 466d59b08b..c3b9b6006c 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -186,6 +186,13 @@ namespace osu.Game.Graphics public readonly Color4 GrayE = Color4Extensions.FromHex(@"eee"); public readonly Color4 GrayF = Color4Extensions.FromHex(@"fff"); + // in latest editor design logic, need to figure out where these sit... + public readonly Color4 Lime1 = Color4Extensions.FromHex(@"b2ff66"); + public readonly Color4 Orange1 = Color4Extensions.FromHex(@"ffd966"); + + // Content Background + public readonly Color4 B5 = Color4Extensions.FromHex(@"222a28"); + public readonly Color4 RedLighter = Color4Extensions.FromHex(@"ffeded"); public readonly Color4 RedLight = Color4Extensions.FromHex(@"ed7787"); public readonly Color4 Red = Color4Extensions.FromHex(@"ed1121"); From 1209c9fa32e0c0c98497748bb54f39760007408c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Apr 2021 19:39:12 +0900 Subject: [PATCH 1490/1791] Allow timeline to expand in height when control points are to be displayed --- .../Compose/Components/Timeline/Timeline.cs | 39 ++++++++++++++++--- .../Timeline/TimelineControlPointDisplay.cs | 6 --- 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index d06f977fc9..bd7b426044 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -57,11 +57,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private Track track; private const float timeline_height = 90; + private const float timeline_expanded_height = 180; public Timeline() { RelativeSizeAxes = Axes.X; - Height = timeline_height; ZoomDuration = 200; ZoomEasing = Easing.OutQuint; @@ -86,9 +86,17 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline AddRange(new Drawable[] { + controlPoints = new TimelineControlPointDisplay + { + RelativeSizeAxes = Axes.X, + Height = timeline_expanded_height, + }, new Container { - RelativeSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.X, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Height = timeline_height, Depth = float.MaxValue, Children = new[] { @@ -102,7 +110,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline }, centreMarker.CreateProxy(), ticks = new TimelineTickDisplay(), - controlPoints = new TimelineControlPointDisplay(), new Box { Name = "zero marker", @@ -116,13 +123,35 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline }); waveformOpacity = config.GetBindable(OsuSetting.EditorWaveformOpacity); + Beatmap.BindTo(beatmap); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + waveformOpacity.BindValueChanged(_ => updateWaveformOpacity(), true); WaveformVisible.ValueChanged += _ => updateWaveformOpacity(); - ControlPointsVisible.ValueChanged += visible => controlPoints.FadeTo(visible.NewValue ? 1 : 0, 200, Easing.OutQuint); TicksVisible.ValueChanged += visible => ticks.FadeTo(visible.NewValue ? 1 : 0, 200, Easing.OutQuint); + ControlPointsVisible.BindValueChanged(visible => + { + if (visible.NewValue) + { + this.ResizeHeightTo(timeline_expanded_height, 200, Easing.OutQuint); + + // delay the fade in else masking looks weird. + controlPoints.Delay(180).FadeIn(400, Easing.OutQuint); + } + else + { + controlPoints.FadeOut(200, Easing.OutQuint); + + // likewise, delay the resize until the fade is complete. + this.Delay(180).ResizeHeightTo(timeline_height, 200, Easing.OutQuint); + } + }, true); - Beatmap.BindTo(beatmap); Beatmap.BindValueChanged(b => { waveform.Waveform = b.NewValue.Waveform; diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs index 18600bcdee..8520567fa9 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs @@ -4,7 +4,6 @@ using System.Collections.Specialized; using System.Linq; using osu.Framework.Bindables; -using osu.Framework.Graphics; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts; @@ -17,11 +16,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { private readonly IBindableList controlPointGroups = new BindableList(); - public TimelineControlPointDisplay() - { - RelativeSizeAxes = Axes.Both; - } - protected override void LoadBeatmap(EditorBeatmap beatmap) { base.LoadBeatmap(beatmap); From a8df2388eb184c8abb47f4e841a954587d4fef25 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Apr 2021 19:39:27 +0900 Subject: [PATCH 1491/1791] Update design for TimingControlPoint --- .../ControlPoints/TimingControlPoint.cs | 2 +- .../Timeline/TimelineControlPointGroup.cs | 2 ++ .../Components/Timeline/TimingPointPiece.cs | 20 +++++++++---------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index 580642f593..ec20328fab 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -20,7 +20,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// private const double default_beat_length = 60000.0 / 60.0; - public override Color4 GetRepresentingColour(OsuColour colours) => colours.YellowDark; + public override Color4 GetRepresentingColour(OsuColour colours) => colours.Orange1; public static readonly TimingControlPoint DEFAULT = new TimingControlPoint { diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs index fb69f16792..c4beb40f92 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs @@ -27,6 +27,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline RelativeSizeAxes = Axes.Y; AutoSizeAxes = Axes.X; + Origin = Anchor.TopCentre; + X = (float)group.Time; } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimingPointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimingPointPiece.cs index ba94916458..cd1470aa0a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimingPointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimingPointPiece.cs @@ -3,15 +3,12 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; -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.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osuTK.Graphics; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { @@ -31,26 +28,27 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [BackgroundDependencyLoader] private void load(OsuColour colours) { - Origin = Anchor.CentreLeft; - Anchor = Anchor.CentreLeft; + Margin = new MarginPadding { Vertical = 10 }; + + const float corner_radius = 5; AutoSizeAxes = Axes.Both; - - Color4 colour = point.GetRepresentingColour(colours); + Masking = true; + CornerRadius = corner_radius; InternalChildren = new Drawable[] { new Box { - Alpha = 0.9f, - Colour = ColourInfo.GradientHorizontal(colour, colour.Opacity(0.5f)), + Colour = point.GetRepresentingColour(colours), RelativeSizeAxes = Axes.Both, }, bpmText = new OsuSpriteText { Alpha = 0.9f, - Padding = new MarginPadding(3), - Font = OsuFont.Default.With(size: 40) + Padding = new MarginPadding { Vertical = 3, Horizontal = 6 }, + Font = OsuFont.Default.With(size: 20, weight: FontWeight.SemiBold), + Colour = colours.B5, } }; From f9b1b7fe255370fb502aebaea0fb5c5327e569b1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Apr 2021 20:10:58 +0900 Subject: [PATCH 1492/1791] Update SamplePointPiece design --- .../Components/Timeline/SamplePointPiece.cs | 61 ++++++++++--------- 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 0f11fb1126..9461f5e885 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -3,9 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; -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.Game.Beatmaps.ControlPoints; @@ -23,7 +21,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private readonly BindableNumber volume; private OsuSpriteText text; - private Box volumeBox; + private Container volumeBox; + + private const int max_volume_height = 22; public SamplePointPiece(SampleControlPoint samplePoint) { @@ -35,8 +35,10 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [BackgroundDependencyLoader] private void load(OsuColour colours) { - Origin = Anchor.TopLeft; - Anchor = Anchor.TopLeft; + Margin = new MarginPadding { Vertical = 5 }; + + Origin = Anchor.BottomCentre; + Anchor = Anchor.BottomCentre; AutoSizeAxes = Axes.X; RelativeSizeAxes = Axes.Y; @@ -45,40 +47,43 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline InternalChildren = new Drawable[] { + volumeBox = new Circle + { + CornerRadius = 5, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Y = -20, + Width = 10, + Colour = colour, + }, new Container { - RelativeSizeAxes = Axes.Y, - Width = 20, + AutoSizeAxes = Axes.X, + Height = 16, + Masking = true, + CornerRadius = 8, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, Children = new Drawable[] { - volumeBox = new Box - { - X = 2, - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Colour = ColourInfo.GradientVertical(colour, Color4.Black), - RelativeSizeAxes = Axes.Both, - }, new Box { - Colour = colour.Lighten(0.2f), - Width = 2, - RelativeSizeAxes = Axes.Y, + Colour = colour, + RelativeSizeAxes = Axes.Both, }, + text = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Padding = new MarginPadding(5), + Font = OsuFont.Default.With(size: 12, weight: FontWeight.SemiBold), + Colour = colours.B5, + } } }, - text = new OsuSpriteText - { - X = 2, - Y = -5, - Anchor = Anchor.BottomLeft, - Alpha = 0.9f, - Rotation = -90, - Font = OsuFont.Default.With(weight: FontWeight.SemiBold) - } }; - volume.BindValueChanged(volume => volumeBox.Height = volume.NewValue / 100f, true); + volume.BindValueChanged(volume => volumeBox.Height = max_volume_height * volume.NewValue / 100f, true); bank.BindValueChanged(bank => text.Text = bank.NewValue, true); } } From 99f05253fd2f0cc7634e06571666af28b24b3e2a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Apr 2021 20:11:16 +0900 Subject: [PATCH 1493/1791] Adjust timeline sizing to closer match designs (but not 1:1 yet) --- .../Edit/Compose/Components/Timeline/Timeline.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index bd7b426044..d688ad511f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -56,8 +56,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private Track track; - private const float timeline_height = 90; - private const float timeline_expanded_height = 180; + private const float timeline_height = 72; + private const float timeline_expanded_height = 150; public Timeline() { @@ -74,6 +74,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private TimelineControlPointDisplay controlPoints; + private Container mainContent; + private Bindable waveformOpacity; [BackgroundDependencyLoader] @@ -91,11 +93,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline RelativeSizeAxes = Axes.X, Height = timeline_expanded_height, }, - new Container + mainContent = new Container { RelativeSizeAxes = Axes.X, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, Height = timeline_height, Depth = float.MaxValue, Children = new[] @@ -139,6 +139,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (visible.NewValue) { this.ResizeHeightTo(timeline_expanded_height, 200, Easing.OutQuint); + mainContent.MoveToY(36, 200, Easing.OutQuint); // delay the fade in else masking looks weird. controlPoints.Delay(180).FadeIn(400, Easing.OutQuint); @@ -149,6 +150,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline // likewise, delay the resize until the fade is complete. this.Delay(180).ResizeHeightTo(timeline_height, 200, Easing.OutQuint); + mainContent.Delay(180).MoveToY(0, 200, Easing.OutQuint); } }, true); From afbb674e526a12148e4c328e5b5a5618720fe081 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Apr 2021 20:11:32 +0900 Subject: [PATCH 1494/1791] TopLeft align check buttons so they don't move while interacting with them --- .../Screens/Edit/Compose/Components/Timeline/TimelineArea.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs index f144fd3a65..ee3543354f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs @@ -56,11 +56,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline }, new FillFlowContainer { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, AutoSizeAxes = Axes.Y, Width = 160, - Padding = new MarginPadding { Horizontal = 10 }, + Padding = new MarginPadding(10), Direction = FillDirection.Vertical, Spacing = new Vector2(0, 4), Children = new[] From be08b9d1eff63dd7ec47c9e26323b90c3e63d208 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Apr 2021 20:54:29 +0900 Subject: [PATCH 1495/1791] Combine logic of Difficulty and Timing pieces where feasible --- .../ControlPoints/DifficultyControlPoint.cs | 2 +- .../Timeline/DifficultyPointPiece.cs | 58 +++---------------- .../Components/Timeline/TimingPointPiece.cs | 37 +----------- .../Components/Timeline/TopPointPiece.cs | 55 ++++++++++++++++++ 4 files changed, 68 insertions(+), 84 deletions(-) create mode 100644 osu.Game/Screens/Edit/Compose/Components/Timeline/TopPointPiece.cs diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs index 73337ab6f5..8a6cfaf688 100644 --- a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs @@ -25,7 +25,7 @@ namespace osu.Game.Beatmaps.ControlPoints MaxValue = 10 }; - public override Color4 GetRepresentingColour(OsuColour colours) => colours.GreenDark; + public override Color4 GetRepresentingColour(OsuColour colours) => colours.Lime1; /// /// The speed multiplier at this control point. diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs index 510ba8c094..3248936765 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs @@ -1,67 +1,27 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osuTK.Graphics; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { - public class DifficultyPointPiece : CompositeDrawable + public class DifficultyPointPiece : TopPointPiece { - private readonly DifficultyControlPoint difficultyPoint; - - private OsuSpriteText speedMultiplierText; private readonly BindableNumber speedMultiplier; - public DifficultyPointPiece(DifficultyControlPoint difficultyPoint) + public DifficultyPointPiece(DifficultyControlPoint point) + : base(point) { - this.difficultyPoint = difficultyPoint; - speedMultiplier = difficultyPoint.SpeedMultiplierBindable.GetBoundCopy(); + speedMultiplier = point.SpeedMultiplierBindable.GetBoundCopy(); + + Y = Height; } - [BackgroundDependencyLoader] - private void load(OsuColour colours) + protected override void LoadComplete() { - RelativeSizeAxes = Axes.Y; - AutoSizeAxes = Axes.X; - - Color4 colour = difficultyPoint.GetRepresentingColour(colours); - - InternalChildren = new Drawable[] - { - new Box - { - Colour = colour, - Width = 2, - RelativeSizeAxes = Axes.Y, - }, - new Container - { - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - Colour = colour, - RelativeSizeAxes = Axes.Both, - }, - speedMultiplierText = new OsuSpriteText - { - Font = OsuFont.Default.With(weight: FontWeight.Bold), - Colour = Color4.White, - } - } - }, - }; - - speedMultiplier.BindValueChanged(multiplier => speedMultiplierText.Text = $"{multiplier.NewValue:n2}x", true); + base.LoadComplete(); + speedMultiplier.BindValueChanged(multiplier => Label.Text = $"{multiplier.NewValue:n2}x", true); } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimingPointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimingPointPiece.cs index cd1470aa0a..fa51281c55 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimingPointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimingPointPiece.cs @@ -3,58 +3,27 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { - public class TimingPointPiece : CompositeDrawable + public class TimingPointPiece : TopPointPiece { - private readonly TimingControlPoint point; - private readonly BindableNumber beatLength; - private OsuSpriteText bpmText; public TimingPointPiece(TimingControlPoint point) + : base(point) { - this.point = point; beatLength = point.BeatLengthBindable.GetBoundCopy(); } [BackgroundDependencyLoader] private void load(OsuColour colours) { - Margin = new MarginPadding { Vertical = 10 }; - - const float corner_radius = 5; - - AutoSizeAxes = Axes.Both; - Masking = true; - CornerRadius = corner_radius; - - InternalChildren = new Drawable[] - { - new Box - { - Colour = point.GetRepresentingColour(colours), - RelativeSizeAxes = Axes.Both, - }, - bpmText = new OsuSpriteText - { - Alpha = 0.9f, - Padding = new MarginPadding { Vertical = 3, Horizontal = 6 }, - Font = OsuFont.Default.With(size: 20, weight: FontWeight.SemiBold), - Colour = colours.B5, - } - }; - beatLength.BindValueChanged(beatLength => { - bpmText.Text = $"{60000 / beatLength.NewValue:n1} BPM"; + Label.Text = $"{60000 / beatLength.NewValue:n1} BPM"; }, true); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TopPointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TopPointPiece.cs new file mode 100644 index 0000000000..60a9e1ed66 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TopPointPiece.cs @@ -0,0 +1,55 @@ +// 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.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Screens.Edit.Compose.Components.Timeline +{ + public class TopPointPiece : CompositeDrawable + { + private readonly ControlPoint point; + + protected OsuSpriteText Label { get; private set; } + + public TopPointPiece(ControlPoint point) + { + this.point = point; + AutoSizeAxes = Axes.X; + Height = 16; + Margin = new MarginPadding(4); + + Masking = true; + CornerRadius = Height / 2; + + Origin = Anchor.TopCentre; + Anchor = Anchor.TopCentre; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + InternalChildren = new Drawable[] + { + new Box + { + Colour = point.GetRepresentingColour(colours), + RelativeSizeAxes = Axes.Both, + }, + Label = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Padding = new MarginPadding(3), + Font = OsuFont.Default.With(size: 14, weight: FontWeight.SemiBold), + Colour = colours.B5, + } + }; + } + } +} From d1c68cb92b0efe025fcaadd28f9c61ea4f4a17bf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Apr 2021 20:55:12 +0900 Subject: [PATCH 1496/1791] Simplify content creation of Timeline / TimelineArea --- .../Edit/Compose/Components/Timeline/Timeline.cs | 8 ++++++-- .../Compose/Components/Timeline/TimelineArea.cs | 16 ++++++++++++---- .../Screens/Edit/EditorScreenWithTimeline.cs | 10 +--------- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index d688ad511f..55fb557474 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -23,6 +23,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [Cached] public class Timeline : ZoomableScrollContainer, IPositionSnapProvider { + private readonly Drawable userContent; public readonly Bindable WaveformVisible = new Bindable(); public readonly Bindable ControlPointsVisible = new Bindable(); @@ -57,10 +58,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private Track track; private const float timeline_height = 72; - private const float timeline_expanded_height = 150; + private const float timeline_expanded_height = 156; - public Timeline() + public Timeline(Drawable userContent) { + this.userContent = userContent; + RelativeSizeAxes = Axes.X; ZoomDuration = 200; @@ -118,6 +121,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Origin = Anchor.TopCentre, Colour = colours.YellowDarker, }, + userContent, } }, }); diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs index ee3543354f..1541ceade5 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs @@ -12,11 +12,19 @@ using osuTK; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { - public class TimelineArea : Container + public class TimelineArea : CompositeDrawable { - public readonly Timeline Timeline = new Timeline(); + public Timeline Timeline; - protected override Container Content => Timeline; + private readonly Drawable userContent; + + public TimelineArea(Drawable content = null) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + userContent = content ?? Drawable.Empty(); + } [BackgroundDependencyLoader] private void load() @@ -122,7 +130,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } } }, - Timeline + Timeline = new Timeline(userContent), }, }, RowDimensions = new[] diff --git a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs index 26083e6a82..0d59a7a1a8 100644 --- a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs +++ b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs @@ -133,15 +133,7 @@ namespace osu.Game.Screens.Edit mainContent.Add(content); content.FadeInFromZero(300, Easing.OutQuint); - LoadComponentAsync(new TimelineArea - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new[] - { - CreateTimelineContent(), - } - }, t => + LoadComponentAsync(new TimelineArea(CreateTimelineContent()), t => { timelineContainer.Add(t); OnTimelineLoaded(t); From 9dea0ae935ac4d4464bfc31a49ac42a4fe86f817 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Apr 2021 20:55:23 +0900 Subject: [PATCH 1497/1791] Update test scene to be able to see a bit more --- osu.Game.Tests/Visual/Editing/TimelineTestScene.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs index 88b4614791..4aed445d9d 100644 --- a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs +++ b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs @@ -53,13 +53,10 @@ namespace osu.Game.Tests.Visual.Editing new AudioVisualiser(), } }, - TimelineArea = new TimelineArea + TimelineArea = new TimelineArea(CreateTestComponent()) { - Child = CreateTestComponent(), Anchor = Anchor.Centre, Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - Size = new Vector2(0.8f, 100), } }); } From f56125bd682b68df3c3a6a20e3c24b6d758486ee Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 14 Apr 2021 21:15:14 +0900 Subject: [PATCH 1498/1791] Update clock from base class --- osu.Game/Screens/Play/GameplayClockContainer.cs | 12 +++++++++++- .../Screens/Play/MasterGameplayClockContainer.cs | 12 +++--------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index d8e6fda87e..6d863f0094 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -77,8 +77,18 @@ namespace osu.Game.Screens.Play Start(); } + protected override void Update() + { + if (!IsPaused.Value) + ClockToProcess.ProcessFrame(); + + base.Update(); + } + protected abstract void OnIsPausedChanged(ValueChangedEvent isPaused); - protected abstract GameplayClock CreateGameplayClock(IClock source); + protected virtual IFrameBasedClock ClockToProcess => AdjustableClock; + + protected abstract GameplayClock CreateGameplayClock(IFrameBasedClock source); } } diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index efc8ca732e..83e21f3c1d 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -128,7 +128,9 @@ namespace osu.Game.Screens.Play Seek(skipTarget); } - protected override GameplayClock CreateGameplayClock(IClock source) + protected override IFrameBasedClock ClockToProcess => userOffsetClock; + + protected override GameplayClock CreateGameplayClock(IFrameBasedClock source) { // Lazer's audio timings in general doesn't match stable. This is the result of user testing, albeit limited. // This only seems to be required on windows. We need to eventually figure out why, with a bit of luck. @@ -140,14 +142,6 @@ namespace osu.Game.Screens.Play return localGameplayClock = new LocalGameplayClock(userOffsetClock); } - protected override void Update() - { - if (!IsPaused.Value) - userOffsetClock.ProcessFrame(); - - base.Update(); - } - /// /// Changes the backing clock to avoid using the originally provided track. /// From 505dc15e0389efb9586ac655f682b727e991dd30 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 14 Apr 2021 23:22:38 +0300 Subject: [PATCH 1499/1791] Add failing test case --- .../Background/TestSceneUserDimBackgrounds.cs | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index 655b426e43..c0aab1b7ef 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -65,6 +65,21 @@ namespace osu.Game.Tests.Visual.Background stack.Push(songSelect = new DummySongSelect()); }); + /// + /// User settings should always be ignored on song select screen. + /// + [Test] + public void TestUserSettingsIgnoredOnSongSelect() + { + setupUserSettings(); + AddAssert("Screen is undimmed", () => songSelect.IsBackgroundUndimmed()); + AddAssert("Screen using background blur", () => songSelect.IsBackgroundBlur()); + performFullSetup(); + AddStep("Exit to song select", () => player.Exit()); + AddUntilStep("Screen is undimmed", () => songSelect.IsBackgroundUndimmed()); + AddUntilStep("Screen using background blur", () => songSelect.IsBackgroundBlur()); + } + /// /// Check if properly triggers the visual settings preview when a user hovers over the visual settings panel. /// @@ -227,17 +242,6 @@ namespace osu.Game.Tests.Visual.Background songSelect.IsBackgroundUndimmed() && songSelect.IsBackgroundCurrent() && songSelect.CheckBackgroundBlur(results.ExpectedBackgroundBlur)); } - /// - /// Check if background gets undimmed and unblurred when leaving for - /// - [Test] - public void TestTransitionOut() - { - performFullSetup(); - AddStep("Exit to song select", () => player.Exit()); - AddUntilStep("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsBlurCorrect()); - } - /// /// Check if hovering on the visual settings dialogue after resuming from player still previews the background dim. /// @@ -333,7 +337,7 @@ namespace osu.Game.Tests.Visual.Background public bool IsBackgroundVisible() => background.CurrentAlpha == 1; - public bool IsBlurCorrect() => background.CurrentBlur == new Vector2(BACKGROUND_BLUR); + public bool IsBackgroundBlur() => background.CurrentBlur == new Vector2(BACKGROUND_BLUR); public bool CheckBackgroundBlur(Vector2 expected) => background.CurrentBlur == expected; From a5fa14ac4ae010b77a66edd79fe394aeddbad022 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 14 Apr 2021 23:17:06 +0300 Subject: [PATCH 1500/1791] Ignore user settings on background screen beatmap by default --- osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs index 10d381b8b7..02166644ab 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs @@ -50,6 +50,9 @@ namespace osu.Game.Screens.Backgrounds InternalChild = dimmable = CreateFadeContainer(); + // Beatmap background screens should not apply user settings by default. + IgnoreUserSettings.Value = true; + dimmable.IgnoreUserSettings.BindTo(IgnoreUserSettings); dimmable.IsBreakTime.BindTo(IsBreakTime); dimmable.BlurAmount.BindTo(BlurAmount); From 7a9ff2ab380030871d434a1af6b0ae5c34ad9436 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 15 Apr 2021 00:48:15 +0300 Subject: [PATCH 1501/1791] Use until steps instead Ah.. --- .../Visual/Background/TestSceneUserDimBackgrounds.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index c0aab1b7ef..f89988cd1a 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -72,8 +72,8 @@ namespace osu.Game.Tests.Visual.Background public void TestUserSettingsIgnoredOnSongSelect() { setupUserSettings(); - AddAssert("Screen is undimmed", () => songSelect.IsBackgroundUndimmed()); - AddAssert("Screen using background blur", () => songSelect.IsBackgroundBlur()); + AddUntilStep("Screen is undimmed", () => songSelect.IsBackgroundUndimmed()); + AddUntilStep("Screen using background blur", () => songSelect.IsBackgroundBlur()); performFullSetup(); AddStep("Exit to song select", () => player.Exit()); AddUntilStep("Screen is undimmed", () => songSelect.IsBackgroundUndimmed()); From 362a5a39d0068d3a143fd245ef80a053995c6a8c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Apr 2021 13:06:52 +0900 Subject: [PATCH 1502/1791] Scale the playfield to avoid off-screen objects --- osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs index 1ba6c47f4c..d646f41588 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs @@ -4,11 +4,14 @@ using osu.Framework.Bindables; using osu.Game.Configuration; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.UI; +using osuTK; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModBarrelRoll : Mod, IUpdatableByPlayfield + public class OsuModBarrelRoll : Mod, IUpdatableByPlayfield, IApplicableToDrawableRuleset { [SettingSource("Roll speed", "Speed at which things rotate")] public BindableNumber SpinSpeed { get; } = new BindableDouble(1) @@ -26,5 +29,11 @@ namespace osu.Game.Rulesets.Osu.Mods { playfield.Rotation = (float)(playfield.Time.Current / 1000 * SpinSpeed.Value); } + + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + { + // scale the playfield to allow all hitobjects to stay within the visible region. + drawableRuleset.Playfield.Scale = new Vector2(OsuPlayfield.BASE_SIZE.Y / OsuPlayfield.BASE_SIZE.X); + } } } From 0d32290cd529cceb63e0fed31b3d46aeb4b2bca4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Apr 2021 13:15:52 +0900 Subject: [PATCH 1503/1791] Show roll speed in rotations-per-minute --- osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs index d646f41588..7cdfdb3c4a 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs @@ -13,12 +13,12 @@ namespace osu.Game.Rulesets.Osu.Mods { public class OsuModBarrelRoll : Mod, IUpdatableByPlayfield, IApplicableToDrawableRuleset { - [SettingSource("Roll speed", "Speed at which things rotate")] - public BindableNumber SpinSpeed { get; } = new BindableDouble(1) + [SettingSource("Roll speed", "Rotations per minute")] + public BindableNumber SpinSpeed { get; } = new BindableDouble(0.5) { - MinValue = 0.1, - MaxValue = 20, - Precision = 0.1, + MinValue = 0.02, + MaxValue = 4, + Precision = 0.01, }; public override string Name => "Barrel Roll"; @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Mods public void Update(Playfield playfield) { - playfield.Rotation = (float)(playfield.Time.Current / 1000 * SpinSpeed.Value); + playfield.Rotation = 360 * (float)(playfield.Time.Current / 60000 * SpinSpeed.Value); } public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) From 175b8da2b27002c262d53cfa5c8f44d46034abbf Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 15 Apr 2021 00:04:38 +0300 Subject: [PATCH 1504/1791] Inverse ignore user settings bindable to "apply user settings" instead --- .../Background/TestSceneUserDimBackgrounds.cs | 16 ++++++++-------- .../Background/TestSceneUserDimContainer.cs | 2 +- osu.Game/Graphics/Containers/UserDimContainer.cs | 8 ++++---- osu.Game/Rulesets/Mods/ModCinema.cs | 4 ++-- .../Backgrounds/BackgroundScreenBeatmap.cs | 15 ++++++--------- osu.Game/Screens/Edit/Editor.cs | 2 +- osu.Game/Screens/Play/DimmableStoryboard.cs | 4 ++-- osu.Game/Screens/Play/Player.cs | 4 ++-- osu.Game/Screens/Play/PlayerLoader.cs | 6 +++--- 9 files changed, 29 insertions(+), 32 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index f89988cd1a..d915442570 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -157,9 +157,9 @@ namespace osu.Game.Tests.Visual.Background { performFullSetup(); AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); - AddStep("Disable user dim", () => songSelect.IgnoreUserSettings.Value = true); + AddStep("Disable user dim", () => songSelect.ApplyUserSettings.Value = false); AddUntilStep("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsUserBlurDisabled()); - AddStep("Enable user dim", () => songSelect.IgnoreUserSettings.Value = false); + AddStep("Enable user dim", () => songSelect.ApplyUserSettings.Value = true); AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); } @@ -176,10 +176,10 @@ namespace osu.Game.Tests.Visual.Background player.ReplacesBackground.Value = true; player.StoryboardEnabled.Value = true; }); - AddStep("Enable user dim", () => player.DimmableStoryboard.IgnoreUserSettings.Value = false); + AddStep("Enable user dim", () => player.DimmableStoryboard.ApplyUserSettings.Value = true); AddStep("Set dim level to 1", () => songSelect.DimLevel.Value = 1f); AddUntilStep("Storyboard is invisible", () => !player.IsStoryboardVisible); - AddStep("Disable user dim", () => player.DimmableStoryboard.IgnoreUserSettings.Value = true); + AddStep("Disable user dim", () => player.DimmableStoryboard.ApplyUserSettings.Value = false); AddUntilStep("Storyboard is visible", () => player.IsStoryboardVisible); } @@ -195,8 +195,8 @@ namespace osu.Game.Tests.Visual.Background AddStep("Ignore user settings", () => { - player.ApplyToBackground(b => b.IgnoreUserSettings.Value = true); - player.DimmableStoryboard.IgnoreUserSettings.Value = true; + player.ApplyToBackground(b => b.ApplyUserSettings.Value = false); + player.DimmableStoryboard.ApplyUserSettings.Value = false; }); AddUntilStep("Storyboard is visible", () => player.IsStoryboardVisible); AddUntilStep("Background is invisible", () => songSelect.IsBackgroundInvisible()); @@ -308,11 +308,11 @@ namespace osu.Game.Tests.Visual.Background protected override BackgroundScreen CreateBackground() { background = new FadeAccessibleBackground(Beatmap.Value); - IgnoreUserSettings.BindTo(background.IgnoreUserSettings); + ApplyUserSettings.BindTo(background.ApplyUserSettings); return background; } - public readonly Bindable IgnoreUserSettings = new Bindable(); + public readonly Bindable ApplyUserSettings = new Bindable(); public readonly Bindable DimLevel = new BindableDouble(); public readonly Bindable BlurLevel = new BindableDouble(); diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs index fede99f450..9c3a044f18 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs @@ -94,7 +94,7 @@ namespace osu.Game.Tests.Visual.Background AddStep("set dim level 0.6", () => userDimContainer.UserDimLevel.Value = test_user_dim); AddUntilStep("dim reached", () => userDimContainer.DimEqual(test_user_dim)); - AddStep("ignore settings", () => userDimContainer.IgnoreUserSettings.Value = true); + AddStep("ignore settings", () => userDimContainer.ApplyUserSettings.Value = false); AddUntilStep("no dim", () => userDimContainer.DimEqual(0)); AddStep("set break", () => isBreakTime.Value = true); AddAssert("no dim", () => userDimContainer.DimEqual(0)); diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs index 4e555ac1eb..a3d59da961 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -24,9 +24,9 @@ namespace osu.Game.Graphics.Containers protected const double BACKGROUND_FADE_DURATION = 800; /// - /// Whether or not user-configured settings relating to brightness of elements should be ignored + /// Whether or not user-configured effect settings should be applied to this container. /// - public readonly Bindable IgnoreUserSettings = new Bindable(); + public readonly Bindable ApplyUserSettings = new Bindable(true); /// /// Whether or not the storyboard loaded should completely hide the background behind it. @@ -52,7 +52,7 @@ namespace osu.Game.Graphics.Containers private float breakLightening => LightenDuringBreaks.Value && IsBreakTime.Value ? BREAK_LIGHTEN_AMOUNT : 0; - protected float DimLevel => Math.Max(!IgnoreUserSettings.Value ? (float)UserDimLevel.Value - breakLightening : 0, 0); + protected float DimLevel => Math.Max(ApplyUserSettings.Value ? (float)UserDimLevel.Value - breakLightening : 0, 0); protected override Container Content => dimContent; @@ -78,7 +78,7 @@ namespace osu.Game.Graphics.Containers IsBreakTime.ValueChanged += _ => UpdateVisuals(); ShowStoryboard.ValueChanged += _ => UpdateVisuals(); StoryboardReplacesBackground.ValueChanged += _ => UpdateVisuals(); - IgnoreUserSettings.ValueChanged += _ => UpdateVisuals(); + ApplyUserSettings.ValueChanged += _ => UpdateVisuals(); } protected override void LoadComplete() diff --git a/osu.Game/Rulesets/Mods/ModCinema.cs b/osu.Game/Rulesets/Mods/ModCinema.cs index c78088ba2d..93062218fe 100644 --- a/osu.Game/Rulesets/Mods/ModCinema.cs +++ b/osu.Game/Rulesets/Mods/ModCinema.cs @@ -37,8 +37,8 @@ namespace osu.Game.Rulesets.Mods public void ApplyToPlayer(Player player) { - player.ApplyToBackground(b => b.IgnoreUserSettings.Value = true); - player.DimmableStoryboard.IgnoreUserSettings.Value = true; + player.ApplyToBackground(b => b.ApplyUserSettings.Value = false); + player.DimmableStoryboard.ApplyUserSettings.Value = false; player.BreakOverlay.Hide(); } diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs index 02166644ab..553a8f3689 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs @@ -27,9 +27,9 @@ namespace osu.Game.Screens.Backgrounds private WorkingBeatmap beatmap; /// - /// Whether or not user-configured settings relating to brightness of elements should be ignored + /// Whether or not user-configured effect settings should be applied to this background screen. /// - public readonly Bindable IgnoreUserSettings = new Bindable(); + public readonly Bindable ApplyUserSettings = new Bindable(); public readonly Bindable StoryboardReplacesBackground = new Bindable(); @@ -50,10 +50,7 @@ namespace osu.Game.Screens.Backgrounds InternalChild = dimmable = CreateFadeContainer(); - // Beatmap background screens should not apply user settings by default. - IgnoreUserSettings.Value = true; - - dimmable.IgnoreUserSettings.BindTo(IgnoreUserSettings); + dimmable.ApplyUserSettings.BindTo(ApplyUserSettings); dimmable.IsBreakTime.BindTo(IsBreakTime); dimmable.BlurAmount.BindTo(BlurAmount); @@ -151,7 +148,7 @@ namespace osu.Game.Screens.Backgrounds /// /// As an optimisation, we add the two blur portions to be applied rather than actually applying two separate blurs. /// - private Vector2 blurTarget => !IgnoreUserSettings.Value + private Vector2 blurTarget => ApplyUserSettings.Value ? new Vector2(BlurAmount.Value + (float)userBlurLevel.Value * USER_BLUR_FACTOR) : new Vector2(BlurAmount.Value); @@ -170,8 +167,8 @@ namespace osu.Game.Screens.Backgrounds } protected override bool ShowDimContent - // The background needs to be hidden in the case of it being replaced by the storyboard - => (!ShowStoryboard.Value && !IgnoreUserSettings.Value) || !StoryboardReplacesBackground.Value; + // The background needs to be hidden in the case of it being replaced by the storyboard. + => (ApplyUserSettings.Value && !ShowStoryboard.Value) || !StoryboardReplacesBackground.Value; protected override void UpdateVisuals() { diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index fffea65456..99c6cce26f 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -467,7 +467,7 @@ namespace osu.Game.Screens.Edit // todo: temporary. we want to be applying dim using the UserDimContainer eventually. b.FadeColour(Color4.DarkGray, 500); - b.IgnoreUserSettings.Value = true; + b.ApplyUserSettings.Value = false; b.BlurAmount.Value = 0; }); diff --git a/osu.Game/Screens/Play/DimmableStoryboard.cs b/osu.Game/Screens/Play/DimmableStoryboard.cs index 58eb95b7c6..105f672847 100644 --- a/osu.Game/Screens/Play/DimmableStoryboard.cs +++ b/osu.Game/Screens/Play/DimmableStoryboard.cs @@ -38,14 +38,14 @@ namespace osu.Game.Screens.Play base.LoadComplete(); } - protected override bool ShowDimContent => IgnoreUserSettings.Value || (ShowStoryboard.Value && DimLevel < 1); + protected override bool ShowDimContent => !ApplyUserSettings.Value || (ShowStoryboard.Value && DimLevel < 1); private void initializeStoryboard(bool async) { if (drawableStoryboard != null) return; - if (!ShowStoryboard.Value && !IgnoreUserSettings.Value) + if (ApplyUserSettings.Value && !ShowStoryboard.Value) return; drawableStoryboard = storyboard.CreateDrawable(); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index dd3f58439b..1c71305baf 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -764,7 +764,7 @@ namespace osu.Game.Screens.Play ApplyToBackground(b => { - b.IgnoreUserSettings.Value = false; + b.ApplyUserSettings.Value = true; b.BlurAmount.Value = 0; // bind component bindables. @@ -913,7 +913,7 @@ namespace osu.Game.Screens.Play float fadeOutDuration = instant ? 0 : 250; this.FadeOut(fadeOutDuration); - ApplyToBackground(b => b.IgnoreUserSettings.Value = true); + ApplyToBackground(b => b.ApplyUserSettings.Value = false); storyboardReplacesBackground.Value = false; } diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index cf15104809..8627432c11 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -229,7 +229,7 @@ namespace osu.Game.Screens.Play content.ScaleTo(0.7f, 150, Easing.InQuint); this.FadeOut(150); - ApplyToBackground(b => b.IgnoreUserSettings.Value = true); + ApplyToBackground(b => b.ApplyUserSettings.Value = false); BackgroundBrightnessReduction = false; Beatmap.Value.Track.RemoveAdjustment(AdjustableProperty.Volume, volumeAdjustment); @@ -277,7 +277,7 @@ namespace osu.Game.Screens.Play // Preview user-defined background dim and blur when hovered on the visual settings panel. ApplyToBackground(b => { - b.IgnoreUserSettings.Value = false; + b.ApplyUserSettings.Value = true; b.BlurAmount.Value = 0; }); @@ -288,7 +288,7 @@ namespace osu.Game.Screens.Play ApplyToBackground(b => { // Returns background dim and blur to the values specified by PlayerLoader. - b.IgnoreUserSettings.Value = true; + b.ApplyUserSettings.Value = false; b.BlurAmount.Value = BACKGROUND_BLUR; }); From 92fd34cea99c659155089ef828f54a65e620989b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 15 Apr 2021 08:02:12 +0300 Subject: [PATCH 1505/1791] Revert "Inverse ignore user settings bindable to "apply user settings" instead" This reverts commit 175b8da2b27002c262d53cfa5c8f44d46034abbf. --- .../Background/TestSceneUserDimBackgrounds.cs | 16 ++++++++-------- .../Background/TestSceneUserDimContainer.cs | 2 +- osu.Game/Graphics/Containers/UserDimContainer.cs | 8 ++++---- osu.Game/Rulesets/Mods/ModCinema.cs | 4 ++-- .../Backgrounds/BackgroundScreenBeatmap.cs | 15 +++++++++------ osu.Game/Screens/Edit/Editor.cs | 2 +- osu.Game/Screens/Play/DimmableStoryboard.cs | 4 ++-- osu.Game/Screens/Play/Player.cs | 4 ++-- osu.Game/Screens/Play/PlayerLoader.cs | 6 +++--- 9 files changed, 32 insertions(+), 29 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index d915442570..f89988cd1a 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -157,9 +157,9 @@ namespace osu.Game.Tests.Visual.Background { performFullSetup(); AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); - AddStep("Disable user dim", () => songSelect.ApplyUserSettings.Value = false); + AddStep("Disable user dim", () => songSelect.IgnoreUserSettings.Value = true); AddUntilStep("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsUserBlurDisabled()); - AddStep("Enable user dim", () => songSelect.ApplyUserSettings.Value = true); + AddStep("Enable user dim", () => songSelect.IgnoreUserSettings.Value = false); AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); } @@ -176,10 +176,10 @@ namespace osu.Game.Tests.Visual.Background player.ReplacesBackground.Value = true; player.StoryboardEnabled.Value = true; }); - AddStep("Enable user dim", () => player.DimmableStoryboard.ApplyUserSettings.Value = true); + AddStep("Enable user dim", () => player.DimmableStoryboard.IgnoreUserSettings.Value = false); AddStep("Set dim level to 1", () => songSelect.DimLevel.Value = 1f); AddUntilStep("Storyboard is invisible", () => !player.IsStoryboardVisible); - AddStep("Disable user dim", () => player.DimmableStoryboard.ApplyUserSettings.Value = false); + AddStep("Disable user dim", () => player.DimmableStoryboard.IgnoreUserSettings.Value = true); AddUntilStep("Storyboard is visible", () => player.IsStoryboardVisible); } @@ -195,8 +195,8 @@ namespace osu.Game.Tests.Visual.Background AddStep("Ignore user settings", () => { - player.ApplyToBackground(b => b.ApplyUserSettings.Value = false); - player.DimmableStoryboard.ApplyUserSettings.Value = false; + player.ApplyToBackground(b => b.IgnoreUserSettings.Value = true); + player.DimmableStoryboard.IgnoreUserSettings.Value = true; }); AddUntilStep("Storyboard is visible", () => player.IsStoryboardVisible); AddUntilStep("Background is invisible", () => songSelect.IsBackgroundInvisible()); @@ -308,11 +308,11 @@ namespace osu.Game.Tests.Visual.Background protected override BackgroundScreen CreateBackground() { background = new FadeAccessibleBackground(Beatmap.Value); - ApplyUserSettings.BindTo(background.ApplyUserSettings); + IgnoreUserSettings.BindTo(background.IgnoreUserSettings); return background; } - public readonly Bindable ApplyUserSettings = new Bindable(); + public readonly Bindable IgnoreUserSettings = new Bindable(); public readonly Bindable DimLevel = new BindableDouble(); public readonly Bindable BlurLevel = new BindableDouble(); diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs index 9c3a044f18..fede99f450 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs @@ -94,7 +94,7 @@ namespace osu.Game.Tests.Visual.Background AddStep("set dim level 0.6", () => userDimContainer.UserDimLevel.Value = test_user_dim); AddUntilStep("dim reached", () => userDimContainer.DimEqual(test_user_dim)); - AddStep("ignore settings", () => userDimContainer.ApplyUserSettings.Value = false); + AddStep("ignore settings", () => userDimContainer.IgnoreUserSettings.Value = true); AddUntilStep("no dim", () => userDimContainer.DimEqual(0)); AddStep("set break", () => isBreakTime.Value = true); AddAssert("no dim", () => userDimContainer.DimEqual(0)); diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs index a3d59da961..4e555ac1eb 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -24,9 +24,9 @@ namespace osu.Game.Graphics.Containers protected const double BACKGROUND_FADE_DURATION = 800; /// - /// Whether or not user-configured effect settings should be applied to this container. + /// Whether or not user-configured settings relating to brightness of elements should be ignored /// - public readonly Bindable ApplyUserSettings = new Bindable(true); + public readonly Bindable IgnoreUserSettings = new Bindable(); /// /// Whether or not the storyboard loaded should completely hide the background behind it. @@ -52,7 +52,7 @@ namespace osu.Game.Graphics.Containers private float breakLightening => LightenDuringBreaks.Value && IsBreakTime.Value ? BREAK_LIGHTEN_AMOUNT : 0; - protected float DimLevel => Math.Max(ApplyUserSettings.Value ? (float)UserDimLevel.Value - breakLightening : 0, 0); + protected float DimLevel => Math.Max(!IgnoreUserSettings.Value ? (float)UserDimLevel.Value - breakLightening : 0, 0); protected override Container Content => dimContent; @@ -78,7 +78,7 @@ namespace osu.Game.Graphics.Containers IsBreakTime.ValueChanged += _ => UpdateVisuals(); ShowStoryboard.ValueChanged += _ => UpdateVisuals(); StoryboardReplacesBackground.ValueChanged += _ => UpdateVisuals(); - ApplyUserSettings.ValueChanged += _ => UpdateVisuals(); + IgnoreUserSettings.ValueChanged += _ => UpdateVisuals(); } protected override void LoadComplete() diff --git a/osu.Game/Rulesets/Mods/ModCinema.cs b/osu.Game/Rulesets/Mods/ModCinema.cs index 93062218fe..c78088ba2d 100644 --- a/osu.Game/Rulesets/Mods/ModCinema.cs +++ b/osu.Game/Rulesets/Mods/ModCinema.cs @@ -37,8 +37,8 @@ namespace osu.Game.Rulesets.Mods public void ApplyToPlayer(Player player) { - player.ApplyToBackground(b => b.ApplyUserSettings.Value = false); - player.DimmableStoryboard.ApplyUserSettings.Value = false; + player.ApplyToBackground(b => b.IgnoreUserSettings.Value = true); + player.DimmableStoryboard.IgnoreUserSettings.Value = true; player.BreakOverlay.Hide(); } diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs index 553a8f3689..02166644ab 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs @@ -27,9 +27,9 @@ namespace osu.Game.Screens.Backgrounds private WorkingBeatmap beatmap; /// - /// Whether or not user-configured effect settings should be applied to this background screen. + /// Whether or not user-configured settings relating to brightness of elements should be ignored /// - public readonly Bindable ApplyUserSettings = new Bindable(); + public readonly Bindable IgnoreUserSettings = new Bindable(); public readonly Bindable StoryboardReplacesBackground = new Bindable(); @@ -50,7 +50,10 @@ namespace osu.Game.Screens.Backgrounds InternalChild = dimmable = CreateFadeContainer(); - dimmable.ApplyUserSettings.BindTo(ApplyUserSettings); + // Beatmap background screens should not apply user settings by default. + IgnoreUserSettings.Value = true; + + dimmable.IgnoreUserSettings.BindTo(IgnoreUserSettings); dimmable.IsBreakTime.BindTo(IsBreakTime); dimmable.BlurAmount.BindTo(BlurAmount); @@ -148,7 +151,7 @@ namespace osu.Game.Screens.Backgrounds /// /// As an optimisation, we add the two blur portions to be applied rather than actually applying two separate blurs. /// - private Vector2 blurTarget => ApplyUserSettings.Value + private Vector2 blurTarget => !IgnoreUserSettings.Value ? new Vector2(BlurAmount.Value + (float)userBlurLevel.Value * USER_BLUR_FACTOR) : new Vector2(BlurAmount.Value); @@ -167,8 +170,8 @@ namespace osu.Game.Screens.Backgrounds } protected override bool ShowDimContent - // The background needs to be hidden in the case of it being replaced by the storyboard. - => (ApplyUserSettings.Value && !ShowStoryboard.Value) || !StoryboardReplacesBackground.Value; + // The background needs to be hidden in the case of it being replaced by the storyboard + => (!ShowStoryboard.Value && !IgnoreUserSettings.Value) || !StoryboardReplacesBackground.Value; protected override void UpdateVisuals() { diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 99c6cce26f..fffea65456 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -467,7 +467,7 @@ namespace osu.Game.Screens.Edit // todo: temporary. we want to be applying dim using the UserDimContainer eventually. b.FadeColour(Color4.DarkGray, 500); - b.ApplyUserSettings.Value = false; + b.IgnoreUserSettings.Value = true; b.BlurAmount.Value = 0; }); diff --git a/osu.Game/Screens/Play/DimmableStoryboard.cs b/osu.Game/Screens/Play/DimmableStoryboard.cs index 105f672847..58eb95b7c6 100644 --- a/osu.Game/Screens/Play/DimmableStoryboard.cs +++ b/osu.Game/Screens/Play/DimmableStoryboard.cs @@ -38,14 +38,14 @@ namespace osu.Game.Screens.Play base.LoadComplete(); } - protected override bool ShowDimContent => !ApplyUserSettings.Value || (ShowStoryboard.Value && DimLevel < 1); + protected override bool ShowDimContent => IgnoreUserSettings.Value || (ShowStoryboard.Value && DimLevel < 1); private void initializeStoryboard(bool async) { if (drawableStoryboard != null) return; - if (ApplyUserSettings.Value && !ShowStoryboard.Value) + if (!ShowStoryboard.Value && !IgnoreUserSettings.Value) return; drawableStoryboard = storyboard.CreateDrawable(); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 1c71305baf..dd3f58439b 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -764,7 +764,7 @@ namespace osu.Game.Screens.Play ApplyToBackground(b => { - b.ApplyUserSettings.Value = true; + b.IgnoreUserSettings.Value = false; b.BlurAmount.Value = 0; // bind component bindables. @@ -913,7 +913,7 @@ namespace osu.Game.Screens.Play float fadeOutDuration = instant ? 0 : 250; this.FadeOut(fadeOutDuration); - ApplyToBackground(b => b.ApplyUserSettings.Value = false); + ApplyToBackground(b => b.IgnoreUserSettings.Value = true); storyboardReplacesBackground.Value = false; } diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 8627432c11..cf15104809 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -229,7 +229,7 @@ namespace osu.Game.Screens.Play content.ScaleTo(0.7f, 150, Easing.InQuint); this.FadeOut(150); - ApplyToBackground(b => b.ApplyUserSettings.Value = false); + ApplyToBackground(b => b.IgnoreUserSettings.Value = true); BackgroundBrightnessReduction = false; Beatmap.Value.Track.RemoveAdjustment(AdjustableProperty.Volume, volumeAdjustment); @@ -277,7 +277,7 @@ namespace osu.Game.Screens.Play // Preview user-defined background dim and blur when hovered on the visual settings panel. ApplyToBackground(b => { - b.ApplyUserSettings.Value = true; + b.IgnoreUserSettings.Value = false; b.BlurAmount.Value = 0; }); @@ -288,7 +288,7 @@ namespace osu.Game.Screens.Play ApplyToBackground(b => { // Returns background dim and blur to the values specified by PlayerLoader. - b.ApplyUserSettings.Value = false; + b.IgnoreUserSettings.Value = true; b.BlurAmount.Value = BACKGROUND_BLUR; }); From 6c5234f8daa605ffb936b492edd0e247ee71351a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 15 Apr 2021 08:04:03 +0300 Subject: [PATCH 1506/1791] Move default `IgnoreUserSettings` value to construction --- .../Screens/Backgrounds/BackgroundScreenBeatmap.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs index 02166644ab..65bc9cfaea 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs @@ -27,9 +27,12 @@ namespace osu.Game.Screens.Backgrounds private WorkingBeatmap beatmap; /// - /// Whether or not user-configured settings relating to brightness of elements should be ignored + /// Whether or not user-configured settings relating to brightness of elements should be ignored. /// - public readonly Bindable IgnoreUserSettings = new Bindable(); + /// + /// Beatmap background screens should not apply user settings by default. + /// + public readonly Bindable IgnoreUserSettings = new Bindable(true); public readonly Bindable StoryboardReplacesBackground = new Bindable(); @@ -50,9 +53,6 @@ namespace osu.Game.Screens.Backgrounds InternalChild = dimmable = CreateFadeContainer(); - // Beatmap background screens should not apply user settings by default. - IgnoreUserSettings.Value = true; - dimmable.IgnoreUserSettings.BindTo(IgnoreUserSettings); dimmable.IsBreakTime.BindTo(IsBreakTime); dimmable.BlurAmount.BindTo(BlurAmount); From 5eaf3ea5765e21a0137b983fa586651d0228ffc5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Apr 2021 14:19:06 +0900 Subject: [PATCH 1507/1791] Reorganise and reword comments to make time override behaviour a bit clearer --- osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs index a7f11b1e6f..279087ead9 100644 --- a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs +++ b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs @@ -128,13 +128,13 @@ namespace osu.Game.Rulesets.Replays double frameStart = getFrameTime(currentFrameIndex); double frameEnd = getFrameTime(currentFrameIndex + 1); - // If the proposed time is after the current frame end time, we progress forwards. - // If the proposed time is before the current frame start time, and we are at the frame boundary, we progress backwards. + // If the proposed time is after the current frame end time, we progress forwards to precisely the new frame's time (regardless of incoming time). if (frameEnd <= time) { time = frameEnd; currentFrameIndex++; } + // If the proposed time is before the current frame start time, and we are at the frame boundary, we progress backwards. else if (time < frameStart && CurrentTime == frameStart) currentFrameIndex--; From ba325de5959bb83828fc222b359fcb9f9c846236 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Apr 2021 14:19:59 +0900 Subject: [PATCH 1508/1791] Merge conditionals for readability --- osu.Game/Rulesets/UI/RulesetInputManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 1c0d820a3d..5ab09f9516 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -107,7 +107,7 @@ namespace osu.Game.Rulesets.UI protected override List GetPendingInputs() { - if (replayInputHandler != null && !replayInputHandler.IsActive) + if (replayInputHandler?.IsActive == false) return emptyInputList; return base.GetPendingInputs(); From 346e36d32a09ebdafb1c902f4eb0b03e45fddf42 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Apr 2021 14:32:01 +0900 Subject: [PATCH 1509/1791] Make `Mod.Description` abstract and add missing descriptions --- osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs | 1 + osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs | 1 + .../NonVisual/DifficultyAdjustmentModCombinationsTest.cs | 5 +++++ osu.Game.Tests/Online/TestAPIModJsonSerialization.cs | 2 ++ osu.Game.Tests/Online/TestAPIModMessagePackSerialization.cs | 3 +++ osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs | 1 + osu.Game.Tests/Visual/UserInterface/TestSceneModButton.cs | 2 ++ osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs | 2 ++ osu.Game/Rulesets/Mods/Mod.cs | 2 +- osu.Game/Rulesets/Mods/ModNoMod.cs | 1 + 10 files changed, 19 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs index 485595cea9..fa1eb10f5e 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs @@ -15,6 +15,7 @@ namespace osu.Game.Rulesets.Mania.Mods public override string Name => "Mirror"; public override string Acronym => "MR"; public override ModType Type => ModType.Conversion; + public override string Description => "Notes are flipped horizontally"; public override double ScoreMultiplier => 1; public override bool Ranked => true; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs index f0db548e74..3b16e9d2b7 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs @@ -9,6 +9,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public override string Name => "Touch Device"; public override string Acronym => "TD"; + public override string Description => "Automatically applied to plays on devices with a touchscreen."; public override double ScoreMultiplier => 1; public override ModType Type => ModType.System; diff --git a/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs b/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs index 1c0bfd56dd..16c1004f37 100644 --- a/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs +++ b/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs @@ -144,6 +144,7 @@ namespace osu.Game.Tests.NonVisual { public override string Name => nameof(ModA); public override string Acronym => nameof(ModA); + public override string Description => string.Empty; public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(ModIncompatibleWithA), typeof(ModIncompatibleWithAAndB) }; @@ -152,6 +153,7 @@ namespace osu.Game.Tests.NonVisual private class ModB : Mod { public override string Name => nameof(ModB); + public override string Description => string.Empty; public override string Acronym => nameof(ModB); public override double ScoreMultiplier => 1; @@ -162,6 +164,7 @@ namespace osu.Game.Tests.NonVisual { public override string Name => nameof(ModC); public override string Acronym => nameof(ModC); + public override string Description => string.Empty; public override double ScoreMultiplier => 1; } @@ -169,6 +172,7 @@ namespace osu.Game.Tests.NonVisual { public override string Name => $"Incompatible With {nameof(ModA)}"; public override string Acronym => $"Incompatible With {nameof(ModA)}"; + public override string Description => string.Empty; public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(ModA) }; @@ -187,6 +191,7 @@ namespace osu.Game.Tests.NonVisual { public override string Name => $"Incompatible With {nameof(ModA)} and {nameof(ModB)}"; public override string Acronym => $"Incompatible With {nameof(ModA)} and {nameof(ModB)}"; + public override string Description => string.Empty; public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(ModA), typeof(ModB) }; diff --git a/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs b/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs index 3afb7481b1..ad2007f202 100644 --- a/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs +++ b/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs @@ -140,6 +140,7 @@ namespace osu.Game.Tests.Online { public override string Name => "Test Mod"; public override string Acronym => "TM"; + public override string Description => "This is a test mod."; public override double ScoreMultiplier => 1; [SettingSource("Test")] @@ -156,6 +157,7 @@ namespace osu.Game.Tests.Online { public override string Name => "Test Mod"; public override string Acronym => "TMTR"; + public override string Description => "This is a test mod."; public override double ScoreMultiplier => 1; [SettingSource("Initial rate", "The starting speed of the track")] diff --git a/osu.Game.Tests/Online/TestAPIModMessagePackSerialization.cs b/osu.Game.Tests/Online/TestAPIModMessagePackSerialization.cs index 74db477cfc..0462e9feb5 100644 --- a/osu.Game.Tests/Online/TestAPIModMessagePackSerialization.cs +++ b/osu.Game.Tests/Online/TestAPIModMessagePackSerialization.cs @@ -100,6 +100,7 @@ namespace osu.Game.Tests.Online { public override string Name => "Test Mod"; public override string Acronym => "TM"; + public override string Description => "This is a test mod."; public override double ScoreMultiplier => 1; [SettingSource("Test")] @@ -116,6 +117,7 @@ namespace osu.Game.Tests.Online { public override string Name => "Test Mod"; public override string Acronym => "TMTR"; + public override string Description => "This is a test mod."; public override double ScoreMultiplier => 1; [SettingSource("Initial rate", "The starting speed of the track")] @@ -150,6 +152,7 @@ namespace osu.Game.Tests.Online { public override string Name => "Test Mod"; public override string Acronym => "TM"; + public override string Description => "This is a test mod."; public override double ScoreMultiplier => 1; [SettingSource("Test")] diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index 88fbf09ef4..280c182259 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -321,6 +321,7 @@ namespace osu.Game.Tests.Visual.Gameplay public override string Name => string.Empty; public override string Acronym => string.Empty; public override double ScoreMultiplier => 1; + public override string Description => string.Empty; public bool Applied { get; private set; } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModButton.cs index 443cf59003..fdc21d80ff 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModButton.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModButton.cs @@ -57,6 +57,8 @@ namespace osu.Game.Tests.Visual.UserInterface private abstract class TestMod : Mod, IApplicableMod { public override double ScoreMultiplier => 1.0; + + public override string Description => "This is a test mod."; } } } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs index 89f9b7381b..2158cf77e5 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs @@ -226,6 +226,8 @@ namespace osu.Game.Tests.Visual.UserInterface { public override double ScoreMultiplier => 1.0; + public override string Description => "This is a customisable test mod."; + public override ModType Type => ModType.Conversion; [SettingSource("Sample float", "Change something for a mod")] diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 832a14ee1e..4879590e24 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Mods /// The user readable description of this mod. /// [JsonIgnore] - public virtual string Description => string.Empty; + public abstract string Description { get; } /// /// The tooltip to display for this mod when used in a . diff --git a/osu.Game/Rulesets/Mods/ModNoMod.cs b/osu.Game/Rulesets/Mods/ModNoMod.cs index 379a2122f2..1009c5bc42 100644 --- a/osu.Game/Rulesets/Mods/ModNoMod.cs +++ b/osu.Game/Rulesets/Mods/ModNoMod.cs @@ -12,6 +12,7 @@ namespace osu.Game.Rulesets.Mods { public override string Name => "No Mod"; public override string Acronym => "NM"; + public override string Description => "No mods applied."; public override double ScoreMultiplier => 1; public override IconUsage? Icon => FontAwesome.Solid.Ban; public override ModType Type => ModType.System; From 23eb1c655ce3b2dd5f6bea7e5f68bdc4d111fe8e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Apr 2021 14:37:47 +0900 Subject: [PATCH 1510/1791] Add missing description --- osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs index 7cdfdb3c4a..3a61968075 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs @@ -23,6 +23,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Name => "Barrel Roll"; public override string Acronym => "BR"; + public override string Description => "The whole playfield is on a wheel!"; public override double ScoreMultiplier => 1; public void Update(Playfield playfield) From 698a9d3feddddbb80fc9a483f02160446b985993 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Apr 2021 14:40:03 +0900 Subject: [PATCH 1511/1791] Add rotation direction setting --- osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs index 3a61968075..b6cfa514a1 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; +using osu.Framework.Graphics; using osu.Game.Configuration; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Objects; @@ -21,6 +22,9 @@ namespace osu.Game.Rulesets.Osu.Mods Precision = 0.01, }; + [SettingSource("Direction", "The direction of rotation")] + public Bindable Direction { get; } = new Bindable(RotationDirection.Clockwise); + public override string Name => "Barrel Roll"; public override string Acronym => "BR"; public override string Description => "The whole playfield is on a wheel!"; @@ -28,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Mods public void Update(Playfield playfield) { - playfield.Rotation = 360 * (float)(playfield.Time.Current / 60000 * SpinSpeed.Value); + playfield.Rotation = (Direction.Value == RotationDirection.CounterClockwise ? -1 : 1) * 360 * (float)(playfield.Time.Current / 60000 * SpinSpeed.Value); } public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) From 55421b0065a8e22b66e260ae594463a04d1bdbf5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Apr 2021 14:41:10 +0900 Subject: [PATCH 1512/1791] Add missing full stop Co-authored-by: Salman Ahmed --- osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs index fa1eb10f5e..12f379bddb 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Mania.Mods public override string Name => "Mirror"; public override string Acronym => "MR"; public override ModType Type => ModType.Conversion; - public override string Description => "Notes are flipped horizontally"; + public override string Description => "Notes are flipped horizontally."; public override double ScoreMultiplier => 1; public override bool Ranked => true; From bc3b2af39d5d391e3bf0c95a5514193e6853faf2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Apr 2021 15:29:22 +0900 Subject: [PATCH 1513/1791] Add rounded corners to timeline ticks display --- .../Timelines/Summary/Visualisations/PointVisualisation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs index 53a1f94731..d647c6bfe8 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs @@ -9,7 +9,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations /// /// Represents a singular point on a timeline part. /// - public class PointVisualisation : Box + public class PointVisualisation : Circle { public const float MAX_WIDTH = 4; From 66bb5766b9486bd06b6059007cde79680e4fcc2c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Apr 2021 14:32:01 +0900 Subject: [PATCH 1514/1791] Make `Mod.Description` abstract and add missing descriptions --- osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs | 1 + osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs | 1 + .../NonVisual/DifficultyAdjustmentModCombinationsTest.cs | 5 +++++ osu.Game.Tests/Online/TestAPIModJsonSerialization.cs | 2 ++ osu.Game.Tests/Online/TestAPIModMessagePackSerialization.cs | 3 +++ osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs | 1 + osu.Game.Tests/Visual/UserInterface/TestSceneModButton.cs | 2 ++ osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs | 2 ++ osu.Game/Rulesets/Mods/Mod.cs | 2 +- osu.Game/Rulesets/Mods/ModNoMod.cs | 1 + 10 files changed, 19 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs index 485595cea9..fa1eb10f5e 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs @@ -15,6 +15,7 @@ namespace osu.Game.Rulesets.Mania.Mods public override string Name => "Mirror"; public override string Acronym => "MR"; public override ModType Type => ModType.Conversion; + public override string Description => "Notes are flipped horizontally"; public override double ScoreMultiplier => 1; public override bool Ranked => true; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs index f0db548e74..3b16e9d2b7 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs @@ -9,6 +9,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public override string Name => "Touch Device"; public override string Acronym => "TD"; + public override string Description => "Automatically applied to plays on devices with a touchscreen."; public override double ScoreMultiplier => 1; public override ModType Type => ModType.System; diff --git a/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs b/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs index 1c0bfd56dd..16c1004f37 100644 --- a/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs +++ b/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs @@ -144,6 +144,7 @@ namespace osu.Game.Tests.NonVisual { public override string Name => nameof(ModA); public override string Acronym => nameof(ModA); + public override string Description => string.Empty; public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(ModIncompatibleWithA), typeof(ModIncompatibleWithAAndB) }; @@ -152,6 +153,7 @@ namespace osu.Game.Tests.NonVisual private class ModB : Mod { public override string Name => nameof(ModB); + public override string Description => string.Empty; public override string Acronym => nameof(ModB); public override double ScoreMultiplier => 1; @@ -162,6 +164,7 @@ namespace osu.Game.Tests.NonVisual { public override string Name => nameof(ModC); public override string Acronym => nameof(ModC); + public override string Description => string.Empty; public override double ScoreMultiplier => 1; } @@ -169,6 +172,7 @@ namespace osu.Game.Tests.NonVisual { public override string Name => $"Incompatible With {nameof(ModA)}"; public override string Acronym => $"Incompatible With {nameof(ModA)}"; + public override string Description => string.Empty; public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(ModA) }; @@ -187,6 +191,7 @@ namespace osu.Game.Tests.NonVisual { public override string Name => $"Incompatible With {nameof(ModA)} and {nameof(ModB)}"; public override string Acronym => $"Incompatible With {nameof(ModA)} and {nameof(ModB)}"; + public override string Description => string.Empty; public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(ModA), typeof(ModB) }; diff --git a/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs b/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs index 3afb7481b1..ad2007f202 100644 --- a/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs +++ b/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs @@ -140,6 +140,7 @@ namespace osu.Game.Tests.Online { public override string Name => "Test Mod"; public override string Acronym => "TM"; + public override string Description => "This is a test mod."; public override double ScoreMultiplier => 1; [SettingSource("Test")] @@ -156,6 +157,7 @@ namespace osu.Game.Tests.Online { public override string Name => "Test Mod"; public override string Acronym => "TMTR"; + public override string Description => "This is a test mod."; public override double ScoreMultiplier => 1; [SettingSource("Initial rate", "The starting speed of the track")] diff --git a/osu.Game.Tests/Online/TestAPIModMessagePackSerialization.cs b/osu.Game.Tests/Online/TestAPIModMessagePackSerialization.cs index 74db477cfc..0462e9feb5 100644 --- a/osu.Game.Tests/Online/TestAPIModMessagePackSerialization.cs +++ b/osu.Game.Tests/Online/TestAPIModMessagePackSerialization.cs @@ -100,6 +100,7 @@ namespace osu.Game.Tests.Online { public override string Name => "Test Mod"; public override string Acronym => "TM"; + public override string Description => "This is a test mod."; public override double ScoreMultiplier => 1; [SettingSource("Test")] @@ -116,6 +117,7 @@ namespace osu.Game.Tests.Online { public override string Name => "Test Mod"; public override string Acronym => "TMTR"; + public override string Description => "This is a test mod."; public override double ScoreMultiplier => 1; [SettingSource("Initial rate", "The starting speed of the track")] @@ -150,6 +152,7 @@ namespace osu.Game.Tests.Online { public override string Name => "Test Mod"; public override string Acronym => "TM"; + public override string Description => "This is a test mod."; public override double ScoreMultiplier => 1; [SettingSource("Test")] diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index 88fbf09ef4..280c182259 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -321,6 +321,7 @@ namespace osu.Game.Tests.Visual.Gameplay public override string Name => string.Empty; public override string Acronym => string.Empty; public override double ScoreMultiplier => 1; + public override string Description => string.Empty; public bool Applied { get; private set; } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModButton.cs index 443cf59003..fdc21d80ff 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModButton.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModButton.cs @@ -57,6 +57,8 @@ namespace osu.Game.Tests.Visual.UserInterface private abstract class TestMod : Mod, IApplicableMod { public override double ScoreMultiplier => 1.0; + + public override string Description => "This is a test mod."; } } } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs index 89f9b7381b..2158cf77e5 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs @@ -226,6 +226,8 @@ namespace osu.Game.Tests.Visual.UserInterface { public override double ScoreMultiplier => 1.0; + public override string Description => "This is a customisable test mod."; + public override ModType Type => ModType.Conversion; [SettingSource("Sample float", "Change something for a mod")] diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 832a14ee1e..4879590e24 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Mods /// The user readable description of this mod. /// [JsonIgnore] - public virtual string Description => string.Empty; + public abstract string Description { get; } /// /// The tooltip to display for this mod when used in a . diff --git a/osu.Game/Rulesets/Mods/ModNoMod.cs b/osu.Game/Rulesets/Mods/ModNoMod.cs index 379a2122f2..1009c5bc42 100644 --- a/osu.Game/Rulesets/Mods/ModNoMod.cs +++ b/osu.Game/Rulesets/Mods/ModNoMod.cs @@ -12,6 +12,7 @@ namespace osu.Game.Rulesets.Mods { public override string Name => "No Mod"; public override string Acronym => "NM"; + public override string Description => "No mods applied."; public override double ScoreMultiplier => 1; public override IconUsage? Icon => FontAwesome.Solid.Ban; public override ModType Type => ModType.System; From ed14e014015042a2527210fab3af73c060677f88 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Apr 2021 14:41:10 +0900 Subject: [PATCH 1515/1791] Add missing full stop Co-authored-by: Salman Ahmed --- osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs index fa1eb10f5e..12f379bddb 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Mania.Mods public override string Name => "Mirror"; public override string Acronym => "MR"; public override ModType Type => ModType.Conversion; - public override string Description => "Notes are flipped horizontally"; + public override string Description => "Notes are flipped horizontally."; public override double ScoreMultiplier => 1; public override bool Ranked => true; From dd9a142e899030c6a53d0fd0275af6a57efd34f6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Apr 2021 16:30:02 +0900 Subject: [PATCH 1516/1791] Fix `TestSceneEditorSummaryTimeline` not displaying actual beatmap content --- .../Editing/TestSceneEditorSummaryTimeline.cs | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSummaryTimeline.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSummaryTimeline.cs index 94a9fd7b35..ba57eeacbe 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSummaryTimeline.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSummaryTimeline.cs @@ -4,6 +4,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Screens.Edit; @@ -16,18 +17,28 @@ namespace osu.Game.Tests.Visual.Editing public class TestSceneEditorSummaryTimeline : EditorClockTestScene { [Cached(typeof(EditorBeatmap))] - private readonly EditorBeatmap editorBeatmap = new EditorBeatmap(new OsuBeatmap()); + private readonly EditorBeatmap editorBeatmap; - [BackgroundDependencyLoader] - private void load() + public TestSceneEditorSummaryTimeline() { - Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); + editorBeatmap = new EditorBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)); + } - Add(new SummaryTimeline + protected override void LoadComplete() + { + base.LoadComplete(); + + AddStep("create timeline", () => { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(500, 50) + // required for track + Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap); + + Add(new SummaryTimeline + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(500, 50) + }); }); } } From 73821beb1d8d127be59f77c9fa980259b3081a5d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Apr 2021 16:30:20 +0900 Subject: [PATCH 1517/1791] Fix break display looking bad on very long beatmaps due to fixed corner radius --- .../Edit/Components/Timelines/Summary/SummaryTimeline.cs | 2 +- .../Summary/Visualisations/DurationVisualisation.cs | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs index 02cd4bccb4..e1a1eff0cb 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs @@ -69,7 +69,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Height = 0.25f + Height = 0.10f } }; } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/DurationVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/DurationVisualisation.cs index de63df5463..86e6446555 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/DurationVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/DurationVisualisation.cs @@ -10,19 +10,15 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations /// /// Represents a spanning point on a timeline part. /// - public class DurationVisualisation : Container + public class DurationVisualisation : Circle { protected DurationVisualisation(double startTime, double endTime) { - Masking = true; - CornerRadius = 5; - RelativePositionAxes = Axes.X; RelativeSizeAxes = Axes.Both; + X = (float)startTime; Width = (float)(endTime - startTime); - - AddInternal(new Box { RelativeSizeAxes = Axes.Both }); } } } From da6f9060fa2eb98c982367ec2a0ef00fcf1edf26 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Apr 2021 16:30:56 +0900 Subject: [PATCH 1518/1791] Centre end circles to avoid visual gaps --- .../Edit/Components/Timelines/Summary/SummaryTimeline.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs index e1a1eff0cb..ada7810599 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs @@ -45,7 +45,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary new Circle { Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreRight, + Origin = Anchor.Centre, Size = new Vector2(5) }, new Box @@ -59,7 +59,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary new Circle { Anchor = Anchor.CentreRight, - Origin = Anchor.CentreLeft, + Origin = Anchor.Centre, Size = new Vector2(5) }, } From 757475e6d4c3f300684d46f5f3c860f73ecd66e5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Apr 2021 16:33:29 +0900 Subject: [PATCH 1519/1791] Use correct representation colours --- .../Components/Timelines/Summary/Parts/GroupVisualisation.cs | 2 +- .../Edit/Components/Timelines/Summary/SummaryTimeline.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs index 93fe6f9989..8bc8618479 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs @@ -39,7 +39,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts return; } - Colour = controlPoints.Any(c => c is TimingControlPoint) ? colours.YellowDark : colours.Green; + Colour = Group.ControlPoints.First().GetRepresentingColour(colours); }, true); } } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs index ada7810599..ae60cd4dd3 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs @@ -38,6 +38,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary }, new Container { + Name = "centre line", RelativeSizeAxes = Axes.Both, Colour = colours.Gray5, Children = new Drawable[] From 18e8682f391ea78fa40487af2d813fe6b6fe77b2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Apr 2021 17:01:25 +0900 Subject: [PATCH 1520/1791] Remove unused using statements --- osu.Game.Tests/Visual/Editing/TestSceneEditorSummaryTimeline.cs | 2 -- .../Timelines/Summary/Visualisations/DurationVisualisation.cs | 1 - 2 files changed, 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSummaryTimeline.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSummaryTimeline.cs index ba57eeacbe..da0c83bb11 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSummaryTimeline.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSummaryTimeline.cs @@ -4,9 +4,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Game.Beatmaps; using osu.Game.Rulesets.Osu; -using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components.Timelines.Summary; using osuTK; diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/DurationVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/DurationVisualisation.cs index 86e6446555..ec68bf9c00 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/DurationVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/DurationVisualisation.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations From bf5af3310aa1d643e08a0135531fff7b9aff9416 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Apr 2021 17:04:11 +0900 Subject: [PATCH 1521/1791] Update break colour to not look like kiai time --- .../Edit/Components/Timelines/Summary/Parts/BreakPart.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs index e8a4b5c8c7..3d535ec915 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs @@ -28,7 +28,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts } [BackgroundDependencyLoader] - private void load(OsuColour colours) => Colour = colours.Yellow; + private void load(OsuColour colours) => Colour = colours.GreyCarmineLight; } } } From 50fad47ebc45d744dcd2f0005a9a66aa80639e63 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 15 Apr 2021 18:06:45 +0900 Subject: [PATCH 1522/1791] Remove usage of Lazy> for NestedHitObjects --- .../Objects/Drawables/DrawableHitObject.cs | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index d95b246c96..669e4cecbe 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -56,8 +56,8 @@ namespace osu.Game.Rulesets.Objects.Drawables public virtual IEnumerable GetSamples() => HitObject.Samples; - private readonly Lazy> nestedHitObjects = new Lazy>(); - public IReadOnlyList NestedHitObjects => nestedHitObjects.IsValueCreated ? nestedHitObjects.Value : (IReadOnlyList)Array.Empty(); + private readonly List nestedHitObjects = new List(); + public IReadOnlyList NestedHitObjects => nestedHitObjects; /// /// Whether this object should handle any user input events. @@ -249,7 +249,7 @@ namespace osu.Game.Rulesets.Objects.Drawables // Must be done before the nested DHO is added to occur before the nested Apply()! drawableNested.ParentHitObject = this; - nestedHitObjects.Value.Add(drawableNested); + nestedHitObjects.Add(drawableNested); AddNestedHitObject(drawableNested); } @@ -305,19 +305,16 @@ namespace osu.Game.Rulesets.Objects.Drawables if (Samples != null) Samples.Samples = null; - if (nestedHitObjects.IsValueCreated) + foreach (var obj in nestedHitObjects) { - foreach (var obj in nestedHitObjects.Value) - { - obj.OnNewResult -= onNewResult; - obj.OnRevertResult -= onRevertResult; - obj.ApplyCustomUpdateState -= onApplyCustomUpdateState; - } - - nestedHitObjects.Value.Clear(); - ClearNestedHitObjects(); + obj.OnNewResult -= onNewResult; + obj.OnRevertResult -= onRevertResult; + obj.ApplyCustomUpdateState -= onApplyCustomUpdateState; } + nestedHitObjects.Clear(); + ClearNestedHitObjects(); + HitObject.DefaultsApplied -= onDefaultsApplied; OnFree(); From d8aa436e81a1f70160983c882cf2b94d83063677 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 15 Apr 2021 18:11:47 +0900 Subject: [PATCH 1523/1791] Remove usage of Lazy> in NestedPlayfields --- osu.Game/Rulesets/UI/Playfield.cs | 35 ++++++++----------------------- 1 file changed, 9 insertions(+), 26 deletions(-) diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index c40ab4bd94..d55005363c 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.UI var enumerable = HitObjectContainer.Objects; - if (nestedPlayfields.IsValueCreated) + if (nestedPlayfields.Count != 0) enumerable = enumerable.Concat(NestedPlayfields.SelectMany(p => p.AllHitObjects)); return enumerable; @@ -76,9 +76,9 @@ namespace osu.Game.Rulesets.UI /// /// All s nested inside this . /// - public IEnumerable NestedPlayfields => nestedPlayfields.IsValueCreated ? nestedPlayfields.Value : Enumerable.Empty(); + public IEnumerable NestedPlayfields => nestedPlayfields; - private readonly Lazy> nestedPlayfields = new Lazy>(); + private readonly List nestedPlayfields = new List(); /// /// Whether judgements should be displayed by this and and all nested s. @@ -217,7 +217,7 @@ namespace osu.Game.Rulesets.UI otherPlayfield.HitObjectUsageBegan += h => HitObjectUsageBegan?.Invoke(h); otherPlayfield.HitObjectUsageFinished += h => HitObjectUsageFinished?.Invoke(h); - nestedPlayfields.Value.Add(otherPlayfield); + nestedPlayfields.Add(otherPlayfield); } protected override void LoadComplete() @@ -279,12 +279,7 @@ namespace osu.Game.Rulesets.UI return true; } - bool removedFromNested = false; - - if (nestedPlayfields.IsValueCreated) - removedFromNested = nestedPlayfields.Value.Any(p => p.Remove(hitObject)); - - return removedFromNested; + return nestedPlayfields.Any(p => p.Remove(hitObject)); } /// @@ -429,10 +424,7 @@ namespace osu.Game.Rulesets.UI return; } - if (!nestedPlayfields.IsValueCreated) - return; - - foreach (var p in nestedPlayfields.Value) + foreach (var p in nestedPlayfields) p.SetKeepAlive(hitObject, keepAlive); } @@ -444,10 +436,7 @@ namespace osu.Game.Rulesets.UI foreach (var (_, entry) in lifetimeEntryMap) entry.KeepAlive = true; - if (!nestedPlayfields.IsValueCreated) - return; - - foreach (var p in nestedPlayfields.Value) + foreach (var p in nestedPlayfields) p.KeepAllAlive(); } @@ -461,10 +450,7 @@ namespace osu.Game.Rulesets.UI { HitObjectContainer.PastLifetimeExtension = value; - if (!nestedPlayfields.IsValueCreated) - return; - - foreach (var nested in nestedPlayfields.Value) + foreach (var nested in nestedPlayfields) nested.PastLifetimeExtension = value; } } @@ -479,10 +465,7 @@ namespace osu.Game.Rulesets.UI { HitObjectContainer.FutureLifetimeExtension = value; - if (!nestedPlayfields.IsValueCreated) - return; - - foreach (var nested in nestedPlayfields.Value) + foreach (var nested in nestedPlayfields) nested.FutureLifetimeExtension = value; } } From 153ee2551048cbbd25bf1c83856adb3adaa154f1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Apr 2021 18:42:07 +0900 Subject: [PATCH 1524/1791] Update base specifications to a more sane default --- .../Timelines/Summary/Visualisations/PointVisualisation.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs index d647c6bfe8..a4b6b0c392 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs @@ -21,9 +21,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations public PointVisualisation() { - Origin = Anchor.TopCentre; - - RelativePositionAxes = Axes.X; + RelativePositionAxes = Axes.Both; RelativeSizeAxes = Axes.Y; Anchor = Anchor.CentreLeft; From 0dc1577f6804a0b7c3863abfe3db39411ab2b980 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Apr 2021 18:42:30 +0900 Subject: [PATCH 1525/1791] Split out control point visualisation logic and add special kiai duration handling --- .../Parts/ControlPointVisualisation.cs | 30 ++++++++ .../Summary/Parts/EffectPointVisualisation.cs | 72 +++++++++++++++++++ .../Summary/Parts/GroupVisualisation.cs | 51 +++++++++---- .../Timelines/Summary/SummaryTimeline.cs | 1 + 4 files changed, 140 insertions(+), 14 deletions(-) create mode 100644 osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs create mode 100644 osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs new file mode 100644 index 0000000000..a8e41d220a --- /dev/null +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs @@ -0,0 +1,30 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics; +using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations; + +namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts +{ + public class ControlPointVisualisation : PointVisualisation + { + protected readonly ControlPoint Point; + + public ControlPointVisualisation(ControlPoint point) + { + Point = point; + + Height = 0.25f; + Origin = Anchor.TopCentre; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Colour = Point.GetRepresentingColour(colours); + } + } +} diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs new file mode 100644 index 0000000000..801372305b --- /dev/null +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs @@ -0,0 +1,72 @@ +// 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 osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics; +using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations; + +namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts +{ + public class EffectPointVisualisation : CompositeDrawable + { + private readonly EffectControlPoint effect; + private Bindable kiai; + + [Resolved] + private EditorBeatmap beatmap { get; set; } + + [Resolved] + private OsuColour colours { get; set; } + + public EffectPointVisualisation(EffectControlPoint point) + { + RelativePositionAxes = Axes.Both; + RelativeSizeAxes = Axes.Y; + + effect = point; + } + + [BackgroundDependencyLoader] + private void load() + { + kiai = effect.KiaiModeBindable.GetBoundCopy(); + kiai.BindValueChanged(_ => + { + ClearInternal(); + + AddInternal(new ControlPointVisualisation(effect)); + + if (!kiai.Value) + return; + + var endControlPoint = beatmap.ControlPointInfo.EffectPoints.FirstOrDefault(c => c.Time > effect.Time && !c.KiaiMode); + + // handle kiai duration + // eventually this will be simpler when we have control points with durations. + if (endControlPoint != null) + { + RelativeSizeAxes = Axes.Both; + Origin = Anchor.TopLeft; + + Width = (float)(endControlPoint.Time - effect.Time); + + AddInternal(new PointVisualisation + { + RelativeSizeAxes = Axes.Both, + Origin = Anchor.TopLeft, + Width = 1, + Height = 0.25f, + Depth = float.MaxValue, + Colour = effect.GetRepresentingColour(colours).Darken(0.5f), + }); + } + }, true); + } + } +} diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs index 8bc8618479..4629f9b540 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs @@ -1,29 +1,33 @@ // 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 osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; -using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations; -using osuTK.Graphics; namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { - public class GroupVisualisation : PointVisualisation + public class GroupVisualisation : CompositeDrawable { + [Resolved] + private OsuColour colours { get; set; } + public readonly ControlPointGroup Group; private readonly IBindableList controlPoints = new BindableList(); - [Resolved] - private OsuColour colours { get; set; } - public GroupVisualisation(ControlPointGroup group) - : base(group.Time) { + RelativePositionAxes = Axes.X; + + RelativeSizeAxes = Axes.Both; + Origin = Anchor.TopLeft; + Group = group; + X = (float)group.Time; } protected override void LoadComplete() @@ -33,13 +37,32 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts controlPoints.BindTo(Group.ControlPoints); controlPoints.BindCollectionChanged((_, __) => { - if (controlPoints.Count == 0) - { - Colour = Color4.Transparent; - return; - } + ClearInternal(); - Colour = Group.ControlPoints.First().GetRepresentingColour(colours); + if (controlPoints.Count == 0) + return; + + foreach (var point in Group.ControlPoints) + { + switch (point) + { + case TimingControlPoint _: + AddInternal(new ControlPointVisualisation(point) { Y = 0, }); + break; + + case DifficultyControlPoint _: + AddInternal(new ControlPointVisualisation(point) { Y = 0.25f, }); + break; + + case SampleControlPoint _: + AddInternal(new ControlPointVisualisation(point) { Y = 0.5f, }); + break; + + case EffectControlPoint effect: + AddInternal(new EffectPointVisualisation(effect) { Y = 0.75f }); + break; + } + } }, true); } } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs index ae60cd4dd3..e90ae411de 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs @@ -27,6 +27,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary Anchor = Anchor.Centre, Origin = Anchor.BottomCentre, RelativeSizeAxes = Axes.Both, + Y = -10, Height = 0.35f }, new BookmarkPart From 17e021c549f424e9151eefe2351395925e08d365 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Apr 2021 18:45:52 +0900 Subject: [PATCH 1526/1791] 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 b5315c3616..32e236ccd5 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 45b3d5c161..954cf511b6 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 105a6e59c2..09f6033bfe 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 1a987dfbc0712b5ee54ba35de6a88a54fb0aa20e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 15 Apr 2021 21:16:38 +0900 Subject: [PATCH 1527/1791] Fix gameplay cursor showing offscreen --- osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs index ec7751d2b4..44ca5e850f 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs @@ -33,7 +33,6 @@ namespace osu.Game.Rulesets.Osu.UI { Add(cursorScaleContainer = new Container { - RelativePositionAxes = Axes.Both, Child = clickToResumeCursor = new OsuClickToResumeCursor { ResumeRequested = Resume } }); } From 71b06d7e61024503a7e983860ecfd07412d8585f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 15 Apr 2021 15:53:21 +0300 Subject: [PATCH 1528/1791] Simplify ExtendableCircle even more --- .../Components/Timeline/TimelineHitObjectBlueprint.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 1c0d6e5ab6..23069f6079 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -345,7 +345,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline /// public class ExtendableCircle : CompositeDrawable { - private readonly CircularContainer content; + private readonly Circle content; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => content.ReceivePositionalInputAt(screenSpacePos); @@ -354,19 +354,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline public ExtendableCircle() { Padding = new MarginPadding { Horizontal = -circle_size / 2f }; - InternalChild = content = new CircularContainer + InternalChild = content = new Circle { RelativeSizeAxes = Axes.Both, - Masking = true, EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Shadow, Radius = 5, Colour = Color4.Black.Opacity(0.4f) - }, - Child = new Box - { - RelativeSizeAxes = Axes.Both } }; } From 6f3158e8c88b0d82541b9990965756ddc74cdaf6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 15 Apr 2021 23:24:13 +0900 Subject: [PATCH 1529/1791] Increase multiplier to 1.8 --- osu.Game/Overlays/Volume/VolumeMeter.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index 57485946c1..202eac93ea 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -237,6 +237,7 @@ namespace osu.Game.Overlays.Volume private double accelerationModifier = 1; private const double max_acceleration = 5; + private const double acceleration_multiplier = 1.8; private ScheduledDelegate accelerationDebounce; @@ -250,7 +251,7 @@ namespace osu.Game.Overlays.Volume accelerationDebounce = Scheduler.AddDelayed(resetAcceleration, 150); delta *= accelerationModifier; - accelerationModifier = Math.Min(max_acceleration, accelerationModifier * 1.2f); + accelerationModifier = Math.Min(max_acceleration, accelerationModifier * acceleration_multiplier); var precision = Bindable.Precision; From 34859a476012ca6895a4e0216ff06f85f48a074f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 15 Apr 2021 23:37:05 +0900 Subject: [PATCH 1530/1791] Invalidate drawnode on change --- osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index b55575696e..7f86e9daf7 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -32,7 +32,17 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private double timeOffset; private float time; - protected Anchor TrailOrigin = Anchor.Centre; + private Anchor trailOrigin = Anchor.Centre; + + protected Anchor TrailOrigin + { + get => trailOrigin; + set + { + trailOrigin = value; + Invalidate(Invalidation.DrawNode); + } + } public CursorTrail() { From 15d48a924b996871ac51e4b23f1a555f343e281e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Apr 2021 00:58:28 +0900 Subject: [PATCH 1531/1791] Set the timeline's height to a sane non-zero default This isn't required but makes the initial appearance animation nicer. --- osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 55fb557474..7e11cfb271 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -65,6 +65,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline this.userContent = userContent; RelativeSizeAxes = Axes.X; + Height = timeline_height; ZoomDuration = 200; ZoomEasing = Easing.OutQuint; From c2340f1fe886ef77f3afa5a72f0b18c675f5807c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Apr 2021 00:59:01 +0900 Subject: [PATCH 1532/1791] Move zoom settings back to run in a non-loaded state --- .../Compose/Components/Timeline/Timeline.cs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 7e11cfb271..621a24c67d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -128,7 +128,21 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline }); waveformOpacity = config.GetBindable(OsuSetting.EditorWaveformOpacity); + Beatmap.BindTo(beatmap); + Beatmap.BindValueChanged(b => + { + waveform.Waveform = b.NewValue.Waveform; + track = b.NewValue.Track; + + // todo: i don't think this is safe, the track may not be loaded yet. + if (track.Length > 0) + { + MaxZoom = getZoomLevelForVisibleMilliseconds(500); + MinZoom = getZoomLevelForVisibleMilliseconds(10000); + Zoom = getZoomLevelForVisibleMilliseconds(2000); + } + }, true); } protected override void LoadComplete() @@ -158,20 +172,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline mainContent.Delay(180).MoveToY(0, 200, Easing.OutQuint); } }, true); - - Beatmap.BindValueChanged(b => - { - waveform.Waveform = b.NewValue.Waveform; - track = b.NewValue.Track; - - // todo: i don't think this is safe, the track may not be loaded yet. - if (track.Length > 0) - { - MaxZoom = getZoomLevelForVisibleMilliseconds(500); - MinZoom = getZoomLevelForVisibleMilliseconds(10000); - Zoom = getZoomLevelForVisibleMilliseconds(2000); - } - }, true); } private void updateWaveformOpacity() => From 42c066e6f2491f539534c02ef44e2760f9e19f19 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Apr 2021 13:38:55 +0900 Subject: [PATCH 1533/1791] Fix slider not displaying in timeline during zero-duration placement --- .../Compose/Components/Timeline/TimelineHitObjectBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 23069f6079..bea1aa2e3a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -133,7 +133,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline var comboColours = skin.GetConfig>(GlobalSkinColours.ComboColours)?.Value ?? Array.Empty(); var comboColour = combo.GetComboColour(comboColours); - if (HitObject is IHasDuration) + if (HitObject is IHasDuration duration && duration.Duration > 0) circle.Colour = ColourInfo.GradientHorizontal(comboColour, comboColour.Lighten(0.4f)); else circle.Colour = comboColour; From 5c0ef55691f4debd21c0b2c1c094ec9a73ad7642 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Apr 2021 14:09:35 +0900 Subject: [PATCH 1534/1791] Rename `SliderPlacementState` to make way for more generic version --- .../Sliders/SliderPlacementBlueprint.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 16e2a52279..efa249694a 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private InputManager inputManager; - private PlacementState state; + private SliderPlacementState state; private PathControlPoint segmentStart; private PathControlPoint cursor; private int currentSegmentLength; @@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders controlPointVisualiser = new PathControlPointVisualiser(HitObject, false) }; - setState(PlacementState.Initial); + setState(SliderPlacementState.Initial); } protected override void LoadComplete() @@ -73,12 +73,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders switch (state) { - case PlacementState.Initial: + case SliderPlacementState.Initial: BeginPlacement(); HitObject.Position = ToLocalSpace(result.ScreenSpacePosition); break; - case PlacementState.Body: + case SliderPlacementState.Body: updateCursor(); break; } @@ -91,11 +91,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders switch (state) { - case PlacementState.Initial: + case SliderPlacementState.Initial: beginCurve(); break; - case PlacementState.Body: + case SliderPlacementState.Body: if (canPlaceNewControlPoint(out var lastPoint)) { // Place a new point by detatching the current cursor. @@ -121,7 +121,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders protected override void OnMouseUp(MouseUpEvent e) { - if (state == PlacementState.Body && e.Button == MouseButton.Right) + if (state == SliderPlacementState.Body && e.Button == MouseButton.Right) endCurve(); base.OnMouseUp(e); } @@ -129,7 +129,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private void beginCurve() { BeginPlacement(commitStart: true); - setState(PlacementState.Body); + setState(SliderPlacementState.Body); } private void endCurve() @@ -219,12 +219,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders tailCirclePiece.UpdateFrom(HitObject.TailCircle); } - private void setState(PlacementState newState) + private void setState(SliderPlacementState newState) { state = newState; } - private enum PlacementState + private enum SliderPlacementState { Initial, Body, From 119c9b4294a2b5574b6f57bec7620562b4ae19ef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Apr 2021 14:10:21 +0900 Subject: [PATCH 1535/1791] Fix placement blueprints not being correctly removed after a rolled back placement --- .../Blueprints/HoldNotePlacementBlueprint.cs | 2 +- .../Blueprints/ManiaPlacementBlueprint.cs | 2 +- .../Blueprints/TaikoSpanPlacementBlueprint.cs | 2 +- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 29 +++++++++++++++---- .../Components/ComposeBlueprintContainer.cs | 27 ++++++++++++----- 5 files changed, 45 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs index 1f92929392..a13afdfffe 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs @@ -82,7 +82,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints { base.UpdateTimeAndPosition(result); - if (PlacementActive) + if (PlacementActive == PlacementState.Active) { if (result.Time is double endTime) { diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs index 5e09054667..8f25668dd0 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs @@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints { base.UpdateTimeAndPosition(result); - if (!PlacementActive) + if (PlacementActive == PlacementState.Waiting) Column = result.Playfield as Column; } } diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs index e53b331f46..59249e6bf4 100644 --- a/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs @@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints { base.UpdateTimeAndPosition(result); - if (PlacementActive) + if (PlacementActive == PlacementState.Active) { if (result.Time is double dragTime) { diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index bfff93e7c5..6c1cd01796 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Edit /// /// Whether the is currently mid-placement, but has not necessarily finished being placed. /// - public bool PlacementActive { get; private set; } + public PlacementState PlacementActive { get; private set; } /// /// The that is being placed. @@ -72,7 +72,8 @@ namespace osu.Game.Rulesets.Edit protected void BeginPlacement(bool commitStart = false) { placementHandler.BeginPlacement(HitObject); - PlacementActive |= commitStart; + if (commitStart) + PlacementActive = PlacementState.Active; } /// @@ -82,10 +83,19 @@ namespace osu.Game.Rulesets.Edit /// Whether the object should be committed. public void EndPlacement(bool commit) { - if (!PlacementActive) - BeginPlacement(); + switch (PlacementActive) + { + case PlacementState.Finished: + return; + + case PlacementState.Waiting: + // ensure placement was started before ending to make state handling simpler. + BeginPlacement(); + break; + } + placementHandler.EndPlacement(HitObject, commit); - PlacementActive = false; + PlacementActive = PlacementState.Finished; } /// @@ -94,7 +104,7 @@ namespace osu.Game.Rulesets.Edit /// The snap result information. public virtual void UpdateTimeAndPosition(SnapResult result) { - if (!PlacementActive) + if (PlacementActive == PlacementState.Waiting) HitObject.StartTime = result.Time ?? EditorClock?.CurrentTime ?? Time.Current; } @@ -125,5 +135,12 @@ namespace osu.Game.Rulesets.Edit return false; } } + + public enum PlacementState + { + Waiting, + Active, + Finished + } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 5ab557804e..b0a6a091f0 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -196,7 +196,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private void refreshTool() { removePlacement(); - createPlacement(); + ensurePlacementCreated(); } private void updatePlacementPosition() @@ -215,15 +215,26 @@ namespace osu.Game.Screens.Edit.Compose.Components { base.Update(); - if (Composer.CursorInPlacementArea) - createPlacement(); - else if (currentPlacement?.PlacementActive == false) - removePlacement(); - if (currentPlacement != null) { - updatePlacementPosition(); + switch (currentPlacement.PlacementActive) + { + case PlacementBlueprint.PlacementState.Waiting: + if (!Composer.CursorInPlacementArea) + removePlacement(); + break; + + case PlacementBlueprint.PlacementState.Finished: + removePlacement(); + break; + } } + + if (Composer.CursorInPlacementArea) + ensurePlacementCreated(); + + if (currentPlacement != null) + updatePlacementPosition(); } protected sealed override SelectionBlueprint CreateBlueprintFor(HitObject hitObject) @@ -249,7 +260,7 @@ namespace osu.Game.Screens.Edit.Compose.Components NewCombo.Value = TernaryState.False; } - private void createPlacement() + private void ensurePlacementCreated() { if (currentPlacement != null) return; From 5652490d61c4ee2c5b046e8baf7e5524303a1a31 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 16 Apr 2021 14:11:55 +0900 Subject: [PATCH 1536/1791] Fix OnUserBeganPlaying not being invoked if already watching --- .../Visual/Gameplay/TestSceneSpectator.cs | 24 ++++++++++++++++ .../Spectator/SpectatorStreamingClient.cs | 28 +++++++++++++++++-- 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 397b37718d..ea66144b21 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.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.Collections.Generic; using System.Linq; using System.Threading; @@ -204,6 +205,29 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("screen didn't change", () => Stack.CurrentScreen is SoloSpectator); } + [Test] + public void OnUserBeganPlayingCallbackInvokedOnNewAdd() + { + bool callbackInvoked = false; + Action callbackAction = (_, __) => callbackInvoked = true; + + AddStep("bind first event", () => testSpectatorStreamingClient.OnUserBeganPlaying += callbackAction); + start(); + AddAssert("callback invoked", () => callbackInvoked); + + AddStep("reset", () => + { + testSpectatorStreamingClient.OnUserBeganPlaying -= callbackAction; + callbackInvoked = false; + }); + + AddStep("bind event again", () => testSpectatorStreamingClient.OnUserBeganPlaying += callbackAction); + AddAssert("callback invoked", () => callbackInvoked); + + // Don't leave the event bound if test run succeeded. + AddStep("reset", () => testSpectatorStreamingClient.OnUserBeganPlaying -= callbackAction); + } + private OsuFramedReplayInputHandler replayHandler => (OsuFramedReplayInputHandler)Stack.ChildrenOfType().First().ReplayInputHandler; diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 3a586874fe..7bea49e102 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -60,6 +60,7 @@ namespace osu.Game.Online.Spectator private IBindable> currentMods { get; set; } private readonly SpectatorState currentState = new SpectatorState(); + private readonly Dictionary currentUserStates = new Dictionary(); private bool isPlaying; @@ -68,10 +69,25 @@ namespace osu.Game.Online.Spectator /// public event Action OnNewFrames; + private event Action onUserBeganPlaying; + /// - /// Called whenever a user starts a play session. + /// Called whenever a user starts a play session, or immediately if the user is being watched and currently in a play session. /// - public event Action OnUserBeganPlaying; + public event Action OnUserBeganPlaying + { + add + { + onUserBeganPlaying += value; + + lock (userLock) + { + foreach (var (userId, state) in currentUserStates) + value?.Invoke(userId, state); + } + } + remove => onUserBeganPlaying -= value; + } /// /// Called whenever a user finishes a play session. @@ -134,7 +150,10 @@ namespace osu.Game.Online.Spectator if (!playingUsers.Contains(userId)) playingUsers.Add(userId); - OnUserBeganPlaying?.Invoke(userId, state); + lock (userLock) + currentUserStates[userId] = state; + + onUserBeganPlaying?.Invoke(userId, state); return Task.CompletedTask; } @@ -143,6 +162,9 @@ namespace osu.Game.Online.Spectator { playingUsers.Remove(userId); + lock (userLock) + currentUserStates.Remove(userId); + OnUserFinishedPlaying?.Invoke(userId, state); return Task.CompletedTask; From 936bde28a35ef8ea0ba9f4af801c09e8ea6b4bcc Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 16 Apr 2021 13:59:11 +0900 Subject: [PATCH 1537/1791] Remove manual handling of IsActive in RulesetInputManager Now it is supported in framework --- osu.Game/Rulesets/UI/RulesetInputManager.cs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 5ab09f9516..d6f002ea2c 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -2,7 +2,6 @@ // 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.Bindables; @@ -11,7 +10,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; -using osu.Framework.Input.StateChanges; using osu.Framework.Input.StateChanges.Events; using osu.Framework.Input.States; using osu.Game.Configuration; @@ -102,17 +100,6 @@ namespace osu.Game.Rulesets.UI #endregion - // to avoid allocation - private readonly List emptyInputList = new List(); - - protected override List GetPendingInputs() - { - if (replayInputHandler?.IsActive == false) - return emptyInputList; - - return base.GetPendingInputs(); - } - #region Setting application (disables etc.) private Bindable mouseDisabled; From 84bc81a6deda614ce407730d62750c7a52195671 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 16 Apr 2021 12:31:32 +0900 Subject: [PATCH 1538/1791] Make FramedReplayInputHandler.CurrentTime non-null --- .../EmptyFreeformFramedReplayInputHandler.cs | 5 +---- .../Replays/PippidonFramedReplayInputHandler.cs | 5 +---- .../Replays/CatchFramedReplayInputHandler.cs | 5 +---- .../Replays/OsuFramedReplayInputHandler.cs | 5 +---- .../Rulesets/Replays/FramedReplayInputHandler.cs | 13 +++++++------ 5 files changed, 11 insertions(+), 22 deletions(-) diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformFramedReplayInputHandler.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformFramedReplayInputHandler.cs index f25ea6ec62..b7e2031bb9 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformFramedReplayInputHandler.cs +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformFramedReplayInputHandler.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using osu.Framework.Input.StateChanges; using osu.Framework.Utils; @@ -30,9 +29,7 @@ namespace osu.Game.Rulesets.EmptyFreeform.Replays if (frame == null) return Vector2.Zero; - Debug.Assert(CurrentTime != null); - - return Interpolation.ValueAt(CurrentTime.Value, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time); + return Interpolation.ValueAt(CurrentTime, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time); } } diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs index 18efa6b885..69c7df5fac 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.Diagnostics; using osu.Framework.Input.StateChanges; using osu.Framework.Utils; using osu.Game.Replays; @@ -29,9 +28,7 @@ namespace osu.Game.Rulesets.Pippidon.Replays if (frame == null) return Vector2.Zero; - Debug.Assert(CurrentTime != null); - - return NextFrame != null ? Interpolation.ValueAt(CurrentTime.Value, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time) : frame.Position; + return NextFrame != null ? Interpolation.ValueAt(CurrentTime, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time) : frame.Position; } } diff --git a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs index 99d899db80..5fea855a16 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using osu.Framework.Input.StateChanges; using osu.Framework.Utils; @@ -29,9 +28,7 @@ namespace osu.Game.Rulesets.Catch.Replays if (frame == null) return null; - Debug.Assert(CurrentTime != null); - - return NextFrame != null ? Interpolation.ValueAt(CurrentTime.Value, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time) : frame.Position; + return NextFrame != null ? Interpolation.ValueAt(CurrentTime, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time) : frame.Position; } } diff --git a/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs b/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs index cf48dc053f..6ac0bbd045 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using osu.Framework.Input.StateChanges; using osu.Framework.Utils; @@ -30,9 +29,7 @@ namespace osu.Game.Rulesets.Osu.Replays if (frame == null) return null; - Debug.Assert(CurrentTime != null); - - return NextFrame != null ? Interpolation.ValueAt(CurrentTime.Value, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time) : frame.Position; + return NextFrame != null ? Interpolation.ValueAt(CurrentTime, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time) : frame.Position; } } diff --git a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs index 279087ead9..0442acebe5 100644 --- a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs +++ b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.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. +#nullable enable + using System; using System.Collections.Generic; using JetBrains.Annotations; @@ -32,7 +34,7 @@ namespace osu.Game.Rulesets.Replays /// /// Returns null if the current time is strictly before the first frame. /// The replay is empty. - public TFrame CurrentFrame + public TFrame? CurrentFrame { get { @@ -49,7 +51,7 @@ namespace osu.Game.Rulesets.Replays /// /// Returns null if the current frame is the last frame. /// The replay is empty. - public TFrame NextFrame + public TFrame? NextFrame { get { @@ -69,8 +71,7 @@ namespace osu.Game.Rulesets.Replays // This input handler should be enabled only if there is at least one replay frame. public override bool IsActive => HasFrames; - // Can make it non-null but that is a breaking change. - protected double? CurrentTime { get; private set; } + protected double CurrentTime { get; private set; } protected virtual double AllowedImportantTimeSpan => sixty_frame_time * 1.2; @@ -101,7 +102,7 @@ namespace osu.Game.Rulesets.Replays return false; return IsImportant(CurrentFrame) && // a button is in a pressed state - Math.Abs(CurrentTime - NextFrame.Time ?? 0) <= AllowedImportantTimeSpan; // the next frame is within an allowable time span + Math.Abs(CurrentTime - NextFrame?.Time ?? 0) <= AllowedImportantTimeSpan; // the next frame is within an allowable time span } } @@ -151,7 +152,7 @@ namespace osu.Game.Rulesets.Replays CurrentTime = Math.Clamp(time, frameStart, frameEnd); // In an important section, a mid-frame time cannot be used and a null is returned instead. - return inImportantSection && frameStart < time && time < frameEnd ? null : CurrentTime; + return inImportantSection && frameStart < time && time < frameEnd ? null : (double?)CurrentTime; } private double getFrameTime(int index) From 91c7d8d26cf2143cb85c53cf9770676947ab57e4 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 16 Apr 2021 12:53:58 +0900 Subject: [PATCH 1539/1791] Introduce `StartFrame` and `EndFrame` to simplify the replay interpolation code --- .../EmptyFreeformFramedReplayInputHandler.cs | 18 ++-------- .../PippidonFramedReplayInputHandler.cs | 18 ++-------- .../Replays/CatchFramedReplayInputHandler.cs | 17 ++-------- .../Replays/OsuFramedReplayInputHandler.cs | 18 ++-------- .../Replays/FramedReplayInputHandler.cs | 34 ++++++++++++------- 5 files changed, 33 insertions(+), 72 deletions(-) diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformFramedReplayInputHandler.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformFramedReplayInputHandler.cs index b7e2031bb9..cc4483de31 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformFramedReplayInputHandler.cs +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformFramedReplayInputHandler.cs @@ -7,7 +7,6 @@ using osu.Framework.Input.StateChanges; using osu.Framework.Utils; using osu.Game.Replays; using osu.Game.Rulesets.Replays; -using osuTK; namespace osu.Game.Rulesets.EmptyFreeform.Replays { @@ -20,24 +19,13 @@ namespace osu.Game.Rulesets.EmptyFreeform.Replays protected override bool IsImportant(EmptyFreeformReplayFrame frame) => frame.Actions.Any(); - protected Vector2 Position - { - get - { - var frame = CurrentFrame; - - if (frame == null) - return Vector2.Zero; - - return Interpolation.ValueAt(CurrentTime, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time); - } - } - public override void CollectPendingInputs(List inputs) { + var position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time); + inputs.Add(new MousePositionAbsoluteInput { - Position = GamefieldToScreenSpace(Position), + Position = GamefieldToScreenSpace(position), }); inputs.Add(new ReplayState { diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs index 69c7df5fac..e005346e1e 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs @@ -6,7 +6,6 @@ using osu.Framework.Input.StateChanges; using osu.Framework.Utils; using osu.Game.Replays; using osu.Game.Rulesets.Replays; -using osuTK; namespace osu.Game.Rulesets.Pippidon.Replays { @@ -19,24 +18,13 @@ namespace osu.Game.Rulesets.Pippidon.Replays protected override bool IsImportant(PippidonReplayFrame frame) => true; - protected Vector2 Position - { - get - { - var frame = CurrentFrame; - - if (frame == null) - return Vector2.Zero; - - return NextFrame != null ? Interpolation.ValueAt(CurrentTime, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time) : frame.Position; - } - } - public override void CollectPendingInputs(List inputs) { + var position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time); + inputs.Add(new MousePositionAbsoluteInput { - Position = GamefieldToScreenSpace(Position) + Position = GamefieldToScreenSpace(position) }); } } diff --git a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs index 5fea855a16..137328b1c3 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs @@ -19,27 +19,14 @@ namespace osu.Game.Rulesets.Catch.Replays protected override bool IsImportant(CatchReplayFrame frame) => frame.Actions.Any(); - protected float? Position - { - get - { - var frame = CurrentFrame; - - if (frame == null) - return null; - - return NextFrame != null ? Interpolation.ValueAt(CurrentTime, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time) : frame.Position; - } - } - public override void CollectPendingInputs(List inputs) { - if (!Position.HasValue) return; + var position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time); inputs.Add(new CatchReplayState { PressedActions = CurrentFrame?.Actions ?? new List(), - CatcherX = Position.Value + CatcherX = position }); } diff --git a/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs b/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs index 6ac0bbd045..7d696dfb79 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs @@ -7,7 +7,6 @@ using osu.Framework.Input.StateChanges; using osu.Framework.Utils; using osu.Game.Replays; using osu.Game.Rulesets.Replays; -using osuTK; namespace osu.Game.Rulesets.Osu.Replays { @@ -20,22 +19,11 @@ namespace osu.Game.Rulesets.Osu.Replays protected override bool IsImportant(OsuReplayFrame frame) => frame.Actions.Any(); - protected Vector2? Position - { - get - { - var frame = CurrentFrame; - - if (frame == null) - return null; - - return NextFrame != null ? Interpolation.ValueAt(CurrentTime, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time) : frame.Position; - } - } - public override void CollectPendingInputs(List inputs) { - inputs.Add(new MousePositionAbsoluteInput { Position = GamefieldToScreenSpace(Position ?? Vector2.Zero) }); + var position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time); + + inputs.Add(new MousePositionAbsoluteInput { Position = GamefieldToScreenSpace(position) }); inputs.Add(new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() }); } } diff --git a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs index 0442acebe5..0f25a45177 100644 --- a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs +++ b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs @@ -33,32 +33,42 @@ namespace osu.Game.Rulesets.Replays /// The current time is always between the start and the end time of the current frame. /// /// Returns null if the current time is strictly before the first frame. + public TFrame? CurrentFrame => currentFrameIndex == -1 ? null : (TFrame)Frames[currentFrameIndex]; + + /// + /// The next frame of the replay. + /// The start time of is always greater or equal to the start time of regardless of the seeking direction. + /// + /// Returns null if the current frame is the last frame. + public TFrame? NextFrame => currentFrameIndex == Frames.Count - 1 ? null : (TFrame)Frames[currentFrameIndex + 1]; + + /// + /// The frame for the start value of the interpolation of the replay movement. + /// /// The replay is empty. - public TFrame? CurrentFrame + public TFrame StartFrame { get { if (!HasFrames) - throw new InvalidOperationException($"Attempted to get {nameof(CurrentFrame)} of an empty replay"); + throw new InvalidOperationException($"Attempted to get {nameof(StartFrame)} of an empty replay"); - return currentFrameIndex == -1 ? null : (TFrame)Frames[currentFrameIndex]; + return (TFrame)Frames[Math.Max(0, currentFrameIndex)]; } } /// - /// The next frame of the replay. - /// The start time is always greater or equal to the start time of regardless of the seeking direction. + /// The frame for the end value of the interpolation of the replay movement. /// - /// Returns null if the current frame is the last frame. /// The replay is empty. - public TFrame? NextFrame + public TFrame EndFrame { get { if (!HasFrames) - throw new InvalidOperationException($"Attempted to get {nameof(NextFrame)} of an empty replay"); + throw new InvalidOperationException($"Attempted to get {nameof(EndFrame)} of an empty replay"); - return currentFrameIndex == Frames.Count - 1 ? null : (TFrame)Frames[currentFrameIndex + 1]; + return (TFrame)Frames[Math.Min(currentFrameIndex + 1, Frames.Count - 1)]; } } @@ -98,11 +108,11 @@ namespace osu.Game.Rulesets.Replays { get { - if (!HasFrames || !FrameAccuratePlayback || CurrentFrame == null) + if (!HasFrames || !FrameAccuratePlayback || currentFrameIndex == -1) return false; - return IsImportant(CurrentFrame) && // a button is in a pressed state - Math.Abs(CurrentTime - NextFrame?.Time ?? 0) <= AllowedImportantTimeSpan; // the next frame is within an allowable time span + return IsImportant(StartFrame) && // a button is in a pressed state + Math.Abs(CurrentTime - EndFrame.Time) <= AllowedImportantTimeSpan; // the next frame is within an allowable time span } } From a965e8a75d1ae770d6e3d1a4548132e3b6abc2ce Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 16 Apr 2021 13:06:02 +0900 Subject: [PATCH 1540/1791] Remove AutoGenerator workaround of now-fixed issue --- .../Replays/CatchAutoGenerator.cs | 4 ---- .../TestSceneAutoGeneration.cs | 16 ++++++++-------- .../Replays/ManiaAutoGenerator.cs | 4 ---- .../Replays/OsuAutoGenerator.cs | 2 -- .../Replays/TaikoAutoGenerator.cs | 1 - 5 files changed, 8 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs index 64ded8e94f..10230b6b78 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs @@ -125,10 +125,6 @@ namespace osu.Game.Rulesets.Catch.Replays private void addFrame(double time, float? position = null, bool dashing = false) { - // todo: can be removed once FramedReplayInputHandler correctly handles rewinding before first frame. - if (Replay.Frames.Count == 0) - Replay.Frames.Add(new CatchReplayFrame(time - 1, position, false, null)); - var last = currentFrame; currentFrame = new CatchReplayFrame(time, position, dashing, last); Replay.Frames.Add(currentFrame); diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs index 399a46aa77..cffec3dfd5 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Tests /// /// The number of frames which are generated at the start of a replay regardless of hitobject content. /// - private const int frame_offset = 1; + private const int frame_offset = 0; [Test] public void TestSingleNote() @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Mania.Tests var generated = new ManiaAutoGenerator(beatmap).Generate(); - Assert.IsTrue(generated.Frames.Count == frame_offset + 2, "Replay must have 3 frames"); + Assert.AreEqual(generated.Frames.Count, frame_offset + 2, "Incorrect number of frames"); Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time"); Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect release time"); Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Special1), "Special1 has not been pressed"); @@ -54,7 +54,7 @@ namespace osu.Game.Rulesets.Mania.Tests var generated = new ManiaAutoGenerator(beatmap).Generate(); - Assert.IsTrue(generated.Frames.Count == frame_offset + 2, "Replay must have 3 frames"); + Assert.AreEqual(generated.Frames.Count, frame_offset + 2, "Incorrect number of frames"); Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit 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"); @@ -74,7 +74,7 @@ namespace osu.Game.Rulesets.Mania.Tests var generated = new ManiaAutoGenerator(beatmap).Generate(); - Assert.IsTrue(generated.Frames.Count == frame_offset + 2, "Replay must have 3 frames"); + Assert.AreEqual(generated.Frames.Count, frame_offset + 2, "Incorrect number of frames"); Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time"); Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, 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"); @@ -96,7 +96,7 @@ namespace osu.Game.Rulesets.Mania.Tests var generated = new ManiaAutoGenerator(beatmap).Generate(); - Assert.IsTrue(generated.Frames.Count == frame_offset + 2, "Replay must have 3 frames"); + Assert.AreEqual(generated.Frames.Count, frame_offset + 2, "Incorrect number of frames"); Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time"); Assert.AreEqual(3000, generated.Frames[frame_offset + 1].Time, "Incorrect release time"); @@ -119,7 +119,7 @@ namespace osu.Game.Rulesets.Mania.Tests var generated = new ManiaAutoGenerator(beatmap).Generate(); - Assert.IsTrue(generated.Frames.Count == frame_offset + 4, "Replay must have 4 generated frames"); + Assert.AreEqual(generated.Frames.Count, frame_offset + 4, "Incorrect number of frames"); Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect first note hit time"); Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect first note release time"); Assert.AreEqual(2000, generated.Frames[frame_offset + 2].Time, "Incorrect second note hit time"); @@ -146,7 +146,7 @@ namespace osu.Game.Rulesets.Mania.Tests var generated = new ManiaAutoGenerator(beatmap).Generate(); - Assert.IsTrue(generated.Frames.Count == frame_offset + 4, "Replay must have 4 generated frames"); + Assert.AreEqual(generated.Frames.Count, frame_offset + 4, "Incorrect number of frames"); Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect first note hit 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"); @@ -173,7 +173,7 @@ namespace osu.Game.Rulesets.Mania.Tests var generated = new ManiaAutoGenerator(beatmap).Generate(); - Assert.IsTrue(generated.Frames.Count == frame_offset + 3, "Replay must have 3 generated frames"); + Assert.AreEqual(generated.Frames.Count, frame_offset + 3, "Incorrect number of frames"); Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect first note hit time"); Assert.AreEqual(3000, generated.Frames[frame_offset + 1].Time, "Incorrect second note press time + first note release time"); Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 2].Time, "Incorrect second note release time"); diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs index 7c51d58b74..ada84dfac2 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs @@ -70,10 +70,6 @@ namespace osu.Game.Rulesets.Mania.Replays } } - // todo: can be removed once FramedReplayInputHandler correctly handles rewinding before first frame. - if (Replay.Frames.Count == 0) - Replay.Frames.Add(new ManiaReplayFrame(group.First().Time - 1)); - Replay.Frames.Add(new ManiaReplayFrame(group.First().Time, actions.ToArray())); } diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index 693943a08a..7b0cf651c8 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -71,8 +71,6 @@ namespace osu.Game.Rulesets.Osu.Replays buttonIndex = 0; - AddFrameToReplay(new OsuReplayFrame(-100000, new Vector2(256, 500))); - AddFrameToReplay(new OsuReplayFrame(Beatmap.HitObjects[0].StartTime - 1500, new Vector2(256, 500))); AddFrameToReplay(new OsuReplayFrame(Beatmap.HitObjects[0].StartTime - 1500, new Vector2(256, 500))); for (int i = 0; i < Beatmap.HitObjects.Count; i++) diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs index a3dbe672a4..fa0134aa94 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs @@ -35,7 +35,6 @@ namespace osu.Game.Rulesets.Taiko.Replays bool hitButton = true; - Frames.Add(new TaikoReplayFrame(-100000)); Frames.Add(new TaikoReplayFrame(Beatmap.HitObjects[0].StartTime - 1000)); for (int i = 0; i < Beatmap.HitObjects.Count; i++) From 965a1ead36c845a332ed03113717e3a116e0d0a3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Apr 2021 14:38:30 +0900 Subject: [PATCH 1541/1791] Disallow zero-length slider blueprint placements --- .../Editor/TestSceneSliderPlacementBlueprint.cs | 4 +--- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs index d2c37061f0..8235e1bc79 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs @@ -41,9 +41,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor addClickStep(MouseButton.Left); addClickStep(MouseButton.Right); - assertPlaced(true); - assertLength(0); - assertControlPointType(0, PathType.Linear); + assertPlaced(false); } [Test] diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index efa249694a..07166a4b74 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -135,7 +135,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private void endCurve() { updateSlider(); - EndPlacement(true); + EndPlacement(HitObject.Path.ExpectedDistance?.Value > 0); } protected override void Update() From d1c72f5e133cad392580c9dcd80f8fe2c9987816 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 16 Apr 2021 15:10:53 +0900 Subject: [PATCH 1542/1791] Apply changes resulting from IBindable interface updates --- osu.Game/Rulesets/Mods/Mod.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 4879590e24..eab886c86e 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Reflection; using Newtonsoft.Json; using osu.Framework.Bindables; +using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics.Sprites; using osu.Framework.Testing; using osu.Game.Configuration; @@ -170,7 +171,12 @@ namespace osu.Game.Rulesets.Mods target.UnbindFrom(sourceBindable); } else - target.Parse(source); + { + if (!(target is IParseable parseable)) + throw new InvalidOperationException($"Bindable type {target.GetType().ReadableName()} is not IParseable."); + + parseable.Parse(source); + } } public bool Equals(IMod other) => other is Mod them && Equals(them); From 8c4804dd7a63e69540dcb22c1e800d9dc9917d39 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 16 Apr 2021 15:40:06 +0900 Subject: [PATCH 1543/1791] Use nameof --- osu.Game/Rulesets/Mods/Mod.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index eab886c86e..7f48888abe 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -173,7 +173,7 @@ namespace osu.Game.Rulesets.Mods else { if (!(target is IParseable parseable)) - throw new InvalidOperationException($"Bindable type {target.GetType().ReadableName()} is not IParseable."); + throw new InvalidOperationException($"Bindable type {target.GetType().ReadableName()} is not {nameof(IParseable)}."); parseable.Parse(source); } From d38e294d96e8a887ab188578925195f159c52ed6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Apr 2021 15:22:32 +0900 Subject: [PATCH 1544/1791] Centralise length validation function --- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 2 +- osu.Game/Rulesets/Objects/SliderPath.cs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 07166a4b74..77ea3b05dc 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -135,7 +135,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private void endCurve() { updateSlider(); - EndPlacement(HitObject.Path.ExpectedDistance?.Value > 0); + EndPlacement(HitObject.Path.HasValidLength); } protected override void Update() diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index e64298f98d..55ef0bc5f6 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -30,6 +30,8 @@ namespace osu.Game.Rulesets.Objects /// public readonly Bindable ExpectedDistance = new Bindable(); + public bool HasValidLength => Distance > 0; + /// /// The control points of the path. /// From 2949a6bbdc1574473aee2d86eabd3534e46c32e5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Apr 2021 15:22:58 +0900 Subject: [PATCH 1545/1791] Handle control point drag revert --- .../Sliders/Components/PathControlPointPiece.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) 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 ce9580d0f4..48e4db11ca 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -185,6 +185,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components protected override void OnDrag(DragEvent e) { + Vector2[] oldControlPoints = slider.Path.ControlPoints.Select(cp => cp.Position.Value).ToArray(); + var oldPosition = slider.Position; + var oldStartTime = slider.StartTime; + if (ControlPoint == slider.Path.ControlPoints[0]) { // Special handling for the head control point - the position of the slider changes which means the snapped position and time have to be taken into account @@ -202,6 +206,16 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components else ControlPoint.Position.Value = dragStartPosition + (e.MousePosition - e.MouseDownPosition); + if (!slider.Path.HasValidLength) + { + for (var i = 0; i < slider.Path.ControlPoints.Count; i++) + slider.Path.ControlPoints[i].Position.Value = oldControlPoints[i]; + + slider.Position = oldPosition; + slider.StartTime = oldStartTime; + return; + } + // Maintain the path type in case it got defaulted to bezier at some point during the drag. PointsInSegment[0].Type.Value = dragPathType; } From 89373638be7c1b17e119e0a5a43d8c83a3798e65 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Apr 2021 15:23:16 +0900 Subject: [PATCH 1546/1791] Handle control point deletion when the resulting slider would be too short to be useful --- .../Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index ba9bb3c485..88fcb1e715 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -215,7 +215,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders } // If there are 0 or 1 remaining control points, the slider is in a degenerate (single point) form and should be deleted - if (controlPoints.Count <= 1) + if (controlPoints.Count <= 1 || !slider.HitObject.Path.HasValidLength) { placementHandler?.Delete(HitObject); return; From ff408b852e6f616c8ee1a1ead405f1b76489e713 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Apr 2021 15:23:27 +0900 Subject: [PATCH 1547/1791] Handle scaling a slider below minimum length --- osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 5a84bd6163..941cde5540 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -226,7 +226,7 @@ namespace osu.Game.Rulesets.Osu.Edit Quad scaledQuad = getSurroundingQuad(new OsuHitObject[] { slider }); (bool xInBounds, bool yInBounds) = isQuadInBounds(scaledQuad); - if (xInBounds && yInBounds) + if (xInBounds && yInBounds && slider.Path.HasValidLength) return; foreach (var point in slider.Path.ControlPoints) From c59906e9253fefed8c72c17b8434c3089c3cb44f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Apr 2021 15:55:33 +0900 Subject: [PATCH 1548/1791] Fix an occasional crash when deleting a HitObject via internal means --- osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index b5a28dc022..b1afbe0d61 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -438,8 +438,8 @@ namespace osu.Game.Screens.Edit.Compose.Components private void onBlueprintDeselected(SelectionBlueprint blueprint) { - SelectionHandler.HandleDeselected(blueprint); SelectionBlueprints.ChangeChildDepth(blueprint, 0); + SelectionHandler.HandleDeselected(blueprint); Composer.Playfield.SetKeepAlive(blueprint.HitObject, false); } From 25f0f17766bffceb205a6f162aa621d7e35970ed Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 16 Apr 2021 16:16:28 +0900 Subject: [PATCH 1549/1791] Attempt to fix match subscreen test failure --- .../Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index caa731f985..7c6c158b5a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -13,6 +13,7 @@ using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; +using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; using osu.Game.Tests.Beatmaps; @@ -128,6 +129,8 @@ namespace osu.Game.Tests.Visual.Multiplayer InputManager.Click(MouseButton.Left); }); + AddUntilStep("wait for ready button to be enabled", () => this.ChildrenOfType().Single().ChildrenOfType().Single().Enabled.Value); + AddStep("click ready button", () => { InputManager.MoveMouseTo(this.ChildrenOfType().Single()); From ab1a1a1df4da3504a215b4acd22e39e88dca3b60 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Apr 2021 16:55:17 +0900 Subject: [PATCH 1550/1791] Add failing test case due to div by zero --- .../Editor/TestSliderScaling.cs | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs new file mode 100644 index 0000000000..e29a67c770 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs @@ -0,0 +1,72 @@ +// 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.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Screens.Edit.Compose.Components; +using osu.Game.Tests.Beatmaps; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Rulesets.Osu.Tests.Editor +{ + [TestFixture] + public class TestSliderScaling : TestSceneOsuEditor + { + private OsuPlayfield playfield; + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(Ruleset.Value, false); + + public override void SetUpSteps() + { + base.SetUpSteps(); + AddStep("get playfield", () => playfield = Editor.ChildrenOfType().First()); + AddStep("seek to first timing point", () => EditorClock.Seek(Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.First().Time)); + } + + [Test] + public void TestScalingLinearSlider() + { + Slider slider = null; + + AddStep("Add slider", () => + { + slider = new Slider { StartTime = EditorClock.CurrentTime, Position = new Vector2(300) }; + + PathControlPoint[] points = + { + new PathControlPoint(new Vector2(0), PathType.Linear), + new PathControlPoint(new Vector2(100, 0)), + }; + + slider.Path = new SliderPath(points); + EditorBeatmap.Add(slider); + }); + + AddAssert("ensure object placed", () => EditorBeatmap.HitObjects.Count == 1); + + moveMouse(new Vector2(300)); + AddStep("select slider", () => InputManager.Click(MouseButton.Left)); + + double distanceBefore = 0; + + AddStep("store distance", () => distanceBefore = slider.Path.Distance); + + AddStep("move mouse to handle", () => InputManager.MoveMouseTo(Editor.ChildrenOfType().Skip(1).First())); + AddStep("begin drag", () => InputManager.PressButton(MouseButton.Left)); + moveMouse(new Vector2(300, 300)); + AddStep("end drag", () => InputManager.ReleaseButton(MouseButton.Left)); + + AddAssert("slider length shrunk", () => slider.Path.Distance < distanceBefore); + } + + private void moveMouse(Vector2 pos) => + AddStep($"move mouse to {pos}", () => InputManager.MoveMouseTo(playfield.ToScreenSpace(pos))); + } +} From 8de68e0ebf11ba4c39438e4c7ff051ff53bfbb53 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Apr 2021 16:55:24 +0900 Subject: [PATCH 1551/1791] Fix div-by-zero when scaling a 1-dimensional slider --- osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 5a84bd6163..047634a3ab 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -205,10 +205,12 @@ namespace osu.Game.Rulesets.Osu.Edit Quad sliderQuad = getSurroundingQuad(slider.Path.ControlPoints.Select(p => p.Position.Value)); - // Limit minimum distance between control points after scaling to almost 0. Less than 0 causes the slider to flip, exactly 0 causes a crash through division by 0. + // Limit minimum distance between control points after scaling to almost 0. Less than 0 causes the slider to flip, exactly 0 causes a crash through division by 0. scale = Vector2.ComponentMax(new Vector2(Precision.FLOAT_EPSILON), sliderQuad.Size + scale) - sliderQuad.Size; - Vector2 pathRelativeDeltaScale = new Vector2(1 + scale.X / sliderQuad.Width, 1 + scale.Y / sliderQuad.Height); + Vector2 pathRelativeDeltaScale = new Vector2( + sliderQuad.Width == 0 ? 0 : 1 + scale.X / sliderQuad.Width, + sliderQuad.Height == 0 ? 0 : 1 + scale.Y / sliderQuad.Height); Queue oldControlPoints = new Queue(); From 30e00cc4aa8228e458979b135c8c2aa8213143b2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Apr 2021 16:38:20 +0900 Subject: [PATCH 1552/1791] Add test coverage of selection / scaling scenarios --- .../Editor/TestSceneSliderLengthValidity.cs | 198 ++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderLengthValidity.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderLengthValidity.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderLengthValidity.cs new file mode 100644 index 0000000000..74560ad15d --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderLengthValidity.cs @@ -0,0 +1,198 @@ +// 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.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Screens.Edit.Compose.Components; +using osu.Game.Tests.Beatmaps; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Rulesets.Osu.Tests.Editor +{ + [TestFixture] + public class TestSceneSliderLengthValidity : TestSceneOsuEditor + { + private OsuPlayfield playfield; + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(Ruleset.Value, false); + + public override void SetUpSteps() + { + base.SetUpSteps(); + AddStep("get playfield", () => playfield = Editor.ChildrenOfType().First()); + AddStep("seek to first timing point", () => EditorClock.Seek(Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.First().Time)); + } + + [Test] + public void TestDraggingStartingPointRemainsValid() + { + Slider slider = null; + + AddStep("Add slider", () => + { + slider = new Slider { StartTime = EditorClock.CurrentTime, Position = new Vector2(300) }; + + PathControlPoint[] points = + { + new PathControlPoint(new Vector2(0), PathType.Linear), + new PathControlPoint(new Vector2(100, 0)), + }; + + slider.Path = new SliderPath(points); + EditorBeatmap.Add(slider); + }); + + AddAssert("ensure object placed", () => EditorBeatmap.HitObjects.Count == 1); + + moveMouse(new Vector2(300)); + AddStep("select slider", () => InputManager.Click(MouseButton.Left)); + + double distanceBefore = 0; + + AddStep("store distance", () => distanceBefore = slider.Path.Distance); + + moveMouse(new Vector2(300, 300)); + + AddStep("begin drag", () => InputManager.PressButton(MouseButton.Left)); + moveMouse(new Vector2(350, 300)); + moveMouse(new Vector2(400, 300)); + AddStep("end drag", () => InputManager.ReleaseButton(MouseButton.Left)); + + AddAssert("slider length shrunk", () => slider.Path.Distance < distanceBefore); + AddAssert("ensure slider still has valid length", () => slider.Path.Distance > 0); + } + + [Test] + public void TestDraggingEndingPointRemainsValid() + { + Slider slider = null; + + AddStep("Add slider", () => + { + slider = new Slider { StartTime = EditorClock.CurrentTime, Position = new Vector2(300) }; + + PathControlPoint[] points = + { + new PathControlPoint(new Vector2(0), PathType.Linear), + new PathControlPoint(new Vector2(100, 0)), + }; + + slider.Path = new SliderPath(points); + EditorBeatmap.Add(slider); + }); + + AddAssert("ensure object placed", () => EditorBeatmap.HitObjects.Count == 1); + + moveMouse(new Vector2(300)); + AddStep("select slider", () => InputManager.Click(MouseButton.Left)); + + double distanceBefore = 0; + + AddStep("store distance", () => distanceBefore = slider.Path.Distance); + + moveMouse(new Vector2(400, 300)); + + AddStep("begin drag", () => InputManager.PressButton(MouseButton.Left)); + moveMouse(new Vector2(350, 300)); + moveMouse(new Vector2(300, 300)); + AddStep("end drag", () => InputManager.ReleaseButton(MouseButton.Left)); + + AddAssert("slider length shrunk", () => slider.Path.Distance < distanceBefore); + AddAssert("ensure slider still has valid length", () => slider.Path.Distance > 0); + } + + /// + /// If a control point is deleted which results in the slider becoming so short it can't exist, + /// for simplicity delete the slider rather than having it in an invalid state. + /// + /// Eventually we may need to change this, based on user feedback. I think it's likely enough of + /// an edge case that we won't get many complaints, though (and there's always the undo button). + /// + [Test] + public void TestDeletingPointCausesSliderDeletion() + { + AddStep("Add slider", () => + { + Slider slider = new Slider { StartTime = EditorClock.CurrentTime, Position = new Vector2(300) }; + + PathControlPoint[] points = + { + new PathControlPoint(new Vector2(0), PathType.PerfectCurve), + new PathControlPoint(new Vector2(100, 0)), + new PathControlPoint(new Vector2(0, 10)) + }; + + slider.Path = new SliderPath(points); + EditorBeatmap.Add(slider); + }); + + AddAssert("ensure object placed", () => EditorBeatmap.HitObjects.Count == 1); + + AddStep("select slider", () => InputManager.Click(MouseButton.Left)); + + moveMouse(new Vector2(400, 300)); + AddStep("delete second point", () => + { + InputManager.PressKey(Key.ShiftLeft); + InputManager.Click(MouseButton.Right); + InputManager.ReleaseKey(Key.ShiftLeft); + }); + + AddAssert("ensure object deleted", () => EditorBeatmap.HitObjects.Count == 0); + } + + /// + /// If a scale operation is performed where a single slider is the only thing selected, the path's shape will change. + /// If the scale results in the path becoming too short, further mouse movement in the same direction will not change the shape. + /// + [Test] + public void TestScalingSliderTooSmallRemainsValid() + { + Slider slider = null; + + AddStep("Add slider", () => + { + slider = new Slider { StartTime = EditorClock.CurrentTime, Position = new Vector2(300) }; + + PathControlPoint[] points = + { + new PathControlPoint(new Vector2(0), PathType.Linear), + new PathControlPoint(new Vector2(0, 50)), + new PathControlPoint(new Vector2(0, 100)) + }; + + slider.Path = new SliderPath(points); + EditorBeatmap.Add(slider); + }); + + AddAssert("ensure object placed", () => EditorBeatmap.HitObjects.Count == 1); + + moveMouse(new Vector2(300)); + AddStep("select slider", () => InputManager.Click(MouseButton.Left)); + + double distanceBefore = 0; + + AddStep("store distance", () => distanceBefore = slider.Path.Distance); + + AddStep("move mouse to handle", () => InputManager.MoveMouseTo(Editor.ChildrenOfType().Skip(1).First())); + AddStep("begin drag", () => InputManager.PressButton(MouseButton.Left)); + moveMouse(new Vector2(350, 400)); + moveMouse(new Vector2(350, 350)); + moveMouse(new Vector2(350, 300)); + AddStep("end drag", () => InputManager.ReleaseButton(MouseButton.Left)); + + AddAssert("slider length shrunk", () => slider.Path.Distance < distanceBefore); + AddAssert("ensure slider still has valid length", () => slider.Path.Distance > 0); + } + + private void moveMouse(Vector2 pos) => + AddStep($"move mouse to {pos}", () => InputManager.MoveMouseTo(playfield.ToScreenSpace(pos))); + } +} From fbf7d838da12f3a2b6d0c576b64558ec30a114d6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Apr 2021 17:30:39 +0900 Subject: [PATCH 1553/1791] 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 32e236ccd5..0bb0bf171c 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 b5405f6262..e0a267241d 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 09f6033bfe..bcd953c0bd 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From ca74f413cd10ccaee2dd1e51c5190019262b76c8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 16 Apr 2021 17:29:42 +0900 Subject: [PATCH 1554/1791] Change to explicit method instead --- .../Visual/Gameplay/TestSceneSpectator.cs | 2 +- .../Spectator/SpectatorStreamingClient.cs | 38 ++++++++++--------- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index ea66144b21..def662d3ea 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -221,7 +221,7 @@ namespace osu.Game.Tests.Visual.Gameplay callbackInvoked = false; }); - AddStep("bind event again", () => testSpectatorStreamingClient.OnUserBeganPlaying += callbackAction); + AddStep("bind event with run once immediately", () => testSpectatorStreamingClient.BindUserBeganPlaying(callbackAction, true)); AddAssert("callback invoked", () => callbackInvoked); // Don't leave the event bound if test run succeeded. diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 7bea49e102..4bbc420223 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -69,25 +69,10 @@ namespace osu.Game.Online.Spectator /// public event Action OnNewFrames; - private event Action onUserBeganPlaying; - /// /// Called whenever a user starts a play session, or immediately if the user is being watched and currently in a play session. /// - public event Action OnUserBeganPlaying - { - add - { - onUserBeganPlaying += value; - - lock (userLock) - { - foreach (var (userId, state) in currentUserStates) - value?.Invoke(userId, state); - } - } - remove => onUserBeganPlaying -= value; - } + public event Action OnUserBeganPlaying; /// /// Called whenever a user finishes a play session. @@ -153,7 +138,7 @@ namespace osu.Game.Online.Spectator lock (userLock) currentUserStates[userId] = state; - onUserBeganPlaying?.Invoke(userId, state); + OnUserBeganPlaying?.Invoke(userId, state); return Task.CompletedTask; } @@ -290,5 +275,24 @@ namespace osu.Game.Online.Spectator lastSendTime = Time.Current; } + + /// + /// Bind an action to with the option of running the bound action once immediately. + /// + /// The action to perform when a user begins playing. + /// Whether the action provided in should be run once immediately for all users currently playing. + public void BindUserBeganPlaying(Action callback, bool runOnceImmediately = false) + { + OnUserBeganPlaying += callback; + + if (!runOnceImmediately) + return; + + lock (userLock) + { + foreach (var (userId, state) in currentUserStates) + callback(userId, state); + } + } } } From 7d23973ef8aa53c77b09201b829800060c61f401 Mon Sep 17 00:00:00 2001 From: jvyden Date: Fri, 16 Apr 2021 05:01:58 -0400 Subject: [PATCH 1555/1791] Reset SessionStatics on activity Closes #12424 --- .../Visual/Components/TestSceneIdleTracker.cs | 39 +++++++++++++++---- osu.Game/Configuration/SessionStatics.cs | 8 ++++ osu.Game/Input/GameIdleTracker.cs | 11 +++++- osu.Game/OsuGame.cs | 2 +- 4 files changed, 51 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs b/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs index 4d64c7d35d..794f6ae83f 100644 --- a/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs +++ b/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs @@ -5,6 +5,7 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Game.Configuration; using osu.Game.Input; using osuTK; using osuTK.Graphics; @@ -21,14 +22,17 @@ namespace osu.Game.Tests.Visual.Components private IdleTrackingBox[] boxes; + public SessionStatics sessionStatics; + [SetUp] public void SetUp() => Schedule(() => { + sessionStatics = new SessionStatics(); InputManager.MoveMouseTo(Vector2.Zero); Children = boxes = new[] { - box1 = new IdleTrackingBox(2000) + box1 = new IdleTrackingBox(2000, sessionStatics) { Name = "TopLeft", RelativeSizeAxes = Axes.Both, @@ -36,7 +40,7 @@ namespace osu.Game.Tests.Visual.Components Anchor = Anchor.TopLeft, Origin = Anchor.TopLeft, }, - box2 = new IdleTrackingBox(4000) + box2 = new IdleTrackingBox(4000, sessionStatics) { Name = "TopRight", RelativeSizeAxes = Axes.Both, @@ -44,7 +48,7 @@ namespace osu.Game.Tests.Visual.Components Anchor = Anchor.TopRight, Origin = Anchor.TopRight, }, - box3 = new IdleTrackingBox(6000) + box3 = new IdleTrackingBox(6000, sessionStatics) { Name = "BottomLeft", RelativeSizeAxes = Axes.Both, @@ -52,7 +56,7 @@ namespace osu.Game.Tests.Visual.Components Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, }, - box4 = new IdleTrackingBox(8000) + box4 = new IdleTrackingBox(8000, sessionStatics) { Name = "BottomRight", RelativeSizeAxes = Axes.Both, @@ -61,6 +65,7 @@ namespace osu.Game.Tests.Visual.Components Origin = Anchor.BottomRight, }, }; + }); [Test] @@ -133,6 +138,26 @@ namespace osu.Game.Tests.Visual.Components waitForAllIdle(); } + [Test] + public void TestSessionStaticsReset() + { + AddStep("move to top left", () => InputManager.MoveMouseTo(box1)); + AddStep("set session statics", () => + { + sessionStatics.SetValue(Static.LoginOverlayDisplayed, true); + sessionStatics.SetValue(Static.MutedAudioNotificationShownOnce, true); + sessionStatics.SetValue(Static.LowBatteryNotificationShownOnce, true); + sessionStatics.SetValue(Static.LastHoverSoundPlaybackTime, (double?)1d); + }); + + AddStep("move away from box1", () => InputManager.MoveMouseTo(box4)); + AddUntilStep("Wait for idle", () => box1.IsIdle); + AddAssert("LoginOverlayDisplayed is default", () => sessionStatics.Get(Static.LoginOverlayDisplayed) == false); + AddAssert("MutedAudioNotificationShownOnce is default", () => sessionStatics.Get(Static.MutedAudioNotificationShownOnce) == false); + AddAssert("LowBatteryNotificationShownOnce is default", () => sessionStatics.Get(Static.LowBatteryNotificationShownOnce) == false); + AddAssert("LastHoverSoundPlaybackTime is default", () => sessionStatics.Get(Static.LastHoverSoundPlaybackTime) == null); + } + private void checkIdleStatus(int box, bool expectedIdle) { AddAssert($"box {box} is {(expectedIdle ? "idle" : "active")}", () => boxes[box - 1].IsIdle == expectedIdle); @@ -140,7 +165,7 @@ namespace osu.Game.Tests.Visual.Components private void waitForAllIdle() { - AddUntilStep("Wait for all idle", () => box1.IsIdle && box2.IsIdle && box3.IsIdle && box4.IsIdle); + AddUntilStep("wait for all idle", () => box1.IsIdle && box2.IsIdle && box3.IsIdle && box4.IsIdle); } private class IdleTrackingBox : CompositeDrawable @@ -149,7 +174,7 @@ namespace osu.Game.Tests.Visual.Components public bool IsIdle => idleTracker.IsIdle.Value; - public IdleTrackingBox(double timeToIdle) + public IdleTrackingBox(int timeToIdle, SessionStatics statics) { Box box; @@ -158,7 +183,7 @@ namespace osu.Game.Tests.Visual.Components InternalChildren = new Drawable[] { - idleTracker = new IdleTracker(timeToIdle), + idleTracker = new GameIdleTracker(timeToIdle, statics), box = new Box { Colour = Color4.White, diff --git a/osu.Game/Configuration/SessionStatics.cs b/osu.Game/Configuration/SessionStatics.cs index 71e1a1efcc..c960409de8 100644 --- a/osu.Game/Configuration/SessionStatics.cs +++ b/osu.Game/Configuration/SessionStatics.cs @@ -20,6 +20,14 @@ namespace osu.Game.Configuration SetDefault(Static.LastHoverSoundPlaybackTime, (double?)null); SetDefault(Static.SeasonalBackgrounds, null); } + + public void ResetValues() + { + SetValue(Static.LoginOverlayDisplayed, false); + SetValue(Static.MutedAudioNotificationShownOnce, false); + SetValue(Static.LowBatteryNotificationShownOnce, false); + SetValue(Static.LastHoverSoundPlaybackTime, (double?)null); + } } public enum Static diff --git a/osu.Game/Input/GameIdleTracker.cs b/osu.Game/Input/GameIdleTracker.cs index 260be7e5c9..53dab0c07b 100644 --- a/osu.Game/Input/GameIdleTracker.cs +++ b/osu.Game/Input/GameIdleTracker.cs @@ -1,7 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Bindables; using osu.Framework.Input; +using osu.Game.Configuration; namespace osu.Game.Input { @@ -9,9 +11,16 @@ namespace osu.Game.Input { private InputManager inputManager; - public GameIdleTracker(int time) + public GameIdleTracker(int time, SessionStatics statics) : base(time) { + IsIdle.ValueChanged += _ => UpdateStatics(_, statics); + } + + protected static void UpdateStatics(ValueChangedEvent e, SessionStatics statics) + { + if (e.OldValue != e.NewValue && e.NewValue) + statics.ResetValues(); } protected override void LoadComplete() diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 809e5d3c1b..97d4011607 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -575,7 +575,7 @@ namespace osu.Game Container logoContainer; BackButton.Receptor receptor; - dependencies.CacheAs(idleTracker = new GameIdleTracker(6000)); + dependencies.CacheAs(idleTracker = new GameIdleTracker(6000, Dependencies.Get())); AddRange(new Drawable[] { From 43e6e5e049a99ef85678cc4953a3f08335a6afdf Mon Sep 17 00:00:00 2001 From: jvyden Date: Fri, 16 Apr 2021 05:15:58 -0400 Subject: [PATCH 1556/1791] increase GameIdleTracker time to 5 minutes --- 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 97d4011607..3930db01b9 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -575,7 +575,7 @@ namespace osu.Game Container logoContainer; BackButton.Receptor receptor; - dependencies.CacheAs(idleTracker = new GameIdleTracker(6000, Dependencies.Get())); + dependencies.CacheAs(idleTracker = new GameIdleTracker(300_000, Dependencies.Get())); AddRange(new Drawable[] { From d9d50f0e88abae5d25c694036ae3ec9dc717dcc2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Apr 2021 18:16:22 +0900 Subject: [PATCH 1557/1791] Add border showing selected blueprints in timeline --- .../Timeline/TimelineHitObjectBlueprint.cs | 47 ++++++++++++++++--- 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index bea1aa2e3a..105e04d441 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -40,7 +40,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private Bindable indexInCurrentComboBindable; private Bindable comboIndexBindable; - private readonly Drawable circle; + private readonly ExtendableCircle circle; + private readonly Border border; private readonly Container colouredComponents; private readonly OsuSpriteText comboIndexText; @@ -62,7 +63,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline RelativeSizeAxes = Axes.X; Height = circle_size; - AddRangeInternal(new[] + AddRangeInternal(new Drawable[] { circle = new ExtendableCircle { @@ -70,6 +71,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, }, + border = new Border + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, colouredComponents = new Container { Anchor = Anchor.CentreLeft, @@ -116,11 +123,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline protected override void OnSelected() { // base logic hides selected blueprints when not selected, but timeline doesn't do that. + updateComboColour(); } protected override void OnDeselected() { // base logic hides selected blueprints when not selected, but timeline doesn't do that. + updateComboColour(); } private void updateComboIndex() => comboIndexText.Text = (indexInCurrentComboBindable.Value + 1).ToString(); @@ -133,6 +142,16 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline var comboColours = skin.GetConfig>(GlobalSkinColours.ComboColours)?.Value ?? Array.Empty(); var comboColour = combo.GetComboColour(comboColours); + if (IsSelected) + { + border.Show(); + comboColour = comboColour.Lighten(0.3f); + } + else + { + border.Hide(); + } + if (HitObject is IHasDuration duration && duration.Duration > 0) circle.Colour = ColourInfo.GradientHorizontal(comboColour, comboColour.Lighten(0.4f)); else @@ -340,22 +359,38 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } } + public class Border : ExtendableCircle + { + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Content.Child.Alpha = 0; + Content.Child.AlwaysPresent = true; + + Content.BorderColour = colours.Yellow; + Content.EdgeEffect = new EdgeEffectParameters(); + } + } + /// /// A circle with externalised end caps so it can take up the full width of a relative width area. /// public class ExtendableCircle : CompositeDrawable { - private readonly Circle content; + protected readonly Circle Content; - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => content.ReceivePositionalInputAt(screenSpacePos); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Content.ReceivePositionalInputAt(screenSpacePos); - public override Quad ScreenSpaceDrawQuad => content.ScreenSpaceDrawQuad; + public override Quad ScreenSpaceDrawQuad => Content.ScreenSpaceDrawQuad; public ExtendableCircle() { Padding = new MarginPadding { Horizontal = -circle_size / 2f }; - InternalChild = content = new Circle + InternalChild = Content = new Circle { + BorderColour = OsuColour.Gray(0.75f), + BorderThickness = 4, + Masking = true, RelativeSizeAxes = Axes.Both, EdgeEffect = new EdgeEffectParameters { From 377e5ce6b396c585a0652cf63253966c59c06948 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 16 Apr 2021 18:21:35 +0900 Subject: [PATCH 1558/1791] Fix test incorrect sending state too often --- osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index def662d3ea..392419649b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -312,7 +312,7 @@ namespace osu.Game.Tests.Visual.Gameplay public override void WatchUser(int userId) { - if (sentState) + if (!PlayingUsers.Contains(userId) && sentState) { // usually the server would do this. sendState(beatmapId); From 46d2181d42930ac61f6da22f8146ba2b74357929 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 16 Apr 2021 18:21:56 +0900 Subject: [PATCH 1559/1791] Remove now unnecessary (duplicating) test --- .../Visual/Gameplay/TestSceneSpectator.cs | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 392419649b..74ce66096e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.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; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -205,29 +204,6 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("screen didn't change", () => Stack.CurrentScreen is SoloSpectator); } - [Test] - public void OnUserBeganPlayingCallbackInvokedOnNewAdd() - { - bool callbackInvoked = false; - Action callbackAction = (_, __) => callbackInvoked = true; - - AddStep("bind first event", () => testSpectatorStreamingClient.OnUserBeganPlaying += callbackAction); - start(); - AddAssert("callback invoked", () => callbackInvoked); - - AddStep("reset", () => - { - testSpectatorStreamingClient.OnUserBeganPlaying -= callbackAction; - callbackInvoked = false; - }); - - AddStep("bind event with run once immediately", () => testSpectatorStreamingClient.BindUserBeganPlaying(callbackAction, true)); - AddAssert("callback invoked", () => callbackInvoked); - - // Don't leave the event bound if test run succeeded. - AddStep("reset", () => testSpectatorStreamingClient.OnUserBeganPlaying -= callbackAction); - } - private OsuFramedReplayInputHandler replayHandler => (OsuFramedReplayInputHandler)Stack.ChildrenOfType().First().ReplayInputHandler; From 274e33184b8afb9d10ef8b2d380edc8b742ee2ab Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 16 Apr 2021 18:22:22 +0900 Subject: [PATCH 1560/1791] Fix SpectatorScreen potentially missing user playing callbacks --- osu.Game/Screens/Spectate/SpectatorScreen.cs | 42 ++++++++++++++------ 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index 6dd3144fc8..7be6c6183b 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -62,26 +62,42 @@ namespace osu.Game.Screens.Spectate { base.LoadComplete(); - spectatorClient.OnUserBeganPlaying += userBeganPlaying; - spectatorClient.OnUserFinishedPlaying += userFinishedPlaying; - spectatorClient.OnNewFrames += userSentFrames; - - foreach (var id in userIds) + populateAllUsers().ContinueWith(_ => Schedule(() => { - userLookupCache.GetUserAsync(id).ContinueWith(u => Schedule(() => + spectatorClient.BindUserBeganPlaying(userBeganPlaying, true); + spectatorClient.OnUserFinishedPlaying += userFinishedPlaying; + spectatorClient.OnNewFrames += userSentFrames; + + managerUpdated = beatmaps.ItemUpdated.GetBoundCopy(); + managerUpdated.BindValueChanged(beatmapUpdated); + + lock (stateLock) { - if (u.Result == null) + foreach (var (id, _) in userMap) + spectatorClient.WatchUser(id); + } + })); + } + + private Task populateAllUsers() + { + var userLookupTasks = new Task[userIds.Length]; + + for (int i = 0; i < userIds.Length; i++) + { + var userId = userIds[i]; + + userLookupTasks[i] = userLookupCache.GetUserAsync(userId).ContinueWith(task => + { + if (!task.IsCompletedSuccessfully) return; lock (stateLock) - userMap[id] = u.Result; - - spectatorClient.WatchUser(id); - }), TaskContinuationOptions.OnlyOnRanToCompletion); + userMap[userId] = task.Result; + }); } - managerUpdated = beatmaps.ItemUpdated.GetBoundCopy(); - managerUpdated.BindValueChanged(beatmapUpdated); + return Task.WhenAll(userLookupTasks); } private void beatmapUpdated(ValueChangedEvent> e) From d760e81a9130e5e9ea484e9a77225e743d4c900f Mon Sep 17 00:00:00 2001 From: jvyden Date: Fri, 16 Apr 2021 05:22:41 -0400 Subject: [PATCH 1561/1791] Fix lint --- osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs b/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs index 794f6ae83f..61efc61537 100644 --- a/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs +++ b/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs @@ -65,7 +65,6 @@ namespace osu.Game.Tests.Visual.Components Origin = Anchor.BottomRight, }, }; - }); [Test] From ec0211809f145e203408523de7b7f94f0d4ca6fd Mon Sep 17 00:00:00 2001 From: jvyden Date: Fri, 16 Apr 2021 05:53:27 -0400 Subject: [PATCH 1562/1791] Apply peppy's suggestions --- .../Visual/Components/TestSceneIdleTracker.cs | 11 ++++++++--- osu.Game/Configuration/SessionStatics.cs | 6 ++---- osu.Game/Input/GameIdleTracker.cs | 11 +---------- osu.Game/OsuGame.cs | 11 ++++++++++- 4 files changed, 21 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs b/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs index 61efc61537..be8e50d649 100644 --- a/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs +++ b/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs @@ -22,7 +22,7 @@ namespace osu.Game.Tests.Visual.Components private IdleTrackingBox[] boxes; - public SessionStatics sessionStatics; + private SessionStatics sessionStatics; [SetUp] public void SetUp() => Schedule(() => @@ -175,6 +175,7 @@ namespace osu.Game.Tests.Visual.Components public IdleTrackingBox(int timeToIdle, SessionStatics statics) { + Box box; Alpha = 0.6f; @@ -182,7 +183,7 @@ namespace osu.Game.Tests.Visual.Components InternalChildren = new Drawable[] { - idleTracker = new GameIdleTracker(timeToIdle, statics), + idleTracker = new GameIdleTracker(timeToIdle), box = new Box { Colour = Color4.White, @@ -190,7 +191,11 @@ namespace osu.Game.Tests.Visual.Components }, }; - idleTracker.IsIdle.BindValueChanged(idle => box.Colour = idle.NewValue ? Color4.White : Color4.Black, true); + idleTracker.IsIdle.BindValueChanged(idle => + { + box.Colour = idle.NewValue ? Color4.White : Color4.Black; + if (idle.NewValue) statics.ResetValues(); + }, true); } } } diff --git a/osu.Game/Configuration/SessionStatics.cs b/osu.Game/Configuration/SessionStatics.cs index c960409de8..99089dd076 100644 --- a/osu.Game/Configuration/SessionStatics.cs +++ b/osu.Game/Configuration/SessionStatics.cs @@ -23,10 +23,8 @@ namespace osu.Game.Configuration public void ResetValues() { - SetValue(Static.LoginOverlayDisplayed, false); - SetValue(Static.MutedAudioNotificationShownOnce, false); - SetValue(Static.LowBatteryNotificationShownOnce, false); - SetValue(Static.LastHoverSoundPlaybackTime, (double?)null); + ConfigStore.Clear(); + InitialiseDefaults(); } } diff --git a/osu.Game/Input/GameIdleTracker.cs b/osu.Game/Input/GameIdleTracker.cs index 53dab0c07b..260be7e5c9 100644 --- a/osu.Game/Input/GameIdleTracker.cs +++ b/osu.Game/Input/GameIdleTracker.cs @@ -1,9 +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.Bindables; using osu.Framework.Input; -using osu.Game.Configuration; namespace osu.Game.Input { @@ -11,16 +9,9 @@ namespace osu.Game.Input { private InputManager inputManager; - public GameIdleTracker(int time, SessionStatics statics) + public GameIdleTracker(int time) : base(time) { - IsIdle.ValueChanged += _ => UpdateStatics(_, statics); - } - - protected static void UpdateStatics(ValueChangedEvent e, SessionStatics statics) - { - if (e.OldValue != e.NewValue && e.NewValue) - statics.ResetValues(); } protected override void LoadComplete() diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 3930db01b9..0abb0373fa 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -575,7 +575,16 @@ namespace osu.Game Container logoContainer; BackButton.Receptor receptor; - dependencies.CacheAs(idleTracker = new GameIdleTracker(300_000, Dependencies.Get())); + dependencies.CacheAs(idleTracker = new GameIdleTracker(6000)); + + GameIdleTracker sessionIdleTracker = new GameIdleTracker(300_000); + Add(sessionIdleTracker); + + sessionIdleTracker.IsIdle.BindValueChanged((e) => + { + if (e.NewValue) + Dependencies.Get().ResetValues(); + }); AddRange(new Drawable[] { From b413ffae3e883384b236b0a3c9a1c27e5ff0f843 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Apr 2021 18:54:33 +0900 Subject: [PATCH 1563/1791] Fix test going offscreen in headless execution --- .../Editor/TestSceneSliderLengthValidity.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderLengthValidity.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderLengthValidity.cs index 74560ad15d..ce529f2a88 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderLengthValidity.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderLengthValidity.cs @@ -159,7 +159,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("Add slider", () => { - slider = new Slider { StartTime = EditorClock.CurrentTime, Position = new Vector2(300) }; + slider = new Slider { StartTime = EditorClock.CurrentTime, Position = new Vector2(300, 200) }; PathControlPoint[] points = { @@ -183,9 +183,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("move mouse to handle", () => InputManager.MoveMouseTo(Editor.ChildrenOfType().Skip(1).First())); AddStep("begin drag", () => InputManager.PressButton(MouseButton.Left)); - moveMouse(new Vector2(350, 400)); - moveMouse(new Vector2(350, 350)); - moveMouse(new Vector2(350, 300)); + moveMouse(new Vector2(300, 300)); + moveMouse(new Vector2(300, 250)); + moveMouse(new Vector2(300, 200)); AddStep("end drag", () => InputManager.ReleaseButton(MouseButton.Left)); AddAssert("slider length shrunk", () => slider.Path.Distance < distanceBefore); From 8d6c30c73bb39b5383a9119ea351a760e3b48722 Mon Sep 17 00:00:00 2001 From: jvyden Date: Fri, 16 Apr 2021 05:57:36 -0400 Subject: [PATCH 1564/1791] Fix lint --- osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs b/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs index be8e50d649..0bc0fbf5d4 100644 --- a/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs +++ b/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs @@ -175,7 +175,6 @@ namespace osu.Game.Tests.Visual.Components public IdleTrackingBox(int timeToIdle, SessionStatics statics) { - Box box; Alpha = 0.6f; From 9c6914d29ded9af216b56e6bcacb19ec2c71f88e Mon Sep 17 00:00:00 2001 From: jvyden Date: Fri, 16 Apr 2021 06:26:45 -0400 Subject: [PATCH 1565/1791] Fix redundant lambda parentheses --- 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 0abb0373fa..898a2baccf 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -580,7 +580,7 @@ namespace osu.Game GameIdleTracker sessionIdleTracker = new GameIdleTracker(300_000); Add(sessionIdleTracker); - sessionIdleTracker.IsIdle.BindValueChanged((e) => + sessionIdleTracker.IsIdle.BindValueChanged(e => { if (e.NewValue) Dependencies.Get().ResetValues(); From a4e3e53a63788fdd09029836c779b94ff7389fd3 Mon Sep 17 00:00:00 2001 From: jvyden Date: Fri, 16 Apr 2021 06:34:57 -0400 Subject: [PATCH 1566/1791] Revert back to hardcoded SessionStatics reset values --- osu.Game/Configuration/SessionStatics.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Configuration/SessionStatics.cs b/osu.Game/Configuration/SessionStatics.cs index 99089dd076..872a910837 100644 --- a/osu.Game/Configuration/SessionStatics.cs +++ b/osu.Game/Configuration/SessionStatics.cs @@ -23,8 +23,11 @@ namespace osu.Game.Configuration public void ResetValues() { - ConfigStore.Clear(); - InitialiseDefaults(); + SetValue(Static.LoginOverlayDisplayed, false); + SetValue(Static.MutedAudioNotificationShownOnce, false); + SetValue(Static.LowBatteryNotificationShownOnce, false); + SetValue(Static.LastHoverSoundPlaybackTime, (double?)null); + SetValue(Static.SeasonalBackgrounds, null); } } From d26fa46ef2a9f80cfbd2c93d54b6572727f4d080 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 16 Apr 2021 19:40:56 +0900 Subject: [PATCH 1567/1791] Record every 60fps interval --- osu.Game/Rulesets/UI/ReplayRecorder.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Rulesets/UI/ReplayRecorder.cs b/osu.Game/Rulesets/UI/ReplayRecorder.cs index a4d46e3888..643ded4cad 100644 --- a/osu.Game/Rulesets/UI/ReplayRecorder.cs +++ b/osu.Game/Rulesets/UI/ReplayRecorder.cs @@ -58,6 +58,12 @@ namespace osu.Game.Rulesets.UI spectatorStreaming?.EndPlaying(); } + protected override void Update() + { + base.Update(); + recordFrame(false); + } + protected override bool OnMouseMove(MouseMoveEvent e) { recordFrame(false); From 6301111fa3c1d274c68766d3c1456ebd74d95138 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 16 Apr 2021 20:15:42 +0900 Subject: [PATCH 1568/1791] Remove ClockToProcess, always process underlying clock --- osu.Game/Screens/Play/GameplayClock.cs | 18 +++++++++--------- .../Screens/Play/GameplayClockContainer.cs | 4 +--- .../Play/MasterGameplayClockContainer.cs | 2 -- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClock.cs b/osu.Game/Screens/Play/GameplayClock.cs index db4b5d300b..54aa395f5f 100644 --- a/osu.Game/Screens/Play/GameplayClock.cs +++ b/osu.Game/Screens/Play/GameplayClock.cs @@ -19,7 +19,7 @@ namespace osu.Game.Screens.Play /// public class GameplayClock : IFrameBasedClock { - private readonly IFrameBasedClock underlyingClock; + internal readonly IFrameBasedClock UnderlyingClock; public readonly BindableBool IsPaused = new BindableBool(); @@ -30,12 +30,12 @@ namespace osu.Game.Screens.Play public GameplayClock(IFrameBasedClock underlyingClock) { - this.underlyingClock = underlyingClock; + UnderlyingClock = underlyingClock; } - public double CurrentTime => underlyingClock.CurrentTime; + public double CurrentTime => UnderlyingClock.CurrentTime; - public double Rate => underlyingClock.Rate; + public double Rate => UnderlyingClock.Rate; /// /// The rate of gameplay when playback is at 100%. @@ -59,19 +59,19 @@ namespace osu.Game.Screens.Play } } - public bool IsRunning => underlyingClock.IsRunning; + public bool IsRunning => UnderlyingClock.IsRunning; public void ProcessFrame() { // intentionally not updating the underlying clock (handled externally). } - public double ElapsedFrameTime => underlyingClock.ElapsedFrameTime; + public double ElapsedFrameTime => UnderlyingClock.ElapsedFrameTime; - public double FramesPerSecond => underlyingClock.FramesPerSecond; + public double FramesPerSecond => UnderlyingClock.FramesPerSecond; - public FrameTimeInfo TimeInfo => underlyingClock.TimeInfo; + public FrameTimeInfo TimeInfo => UnderlyingClock.TimeInfo; - public IClock Source => underlyingClock; + public IClock Source => UnderlyingClock; } } diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 6d863f0094..b7dc55277f 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -80,15 +80,13 @@ namespace osu.Game.Screens.Play protected override void Update() { if (!IsPaused.Value) - ClockToProcess.ProcessFrame(); + GameplayClock.UnderlyingClock.ProcessFrame(); base.Update(); } protected abstract void OnIsPausedChanged(ValueChangedEvent isPaused); - protected virtual IFrameBasedClock ClockToProcess => AdjustableClock; - protected abstract GameplayClock CreateGameplayClock(IFrameBasedClock source); } } diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 83e21f3c1d..5eb82bf0fa 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -128,8 +128,6 @@ namespace osu.Game.Screens.Play Seek(skipTarget); } - protected override IFrameBasedClock ClockToProcess => userOffsetClock; - protected override GameplayClock CreateGameplayClock(IFrameBasedClock source) { // Lazer's audio timings in general doesn't match stable. This is the result of user testing, albeit limited. From 3a78c19f9695ad5519e8494e8e888f25d50998b9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 16 Apr 2021 20:33:29 +0900 Subject: [PATCH 1569/1791] More refactoring/xmldocs --- .../Screens/Play/GameplayClockContainer.cs | 64 ++++++++++++++----- .../Play/MasterGameplayClockContainer.cs | 16 +++-- 2 files changed, 61 insertions(+), 19 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index b7dc55277f..642ede5f1c 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -9,26 +9,36 @@ using osu.Framework.Timing; namespace osu.Game.Screens.Play { + /// + /// Encapsulates gameplay timing logic and provides a via DI for gameplay components to use. + /// public abstract class GameplayClockContainer : Container { /// - /// The final clock which is exposed to underlying components. + /// The final clock which is exposed to gameplay components. /// public GameplayClock GameplayClock { get; private set; } + /// + /// Whether gameplay is paused. + /// public readonly BindableBool IsPaused = new BindableBool(); /// - /// The decoupled clock used for gameplay. Should be used for seeks and clock control. + /// The adjustable source clock used for gameplay. Should be used for seeks and clock control. /// - protected readonly DecoupleableInterpolatingFramedClock AdjustableClock; + protected readonly DecoupleableInterpolatingFramedClock AdjustableSource; + /// + /// Creates a new . + /// + /// The source used for timing. protected GameplayClockContainer(IClock sourceClock) { RelativeSizeAxes = Axes.Both; - AdjustableClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; - AdjustableClock.ChangeSource(sourceClock); + AdjustableSource = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; + AdjustableSource.ChangeSource(sourceClock); IsPaused.BindValueChanged(OnIsPausedChanged); } @@ -37,21 +47,24 @@ namespace osu.Game.Screens.Play { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - dependencies.CacheAs(GameplayClock = CreateGameplayClock(AdjustableClock)); + dependencies.CacheAs(GameplayClock = CreateGameplayClock(AdjustableSource)); GameplayClock.IsPaused.BindTo(IsPaused); return dependencies; } + /// + /// Starts gameplay. + /// public virtual void Start() { - if (!AdjustableClock.IsRunning) + if (!AdjustableSource.IsRunning) { // Seeking the decoupled clock to its current time ensures that its source clock will be seeked to the same time // This accounts for the clock source potentially taking time to enter a completely stopped state Seek(GameplayClock.CurrentTime); - AdjustableClock.Start(); + AdjustableSource.Start(); } IsPaused.Value = false; @@ -59,19 +72,22 @@ namespace osu.Game.Screens.Play /// /// Seek to a specific time in gameplay. - /// - /// Adjusts for any offsets which have been applied (so the seek may not be the expected point in time on the underlying audio track). - /// /// /// The destination time to seek to. - public virtual void Seek(double time) => AdjustableClock.Seek(time); + public virtual void Seek(double time) => AdjustableSource.Seek(time); + /// + /// Stops gameplay. + /// public virtual void Stop() => IsPaused.Value = true; + /// + /// Restarts gameplay. + /// public virtual void Restart() { - AdjustableClock.Seek(0); - AdjustableClock.Stop(); + AdjustableSource.Seek(0); + AdjustableSource.Stop(); if (!IsPaused.Value) Start(); @@ -85,8 +101,26 @@ namespace osu.Game.Screens.Play base.Update(); } - protected abstract void OnIsPausedChanged(ValueChangedEvent isPaused); + /// + /// Invoked when the value of is changed to start or stop the clock. + /// + /// Whether the clock should now be paused. + protected virtual void OnIsPausedChanged(ValueChangedEvent isPaused) + { + if (isPaused.NewValue) + AdjustableSource.Stop(); + else + AdjustableSource.Start(); + } + /// + /// Creates the final which is exposed via DI to be used by gameplay components. + /// + /// + /// Any intermediate clocks such as platform offsets should be applied here. + /// + /// The providing the source time. + /// The final . protected abstract GameplayClock CreateGameplayClock(IFrameBasedClock source); } } diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 5eb82bf0fa..e7b4645734 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -23,7 +23,7 @@ namespace osu.Game.Screens.Play /// public const double MINIMUM_SKIP_TIME = 1000; - protected Track Track => (Track)AdjustableClock.Source; + protected Track Track => (Track)AdjustableSource.Source; public readonly BindableNumber UserPlaybackRate = new BindableDouble(1) { @@ -84,17 +84,25 @@ namespace osu.Game.Screens.Play Seek(startTime); - AdjustableClock.ProcessFrame(); + AdjustableSource.ProcessFrame(); } protected override void OnIsPausedChanged(ValueChangedEvent isPaused) { + // The source is stopped by a frequency fade first. if (isPaused.NewValue) - this.TransformBindableTo(pauseFreqAdjust, 0, 200, Easing.Out).OnComplete(_ => AdjustableClock.Stop()); + this.TransformBindableTo(pauseFreqAdjust, 0, 200, Easing.Out).OnComplete(_ => AdjustableSource.Stop()); else this.TransformBindableTo(pauseFreqAdjust, 1, 200, Easing.In); } + /// + /// Seek to a specific time in gameplay. + /// + /// + /// Adjusts for any offsets which have been applied (so the seek may not be the expected point in time on the underlying audio track). + /// + /// The destination time to seek to. public override void Seek(double time) { // remove the offset component here because most of the time we want the seek to be aligned to gameplay, not the audio track. @@ -146,7 +154,7 @@ namespace osu.Game.Screens.Play public void StopUsingBeatmapClock() { removeSourceClockAdjustments(); - AdjustableClock.ChangeSource(new TrackVirtual(beatmap.Track.Length)); + AdjustableSource.ChangeSource(new TrackVirtual(beatmap.Track.Length)); } private bool speedAdjustmentsApplied; From 314b1646bd22db171ecf35c805860dd578394b41 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 16 Apr 2021 20:47:09 +0900 Subject: [PATCH 1570/1791] Add xmldoc to MasterGameplayClockContainer --- osu.Game/Screens/Play/MasterGameplayClockContainer.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index e7b4645734..db0aa23001 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -16,6 +16,16 @@ using osu.Game.Configuration; namespace osu.Game.Screens.Play { + /// + /// A which uses a as a source. + /// + /// This is the most complete which takes into account all user and platform offsets, + /// and provides implementations for user actions such as skipping or adjusting playback rates that may occur during gameplay. + /// + /// + /// + /// This is intended to be used as a single controller for gameplay, or as a reference source for other s. + /// public class MasterGameplayClockContainer : GameplayClockContainer { /// From 44e13a91ade2e0aa18fb8d159eebed51e9b30787 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 16 Apr 2021 20:51:42 +0900 Subject: [PATCH 1571/1791] Rename test scene to match class --- ...ockContainer.cs => TestSceneMasterGameplayClockContainer.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename osu.Game.Tests/Gameplay/{TestSceneGameplayClockContainer.cs => TestSceneMasterGameplayClockContainer.cs} (92%) diff --git a/osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs b/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs similarity index 92% rename from osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs rename to osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs index 4d5dcabbba..77ada958d7 100644 --- a/osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs +++ b/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs @@ -10,7 +10,7 @@ using osu.Game.Tests.Visual; namespace osu.Game.Tests.Gameplay { [HeadlessTest] - public class TestSceneGameplayClockContainer : OsuTestScene + public class TestSceneMasterGameplayClockContainer : OsuTestScene { [Test] public void TestStartThenElapsedTime() From dc899515ec7ffd460edd3a148a651c344daaf74f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 17 Apr 2021 00:56:53 +0900 Subject: [PATCH 1572/1791] Empty commit From d5a1e00feb34dbfe3ec6a67ba5a64d51df47c905 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 17 Apr 2021 03:32:47 +0300 Subject: [PATCH 1573/1791] Improve "barrel roll" mod settings description --- osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs index b6cfa514a1..edf846e6ec 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.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.Linq; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Game.Configuration; using osu.Game.Rulesets.Mods; @@ -30,6 +32,8 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Description => "The whole playfield is on a wheel!"; public override double ScoreMultiplier => 1; + public override string SettingDescription => $"{SpinSpeed.Value}rpm, {Direction.Value.GetDescription().ToLowerInvariant()}"; + public void Update(Playfield playfield) { playfield.Rotation = (Direction.Value == RotationDirection.CounterClockwise ? -1 : 1) * 360 * (float)(playfield.Time.Current / 60000 * SpinSpeed.Value); From 78c850878486280548694a5641b29bbf59e50a71 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 17 Apr 2021 03:52:07 +0300 Subject: [PATCH 1574/1791] Remove unused using directive gotta git gud --- osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs index edf846e6ec..af3986aceb 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.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.Linq; using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; From 892a8a7cd2e2305dbfa159b0044333aa61e34507 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 17 Apr 2021 04:23:48 +0300 Subject: [PATCH 1575/1791] Remove unnecessary comma --- osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs index af3986aceb..00596e1486 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Description => "The whole playfield is on a wheel!"; public override double ScoreMultiplier => 1; - public override string SettingDescription => $"{SpinSpeed.Value}rpm, {Direction.Value.GetDescription().ToLowerInvariant()}"; + public override string SettingDescription => $"{SpinSpeed.Value}rpm {Direction.Value.GetDescription().ToLowerInvariant()}"; public void Update(Playfield playfield) { From c1082ddb9a7e03ac420a4cb7aa5e468dde614b22 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 17 Apr 2021 06:28:07 +0300 Subject: [PATCH 1576/1791] Add space before the unit Co-authored-by: Joseph Madamba --- osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs index 00596e1486..bcbb0f4366 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Description => "The whole playfield is on a wheel!"; public override double ScoreMultiplier => 1; - public override string SettingDescription => $"{SpinSpeed.Value}rpm {Direction.Value.GetDescription().ToLowerInvariant()}"; + public override string SettingDescription => $"{SpinSpeed.Value} rpm {Direction.Value.GetDescription().ToLowerInvariant()}"; public void Update(Playfield playfield) { From 250c7403e87f88c65139a55792902dc8d173a995 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 17 Apr 2021 13:50:00 +0200 Subject: [PATCH 1577/1791] Fix idle tracker assuming time starts at 0 `IdleTracker` in its construction quietly assumed that the clock it receives from its parent starts ticking from 0 at the point at which it is passed down. This is not necessarily the case when headless executions are involved, which means that the initial state of the tracker could be computed as idle incorrectly. Resolve by explicitly reading the clock time at the point of `LoadComplete()`. --- osu.Game/Input/IdleTracker.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Input/IdleTracker.cs b/osu.Game/Input/IdleTracker.cs index 63a6348b57..2d6a21d1cf 100644 --- a/osu.Game/Input/IdleTracker.cs +++ b/osu.Game/Input/IdleTracker.cs @@ -42,6 +42,12 @@ namespace osu.Game.Input RelativeSizeAxes = Axes.Both; } + protected override void LoadComplete() + { + base.LoadComplete(); + updateLastInteractionTime(); + } + protected override void Update() { base.Update(); From f3ea51eeedc8113ac83c79e5f0daaa241e755f20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 17 Apr 2021 14:23:32 +0200 Subject: [PATCH 1578/1791] Adjust tests to not rely on invalid assumption --- osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs b/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs index 4d64c7d35d..fdee53f0be 100644 --- a/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs +++ b/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs @@ -81,6 +81,13 @@ namespace osu.Game.Tests.Visual.Components [Test] public void TestMovement() { + checkIdleStatus(1, false); + checkIdleStatus(2, false); + checkIdleStatus(3, false); + checkIdleStatus(4, false); + + waitForAllIdle(); + AddStep("move to top right", () => InputManager.MoveMouseTo(box2)); checkIdleStatus(1, true); @@ -102,6 +109,8 @@ namespace osu.Game.Tests.Visual.Components [Test] public void TestTimings() { + waitForAllIdle(); + AddStep("move to centre", () => InputManager.MoveMouseTo(Content)); checkIdleStatus(1, false); From 6773162f17bab34cb88272d915d07b19bfab7f47 Mon Sep 17 00:00:00 2001 From: jvyden Date: Sat, 17 Apr 2021 08:47:27 -0400 Subject: [PATCH 1579/1791] Implicitly set defaults when resetting values --- osu.Game/Configuration/SessionStatics.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Configuration/SessionStatics.cs b/osu.Game/Configuration/SessionStatics.cs index 872a910837..e57b1c2fdf 100644 --- a/osu.Game/Configuration/SessionStatics.cs +++ b/osu.Game/Configuration/SessionStatics.cs @@ -23,11 +23,11 @@ namespace osu.Game.Configuration public void ResetValues() { - SetValue(Static.LoginOverlayDisplayed, false); - SetValue(Static.MutedAudioNotificationShownOnce, false); - SetValue(Static.LowBatteryNotificationShownOnce, false); - SetValue(Static.LastHoverSoundPlaybackTime, (double?)null); - SetValue(Static.SeasonalBackgrounds, null); + GetOriginalBindable(Static.LoginOverlayDisplayed).SetDefault(); + GetOriginalBindable(Static.MutedAudioNotificationShownOnce).SetDefault(); + GetOriginalBindable(Static.LowBatteryNotificationShownOnce).SetDefault(); + GetOriginalBindable(Static.LastHoverSoundPlaybackTime).SetDefault(); + GetOriginalBindable(Static.SeasonalBackgrounds).SetDefault(); } } From cf3aaff7bd1bf796f826d18f8a56c7a07d1359c6 Mon Sep 17 00:00:00 2001 From: Fabian Date: Sat, 17 Apr 2021 16:01:23 +0200 Subject: [PATCH 1580/1791] Add floating fruits mod --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 3 ++- .../Mods/CatchModFloatingFruits.cs | 23 +++++++++++++++++++ osu.Game/Screens/Play/Player.cs | 2 +- 3 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index f4ddbd3021..6206815728 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -126,7 +126,8 @@ namespace osu.Game.Rulesets.Catch case ModType.Fun: return new Mod[] { - new MultiMod(new ModWindUp(), new ModWindDown()) + new MultiMod(new ModWindUp(), new ModWindDown()), + new CatchModFloatingFruits() }; default: diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs new file mode 100644 index 0000000000..4e25739521 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs @@ -0,0 +1,23 @@ +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Play; + +namespace osu.Game.Rulesets.Catch.Mods +{ + public class CatchModFloatingFruits : Mod, IApplicableToPlayer + { + public override string Name => "Floating Fruits"; + public override string Acronym => "FF"; + public override string Description => "The fruits are... floating?"; + public override double ScoreMultiplier => 1; + public override IconUsage? Icon => FontAwesome.Brands.Fly; + + public void ApplyToPlayer(Player player) + { + player.DrawableRuleset.Anchor = Anchor.Centre; + player.DrawableRuleset.Origin = Anchor.Centre; + player.DrawableRuleset.Scale = new osuTK.Vector2(1, -1); + } + } +} diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index dd3f58439b..59231a1c30 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -110,7 +110,7 @@ namespace osu.Game.Screens.Play protected HealthProcessor HealthProcessor { get; private set; } - protected DrawableRuleset DrawableRuleset { get; private set; } + public DrawableRuleset DrawableRuleset { get; set; } protected HUDOverlay HUDOverlay { get; private set; } From 5d274dbce86b3b8b03abb72f176e57a4209c4576 Mon Sep 17 00:00:00 2001 From: Fabian Date: Sat, 17 Apr 2021 16:38:28 +0200 Subject: [PATCH 1581/1791] replace IApplicableToPlayer with IApplicableToDrawableRuleset --- .../Mods/CatchModFloatingFruits.cs | 13 ++++++++----- osu.Game/Screens/Play/Player.cs | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs index 4e25739521..4e34cbe9e2 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs @@ -1,11 +1,13 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; +using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; namespace osu.Game.Rulesets.Catch.Mods { - public class CatchModFloatingFruits : Mod, IApplicableToPlayer + public class CatchModFloatingFruits : Mod, IApplicableToDrawableRuleset { public override string Name => "Floating Fruits"; public override string Acronym => "FF"; @@ -13,11 +15,12 @@ namespace osu.Game.Rulesets.Catch.Mods public override double ScoreMultiplier => 1; public override IconUsage? Icon => FontAwesome.Brands.Fly; - public void ApplyToPlayer(Player player) + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - player.DrawableRuleset.Anchor = Anchor.Centre; - player.DrawableRuleset.Origin = Anchor.Centre; - player.DrawableRuleset.Scale = new osuTK.Vector2(1, -1); + drawableRuleset.Anchor = Anchor.Centre; + drawableRuleset.Origin = Anchor.Centre; + drawableRuleset.Scale = new osuTK.Vector2(1, -1); } + } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 59231a1c30..dd3f58439b 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -110,7 +110,7 @@ namespace osu.Game.Screens.Play protected HealthProcessor HealthProcessor { get; private set; } - public DrawableRuleset DrawableRuleset { get; set; } + protected DrawableRuleset DrawableRuleset { get; private set; } protected HUDOverlay HUDOverlay { get; private set; } From 448574e7e67cba8ead7bf1c27e0b892d9fec3754 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 17 Apr 2021 17:33:53 +0200 Subject: [PATCH 1582/1791] Use `WorkingBeatmap` instead of `IBeatmap` This lets us access things like the background, track, etc. which are necessary for quality and filesize checks. Also improves the structure of the `CheckBackgroundTest` class in the process. --- .../Checks/CheckOffscreenObjectsTest.cs | 70 ++++++++----------- .../Edit/Checks/CheckOffscreenObjects.cs | 4 +- .../Edit/OsuBeatmapVerifier.cs | 2 +- .../Editing/Checks/CheckBackgroundTest.cs | 7 +- osu.Game/Rulesets/Edit/BeatmapVerifier.cs | 2 +- .../Rulesets/Edit/Checks/CheckBackground.cs | 10 +-- .../Rulesets/Edit/Checks/Components/ICheck.cs | 4 +- osu.Game/Rulesets/Edit/IBeatmapVerifier.cs | 2 +- osu.Game/Screens/Edit/Verify/VerifyScreen.cs | 3 +- 9 files changed, 46 insertions(+), 58 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs index f9445a9a96..db347960ef 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs @@ -10,6 +10,7 @@ using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Edit.Checks; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.UI; +using osu.Game.Tests.Beatmaps; using osuTK; namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks @@ -30,25 +31,23 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks [Test] public void TestCircleInCenter() { - var beatmap = new Beatmap + assertOk(new Beatmap { HitObjects = new List { new HitCircle { StartTime = 3000, - Position = playfield_centre // Playfield is 640 x 480. + Position = playfield_centre } } - }; - - Assert.That(check.Run(beatmap), Is.Empty); + }); } [Test] public void TestCircleNearEdge() { - var beatmap = new Beatmap + assertOk(new Beatmap { HitObjects = new List { @@ -58,15 +57,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks Position = new Vector2(5, 5) } } - }; - - Assert.That(check.Run(beatmap), Is.Empty); + }); } [Test] public void TestCircleNearEdgeStackedOffscreen() { - var beatmap = new Beatmap + assertOffscreenCircle(new Beatmap { HitObjects = new List { @@ -77,15 +74,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks StackHeight = 5 } } - }; - - assertOffscreenCircle(beatmap); + }); } [Test] public void TestCircleOffscreen() { - var beatmap = new Beatmap + assertOffscreenCircle(new Beatmap { HitObjects = new List { @@ -95,15 +90,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks Position = new Vector2(0, 0) } } - }; - - assertOffscreenCircle(beatmap); + }); } [Test] public void TestSliderInCenter() { - var beatmap = new Beatmap + assertOk(new Beatmap { HitObjects = new List { @@ -118,15 +111,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks }), } } - }; - - Assert.That(check.Run(beatmap), Is.Empty); + }); } [Test] public void TestSliderNearEdge() { - var beatmap = new Beatmap + assertOk(new Beatmap { HitObjects = new List { @@ -141,15 +132,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks }), } } - }; - - Assert.That(check.Run(beatmap), Is.Empty); + }); } [Test] public void TestSliderNearEdgeStackedOffscreen() { - var beatmap = new Beatmap + assertOk(new Beatmap { HitObjects = new List { @@ -165,15 +154,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks StackHeight = 5 } } - }; - - assertOffscreenSlider(beatmap); + }); } [Test] public void TestSliderOffscreenStart() { - var beatmap = new Beatmap + assertOffscreenSlider(new Beatmap { HitObjects = new List { @@ -188,15 +175,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks }), } } - }; - - assertOffscreenSlider(beatmap); + }); } [Test] public void TestSliderOffscreenEnd() { - var beatmap = new Beatmap + assertOffscreenSlider(new Beatmap { HitObjects = new List { @@ -211,15 +196,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks }), } } - }; - - assertOffscreenSlider(beatmap); + }); } [Test] public void TestSliderOffscreenPath() { - var beatmap = new Beatmap + assertOffscreenSlider(new Beatmap { HitObjects = new List { @@ -236,14 +219,17 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks }), } } - }; + }); + } - assertOffscreenSlider(beatmap); + private void assertOk(IBeatmap beatmap) + { + Assert.That(check.Run(new TestWorkingBeatmap(beatmap)), Is.Empty); } private void assertOffscreenCircle(IBeatmap beatmap) { - var issues = check.Run(beatmap).ToList(); + var issues = check.Run(new TestWorkingBeatmap(beatmap)).ToList(); Assert.That(issues, Has.Count.EqualTo(1)); Assert.That(issues.Single().Template is CheckOffscreenObjects.IssueTemplateOffscreenCircle); @@ -251,7 +237,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks private void assertOffscreenSlider(IBeatmap beatmap) { - var issues = check.Run(beatmap).ToList(); + var issues = check.Run(new TestWorkingBeatmap(beatmap)).ToList(); Assert.That(issues, Has.Count.EqualTo(1)); Assert.That(issues.Single().Template is CheckOffscreenObjects.IssueTemplateOffscreenSlider); diff --git a/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs b/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs index 27cae2ecc1..54b167aaf3 100644 --- a/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs +++ b/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs @@ -31,9 +31,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Checks new IssueTemplateOffscreenSlider(this) }; - public IEnumerable Run(IBeatmap beatmap) + public IEnumerable Run(WorkingBeatmap workingBeatmap) { - foreach (var hitobject in beatmap.HitObjects) + foreach (var hitobject in workingBeatmap.Beatmap.HitObjects) { switch (hitobject) { diff --git a/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs b/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs index 1c7ab00bbb..9b9383d547 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs @@ -17,6 +17,6 @@ namespace osu.Game.Rulesets.Osu.Edit new CheckOffscreenObjects() }; - public IEnumerable Run(IBeatmap beatmap) => checks.SelectMany(check => check.Run(beatmap)); + public IEnumerable Run(WorkingBeatmap workingBeatmap) => checks.SelectMany(check => check.Run(workingBeatmap)); } } diff --git a/osu.Game.Tests/Editing/Checks/CheckBackgroundTest.cs b/osu.Game.Tests/Editing/Checks/CheckBackgroundTest.cs index 635e3bb0f3..d61f0989a6 100644 --- a/osu.Game.Tests/Editing/Checks/CheckBackgroundTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckBackgroundTest.cs @@ -7,6 +7,7 @@ using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit.Checks; using osu.Game.Rulesets.Objects; +using osu.Game.Tests.Beatmaps; namespace osu.Game.Tests.Editing.Checks { @@ -14,13 +15,13 @@ namespace osu.Game.Tests.Editing.Checks public class CheckBackgroundTest { private CheckBackground check; - private IBeatmap beatmap; + private WorkingBeatmap beatmap; [SetUp] public void Setup() { check = new CheckBackground(); - beatmap = new Beatmap + beatmap = new TestWorkingBeatmap(new Beatmap { BeatmapInfo = new BeatmapInfo { @@ -33,7 +34,7 @@ namespace osu.Game.Tests.Editing.Checks }) } } - }; + }); } [Test] diff --git a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs index f9bced7beb..40714e8c7e 100644 --- a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs +++ b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs @@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Edit new CheckBackground(), }; - public IEnumerable Run(IBeatmap beatmap) => checks.SelectMany(check => check.Run(beatmap)); + public IEnumerable Run(WorkingBeatmap workingBeatmap) => checks.SelectMany(check => check.Run(workingBeatmap)); } } diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs b/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs index 93da42425c..d2fffeea4e 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs @@ -18,9 +18,9 @@ namespace osu.Game.Rulesets.Edit.Checks new IssueTemplateDoesNotExist(this) }; - public IEnumerable Run(IBeatmap beatmap) + public IEnumerable Run(WorkingBeatmap workingBeatmap) { - if (beatmap.Metadata.BackgroundFile == null) + if (workingBeatmap.Metadata?.BackgroundFile == null) { yield return new IssueTemplateNoneSet(this).Create(); @@ -29,13 +29,13 @@ namespace osu.Game.Rulesets.Edit.Checks // If the background is set, also make sure it still exists. - var set = beatmap.BeatmapInfo.BeatmapSet; - var file = set.Files.FirstOrDefault(f => f.Filename == beatmap.Metadata.BackgroundFile); + var set = workingBeatmap.BeatmapInfo.BeatmapSet; + var file = set.Files.FirstOrDefault(f => f.Filename == workingBeatmap.Metadata.BackgroundFile); if (file != null) yield break; - yield return new IssueTemplateDoesNotExist(this).Create(beatmap.Metadata.BackgroundFile); + yield return new IssueTemplateDoesNotExist(this).Create(workingBeatmap.Metadata.BackgroundFile); } public class IssueTemplateNoneSet : IssueTemplate diff --git a/osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs b/osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs index f284240092..a2814ee603 100644 --- a/osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs +++ b/osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Edit.Checks.Components /// /// Runs this check and returns any issues detected for the provided beatmap. /// - /// The beatmap to run the check on. - public IEnumerable Run(IBeatmap beatmap); + /// The beatmap to run the check on. + public IEnumerable Run(WorkingBeatmap workingBeatmap); } } diff --git a/osu.Game/Rulesets/Edit/IBeatmapVerifier.cs b/osu.Game/Rulesets/Edit/IBeatmapVerifier.cs index 61d8119635..12be8815e1 100644 --- a/osu.Game/Rulesets/Edit/IBeatmapVerifier.cs +++ b/osu.Game/Rulesets/Edit/IBeatmapVerifier.cs @@ -12,6 +12,6 @@ namespace osu.Game.Rulesets.Edit /// public interface IBeatmapVerifier { - public IEnumerable Run(IBeatmap beatmap); + public IEnumerable Run(WorkingBeatmap workingBeatmap); } } diff --git a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs index 550fbe2950..160a14caac 100644 --- a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs +++ b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs @@ -7,6 +7,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; @@ -61,7 +62,7 @@ namespace osu.Game.Screens.Edit.Verify private EditorClock clock { get; set; } [Resolved] - protected EditorBeatmap Beatmap { get; private set; } + protected WorkingBeatmap Beatmap { get; private set; } [Resolved] private Bindable selectedIssue { get; set; } From 400f8b3938428d22ca25d5760214045f42b3328d Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 17 Apr 2021 17:47:13 +0200 Subject: [PATCH 1583/1791] Add `GetStream` to `IWorkingBeatmap` This is necessary to obtain the filesize of the audio and background files. --- osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs | 2 ++ osu.Game.Tests/WaveformTestBeatmap.cs | 2 ++ osu.Game/Beatmaps/BeatmapManager.cs | 1 + osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs | 3 +++ osu.Game/Beatmaps/DummyWorkingBeatmap.cs | 3 +++ osu.Game/Beatmaps/WorkingBeatmap.cs | 3 +++ osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs | 2 ++ osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs | 2 ++ osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs | 3 +++ 9 files changed, 21 insertions(+) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs index 920cc36776..a18f82fe4a 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs @@ -168,6 +168,8 @@ namespace osu.Game.Tests.Beatmaps.Formats protected override Texture GetBackground() => throw new NotImplementedException(); protected override Track GetBeatmapTrack() => throw new NotImplementedException(); + + public override Stream GetStream(string storagePath) => throw new NotImplementedException(); } } } diff --git a/osu.Game.Tests/WaveformTestBeatmap.cs b/osu.Game.Tests/WaveformTestBeatmap.cs index 8c8c827404..cbed28641c 100644 --- a/osu.Game.Tests/WaveformTestBeatmap.cs +++ b/osu.Game.Tests/WaveformTestBeatmap.cs @@ -52,6 +52,8 @@ namespace osu.Game.Tests protected override Waveform GetWaveform() => new Waveform(trackStore.GetStream(firstAudioFile)); + public override Stream GetStream(string storagePath) => null; + protected override Track GetBeatmapTrack() => trackStore.Get(firstAudioFile); private string firstAudioFile diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index b4ea898b7d..5e975de77c 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -526,6 +526,7 @@ namespace osu.Game.Beatmaps protected override IBeatmap GetBeatmap() => beatmap; protected override Texture GetBackground() => null; protected override Track GetBeatmapTrack() => null; + public override Stream GetStream(string storagePath) => null; } } diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs index 62cf29dc03..c1fc7a72e2 100644 --- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.IO; using osu.Framework.Audio.Track; using osu.Framework.Graphics.Textures; using osu.Framework.Logging; @@ -142,6 +143,8 @@ namespace osu.Game.Beatmaps return null; } } + + public override Stream GetStream(string storagePath) => resources.Files.GetStream(storagePath); } } } diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs index c114358771..6922d1c286 100644 --- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Threading; using JetBrains.Annotations; using osu.Framework.Audio; @@ -48,6 +49,8 @@ namespace osu.Game.Beatmaps protected override Track GetBeatmapTrack() => GetVirtualTrack(); + public override Stream GetStream(string storagePath) => null; + private class DummyRulesetInfo : RulesetInfo { public override Ruleset CreateInstance() => new DummyRuleset(); diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index f7f276230f..e0eeaf6db0 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -326,6 +327,8 @@ namespace osu.Game.Beatmaps protected virtual ISkin GetSkin() => new DefaultSkin(); private readonly RecyclableLazy skin; + public abstract Stream GetStream(string storagePath); + public class RecyclableLazy { private Lazy lazy; diff --git a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs index f2e0320ce3..66784fda54 100644 --- a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs +++ b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs @@ -116,6 +116,8 @@ namespace osu.Game.Screens.Edit protected override Texture GetBackground() => throw new NotImplementedException(); protected override Track GetBeatmapTrack() => throw new NotImplementedException(); + + public override Stream GetStream(string storagePath) => throw new NotImplementedException(); } } } diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs index 5ef2458919..1c5e551042 100644 --- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs +++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs @@ -215,6 +215,8 @@ namespace osu.Game.Tests.Beatmaps protected override Track GetBeatmapTrack() => throw new NotImplementedException(); + public override Stream GetStream(string storagePath) => throw new NotImplementedException(); + protected override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap, Ruleset ruleset) { var converter = base.CreateBeatmapConverter(beatmap, ruleset); diff --git a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs index bfcb2403c1..852006bc9b 100644 --- a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.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.IO; using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Graphics.Textures; @@ -35,6 +36,8 @@ namespace osu.Game.Tests.Beatmaps protected override Storyboard GetStoryboard() => storyboard ?? base.GetStoryboard(); + public override Stream GetStream(string storagePath) => null; + protected override Texture GetBackground() => null; protected override Track GetBeatmapTrack() => null; From b36da2664c2be2e9be6a2c69383dc3b95d459080 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 17 Apr 2021 17:49:10 +0200 Subject: [PATCH 1584/1791] Add `GetPathForFile` to `BeatmapSetInfo` This is used in several places, and so should probably have a function rather than remaining as duplicated code. Also applies this together with the previous commit to `BeatmapManagerWorkingBeatmap`. --- .../Beatmaps/BeatmapManager_WorkingBeatmap.cs | 15 ++++++--------- osu.Game/Beatmaps/BeatmapSetInfo.cs | 2 ++ 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs index c1fc7a72e2..d78ffbbfb6 100644 --- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs @@ -3,7 +3,6 @@ using System; using System.Diagnostics.CodeAnalysis; -using System.Linq; using System.IO; using osu.Framework.Audio.Track; using osu.Framework.Graphics.Textures; @@ -37,7 +36,7 @@ namespace osu.Game.Beatmaps try { - using (var stream = new LineBufferedReader(resources.Files.GetStream(getPathForFile(BeatmapInfo.Path)))) + using (var stream = new LineBufferedReader(GetStream(BeatmapSetInfo.GetPathForFile(BeatmapInfo.Path)))) return Decoder.GetDecoder(stream).Decode(stream); } catch (Exception e) @@ -47,8 +46,6 @@ namespace osu.Game.Beatmaps } } - private string getPathForFile(string filename) => BeatmapSetInfo.Files.SingleOrDefault(f => string.Equals(f.Filename, filename, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; - protected override bool BackgroundStillValid(Texture b) => false; // bypass lazy logic. we want to return a new background each time for refcounting purposes. protected override Texture GetBackground() @@ -58,7 +55,7 @@ namespace osu.Game.Beatmaps try { - return resources.LargeTextureStore.Get(getPathForFile(Metadata.BackgroundFile)); + return resources.LargeTextureStore.Get(BeatmapSetInfo.GetPathForFile(Metadata.BackgroundFile)); } catch (Exception e) { @@ -74,7 +71,7 @@ namespace osu.Game.Beatmaps try { - return resources.Tracks.Get(getPathForFile(Metadata.AudioFile)); + return resources.Tracks.Get(BeatmapSetInfo.GetPathForFile(Metadata.AudioFile)); } catch (Exception e) { @@ -90,7 +87,7 @@ namespace osu.Game.Beatmaps try { - var trackData = resources.Files.GetStream(getPathForFile(Metadata.AudioFile)); + var trackData = GetStream(BeatmapSetInfo.GetPathForFile(Metadata.AudioFile)); return trackData == null ? null : new Waveform(trackData); } catch (Exception e) @@ -106,7 +103,7 @@ namespace osu.Game.Beatmaps try { - using (var stream = new LineBufferedReader(resources.Files.GetStream(getPathForFile(BeatmapInfo.Path)))) + using (var stream = new LineBufferedReader(GetStream(BeatmapSetInfo.GetPathForFile(BeatmapInfo.Path)))) { var decoder = Decoder.GetDecoder(stream); @@ -115,7 +112,7 @@ namespace osu.Game.Beatmaps storyboard = decoder.Decode(stream); else { - using (var secondaryStream = new LineBufferedReader(resources.Files.GetStream(getPathForFile(BeatmapSetInfo.StoryboardFile)))) + using (var secondaryStream = new LineBufferedReader(GetStream(BeatmapSetInfo.GetPathForFile(BeatmapSetInfo.StoryboardFile)))) storyboard = decoder.Decode(stream, secondaryStream); } } diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index 7bc1c8c7b9..774bd0bc62 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -59,6 +59,8 @@ namespace osu.Game.Beatmaps public string StoryboardFile => Files?.Find(f => f.Filename.EndsWith(".osb", StringComparison.OrdinalIgnoreCase))?.Filename; + public string GetPathForFile(string filename) => Files?.SingleOrDefault(f => string.Equals(f.Filename, filename, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; + public List Files { get; set; } public override string ToString() => Metadata?.ToString() ?? base.ToString(); From 62c54e00cb7eee3b424e965a09462431ab03cf9c Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 17 Apr 2021 18:01:04 +0200 Subject: [PATCH 1585/1791] Add check for background resolution and filesize --- osu.Game/Rulesets/Edit/BeatmapVerifier.cs | 1 + .../Edit/Checks/CheckBackgroundQuality.cs | 98 +++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs diff --git a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs index 40714e8c7e..24a4f473de 100644 --- a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs +++ b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs @@ -17,6 +17,7 @@ namespace osu.Game.Rulesets.Edit private readonly List checks = new List { new CheckBackground(), + new CheckBackgroundQuality() }; public IEnumerable Run(WorkingBeatmap workingBeatmap) => checks.SelectMany(check => check.Run(workingBeatmap)); diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs b/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs new file mode 100644 index 0000000000..ed504f52ed --- /dev/null +++ b/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs @@ -0,0 +1,98 @@ +// 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.Rulesets.Edit.Checks.Components; + +namespace osu.Game.Rulesets.Edit.Checks +{ + public class CheckBackgroundQuality : ICheck + { + // These are the requirements as stated in the Ranking Criteria. + // See https://osu.ppy.sh/wiki/en/Ranking_Criteria#rules.5 + private const int min_width = 160; + private const int max_width = 2560; + private const int min_height = 120; + private const int max_height = 1440; + private const double max_filesize_mb = 2.5d; + + // It's usually possible to find a higher resolution of the same image if lower than these. + private const int low_width = 960; + private const int low_height = 540; + + public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Resources, "Too high or low background resolution"); + + public IEnumerable PossibleTemplates => new IssueTemplate[] + { + new IssueTemplateTooHighResolution(this), + new IssueTemplateTooLowResolution(this), + new IssueTemplateTooUncompressed(this) + }; + + public IEnumerable Run(WorkingBeatmap workingBeatmap) + { + if (workingBeatmap.Metadata?.BackgroundFile == null) + yield break; + + var texture = workingBeatmap.Background; + if (texture == null) + yield break; + + if (texture.Width > max_width || texture.Height > max_height) + yield return new IssueTemplateTooHighResolution(this).Create(texture.Width, texture.Height); + + if (texture.Width < min_width || texture.Height < min_height) + yield return new IssueTemplateTooLowResolution(this).Create(texture.Width, texture.Height); + + if (texture.Width < low_width || texture.Height < low_height) + yield return new IssueTemplateLowResolution(this).Create(texture.Width, texture.Height); + + string storagePath = workingBeatmap.BeatmapInfo.BeatmapSet.GetPathForFile(workingBeatmap.Metadata.BackgroundFile); + double filesizeMb = workingBeatmap.GetStream(storagePath).Length / (1024d * 1024d); + + if (filesizeMb > max_filesize_mb) + yield return new IssueTemplateTooUncompressed(this).Create(filesizeMb); + } + + public class IssueTemplateTooHighResolution : IssueTemplate + { + public IssueTemplateTooHighResolution(ICheck check) + : base(check, IssueType.Problem, "The background resolution ({0} x {1}) exceeds {2} x {3}.") + { + } + + public Issue Create(double width, double height) => new Issue(this, width, height, max_width, max_height); + } + + public class IssueTemplateTooLowResolution : IssueTemplate + { + public IssueTemplateTooLowResolution(ICheck check) + : base(check, IssueType.Problem, "The background resolution ({0} x {1}) is lower than {2} x {3}.") + { + } + + public Issue Create(double width, double height) => new Issue(this, width, height, min_width, min_height); + } + + public class IssueTemplateLowResolution : IssueTemplate + { + public IssueTemplateLowResolution(ICheck check) + : base(check, IssueType.Warning, "The background resolution ({0} x {1}) is lower than {2} x {3}.") + { + } + + public Issue Create(double width, double height) => new Issue(this, width, height, low_width, low_height); + } + + public class IssueTemplateTooUncompressed : IssueTemplate + { + public IssueTemplateTooUncompressed(ICheck check) + : base(check, IssueType.Problem, "The background filesize ({0:0.#} MB) exceeds {1} MB.") + { + } + + public Issue Create(double actualMb) => new Issue(this, actualMb, max_filesize_mb); + } + } +} From cb41c89935a4e7b0e76865efd6e8dd36ceb22124 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sat, 17 Apr 2021 20:10:07 +0200 Subject: [PATCH 1586/1791] Don't return low res and too low res at the same time --- osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs b/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs index ed504f52ed..a3f363554e 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs @@ -44,8 +44,7 @@ namespace osu.Game.Rulesets.Edit.Checks if (texture.Width < min_width || texture.Height < min_height) yield return new IssueTemplateTooLowResolution(this).Create(texture.Width, texture.Height); - - if (texture.Width < low_width || texture.Height < low_height) + else if (texture.Width < low_width || texture.Height < low_height) yield return new IssueTemplateLowResolution(this).Create(texture.Width, texture.Height); string storagePath = workingBeatmap.BeatmapInfo.BeatmapSet.GetPathForFile(workingBeatmap.Metadata.BackgroundFile); From eec77b052790f720fa716d10b96aad540b9cb083 Mon Sep 17 00:00:00 2001 From: Fabian Date: Sat, 17 Apr 2021 23:55:39 +0200 Subject: [PATCH 1587/1791] replace icon --- osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs index 4e34cbe9e2..4c46e24f1b 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Catch.Mods public override string Acronym => "FF"; public override string Description => "The fruits are... floating?"; public override double ScoreMultiplier => 1; - public override IconUsage? Icon => FontAwesome.Brands.Fly; + public override IconUsage? Icon => FontAwesome.Solid.Cloud; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { From bf8789528ae81cd332416ab0ed11572c53693a4a Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 18 Apr 2021 01:13:57 +0200 Subject: [PATCH 1588/1791] Add `GetStream` to `IWorkingBeatmap` --- osu.Game/Beatmaps/IWorkingBeatmap.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Beatmaps/IWorkingBeatmap.cs b/osu.Game/Beatmaps/IWorkingBeatmap.cs index bcd94d76fd..f1bf6f48ef 100644 --- a/osu.Game/Beatmaps/IWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/IWorkingBeatmap.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.IO; using osu.Framework.Audio.Track; using osu.Framework.Graphics.Textures; using osu.Game.Rulesets; @@ -67,5 +68,7 @@ namespace osu.Game.Beatmaps /// /// A fresh track instance, which will also be available via . Track LoadTrack(); + + Stream GetStream(string storagePath); } } From ef65c8910f0a6a9d76327810c70733630aed8c25 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 18 Apr 2021 01:15:13 +0200 Subject: [PATCH 1589/1791] Fix resolved fields --- osu.Game/Screens/Edit/Verify/VerifyScreen.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs index 160a14caac..6aa479b38d 100644 --- a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs +++ b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs @@ -62,7 +62,10 @@ namespace osu.Game.Screens.Edit.Verify private EditorClock clock { get; set; } [Resolved] - protected WorkingBeatmap Beatmap { get; private set; } + private BeatmapManager beatmapManager { get; set; } + + [Resolved] + private EditorBeatmap beatmap { get; set; } [Resolved] private Bindable selectedIssue { get; set; } @@ -74,7 +77,7 @@ namespace osu.Game.Screens.Edit.Verify private void load(OsuColour colours) { generalVerifier = new BeatmapVerifier(); - rulesetVerifier = Beatmap.BeatmapInfo.Ruleset?.CreateInstance()?.CreateBeatmapVerifier(); + rulesetVerifier = beatmap.BeatmapInfo.Ruleset?.CreateInstance()?.CreateBeatmapVerifier(); RelativeSizeAxes = Axes.Both; @@ -120,10 +123,11 @@ namespace osu.Game.Screens.Edit.Verify private void refresh() { - var issues = generalVerifier.Run(Beatmap); + var workingBeatmap = beatmapManager.GetWorkingBeatmap(beatmap.BeatmapInfo); + var issues = generalVerifier.Run(workingBeatmap); if (rulesetVerifier != null) - issues = issues.Concat(rulesetVerifier.Run(Beatmap)); + issues = issues.Concat(rulesetVerifier.Run(workingBeatmap)); table.Issues = issues .OrderBy(issue => issue.Template.Type) From abf512532e2f8d7e79d8b8fafe419b651fb1f896 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 18 Apr 2021 01:19:25 +0200 Subject: [PATCH 1590/1791] Clean up check logic Makes use of the new `BeatmapSet.GetPathForFile` method and removes dependency on `WorkingBeatmap` specifically, allowing us to switch to `IWorkingBeatmap` later. --- osu.Game/Rulesets/Edit/Checks/CheckBackground.cs | 14 ++++++-------- .../Rulesets/Edit/Checks/CheckBackgroundQuality.cs | 5 +++-- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs b/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs index d2fffeea4e..637e603e17 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit.Checks.Components; @@ -20,7 +19,9 @@ namespace osu.Game.Rulesets.Edit.Checks public IEnumerable Run(WorkingBeatmap workingBeatmap) { - if (workingBeatmap.Metadata?.BackgroundFile == null) + string backgroundFile = workingBeatmap.Beatmap.Metadata?.BackgroundFile; + + if (backgroundFile == null) { yield return new IssueTemplateNoneSet(this).Create(); @@ -28,14 +29,11 @@ namespace osu.Game.Rulesets.Edit.Checks } // If the background is set, also make sure it still exists. - - var set = workingBeatmap.BeatmapInfo.BeatmapSet; - var file = set.Files.FirstOrDefault(f => f.Filename == workingBeatmap.Metadata.BackgroundFile); - - if (file != null) + var storagePath = workingBeatmap.Beatmap.BeatmapInfo.BeatmapSet.GetPathForFile(backgroundFile); + if (storagePath != null) yield break; - yield return new IssueTemplateDoesNotExist(this).Create(workingBeatmap.Metadata.BackgroundFile); + yield return new IssueTemplateDoesNotExist(this).Create(backgroundFile); } public class IssueTemplateNoneSet : IssueTemplate diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs b/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs index a3f363554e..48d536079a 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs @@ -32,7 +32,8 @@ namespace osu.Game.Rulesets.Edit.Checks public IEnumerable Run(WorkingBeatmap workingBeatmap) { - if (workingBeatmap.Metadata?.BackgroundFile == null) + var backgroundFile = workingBeatmap.Beatmap.Metadata?.BackgroundFile; + if (backgroundFile == null) yield break; var texture = workingBeatmap.Background; @@ -47,7 +48,7 @@ namespace osu.Game.Rulesets.Edit.Checks else if (texture.Width < low_width || texture.Height < low_height) yield return new IssueTemplateLowResolution(this).Create(texture.Width, texture.Height); - string storagePath = workingBeatmap.BeatmapInfo.BeatmapSet.GetPathForFile(workingBeatmap.Metadata.BackgroundFile); + string storagePath = workingBeatmap.Beatmap.BeatmapInfo.BeatmapSet.GetPathForFile(backgroundFile); double filesizeMb = workingBeatmap.GetStream(storagePath).Length / (1024d * 1024d); if (filesizeMb > max_filesize_mb) From 56bf49c85c525547c6ebe8bb1656b69e1cd2b0ec Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 18 Apr 2021 01:21:20 +0200 Subject: [PATCH 1591/1791] Take `IWorkingBeatmap` instead of `WorkingBeatmap` This makes testing much easier, and allows for checking of any class deriving from that interface, including `WorkingBeatmap`. --- osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs | 2 +- osu.Game/Rulesets/Edit/Checks/CheckBackground.cs | 2 +- osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs | 2 +- osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs b/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs index 54b167aaf3..c210f73b5f 100644 --- a/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs +++ b/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Checks new IssueTemplateOffscreenSlider(this) }; - public IEnumerable Run(WorkingBeatmap workingBeatmap) + public IEnumerable Run(IWorkingBeatmap workingBeatmap) { foreach (var hitobject in workingBeatmap.Beatmap.HitObjects) { diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs b/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs index 637e603e17..71821b8073 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Edit.Checks new IssueTemplateDoesNotExist(this) }; - public IEnumerable Run(WorkingBeatmap workingBeatmap) + public IEnumerable Run(IWorkingBeatmap workingBeatmap) { string backgroundFile = workingBeatmap.Beatmap.Metadata?.BackgroundFile; diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs b/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs index 48d536079a..1494ae5da4 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Edit.Checks new IssueTemplateTooUncompressed(this) }; - public IEnumerable Run(WorkingBeatmap workingBeatmap) + public IEnumerable Run(IWorkingBeatmap workingBeatmap) { var backgroundFile = workingBeatmap.Beatmap.Metadata?.BackgroundFile; if (backgroundFile == null) diff --git a/osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs b/osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs index a2814ee603..c3a64b58e9 100644 --- a/osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs +++ b/osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs @@ -25,6 +25,6 @@ namespace osu.Game.Rulesets.Edit.Checks.Components /// Runs this check and returns any issues detected for the provided beatmap. /// /// The beatmap to run the check on. - public IEnumerable Run(WorkingBeatmap workingBeatmap); + public IEnumerable Run(IWorkingBeatmap workingBeatmap); } } From 0502fbb4296e103d4841ce86becb57aef38e73a1 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 18 Apr 2021 01:21:51 +0200 Subject: [PATCH 1592/1791] Add background quality check tests --- .../Checks/CheckBackgroundQualityTest.cs | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs diff --git a/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs b/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs new file mode 100644 index 0000000000..8dad5bbf34 --- /dev/null +++ b/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs @@ -0,0 +1,130 @@ +// 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.IO; +using System.Linq; +using JetBrains.Annotations; +using Moq; +using NUnit.Framework; +using osu.Framework.Graphics.Textures; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit.Checks; +using osu.Game.Rulesets.Objects; +using FileInfo = osu.Game.IO.FileInfo; + +namespace osu.Game.Tests.Editing.Checks +{ + [TestFixture] + public class CheckBackgroundQualityTest + { + private CheckBackgroundQuality check; + private IBeatmap beatmap; + + [SetUp] + public void Setup() + { + check = new CheckBackgroundQuality(); + beatmap = new Beatmap + { + BeatmapInfo = new BeatmapInfo + { + Metadata = new BeatmapMetadata { BackgroundFile = "abc123.jpg" }, + BeatmapSet = new BeatmapSetInfo + { + Files = new List(new[] + { + new BeatmapSetFileInfo + { + Filename = "abc123.jpg", + FileInfo = new FileInfo + { + Hash = "abcdef" + } + } + }) + } + } + }; + } + + [Test] + public void TestBackgroundMissing() + { + // While this is a problem, it is out of scope for this check and is caught by a different one. + beatmap.Metadata.BackgroundFile = null; + var mock = getMockWorkingBeatmap(null, System.Array.Empty()); + + Assert.That(check.Run(mock.Object), Is.Empty); + } + + [Test] + public void TestBackgroundAcceptable() + { + var mock = getMockWorkingBeatmap(new Texture(1920, 1080)); + + Assert.That(check.Run(mock.Object), Is.Empty); + } + + [Test] + public void TestBackgroundTooHighResolution() + { + var mock = getMockWorkingBeatmap(new Texture(3840, 2160)); + + var issues = check.Run(mock.Object).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckBackgroundQuality.IssueTemplateTooHighResolution); + } + + [Test] + public void TestBackgroundLowResolution() + { + var mock = getMockWorkingBeatmap(new Texture(640, 480)); + + var issues = check.Run(mock.Object).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckBackgroundQuality.IssueTemplateLowResolution); + } + + [Test] + public void TestBackgroundTooLowResolution() + { + var mock = getMockWorkingBeatmap(new Texture(100, 100)); + + var issues = check.Run(mock.Object).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckBackgroundQuality.IssueTemplateTooLowResolution); + } + + [Test] + public void TestBackgroundTooUncompressed() + { + var mock = getMockWorkingBeatmap(new Texture(1920, 1080), new byte[1024 * 1024 * 3]); + + var issues = check.Run(mock.Object).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckBackgroundQuality.IssueTemplateTooUncompressed); + } + + /// + /// Returns the mock of the working beatmap with the given background and filesize. + /// + /// The texture of the background. + /// The bytes that represent the background file. + private Mock getMockWorkingBeatmap(Texture background, [CanBeNull] byte[] fileBytes = null) + { + var stream = new MemoryStream(fileBytes ?? new byte[1024 * 1024]); + + var mock = new Mock(); + mock.SetupGet(_ => _.Beatmap).Returns(beatmap); + mock.SetupGet(_ => _.Background).Returns(background); + mock.Setup(_ => _.GetStream(It.IsAny())).Returns(stream); + + return mock; + } + } +} From 010720de74c67df74f140aa2263cb85c4054b563 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 18 Apr 2021 02:07:33 +0200 Subject: [PATCH 1593/1791] Factor out general file presence checking This allows us to use the same method of checking for other files that should exist, for example the audio file. By using the same method, they all share test cases too. --- ...groundTest.cs => CheckFilePresenceTest.cs} | 10 +++---- osu.Game/Rulesets/Edit/BeatmapVerifier.cs | 7 +++-- .../Edit/Checks/CheckBackgroundPresence.cs | 15 ++++++++++ ...heckBackground.cs => CheckFilePresence.cs} | 28 +++++++++++-------- 4 files changed, 41 insertions(+), 19 deletions(-) rename osu.Game.Tests/Editing/Checks/{CheckBackgroundTest.cs => CheckFilePresenceTest.cs} (84%) create mode 100644 osu.Game/Rulesets/Edit/Checks/CheckBackgroundPresence.cs rename osu.Game/Rulesets/Edit/Checks/{CheckBackground.cs => CheckFilePresence.cs} (57%) diff --git a/osu.Game.Tests/Editing/Checks/CheckBackgroundTest.cs b/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs similarity index 84% rename from osu.Game.Tests/Editing/Checks/CheckBackgroundTest.cs rename to osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs index d61f0989a6..1149115b55 100644 --- a/osu.Game.Tests/Editing/Checks/CheckBackgroundTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs @@ -12,15 +12,15 @@ using osu.Game.Tests.Beatmaps; namespace osu.Game.Tests.Editing.Checks { [TestFixture] - public class CheckBackgroundTest + public class CheckFilePresenceTest { - private CheckBackground check; + private CheckBackgroundPresence check; private WorkingBeatmap beatmap; [SetUp] public void Setup() { - check = new CheckBackground(); + check = new CheckBackgroundPresence(); beatmap = new TestWorkingBeatmap(new Beatmap { BeatmapInfo = new BeatmapInfo @@ -51,7 +51,7 @@ namespace osu.Game.Tests.Editing.Checks var issues = check.Run(beatmap).ToList(); Assert.That(issues, Has.Count.EqualTo(1)); - Assert.That(issues.Single().Template is CheckBackground.IssueTemplateDoesNotExist); + Assert.That(issues.Single().Template is CheckFilePresence.IssueTemplateDoesNotExist); } [Test] @@ -62,7 +62,7 @@ namespace osu.Game.Tests.Editing.Checks var issues = check.Run(beatmap).ToList(); Assert.That(issues, Has.Count.EqualTo(1)); - Assert.That(issues.Single().Template is CheckBackground.IssueTemplateNoneSet); + Assert.That(issues.Single().Template is CheckFilePresence.IssueTemplateNoneSet); } } } diff --git a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs index 24a4f473de..90447049f8 100644 --- a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs +++ b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs @@ -16,8 +16,11 @@ namespace osu.Game.Rulesets.Edit { private readonly List checks = new List { - new CheckBackground(), - new CheckBackgroundQuality() + // Resources + new CheckBackgroundPresence(), + new CheckBackgroundQuality(), + // Audio + new CheckAudioPresence(), }; public IEnumerable Run(WorkingBeatmap workingBeatmap) => checks.SelectMany(check => check.Run(workingBeatmap)); diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBackgroundPresence.cs b/osu.Game/Rulesets/Edit/Checks/CheckBackgroundPresence.cs new file mode 100644 index 0000000000..3a229b889b --- /dev/null +++ b/osu.Game/Rulesets/Edit/Checks/CheckBackgroundPresence.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.Game.Beatmaps; +using osu.Game.Rulesets.Edit.Checks.Components; + +namespace osu.Game.Rulesets.Edit.Checks +{ + public class CheckBackgroundPresence : CheckFilePresence + { + protected override CheckCategory Category => CheckCategory.Resources; + protected override string TypeOfFile => "background"; + protected override string GetFilename(IWorkingBeatmap workingBeatmap) => workingBeatmap.Beatmap.Metadata?.BackgroundFile; + } +} diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs b/osu.Game/Rulesets/Edit/Checks/CheckFilePresence.cs similarity index 57% rename from osu.Game/Rulesets/Edit/Checks/CheckBackground.cs rename to osu.Game/Rulesets/Edit/Checks/CheckFilePresence.cs index 71821b8073..37fa79568f 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckBackground.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckFilePresence.cs @@ -7,9 +7,13 @@ using osu.Game.Rulesets.Edit.Checks.Components; namespace osu.Game.Rulesets.Edit.Checks { - public class CheckBackground : ICheck + public abstract class CheckFilePresence : ICheck { - public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Resources, "Missing background"); + protected abstract CheckCategory Category { get; } + protected abstract string TypeOfFile { get; } + protected abstract string GetFilename(IWorkingBeatmap workingBeatmap); + + public CheckMetadata Metadata => new CheckMetadata(Category, $"Missing {TypeOfFile}"); public IEnumerable PossibleTemplates => new IssueTemplate[] { @@ -19,41 +23,41 @@ namespace osu.Game.Rulesets.Edit.Checks public IEnumerable Run(IWorkingBeatmap workingBeatmap) { - string backgroundFile = workingBeatmap.Beatmap.Metadata?.BackgroundFile; + var filename = GetFilename(workingBeatmap); - if (backgroundFile == null) + if (filename == null) { - yield return new IssueTemplateNoneSet(this).Create(); + yield return new IssueTemplateNoneSet(this).Create(TypeOfFile); yield break; } - // If the background is set, also make sure it still exists. - var storagePath = workingBeatmap.Beatmap.BeatmapInfo.BeatmapSet.GetPathForFile(backgroundFile); + // If the file is set, also make sure it still exists. + var storagePath = workingBeatmap.Beatmap.BeatmapInfo.BeatmapSet.GetPathForFile(filename); if (storagePath != null) yield break; - yield return new IssueTemplateDoesNotExist(this).Create(backgroundFile); + yield return new IssueTemplateDoesNotExist(this).Create(TypeOfFile, filename); } public class IssueTemplateNoneSet : IssueTemplate { public IssueTemplateNoneSet(ICheck check) - : base(check, IssueType.Problem, "No background has been set.") + : base(check, IssueType.Problem, "No {0} has been set.") { } - public Issue Create() => new Issue(this); + public Issue Create(string typeOfFile) => new Issue(this, typeOfFile); } public class IssueTemplateDoesNotExist : IssueTemplate { public IssueTemplateDoesNotExist(ICheck check) - : base(check, IssueType.Problem, "The background file \"{0}\" does not exist.") + : base(check, IssueType.Problem, "The {0} file \"{1}\" does not exist.") { } - public Issue Create(string filename) => new Issue(this, filename); + public Issue Create(string typeOfFile, string filename) => new Issue(this, typeOfFile, filename); } } } From 9a69ca34a6ec004e6b336826f5623cbcd1c33a35 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Sun, 18 Apr 2021 02:07:57 +0200 Subject: [PATCH 1594/1791] Add audio presence check --- .../Rulesets/Edit/Checks/CheckAudioPresence.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 osu.Game/Rulesets/Edit/Checks/CheckAudioPresence.cs diff --git a/osu.Game/Rulesets/Edit/Checks/CheckAudioPresence.cs b/osu.Game/Rulesets/Edit/Checks/CheckAudioPresence.cs new file mode 100644 index 0000000000..55e53ef519 --- /dev/null +++ b/osu.Game/Rulesets/Edit/Checks/CheckAudioPresence.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.Game.Beatmaps; +using osu.Game.Rulesets.Edit.Checks.Components; + +namespace osu.Game.Rulesets.Edit.Checks +{ + public class CheckAudioPresence : CheckFilePresence + { + protected override CheckCategory Category => CheckCategory.Audio; + protected override string TypeOfFile => "audio"; + protected override string GetFilename(IWorkingBeatmap workingBeatmap) => workingBeatmap.Beatmap.Metadata?.AudioFile; + } +} From 2678089e0b0610ea78afb373a36f81d54af171cc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 18 Apr 2021 20:18:30 +0900 Subject: [PATCH 1595/1791] Add test case failing on selection after paste --- .../Editing/TestSceneEditorClipboard.cs | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs index 29046c82a6..01d9966736 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorClipboard.cs @@ -3,12 +3,16 @@ using System.Linq; using NUnit.Framework; +using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Rulesets; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Screens.Edit.Compose.Components; +using osu.Game.Screens.Edit.Compose.Components.Timeline; using osu.Game.Tests.Beatmaps; using osuTK; @@ -110,8 +114,9 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("duration matches", () => ((Spinner)EditorBeatmap.HitObjects.Single()).Duration == 5000); } - [Test] - public void TestCopyPaste() + [TestCase(false)] + [TestCase(true)] + public void TestCopyPaste(bool deselectAfterCopy) { var addedObject = new HitCircle { StartTime = 1000 }; @@ -123,11 +128,22 @@ namespace osu.Game.Tests.Visual.Editing AddStep("move forward in time", () => EditorClock.Seek(2000)); + if (deselectAfterCopy) + { + AddStep("deselect", () => EditorBeatmap.SelectedHitObjects.Clear()); + + AddUntilStep("timeline selection box is not visible", () => Editor.ChildrenOfType().First().ChildrenOfType().First().Alpha == 0); + AddUntilStep("composer selection box is not visible", () => Editor.ChildrenOfType().First().ChildrenOfType().First().Alpha == 0); + } + AddStep("paste hitobject", () => Editor.Paste()); AddAssert("are two objects", () => EditorBeatmap.HitObjects.Count == 2); AddAssert("new object selected", () => EditorBeatmap.SelectedHitObjects.Single().StartTime == 2000); + + AddUntilStep("timeline selection box is visible", () => Editor.ChildrenOfType().First().ChildrenOfType().First().Alpha > 0); + AddUntilStep("composer selection box is visible", () => Editor.ChildrenOfType().First().ChildrenOfType().First().Alpha > 0); } [Test] From e76565dbc57f874fd4143d5d8717aeeb99e5a5eb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 18 Apr 2021 20:11:05 +0900 Subject: [PATCH 1596/1791] Fix selection box not displaying after pasting a selection in the editor Closes #12471. --- .../Edit/Compose/Components/SelectionHandler.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 389ef78ed5..40641ee41f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -33,10 +33,14 @@ namespace osu.Game.Screens.Edit.Compose.Components /// public class SelectionHandler : CompositeDrawable, IKeyBindingHandler, IHasContextMenu { + /// + /// The currently selected blueprints. + /// Should be used when operations are dealing directly with the visible blueprints. + /// For more general selection operations, use EditorBeatmap.SelectedHitObjects instead. + /// public IEnumerable SelectedBlueprints => selectedBlueprints; - private readonly List selectedBlueprints; - public int SelectedCount => selectedBlueprints.Count; + private readonly List selectedBlueprints; private Drawable content; @@ -295,7 +299,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// private void updateVisibility() { - int count = selectedBlueprints.Count; + int count = EditorBeatmap.SelectedHitObjects.Count; selectionDetailsText.Text = count > 0 ? count.ToString() : string.Empty; From cfc76640941f5dcb41dc4e5b319b207be7e85214 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 18 Apr 2021 21:05:23 +0900 Subject: [PATCH 1597/1791] Use full path --- osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 40641ee41f..b06e982859 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -36,7 +36,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// The currently selected blueprints. /// Should be used when operations are dealing directly with the visible blueprints. - /// For more general selection operations, use EditorBeatmap.SelectedHitObjects instead. + /// For more general selection operations, use instead. /// public IEnumerable SelectedBlueprints => selectedBlueprints; From e63edf5b4914ec04f55b2de779709e2120d93315 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 18 Apr 2021 23:50:09 +0900 Subject: [PATCH 1598/1791] Fix volume control displaying on non-vertical scroll events Closes #12475. --- osu.Game/Overlays/Volume/VolumeControlReceptor.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Overlays/Volume/VolumeControlReceptor.cs b/osu.Game/Overlays/Volume/VolumeControlReceptor.cs index 34b86b2f81..ae9c2eb394 100644 --- a/osu.Game/Overlays/Volume/VolumeControlReceptor.cs +++ b/osu.Game/Overlays/Volume/VolumeControlReceptor.cs @@ -44,6 +44,9 @@ namespace osu.Game.Overlays.Volume protected override bool OnScroll(ScrollEvent e) { + if (e.ScrollDelta.Y == 0) + return false; + // forward any unhandled mouse scroll events to the volume control. ScrollActionRequested?.Invoke(GlobalAction.IncreaseVolume, e.ScrollDelta.Y, e.IsPrecise); return true; From af79ad537c048891659eaabadaa3a1877b7aae89 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 18 Apr 2021 23:51:37 +0900 Subject: [PATCH 1599/1791] Avoid unnecessary debounce triggers on zero-delta scrolls --- osu.Game/Overlays/Volume/VolumeMeter.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index 202eac93ea..f1f21bec49 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -245,6 +245,9 @@ namespace osu.Game.Overlays.Volume private void adjust(double delta, bool isPrecise) { + if (delta == 0) + return; + // every adjust increment increases the rate at which adjustments happen up to a cutoff. // this debounce will reset on inactivity. accelerationDebounce?.Cancel(); From 1b2c43b92cab2badef24f03755e3d8b282cfc04d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 18 Apr 2021 19:29:04 +0200 Subject: [PATCH 1600/1791] Add basic structure of colour palette --- .../TestSceneLabelledColourPalette.cs | 49 +++++++++ .../Graphics/UserInterfaceV2/ColourDisplay.cs | 103 ++++++++++++++++++ .../Graphics/UserInterfaceV2/ColourPalette.cs | 67 ++++++++++++ .../UserInterfaceV2/LabelledColourPalette.cs | 20 ++++ 4 files changed, 239 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneLabelledColourPalette.cs create mode 100644 osu.Game/Graphics/UserInterfaceV2/ColourDisplay.cs create mode 100644 osu.Game/Graphics/UserInterfaceV2/ColourPalette.cs create mode 100644 osu.Game/Graphics/UserInterfaceV2/LabelledColourPalette.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledColourPalette.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledColourPalette.cs new file mode 100644 index 0000000000..afe95a3745 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledColourPalette.cs @@ -0,0 +1,49 @@ +// 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.Graphics.UserInterfaceV2; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneLabelledColourPalette : OsuTestScene + { + private LabelledColourPalette component; + + [Test] + public void TestPalette([Values] bool hasDescription) => createColourPalette(hasDescription); + + private void createColourPalette(bool hasDescription = false) + { + AddStep("create component", () => + { + Child = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 500, + AutoSizeAxes = Axes.Y, + Child = component = new LabelledColourPalette + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + }; + + component.Label = "a sample component"; + component.Description = hasDescription ? "this text describes the component" : string.Empty; + + component.Colours.AddRange(new[] + { + Color4.DarkRed, + Color4.Aquamarine, + Color4.Goldenrod, + Color4.Gainsboro + }); + }); + } + } +} diff --git a/osu.Game/Graphics/UserInterfaceV2/ColourDisplay.cs b/osu.Game/Graphics/UserInterfaceV2/ColourDisplay.cs new file mode 100644 index 0000000000..ef5be62a37 --- /dev/null +++ b/osu.Game/Graphics/UserInterfaceV2/ColourDisplay.cs @@ -0,0 +1,103 @@ +// 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.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Localisation; +using osu.Game.Graphics.Sprites; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Graphics.UserInterfaceV2 +{ + public class ColourDisplay : CompositeDrawable, IHasCurrentValue + { + private readonly BindableWithCurrent current = new BindableWithCurrent(); + + private Box fill; + private OsuSpriteText colourHexCode; + private OsuSpriteText colourName; + + public Bindable Current + { + get => current.Current; + set => current.Current = value; + } + + private LocalisableString name; + + public LocalisableString ColourName + { + get => name; + set + { + if (name == value) + return; + + name = value; + + colourName.Text = name; + } + } + + [BackgroundDependencyLoader] + private void load() + { + AutoSizeAxes = Axes.Y; + Width = 100; + + InternalChild = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 10), + Children = new Drawable[] + { + new CircularContainer + { + RelativeSizeAxes = Axes.X, + Height = 100, + Masking = true, + Children = new Drawable[] + { + fill = new Box + { + RelativeSizeAxes = Axes.Both + }, + colourHexCode = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.Default.With(size: 12) + } + } + }, + colourName = new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre + } + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + current.BindValueChanged(_ => updateColour(), true); + } + + private void updateColour() + { + fill.Colour = current.Value; + colourHexCode.Text = current.Value.ToHex(); + } + } +} diff --git a/osu.Game/Graphics/UserInterfaceV2/ColourPalette.cs b/osu.Game/Graphics/UserInterfaceV2/ColourPalette.cs new file mode 100644 index 0000000000..8fa76a017c --- /dev/null +++ b/osu.Game/Graphics/UserInterfaceV2/ColourPalette.cs @@ -0,0 +1,67 @@ +// 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 osuTK; +using osuTK.Graphics; + +namespace osu.Game.Graphics.UserInterfaceV2 +{ + public class ColourPalette : CompositeDrawable + { + public BindableList Colours { get; } = new BindableList(); + + private FillFlowContainer palette; + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + InternalChild = palette = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(10), + Direction = FillDirection.Full + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Colours.BindCollectionChanged((_, __) => updatePalette(), true); + } + + private void updatePalette() + { + palette.Clear(); + + foreach (var item in Colours) + { + palette.Add(new ColourDisplay + { + Current = { Value = item } + }); + } + + reindexItems(); + } + + private void reindexItems() + { + int index = 1; + + foreach (var colour in palette) + { + colour.ColourName = $"Colour {index}"; + index += 1; + } + } + } +} diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledColourPalette.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledColourPalette.cs new file mode 100644 index 0000000000..f6f2d92d01 --- /dev/null +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledColourPalette.cs @@ -0,0 +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.Bindables; +using osuTK.Graphics; + +namespace osu.Game.Graphics.UserInterfaceV2 +{ + public class LabelledColourPalette : LabelledDrawable + { + public LabelledColourPalette() + : base(true) + { + } + + public BindableList Colours => Component.Colours; + + protected override ColourPalette CreateComponent() => new ColourPalette(); + } +} From 67c19df00050c9d0f6fef7f69e9dc98edb2f4c75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 18 Apr 2021 19:35:42 +0200 Subject: [PATCH 1601/1791] Add test coverage for adding/removing colours --- .../TestSceneLabelledColourPalette.cs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledColourPalette.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledColourPalette.cs index afe95a3745..77d313e6ee 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledColourPalette.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledColourPalette.cs @@ -4,6 +4,7 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Utils; using osu.Game.Graphics.UserInterfaceV2; using osuTK.Graphics; @@ -14,7 +15,17 @@ namespace osu.Game.Tests.Visual.UserInterface private LabelledColourPalette component; [Test] - public void TestPalette([Values] bool hasDescription) => createColourPalette(hasDescription); + public void TestPalette([Values] bool hasDescription) + { + createColourPalette(hasDescription); + + AddRepeatStep("add random colour", () => component.Colours.Add(randomColour()), 4); + AddRepeatStep("remove random colour", () => + { + if (component.Colours.Count > 0) + component.Colours.RemoveAt(RNG.Next(component.Colours.Count)); + }, 5); + } private void createColourPalette(bool hasDescription = false) { @@ -45,5 +56,11 @@ namespace osu.Game.Tests.Visual.UserInterface }); }); } + + private Color4 randomColour() => new Color4( + RNG.NextSingle(), + RNG.NextSingle(), + RNG.NextSingle(), + 1); } } From a8027d87b6c9bd31c895d1eb243fdf6af689567e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 18 Apr 2021 19:46:54 +0200 Subject: [PATCH 1602/1791] Fix unreadable colour hex code text due to low contrast Logic is shared with the timeline blueprints which also have the same problem of displaying text on top of a combo colour. Slightly modified the formula. Seems to yield better results on a subjective check. --- .../Graphics/UserInterfaceV2/ColourDisplay.cs | 2 ++ .../Timeline/TimelineHitObjectBlueprint.cs | 6 ++--- osu.Game/Utils/ColourUtils.cs | 23 +++++++++++++++++++ 3 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 osu.Game/Utils/ColourUtils.cs diff --git a/osu.Game/Graphics/UserInterfaceV2/ColourDisplay.cs b/osu.Game/Graphics/UserInterfaceV2/ColourDisplay.cs index ef5be62a37..5d9d2521cb 100644 --- a/osu.Game/Graphics/UserInterfaceV2/ColourDisplay.cs +++ b/osu.Game/Graphics/UserInterfaceV2/ColourDisplay.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Localisation; using osu.Game.Graphics.Sprites; +using osu.Game.Utils; using osuTK; using osuTK.Graphics; @@ -98,6 +99,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 { fill.Colour = current.Value; colourHexCode.Text = current.Value.ToHex(); + colourHexCode.Colour = ColourUtils.ForegroundTextColourFor(current.Value); } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 105e04d441..dc67009d08 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -21,6 +21,7 @@ using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Skinning; +using osu.Game.Utils; using osuTK; using osuTK.Graphics; @@ -158,10 +159,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline circle.Colour = comboColour; var col = circle.Colour.TopLeft.Linear; - float brightness = col.R + col.G + col.B; - - // decide the combo index colour based on brightness? - colouredComponents.Colour = OsuColour.Gray(brightness > 0.5f ? 0.2f : 0.9f); + colouredComponents.Colour = ColourUtils.ForegroundTextColourFor(col); } protected override void Update() diff --git a/osu.Game/Utils/ColourUtils.cs b/osu.Game/Utils/ColourUtils.cs new file mode 100644 index 0000000000..33f6f5981f --- /dev/null +++ b/osu.Game/Utils/ColourUtils.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.Graphics; +using osuTK.Graphics; + +namespace osu.Game.Utils +{ + public static class ColourUtils + { + /// + /// Returns a foreground text colour that is supposed to contrast well on top of + /// the supplied . + /// + public static Color4 ForegroundTextColourFor(Color4 backgroundColour) + { + // formula taken from the RGB->YIQ conversions: https://en.wikipedia.org/wiki/YIQ + // brightness here is equivalent to the Y component in the above colour model, which is a rough estimate of lightness. + float brightness = 0.299f * backgroundColour.R + 0.587f * backgroundColour.G + 0.114f * backgroundColour.B; + return OsuColour.Gray(brightness > 0.5f ? 0.2f : 0.9f); + } + } +} From 0cd1aa8c1cca9a984968a6c84ae25a5b55ee2cff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 18 Apr 2021 19:56:03 +0200 Subject: [PATCH 1603/1791] Add support for custom colour prefixes --- .../TestSceneLabelledColourPalette.cs | 3 +++ .../Graphics/UserInterfaceV2/ColourPalette.cs | 19 ++++++++++++++++++- .../UserInterfaceV2/LabelledColourPalette.cs | 6 ++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledColourPalette.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledColourPalette.cs index 77d313e6ee..325e71f76a 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledColourPalette.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledColourPalette.cs @@ -25,6 +25,8 @@ namespace osu.Game.Tests.Visual.UserInterface if (component.Colours.Count > 0) component.Colours.RemoveAt(RNG.Next(component.Colours.Count)); }, 5); + + AddStep("set custom prefix", () => component.ColourNamePrefix = "Combo"); } private void createColourPalette(bool hasDescription = false) @@ -41,6 +43,7 @@ namespace osu.Game.Tests.Visual.UserInterface { Anchor = Anchor.Centre, Origin = Anchor.Centre, + ColourNamePrefix = "My colour #" } }; diff --git a/osu.Game/Graphics/UserInterfaceV2/ColourPalette.cs b/osu.Game/Graphics/UserInterfaceV2/ColourPalette.cs index 8fa76a017c..38ac51b955 100644 --- a/osu.Game/Graphics/UserInterfaceV2/ColourPalette.cs +++ b/osu.Game/Graphics/UserInterfaceV2/ColourPalette.cs @@ -14,6 +14,23 @@ namespace osu.Game.Graphics.UserInterfaceV2 { public BindableList Colours { get; } = new BindableList(); + private string colourNamePrefix = "Colour"; + + public string ColourNamePrefix + { + get => colourNamePrefix; + set + { + if (colourNamePrefix == value) + return; + + colourNamePrefix = value; + + if (IsLoaded) + reindexItems(); + } + } + private FillFlowContainer palette; [BackgroundDependencyLoader] @@ -59,7 +76,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 foreach (var colour in palette) { - colour.ColourName = $"Colour {index}"; + colour.ColourName = $"{colourNamePrefix} {index}"; index += 1; } } diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledColourPalette.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledColourPalette.cs index f6f2d92d01..58443953bc 100644 --- a/osu.Game/Graphics/UserInterfaceV2/LabelledColourPalette.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledColourPalette.cs @@ -15,6 +15,12 @@ namespace osu.Game.Graphics.UserInterfaceV2 public BindableList Colours => Component.Colours; + public string ColourNamePrefix + { + get => Component.ColourNamePrefix; + set => Component.ColourNamePrefix = value; + } + protected override ColourPalette CreateComponent() => new ColourPalette(); } } From 577755ee1943c65b4e26dfa7cef7c39a43896a3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 18 Apr 2021 20:15:30 +0200 Subject: [PATCH 1604/1791] Add placeholder when no colours are visible Will be removed once combo colours are mutable. --- .../TestSceneLabelledColourPalette.cs | 7 ++-- .../Graphics/UserInterfaceV2/ColourPalette.cs | 42 ++++++++++++++++--- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledColourPalette.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledColourPalette.cs index 325e71f76a..826da17ca8 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledColourPalette.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledColourPalette.cs @@ -20,13 +20,14 @@ namespace osu.Game.Tests.Visual.UserInterface createColourPalette(hasDescription); AddRepeatStep("add random colour", () => component.Colours.Add(randomColour()), 4); + + AddStep("set custom prefix", () => component.ColourNamePrefix = "Combo"); + AddRepeatStep("remove random colour", () => { if (component.Colours.Count > 0) component.Colours.RemoveAt(RNG.Next(component.Colours.Count)); - }, 5); - - AddStep("set custom prefix", () => component.ColourNamePrefix = "Combo"); + }, 8); } private void createColourPalette(bool hasDescription = false) diff --git a/osu.Game/Graphics/UserInterfaceV2/ColourPalette.cs b/osu.Game/Graphics/UserInterfaceV2/ColourPalette.cs index 38ac51b955..3ca5c2d8d1 100644 --- a/osu.Game/Graphics/UserInterfaceV2/ColourPalette.cs +++ b/osu.Game/Graphics/UserInterfaceV2/ColourPalette.cs @@ -1,10 +1,12 @@ // 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 osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.Sprites; using osuTK; using osuTK.Graphics; @@ -32,6 +34,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 } private FillFlowContainer palette; + private Container placeholder; [BackgroundDependencyLoader] private void load() @@ -39,12 +42,27 @@ namespace osu.Game.Graphics.UserInterfaceV2 RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - InternalChild = palette = new FillFlowContainer + InternalChildren = new Drawable[] { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(10), - Direction = FillDirection.Full + palette = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(10), + Direction = FillDirection.Full + }, + placeholder = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Child = new OsuSpriteText + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Text = "(none)", + Font = OsuFont.Default.With(weight: FontWeight.Bold) + } + } }; } @@ -53,12 +71,26 @@ namespace osu.Game.Graphics.UserInterfaceV2 base.LoadComplete(); Colours.BindCollectionChanged((_, __) => updatePalette(), true); + FinishTransforms(true); } + private const int fade_duration = 200; + private void updatePalette() { palette.Clear(); + if (Colours.Any()) + { + palette.FadeIn(fade_duration, Easing.OutQuint); + placeholder.FadeOut(fade_duration, Easing.OutQuint); + } + else + { + palette.FadeOut(fade_duration, Easing.OutQuint); + placeholder.FadeIn(fade_duration, Easing.OutQuint); + } + foreach (var item in Colours) { palette.Add(new ColourDisplay From 07a00cd68130e973de37e68f119191c8bc971f53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 18 Apr 2021 20:16:17 +0200 Subject: [PATCH 1605/1791] Add colours section with combo colour display --- osu.Game/Screens/Edit/Setup/ColoursSection.cs | 37 +++++++++++++++++++ osu.Game/Screens/Edit/Setup/SetupScreen.cs | 1 + 2 files changed, 38 insertions(+) create mode 100644 osu.Game/Screens/Edit/Setup/ColoursSection.cs diff --git a/osu.Game/Screens/Edit/Setup/ColoursSection.cs b/osu.Game/Screens/Edit/Setup/ColoursSection.cs new file mode 100644 index 0000000000..91dfd74a78 --- /dev/null +++ b/osu.Game/Screens/Edit/Setup/ColoursSection.cs @@ -0,0 +1,37 @@ +// 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.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Localisation; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Skinning; +using osuTK.Graphics; + +namespace osu.Game.Screens.Edit.Setup +{ + internal class ColoursSection : SetupSection + { + public override LocalisableString Title => "Colours"; + + private LabelledColourPalette comboColours; + + [BackgroundDependencyLoader] + private void load() + { + Children = new Drawable[] + { + comboColours = new LabelledColourPalette + { + Label = "Hitcircle / Slider Combos", + ColourNamePrefix = "Combo" + } + }; + + var colours = Beatmap.BeatmapSkin.GetConfig>(GlobalSkinColours.ComboColours)?.Value; + if (colours != null) + comboColours.Colours.AddRange(colours); + } + } +} diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index 70671b487c..dc81b31f65 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -61,6 +61,7 @@ namespace osu.Game.Screens.Edit.Setup new ResourcesSection(), new MetadataSection(), new DifficultySection(), + new ColoursSection() } }, } From 6f2ebb20a7fedc0d02037540b3610e7e64288239 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 18 Apr 2021 20:29:09 +0200 Subject: [PATCH 1606/1791] Fix test failing due to null beatmap skin Also annotate the field on `EditorBeatmap` as nullable for future travelers. --- osu.Game/Screens/Edit/EditorBeatmap.cs | 1 + osu.Game/Screens/Edit/Setup/ColoursSection.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 4f1b0484d2..4bf4a3b8f3 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -46,6 +46,7 @@ namespace osu.Game.Screens.Edit public readonly IBeatmap PlayableBeatmap; + [CanBeNull] public readonly ISkin BeatmapSkin; [Resolved] diff --git a/osu.Game/Screens/Edit/Setup/ColoursSection.cs b/osu.Game/Screens/Edit/Setup/ColoursSection.cs index 91dfd74a78..cb7deadcb7 100644 --- a/osu.Game/Screens/Edit/Setup/ColoursSection.cs +++ b/osu.Game/Screens/Edit/Setup/ColoursSection.cs @@ -29,7 +29,7 @@ namespace osu.Game.Screens.Edit.Setup } }; - var colours = Beatmap.BeatmapSkin.GetConfig>(GlobalSkinColours.ComboColours)?.Value; + var colours = Beatmap.BeatmapSkin?.GetConfig>(GlobalSkinColours.ComboColours)?.Value; if (colours != null) comboColours.Colours.AddRange(colours); } From 89940e7bb9fc9330eb3ad3e13a4602f04b4912d8 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sun, 18 Apr 2021 18:35:44 -0700 Subject: [PATCH 1607/1791] Fix download button check icon not scaling on mouse down --- osu.Game/Graphics/UserInterface/DownloadButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/DownloadButton.cs b/osu.Game/Graphics/UserInterface/DownloadButton.cs index 7a8db158c1..af270f30ae 100644 --- a/osu.Game/Graphics/UserInterface/DownloadButton.cs +++ b/osu.Game/Graphics/UserInterface/DownloadButton.cs @@ -27,7 +27,7 @@ namespace osu.Game.Graphics.UserInterface [BackgroundDependencyLoader] private void load() { - AddInternal(checkmark = new SpriteIcon + Add(checkmark = new SpriteIcon { Anchor = Anchor.Centre, Origin = Anchor.Centre, From 2b6caf9b650fd5cfc5af4e168d1d794322ceb4a8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Apr 2021 11:25:43 +0900 Subject: [PATCH 1608/1791] Fix duplicate code in setting default logic --- osu.Game/Configuration/SessionStatics.cs | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/osu.Game/Configuration/SessionStatics.cs b/osu.Game/Configuration/SessionStatics.cs index e57b1c2fdf..ac94c39bd2 100644 --- a/osu.Game/Configuration/SessionStatics.cs +++ b/osu.Game/Configuration/SessionStatics.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.Bindables; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; @@ -12,23 +13,18 @@ namespace osu.Game.Configuration /// public class SessionStatics : InMemoryConfigManager { - protected override void InitialiseDefaults() - { - SetDefault(Static.LoginOverlayDisplayed, false); - SetDefault(Static.MutedAudioNotificationShownOnce, false); - SetDefault(Static.LowBatteryNotificationShownOnce, false); - SetDefault(Static.LastHoverSoundPlaybackTime, (double?)null); - SetDefault(Static.SeasonalBackgrounds, null); - } + protected override void InitialiseDefaults() => ResetValues(); public void ResetValues() { - GetOriginalBindable(Static.LoginOverlayDisplayed).SetDefault(); - GetOriginalBindable(Static.MutedAudioNotificationShownOnce).SetDefault(); - GetOriginalBindable(Static.LowBatteryNotificationShownOnce).SetDefault(); - GetOriginalBindable(Static.LastHoverSoundPlaybackTime).SetDefault(); - GetOriginalBindable(Static.SeasonalBackgrounds).SetDefault(); + ensureDefault(SetDefault(Static.LoginOverlayDisplayed, false)); + ensureDefault(SetDefault(Static.MutedAudioNotificationShownOnce, false)); + ensureDefault(SetDefault(Static.LowBatteryNotificationShownOnce, false)); + ensureDefault(SetDefault(Static.LastHoverSoundPlaybackTime, (double?)null)); + ensureDefault(SetDefault(Static.SeasonalBackgrounds, null)); } + + private void ensureDefault(Bindable bindable) => bindable.SetDefault(); } public enum Static From dbb8f7f4a95523e7770391f9a6c8578d39eae84a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Apr 2021 11:30:55 +0900 Subject: [PATCH 1609/1791] Tidy up initialisation code and avoid using DI on inherited class --- osu.Game/OsuGame.cs | 12 ++++++------ osu.Game/OsuGameBase.cs | 4 +++- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 898a2baccf..1a2555f3f1 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -577,15 +577,15 @@ namespace osu.Game dependencies.CacheAs(idleTracker = new GameIdleTracker(6000)); - GameIdleTracker sessionIdleTracker = new GameIdleTracker(300_000); - Add(sessionIdleTracker); - - sessionIdleTracker.IsIdle.BindValueChanged(e => + var sessionIdleTracker = new GameIdleTracker(300000); + sessionIdleTracker.IsIdle.BindValueChanged(idle => { - if (e.NewValue) - Dependencies.Get().ResetValues(); + if (idle.NewValue) + SessionStatics.ResetValues(); }); + Add(new GameIdleTracker(300000)); + AddRange(new Drawable[] { new VolumeControlReceptor diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 406819cbd2..fbe4022cc1 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -61,6 +61,8 @@ namespace osu.Game protected OsuConfigManager LocalConfig; + protected SessionStatics SessionStatics { get; private set; } + protected BeatmapManager BeatmapManager; protected ScoreManager ScoreManager; @@ -289,7 +291,7 @@ namespace osu.Game if (powerStatus != null) dependencies.CacheAs(powerStatus); - dependencies.Cache(new SessionStatics()); + dependencies.Cache(SessionStatics = new SessionStatics()); dependencies.Cache(new OsuColour()); RegisterImportHandler(BeatmapManager); From b727faace38e51a056c4131443fbf3a5356fbd71 Mon Sep 17 00:00:00 2001 From: jvyden Date: Sun, 18 Apr 2021 23:03:43 -0400 Subject: [PATCH 1610/1791] Revert changes to IdleTracker --- .../Visual/Components/TestSceneIdleTracker.cs | 41 +++---------------- 1 file changed, 5 insertions(+), 36 deletions(-) diff --git a/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs b/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs index c3ea63ea65..d888f6a01e 100644 --- a/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs +++ b/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs @@ -5,7 +5,6 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Configuration; using osu.Game.Input; using osuTK; using osuTK.Graphics; @@ -22,17 +21,14 @@ namespace osu.Game.Tests.Visual.Components private IdleTrackingBox[] boxes; - private SessionStatics sessionStatics; - [SetUp] public void SetUp() => Schedule(() => { - sessionStatics = new SessionStatics(); InputManager.MoveMouseTo(Vector2.Zero); Children = boxes = new[] { - box1 = new IdleTrackingBox(2000, sessionStatics) + box1 = new IdleTrackingBox(2000) { Name = "TopLeft", RelativeSizeAxes = Axes.Both, @@ -40,7 +36,7 @@ namespace osu.Game.Tests.Visual.Components Anchor = Anchor.TopLeft, Origin = Anchor.TopLeft, }, - box2 = new IdleTrackingBox(4000, sessionStatics) + box2 = new IdleTrackingBox(4000) { Name = "TopRight", RelativeSizeAxes = Axes.Both, @@ -48,7 +44,7 @@ namespace osu.Game.Tests.Visual.Components Anchor = Anchor.TopRight, Origin = Anchor.TopRight, }, - box3 = new IdleTrackingBox(6000, sessionStatics) + box3 = new IdleTrackingBox(6000) { Name = "BottomLeft", RelativeSizeAxes = Axes.Both, @@ -56,7 +52,7 @@ namespace osu.Game.Tests.Visual.Components Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, }, - box4 = new IdleTrackingBox(8000, sessionStatics) + box4 = new IdleTrackingBox(8000) { Name = "BottomRight", RelativeSizeAxes = Axes.Both, @@ -145,27 +141,6 @@ namespace osu.Game.Tests.Visual.Components waitForAllIdle(); } - - [Test] - public void TestSessionStaticsReset() - { - AddStep("move to top left", () => InputManager.MoveMouseTo(box1)); - AddStep("set session statics", () => - { - sessionStatics.SetValue(Static.LoginOverlayDisplayed, true); - sessionStatics.SetValue(Static.MutedAudioNotificationShownOnce, true); - sessionStatics.SetValue(Static.LowBatteryNotificationShownOnce, true); - sessionStatics.SetValue(Static.LastHoverSoundPlaybackTime, (double?)1d); - }); - - AddStep("move away from box1", () => InputManager.MoveMouseTo(box4)); - AddUntilStep("Wait for idle", () => box1.IsIdle); - AddAssert("LoginOverlayDisplayed is default", () => sessionStatics.Get(Static.LoginOverlayDisplayed) == false); - AddAssert("MutedAudioNotificationShownOnce is default", () => sessionStatics.Get(Static.MutedAudioNotificationShownOnce) == false); - AddAssert("LowBatteryNotificationShownOnce is default", () => sessionStatics.Get(Static.LowBatteryNotificationShownOnce) == false); - AddAssert("LastHoverSoundPlaybackTime is default", () => sessionStatics.Get(Static.LastHoverSoundPlaybackTime) == null); - } - private void checkIdleStatus(int box, bool expectedIdle) { AddAssert($"box {box} is {(expectedIdle ? "idle" : "active")}", () => boxes[box - 1].IsIdle == expectedIdle); @@ -182,7 +157,7 @@ namespace osu.Game.Tests.Visual.Components public bool IsIdle => idleTracker.IsIdle.Value; - public IdleTrackingBox(int timeToIdle, SessionStatics statics) + public IdleTrackingBox(int timeToIdle) { Box box; @@ -198,12 +173,6 @@ namespace osu.Game.Tests.Visual.Components RelativeSizeAxes = Axes.Both, }, }; - - idleTracker.IsIdle.BindValueChanged(idle => - { - box.Colour = idle.NewValue ? Color4.White : Color4.Black; - if (idle.NewValue) statics.ResetValues(); - }, true); } } } From c39ab2c69219ff8d47337993597d56fc51fd01ab Mon Sep 17 00:00:00 2001 From: jvyden Date: Sun, 18 Apr 2021 23:04:28 -0400 Subject: [PATCH 1611/1791] Add SessionStaticsTest --- .../NonVisual/SessionStaticsTest.cs | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 osu.Game.Tests/NonVisual/SessionStaticsTest.cs diff --git a/osu.Game.Tests/NonVisual/SessionStaticsTest.cs b/osu.Game.Tests/NonVisual/SessionStaticsTest.cs new file mode 100644 index 0000000000..d5fd803986 --- /dev/null +++ b/osu.Game.Tests/NonVisual/SessionStaticsTest.cs @@ -0,0 +1,47 @@ +// 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.Game.Configuration; +using osu.Game.Input; + +namespace osu.Game.Tests.NonVisual +{ + [TestFixture] + public class SessionStaticsTest + { + private SessionStatics sessionStatics; + private IdleTracker sessionIdleTracker; + + [SetUp] + public void SetUp() + { + sessionStatics = new SessionStatics(); + sessionIdleTracker = new GameIdleTracker(1000); + + sessionStatics.SetValue(Static.LoginOverlayDisplayed, true); + sessionStatics.SetValue(Static.MutedAudioNotificationShownOnce, true); + sessionStatics.SetValue(Static.LowBatteryNotificationShownOnce, true); + sessionStatics.SetValue(Static.LastHoverSoundPlaybackTime, (double?)1d); + + sessionIdleTracker.IsIdle.BindValueChanged(e => + { + if (e.NewValue) + sessionStatics.ResetValues(); + }); + } + + [Test] + [Timeout(2000)] + public void TestSessionStaticsReset() + { + sessionIdleTracker.IsIdle.BindValueChanged(e => + { + Assert.IsTrue(sessionStatics.GetBindable(Static.LoginOverlayDisplayed).IsDefault); + Assert.IsTrue(sessionStatics.GetBindable(Static.MutedAudioNotificationShownOnce).IsDefault); + Assert.IsTrue(sessionStatics.GetBindable(Static.LowBatteryNotificationShownOnce).IsDefault); + Assert.IsTrue(sessionStatics.GetBindable(Static.LastHoverSoundPlaybackTime).IsDefault); + }); + } + } +} From 999f2d810caba2e15d720aebcfdc1e2e47fcdf8b Mon Sep 17 00:00:00 2001 From: jvyden Date: Sun, 18 Apr 2021 23:30:07 -0400 Subject: [PATCH 1612/1791] Fix accidentally removed code --- osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs b/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs index d888f6a01e..77eadc14be 100644 --- a/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs +++ b/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs @@ -173,6 +173,8 @@ namespace osu.Game.Tests.Visual.Components RelativeSizeAxes = Axes.Both, }, }; + + idleTracker.IsIdle.BindValueChanged(idle => box.Colour = idle.NewValue ? Color4.White : Color4.Black, true); } } } From f9f514ffec646a245d8c310594c4bae619b4a0c8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Apr 2021 12:37:56 +0900 Subject: [PATCH 1613/1791] Add basic xmldoc to show how the two colour classes interact --- osu.Game/Graphics/UserInterfaceV2/ColourDisplay.cs | 3 +++ osu.Game/Graphics/UserInterfaceV2/ColourPalette.cs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/osu.Game/Graphics/UserInterfaceV2/ColourDisplay.cs b/osu.Game/Graphics/UserInterfaceV2/ColourDisplay.cs index 5d9d2521cb..c07e5d5bf7 100644 --- a/osu.Game/Graphics/UserInterfaceV2/ColourDisplay.cs +++ b/osu.Game/Graphics/UserInterfaceV2/ColourDisplay.cs @@ -16,6 +16,9 @@ using osuTK.Graphics; namespace osu.Game.Graphics.UserInterfaceV2 { + /// + /// A component which displays a colour along with related description text. + /// public class ColourDisplay : CompositeDrawable, IHasCurrentValue { private readonly BindableWithCurrent current = new BindableWithCurrent(); diff --git a/osu.Game/Graphics/UserInterfaceV2/ColourPalette.cs b/osu.Game/Graphics/UserInterfaceV2/ColourPalette.cs index 3ca5c2d8d1..ba950048dc 100644 --- a/osu.Game/Graphics/UserInterfaceV2/ColourPalette.cs +++ b/osu.Game/Graphics/UserInterfaceV2/ColourPalette.cs @@ -12,6 +12,9 @@ using osuTK.Graphics; namespace osu.Game.Graphics.UserInterfaceV2 { + /// + /// A component which displays a collection of colours in individual s. + /// public class ColourPalette : CompositeDrawable { public BindableList Colours { get; } = new BindableList(); From a854ce429a8a30f042d98521248a0b7b99456f19 Mon Sep 17 00:00:00 2001 From: jvyden Date: Sun, 18 Apr 2021 23:49:13 -0400 Subject: [PATCH 1614/1791] add blank line between method --- osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs b/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs index 77eadc14be..86a9d555a3 100644 --- a/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs +++ b/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs @@ -141,6 +141,7 @@ namespace osu.Game.Tests.Visual.Components waitForAllIdle(); } + private void checkIdleStatus(int box, bool expectedIdle) { AddAssert($"box {box} is {(expectedIdle ? "idle" : "active")}", () => boxes[box - 1].IsIdle == expectedIdle); From 658c23c92522cc634063b012062ffad85b8e5b6c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Apr 2021 13:15:17 +0900 Subject: [PATCH 1615/1791] Give more space to the parameter adjustment area --- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index c5d2dd756a..74ca6bc6f9 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -32,7 +32,7 @@ namespace osu.Game.Screens.Edit.Timing ColumnDimensions = new[] { new Dimension(), - new Dimension(GridSizeMode.Absolute, 200), + new Dimension(GridSizeMode.Absolute, 350), }, Content = new[] { From 0c918410d07026a9b9bfd145ce1eb6ef0ed30a0a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Apr 2021 13:15:24 +0900 Subject: [PATCH 1616/1791] Make "add" button more visible --- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 74ca6bc6f9..d7a5442de1 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -106,9 +106,9 @@ namespace osu.Game.Screens.Edit.Timing }, new OsuButton { - Text = "+", + Text = "+ Add at current time", Action = addNew, - Size = new Vector2(30, 30), + Size = new Vector2(160, 30), Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, }, From 513e470b525c4948d793555beb0df0928b3b9019 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Apr 2021 13:24:53 +0900 Subject: [PATCH 1617/1791] Adjust grid spacing to allow attributes to use more width --- osu.Game/Screens/Edit/Timing/ControlPointTable.cs | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index dd51056bf1..6d4f3d1b3a 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -66,9 +66,7 @@ namespace osu.Game.Screens.Edit.Timing { var columns = new List { - new TableColumn(string.Empty, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), - new TableColumn("Time", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), - new TableColumn(), + new TableColumn("Time", Anchor.CentreLeft, new Dimension(GridSizeMode.Absolute, 120)), new TableColumn("Attributes", Anchor.CentreLeft), }; @@ -77,18 +75,11 @@ namespace osu.Game.Screens.Edit.Timing private Drawable[] createContent(int index, ControlPointGroup group) => new Drawable[] { - new OsuSpriteText - { - Text = $"#{index + 1}", - Font = OsuFont.GetFont(size: TEXT_SIZE, weight: FontWeight.Bold), - Margin = new MarginPadding(10) - }, new OsuSpriteText { Text = group.Time.ToEditorFormattedString(), Font = OsuFont.GetFont(size: TEXT_SIZE, weight: FontWeight.Bold) }, - null, new ControlGroupAttributes(group), }; From ffc1e841e045270e0d28c670778b516264f7db08 Mon Sep 17 00:00:00 2001 From: jvyden Date: Mon, 19 Apr 2021 01:06:26 -0400 Subject: [PATCH 1618/1791] Fix sessionIdleTracker not being properly added to OsuGame --- 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 1a2555f3f1..28f32ba455 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -584,7 +584,7 @@ namespace osu.Game SessionStatics.ResetValues(); }); - Add(new GameIdleTracker(300000)); + Add(sessionIdleTracker); AddRange(new Drawable[] { From e143afb598c3d3c8c688f2341ef8320d03e91018 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Apr 2021 13:57:46 +0900 Subject: [PATCH 1619/1791] Split out rounded content screen from SetupScreen for use in other places --- .../Edit/RoundedContentEditorScreen.cs | 61 +++++++++++++++++++ osu.Game/Screens/Edit/Setup/SetupScreen.cs | 49 +++------------ .../Screens/Edit/Setup/SetupScreenHeader.cs | 2 +- osu.Game/Screens/Edit/Setup/SetupSection.cs | 2 +- osu.Game/Screens/Edit/Verify/VerifyScreen.cs | 9 ++- 5 files changed, 77 insertions(+), 46 deletions(-) create mode 100644 osu.Game/Screens/Edit/RoundedContentEditorScreen.cs diff --git a/osu.Game/Screens/Edit/RoundedContentEditorScreen.cs b/osu.Game/Screens/Edit/RoundedContentEditorScreen.cs new file mode 100644 index 0000000000..a55ae3f635 --- /dev/null +++ b/osu.Game/Screens/Edit/RoundedContentEditorScreen.cs @@ -0,0 +1,61 @@ +// 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.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Overlays; + +namespace osu.Game.Screens.Edit +{ + public class RoundedContentEditorScreen : EditorScreen + { + public const int HORIZONTAL_PADDING = 100; + + [Resolved] + private OsuColour colours { get; set; } + + [Cached] + protected readonly OverlayColourProvider ColourProvider; + + private Container roundedContent; + + protected override Container Content => roundedContent; + + public RoundedContentEditorScreen(EditorScreenMode mode) + : base(mode) + { + ColourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); + } + + [BackgroundDependencyLoader] + private void load() + { + base.Content.Add(new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(50), + Child = new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 10, + Children = new Drawable[] + { + new Box + { + Colour = ColourProvider.Dark4, + RelativeSizeAxes = Axes.Both, + }, + roundedContent = new Container + { + RelativeSizeAxes = Axes.Both, + }, + } + } + }); + } + } +} diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index 70671b487c..15cb384573 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -3,24 +3,12 @@ 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.Overlays; namespace osu.Game.Screens.Edit.Setup { - public class SetupScreen : EditorScreen + public class SetupScreen : RoundedContentEditorScreen { - public const int HORIZONTAL_PADDING = 100; - - [Resolved] - private OsuColour colours { get; set; } - - [Cached] - protected readonly OverlayColourProvider ColourProvider; - [Cached] private SectionsContainer sections = new SectionsContainer(); @@ -30,42 +18,25 @@ namespace osu.Game.Screens.Edit.Setup public SetupScreen() : base(EditorScreenMode.SongSetup) { - ColourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); } [BackgroundDependencyLoader] private void load() { - Child = new Container + AddRange(new Drawable[] { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(50), - Child = new Container + sections = new SectionsContainer { + FixedHeader = header, RelativeSizeAxes = Axes.Both, - Masking = true, - CornerRadius = 10, - Children = new Drawable[] + Children = new SetupSection[] { - new Box - { - Colour = ColourProvider.Dark4, - RelativeSizeAxes = Axes.Both, - }, - sections = new SectionsContainer - { - FixedHeader = header, - RelativeSizeAxes = Axes.Both, - Children = new SetupSection[] - { - new ResourcesSection(), - new MetadataSection(), - new DifficultySection(), - } - }, + new ResourcesSection(), + new MetadataSection(), + new DifficultySection(), } - } - }; + }, + }); } } } diff --git a/osu.Game/Screens/Edit/Setup/SetupScreenHeader.cs b/osu.Game/Screens/Edit/Setup/SetupScreenHeader.cs index 06aad69afa..10daacc359 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreenHeader.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreenHeader.cs @@ -93,7 +93,7 @@ namespace osu.Game.Screens.Edit.Setup public SetupScreenTabControl() { - TabContainer.Margin = new MarginPadding { Horizontal = SetupScreen.HORIZONTAL_PADDING }; + TabContainer.Margin = new MarginPadding { Horizontal = RoundedContentEditorScreen.HORIZONTAL_PADDING }; AddInternal(background = new Box { diff --git a/osu.Game/Screens/Edit/Setup/SetupSection.cs b/osu.Game/Screens/Edit/Setup/SetupSection.cs index de62c3a468..b3ae15900f 100644 --- a/osu.Game/Screens/Edit/Setup/SetupSection.cs +++ b/osu.Game/Screens/Edit/Setup/SetupSection.cs @@ -33,7 +33,7 @@ namespace osu.Game.Screens.Edit.Setup Padding = new MarginPadding { Vertical = 10, - Horizontal = SetupScreen.HORIZONTAL_PADDING + Horizontal = RoundedContentEditorScreen.HORIZONTAL_PADDING }; InternalChild = new FillFlowContainer diff --git a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs index 550fbe2950..a2ee153951 100644 --- a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs +++ b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs @@ -7,16 +7,16 @@ using osu.Framework.Bindables; 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.UserInterface; +using osu.Game.Overlays; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Checks.Components; using osuTK; namespace osu.Game.Screens.Edit.Verify { - public class VerifyScreen : EditorScreen + public class VerifyScreen : RoundedContentEditorScreen { [Cached] private Bindable selectedIssue = new Bindable(); @@ -32,7 +32,6 @@ namespace osu.Game.Screens.Edit.Verify Child = new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(20), Child = new GridContainer { RelativeSizeAxes = Axes.Both, @@ -70,7 +69,7 @@ namespace osu.Game.Screens.Edit.Verify private BeatmapVerifier generalVerifier; [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OverlayColourProvider colours) { generalVerifier = new BeatmapVerifier(); rulesetVerifier = Beatmap.BeatmapInfo.Ruleset?.CreateInstance()?.CreateBeatmapVerifier(); @@ -81,7 +80,7 @@ namespace osu.Game.Screens.Edit.Verify { new Box { - Colour = colours.Gray0, + Colour = colours.Background2, RelativeSizeAxes = Axes.Both, }, new OsuScrollContainer From f4baff9e0495b3939b5970bdcdbc6f32fc114088 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Apr 2021 14:11:26 +0900 Subject: [PATCH 1620/1791] Make `TimingScreen` use rounded screen and adjust spacing/padding --- osu.Game/Screens/Edit/EditorTable.cs | 7 +-- .../Edit/Timing/ControlPointSettings.cs | 6 +-- osu.Game/Screens/Edit/Timing/Section.cs | 20 ++++----- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 43 +++++++++---------- 4 files changed, 37 insertions(+), 39 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorTable.cs b/osu.Game/Screens/Edit/EditorTable.cs index ef1c88db9a..9a793c8c97 100644 --- a/osu.Game/Screens/Edit/EditorTable.cs +++ b/osu.Game/Screens/Edit/EditorTable.cs @@ -9,6 +9,7 @@ using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Overlays; using osuTK.Graphics; namespace osu.Game.Screens.Edit @@ -93,10 +94,10 @@ namespace osu.Game.Screens.Edit private Color4 colourSelected; [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OverlayColourProvider colours) { - hoveredBackground.Colour = colourHover = colours.BlueDarker; - colourSelected = colours.YellowDarker; + hoveredBackground.Colour = colourHover = colours.Background3; + colourSelected = colours.Background1; } private bool selected; diff --git a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs index c40061b97c..921fa675b3 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs @@ -6,15 +6,15 @@ 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.Overlays; namespace osu.Game.Screens.Edit.Timing { public class ControlPointSettings : CompositeDrawable { [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OverlayColourProvider colours) { RelativeSizeAxes = Axes.Both; @@ -22,7 +22,7 @@ namespace osu.Game.Screens.Edit.Timing { new Box { - Colour = colours.Gray3, + Colour = colours.Background4, RelativeSizeAxes = Axes.Both, }, new OsuScrollContainer diff --git a/osu.Game/Screens/Edit/Timing/Section.cs b/osu.Game/Screens/Edit/Timing/Section.cs index 5269fa9774..8659b7aff6 100644 --- a/osu.Game/Screens/Edit/Timing/Section.cs +++ b/osu.Game/Screens/Edit/Timing/Section.cs @@ -8,8 +8,9 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; +using osuTK; namespace osu.Game.Screens.Edit.Timing { @@ -23,7 +24,7 @@ namespace osu.Game.Screens.Edit.Timing protected Bindable ControlPoint { get; } = new Bindable(); - private const float header_height = 20; + private const float header_height = 50; [Resolved] protected EditorBeatmap Beatmap { get; private set; } @@ -35,7 +36,7 @@ namespace osu.Game.Screens.Edit.Timing protected IEditorChangeHandler ChangeHandler { get; private set; } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OverlayColourProvider colours) { RelativeSizeAxes = Axes.X; AutoSizeDuration = 200; @@ -46,19 +47,17 @@ namespace osu.Game.Screens.Edit.Timing InternalChildren = new Drawable[] { - new Box - { - Colour = colours.Gray1, - RelativeSizeAxes = Axes.Both, - }, new Container { RelativeSizeAxes = Axes.X, Height = header_height, + Padding = new MarginPadding { Horizontal = 10 }, Children = new Drawable[] { checkbox = new OsuCheckbox { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, LabelText = typeof(T).Name.Replace(nameof(Beatmaps.ControlPoints.ControlPoint), string.Empty) } } @@ -72,12 +71,13 @@ namespace osu.Game.Screens.Edit.Timing { new Box { - Colour = colours.Gray2, + Colour = colours.Background3, RelativeSizeAxes = Axes.Both, }, Flow = new FillFlowContainer { - Padding = new MarginPadding(10), + Padding = new MarginPadding(20), + Spacing = new Vector2(20), RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index d7a5442de1..0b446314e8 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -8,15 +8,14 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; -using osu.Game.Screens.Edit.Compose.Components.Timeline; +using osu.Game.Overlays; using osuTK; namespace osu.Game.Screens.Edit.Timing { - public class TimingScreen : EditorScreenWithTimeline + public class TimingScreen : RoundedContentEditorScreen { [Cached] private Bindable selectedGroup = new Bindable(); @@ -26,28 +25,26 @@ namespace osu.Game.Screens.Edit.Timing { } - protected override Drawable CreateMainContent() => new GridContainer + [BackgroundDependencyLoader] + private void load() { - RelativeSizeAxes = Axes.Both, - ColumnDimensions = new[] + Add(new GridContainer { - new Dimension(), - new Dimension(GridSizeMode.Absolute, 350), - }, - Content = new[] - { - new Drawable[] + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] { - new ControlPointList(), - new ControlPointSettings(), + new Dimension(), + new Dimension(GridSizeMode.Absolute, 350), }, - } - }; - - protected override void OnTimelineLoaded(TimelineArea timelineArea) - { - base.OnTimelineLoaded(timelineArea); - timelineArea.Timeline.Zoom = timelineArea.Timeline.MinZoom; + Content = new[] + { + new Drawable[] + { + new ControlPointList(), + new ControlPointSettings(), + }, + } + }); } public class ControlPointList : CompositeDrawable @@ -70,7 +67,7 @@ namespace osu.Game.Screens.Edit.Timing private IEditorChangeHandler changeHandler { get; set; } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OverlayColourProvider colours) { RelativeSizeAxes = Axes.Both; @@ -78,7 +75,7 @@ namespace osu.Game.Screens.Edit.Timing { new Box { - Colour = colours.Gray0, + Colour = colours.Background2, RelativeSizeAxes = Axes.Both, }, new OsuScrollContainer From 5885c24e003bd02c2ad1d1d01756e2dc513b9850 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 19 Apr 2021 16:00:56 +0900 Subject: [PATCH 1621/1791] Clear current user states on disconnect --- osu.Game/Online/Spectator/SpectatorStreamingClient.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 4bbc420223..03387cfb9f 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -125,6 +125,7 @@ namespace osu.Game.Online.Spectator else { playingUsers.Clear(); + currentUserStates.Clear(); } }, true); } From de9e37857ee380e203668841daecd4c11ae1d839 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 19 Apr 2021 16:06:40 +0900 Subject: [PATCH 1622/1791] Lock around playingUsers --- .../Spectator/SpectatorStreamingClient.cs | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 03387cfb9f..10a707a4ce 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -47,6 +47,8 @@ namespace osu.Game.Online.Spectator private readonly BindableList playingUsers = new BindableList(); + private readonly Dictionary currentUserStates = new Dictionary(); + [CanBeNull] private IBeatmap currentBeatmap; @@ -60,7 +62,6 @@ namespace osu.Game.Online.Spectator private IBindable> currentMods { get; set; } private readonly SpectatorState currentState = new SpectatorState(); - private readonly Dictionary currentUserStates = new Dictionary(); private bool isPlaying; @@ -124,8 +125,11 @@ namespace osu.Game.Online.Spectator } else { - playingUsers.Clear(); - currentUserStates.Clear(); + lock (userLock) + { + playingUsers.Clear(); + currentUserStates.Clear(); + } } }, true); } @@ -133,11 +137,13 @@ namespace osu.Game.Online.Spectator Task ISpectatorClient.UserBeganPlaying(int userId, SpectatorState state) { - if (!playingUsers.Contains(userId)) - playingUsers.Add(userId); - lock (userLock) + { + if (!playingUsers.Contains(userId)) + playingUsers.Add(userId); + currentUserStates[userId] = state; + } OnUserBeganPlaying?.Invoke(userId, state); @@ -146,10 +152,11 @@ namespace osu.Game.Online.Spectator Task ISpectatorClient.UserFinishedPlaying(int userId, SpectatorState state) { - playingUsers.Remove(userId); - lock (userLock) + { + playingUsers.Remove(userId); currentUserStates.Remove(userId); + } OnUserFinishedPlaying?.Invoke(userId, state); From 83716ddb089a2e9ac43026aa6440998ccbc0e6ad Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 19 Apr 2021 16:07:00 +0900 Subject: [PATCH 1623/1791] Rename currentUserStates -> playingUserStates --- osu.Game/Online/Spectator/SpectatorStreamingClient.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 10a707a4ce..9def009469 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -47,7 +47,7 @@ namespace osu.Game.Online.Spectator private readonly BindableList playingUsers = new BindableList(); - private readonly Dictionary currentUserStates = new Dictionary(); + private readonly Dictionary playingUserStates = new Dictionary(); [CanBeNull] private IBeatmap currentBeatmap; @@ -128,7 +128,7 @@ namespace osu.Game.Online.Spectator lock (userLock) { playingUsers.Clear(); - currentUserStates.Clear(); + playingUserStates.Clear(); } } }, true); @@ -142,7 +142,7 @@ namespace osu.Game.Online.Spectator if (!playingUsers.Contains(userId)) playingUsers.Add(userId); - currentUserStates[userId] = state; + playingUserStates[userId] = state; } OnUserBeganPlaying?.Invoke(userId, state); @@ -155,7 +155,7 @@ namespace osu.Game.Online.Spectator lock (userLock) { playingUsers.Remove(userId); - currentUserStates.Remove(userId); + playingUserStates.Remove(userId); } OnUserFinishedPlaying?.Invoke(userId, state); @@ -298,7 +298,7 @@ namespace osu.Game.Online.Spectator lock (userLock) { - foreach (var (userId, state) in currentUserStates) + foreach (var (userId, state) in playingUserStates) callback(userId, state); } } From a10a8680d0cedbb71666c6883f6ea4082997c48c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Apr 2021 15:06:07 +0900 Subject: [PATCH 1624/1791] Add new display for timing row attributes --- osu.Game/Beatmaps/Timing/TimeSignatures.cs | 5 ++ osu.Game/Screens/Edit/EditorTable.cs | 2 +- .../Screens/Edit/Timing/ControlPointTable.cs | 17 +++-- osu.Game/Screens/Edit/Timing/RowAttribute.cs | 68 ++++++++++++------- .../Timing/RowAttributes/EmptyRowAttribute.cs | 63 +++++++++++++++++ .../RowAttributes/TimingRowAttribute.cs | 45 ++++++++++++ 6 files changed, 170 insertions(+), 30 deletions(-) create mode 100644 osu.Game/Screens/Edit/Timing/RowAttributes/EmptyRowAttribute.cs create mode 100644 osu.Game/Screens/Edit/Timing/RowAttributes/TimingRowAttribute.cs diff --git a/osu.Game/Beatmaps/Timing/TimeSignatures.cs b/osu.Game/Beatmaps/Timing/TimeSignatures.cs index 147f6239b4..33e6342ae6 100644 --- a/osu.Game/Beatmaps/Timing/TimeSignatures.cs +++ b/osu.Game/Beatmaps/Timing/TimeSignatures.cs @@ -1,11 +1,16 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.ComponentModel; + namespace osu.Game.Beatmaps.Timing { public enum TimeSignatures { + [Description("4/4")] SimpleQuadruple = 4, + + [Description("3/4")] SimpleTriple = 3 } } diff --git a/osu.Game/Screens/Edit/EditorTable.cs b/osu.Game/Screens/Edit/EditorTable.cs index 9a793c8c97..5a34a79726 100644 --- a/osu.Game/Screens/Edit/EditorTable.cs +++ b/osu.Game/Screens/Edit/EditorTable.cs @@ -20,7 +20,7 @@ namespace osu.Game.Screens.Edit protected const float ROW_HEIGHT = 25; - protected const int TEXT_SIZE = 14; + public const int TEXT_SIZE = 14; protected readonly FillFlowContainer BackgroundFlow; diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 6d4f3d1b3a..a680a087b8 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -12,6 +12,7 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.Extensions; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Screens.Edit.Timing.RowAttributes; using osuTK; using osuTK.Graphics; @@ -119,7 +120,12 @@ namespace osu.Game.Screens.Edit.Timing private void createChildren() { - fill.ChildrenEnumerable = controlPoints.Select(createAttribute).Where(c => c != null); + fill.ChildrenEnumerable = controlPoints + .Select(createAttribute) + .Where(c => c != null) + // arbitrary ordering to make timing points first. + // probably want to explicitly define order in the future. + .OrderByDescending(c => c.GetType().Name); } private Drawable createAttribute(ControlPoint controlPoint) @@ -129,20 +135,19 @@ namespace osu.Game.Screens.Edit.Timing switch (controlPoint) { case TimingControlPoint timing: - return new RowAttribute("timing", () => $"{60000 / timing.BeatLength:n1}bpm {timing.TimeSignature}", colour); + return new TimingRowAttribute(timing); case DifficultyControlPoint difficulty: - - return new RowAttribute("difficulty", () => $"{difficulty.SpeedMultiplier:n2}x", colour); + return new EmptyRowAttribute("difficulty", () => $"{difficulty.SpeedMultiplier:n2}x", colour); case EffectControlPoint effect: - return new RowAttribute("effect", () => string.Join(" ", + return new EmptyRowAttribute("effect", () => string.Join(" ", effect.KiaiMode ? "Kiai" : string.Empty, effect.OmitFirstBarLine ? "NoBarLine" : string.Empty ).Trim(), colour); case SampleControlPoint sample: - return new RowAttribute("sample", () => $"{sample.SampleBank} {sample.SampleVolume}%", colour); + return new EmptyRowAttribute("sample", () => $"{sample.SampleBank} {sample.SampleVolume}%", colour); } return null; diff --git a/osu.Game/Screens/Edit/Timing/RowAttribute.cs b/osu.Game/Screens/Edit/Timing/RowAttribute.cs index 2757e08026..55051a1bb2 100644 --- a/osu.Game/Screens/Edit/Timing/RowAttribute.cs +++ b/osu.Game/Screens/Edit/Timing/RowAttribute.cs @@ -1,33 +1,31 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osuTK.Graphics; +using osu.Game.Overlays; +using osuTK; namespace osu.Game.Screens.Edit.Timing { - public class RowAttribute : CompositeDrawable, IHasTooltip + public class RowAttribute : CompositeDrawable { - private readonly string header; - private readonly Func content; - private readonly Color4 colour; + private readonly ControlPoint point; - public RowAttribute(string header, Func content, Color4 colour) + protected FillFlowContainer Content { get; private set; } + + public RowAttribute(ControlPoint point) { - this.header = header; - this.content = content; - this.colour = colour; + this.point = point; } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OsuColour colours, OverlayColourProvider overlayColours) { AutoSizeAxes = Axes.X; @@ -43,21 +41,45 @@ namespace osu.Game.Screens.Edit.Timing { new Box { - Colour = colour, + Colour = overlayColours.Background4, RelativeSizeAxes = Axes.Both, }, - new OsuSpriteText + Content = new FillFlowContainer { - Padding = new MarginPadding(2), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.Default.With(weight: FontWeight.SemiBold, size: 12), - Text = header, - Colour = colours.Gray0 - }, + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Direction = FillDirection.Horizontal, + Margin = new MarginPadding { Right = 5 }, + Spacing = new Vector2(5), + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Children = new Drawable[] + { + new Box + { + Colour = point.GetRepresentingColour(colours), + RelativeSizeAxes = Axes.Both, + }, + new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Padding = new MarginPadding(3), + Font = OsuFont.Default.With(weight: FontWeight.SemiBold, size: 12), + Text = point.GetType().Name.Replace("ControlPoint", string.Empty).ToLowerInvariant(), + Colour = colours.Gray0 + }, + }, + }, + }, + } }; } - - public string TooltipText => content(); } } diff --git a/osu.Game/Screens/Edit/Timing/RowAttributes/EmptyRowAttribute.cs b/osu.Game/Screens/Edit/Timing/RowAttributes/EmptyRowAttribute.cs new file mode 100644 index 0000000000..8a73853eb3 --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/RowAttributes/EmptyRowAttribute.cs @@ -0,0 +1,63 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osuTK.Graphics; + +namespace osu.Game.Screens.Edit.Timing.RowAttributes +{ + public class EmptyRowAttribute : CompositeDrawable, IHasTooltip + { + private readonly string header; + private readonly Func content; + private readonly Color4 colour; + + public EmptyRowAttribute(string header, Func content, Color4 colour) + { + this.header = header; + this.content = content; + this.colour = colour; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AutoSizeAxes = Axes.X; + + Height = 20; + + Anchor = Anchor.CentreLeft; + Origin = Anchor.CentreLeft; + + Masking = true; + CornerRadius = 5; + + InternalChildren = new Drawable[] + { + new Box + { + Colour = colour, + RelativeSizeAxes = Axes.Both, + }, + new OsuSpriteText + { + Padding = new MarginPadding(2), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.Default.With(weight: FontWeight.SemiBold, size: 12), + Text = header, + Colour = colours.Gray0 + }, + }; + } + + public string TooltipText => content(); + } +} diff --git a/osu.Game/Screens/Edit/Timing/RowAttributes/TimingRowAttribute.cs b/osu.Game/Screens/Edit/Timing/RowAttributes/TimingRowAttribute.cs new file mode 100644 index 0000000000..07e041650c --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/RowAttributes/TimingRowAttribute.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 osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions; +using osu.Framework.Graphics; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Beatmaps.Timing; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Screens.Edit.Timing.RowAttributes +{ + internal class TimingRowAttribute : RowAttribute + { + private readonly BindableNumber beatLength; + private readonly Bindable timeSignature; + private OsuSpriteText text; + + public TimingRowAttribute(TimingControlPoint timing) + : base(timing) + { + timeSignature = timing.TimeSignatureBindable.GetBoundCopy(); + beatLength = timing.BeatLengthBindable.GetBoundCopy(); + } + + [BackgroundDependencyLoader] + private void load() + { + Content.Add(text = new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = OsuFont.GetFont(size: EditorTable.TEXT_SIZE, weight: FontWeight.Regular), + }); + + timeSignature.BindValueChanged(_ => updateText()); + beatLength.BindValueChanged(_ => updateText(), true); + } + + private void updateText() => + text.Text = $"{60000 / beatLength.Value:n1}bpm {timeSignature.Value.GetDescription()}"; + } +} From d3cebfb6fb63d0dabd62f695f58ce41e241b5b27 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Apr 2021 15:27:38 +0900 Subject: [PATCH 1625/1791] Use explicit label --- osu.Game/Screens/Edit/Timing/RowAttribute.cs | 6 ++++-- .../Screens/Edit/Timing/RowAttributes/TimingRowAttribute.cs | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/RowAttribute.cs b/osu.Game/Screens/Edit/Timing/RowAttribute.cs index 55051a1bb2..4cfdeb06c3 100644 --- a/osu.Game/Screens/Edit/Timing/RowAttribute.cs +++ b/osu.Game/Screens/Edit/Timing/RowAttribute.cs @@ -16,12 +16,14 @@ namespace osu.Game.Screens.Edit.Timing public class RowAttribute : CompositeDrawable { private readonly ControlPoint point; + private readonly string label; protected FillFlowContainer Content { get; private set; } - public RowAttribute(ControlPoint point) + public RowAttribute(ControlPoint point, string label) { this.point = point; + this.label = label; } [BackgroundDependencyLoader] @@ -72,7 +74,7 @@ namespace osu.Game.Screens.Edit.Timing Origin = Anchor.CentreLeft, Padding = new MarginPadding(3), Font = OsuFont.Default.With(weight: FontWeight.SemiBold, size: 12), - Text = point.GetType().Name.Replace("ControlPoint", string.Empty).ToLowerInvariant(), + Text = label, Colour = colours.Gray0 }, }, diff --git a/osu.Game/Screens/Edit/Timing/RowAttributes/TimingRowAttribute.cs b/osu.Game/Screens/Edit/Timing/RowAttributes/TimingRowAttribute.cs index 07e041650c..c6e2649f8e 100644 --- a/osu.Game/Screens/Edit/Timing/RowAttributes/TimingRowAttribute.cs +++ b/osu.Game/Screens/Edit/Timing/RowAttributes/TimingRowAttribute.cs @@ -12,14 +12,14 @@ using osu.Game.Graphics.Sprites; namespace osu.Game.Screens.Edit.Timing.RowAttributes { - internal class TimingRowAttribute : RowAttribute + public class TimingRowAttribute : RowAttribute { private readonly BindableNumber beatLength; private readonly Bindable timeSignature; private OsuSpriteText text; public TimingRowAttribute(TimingControlPoint timing) - : base(timing) + : base(timing, "timing") { timeSignature = timing.TimeSignatureBindable.GetBoundCopy(); beatLength = timing.BeatLengthBindable.GetBoundCopy(); From 3aad0a8b9c6df9efe7381a5050fbd42c34ce8d37 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Apr 2021 15:57:27 +0900 Subject: [PATCH 1626/1791] Add new display for difficulty row attribute --- .../Screens/Edit/Timing/ControlPointTable.cs | 2 +- osu.Game/Screens/Edit/Timing/RowAttribute.cs | 8 ++-- .../RowAttributes/AttributeProgressBar.cs | 41 ++++++++++++++++ .../RowAttributes/DifficultyRowAttribute.cs | 48 +++++++++++++++++++ 4 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 osu.Game/Screens/Edit/Timing/RowAttributes/AttributeProgressBar.cs create mode 100644 osu.Game/Screens/Edit/Timing/RowAttributes/DifficultyRowAttribute.cs diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index a680a087b8..f71f31acb7 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -138,7 +138,7 @@ namespace osu.Game.Screens.Edit.Timing return new TimingRowAttribute(timing); case DifficultyControlPoint difficulty: - return new EmptyRowAttribute("difficulty", () => $"{difficulty.SpeedMultiplier:n2}x", colour); + return new DifficultyRowAttribute(difficulty); case EffectControlPoint effect: return new EmptyRowAttribute("effect", () => string.Join(" ", diff --git a/osu.Game/Screens/Edit/Timing/RowAttribute.cs b/osu.Game/Screens/Edit/Timing/RowAttribute.cs index 4cfdeb06c3..e46459892c 100644 --- a/osu.Game/Screens/Edit/Timing/RowAttribute.cs +++ b/osu.Game/Screens/Edit/Timing/RowAttribute.cs @@ -15,14 +15,16 @@ namespace osu.Game.Screens.Edit.Timing { public class RowAttribute : CompositeDrawable { - private readonly ControlPoint point; + protected readonly ControlPoint Point; + private readonly string label; protected FillFlowContainer Content { get; private set; } public RowAttribute(ControlPoint point, string label) { - this.point = point; + Point = point; + this.label = label; } @@ -65,7 +67,7 @@ namespace osu.Game.Screens.Edit.Timing { new Box { - Colour = point.GetRepresentingColour(colours), + Colour = Point.GetRepresentingColour(colours), RelativeSizeAxes = Axes.Both, }, new OsuSpriteText diff --git a/osu.Game/Screens/Edit/Timing/RowAttributes/AttributeProgressBar.cs b/osu.Game/Screens/Edit/Timing/RowAttributes/AttributeProgressBar.cs new file mode 100644 index 0000000000..9d7def4c33 --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/RowAttributes/AttributeProgressBar.cs @@ -0,0 +1,41 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; +using osuTK; + +namespace osu.Game.Screens.Edit.Timing.RowAttributes +{ + public class AttributeProgressBar : ProgressBar + { + private readonly ControlPoint controlPoint; + + public AttributeProgressBar(ControlPoint controlPoint) + : base(false) + { + this.controlPoint = controlPoint; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours, OverlayColourProvider overlayColours) + { + Anchor = Anchor.CentreLeft; + Origin = Anchor.CentreLeft; + + Masking = true; + + RelativeSizeAxes = Axes.None; + + Size = new Vector2(80, 8); + CornerRadius = Height / 2; + + BackgroundColour = overlayColours.Background6; + Colour = controlPoint.GetRepresentingColour(colours); + } + } +} diff --git a/osu.Game/Screens/Edit/Timing/RowAttributes/DifficultyRowAttribute.cs b/osu.Game/Screens/Edit/Timing/RowAttributes/DifficultyRowAttribute.cs new file mode 100644 index 0000000000..1fdd8c2708 --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/RowAttributes/DifficultyRowAttribute.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 osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Screens.Edit.Timing.RowAttributes +{ + public class DifficultyRowAttribute : RowAttribute + { + private readonly BindableNumber speedMultiplier; + + private OsuSpriteText text; + + public DifficultyRowAttribute(DifficultyControlPoint difficulty) + : base(difficulty, "difficulty") + { + speedMultiplier = difficulty.SpeedMultiplierBindable.GetBoundCopy(); + } + + [BackgroundDependencyLoader] + private void load() + { + Content.AddRange(new Drawable[] + { + text = new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Width = 40, + Font = OsuFont.GetFont(size: EditorTable.TEXT_SIZE, weight: FontWeight.Regular), + }, + new AttributeProgressBar(Point) + { + Current = speedMultiplier, + } + }); + + speedMultiplier.BindValueChanged(_ => updateText(), true); + } + + private void updateText() => text.Text = $"{speedMultiplier.Value:n2}x"; + } +} From 6465a72060ab9cf3b7515bc288f56576b415bd1f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Apr 2021 16:23:26 +0900 Subject: [PATCH 1627/1791] Add bubbled word class for use in attribute rows --- .../RowAttributes/AttributeBubbledWord.cs | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 osu.Game/Screens/Edit/Timing/RowAttributes/AttributeBubbledWord.cs diff --git a/osu.Game/Screens/Edit/Timing/RowAttributes/AttributeBubbledWord.cs b/osu.Game/Screens/Edit/Timing/RowAttributes/AttributeBubbledWord.cs new file mode 100644 index 0000000000..3e956cbe92 --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/RowAttributes/AttributeBubbledWord.cs @@ -0,0 +1,71 @@ +// 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.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Overlays; + +namespace osu.Game.Screens.Edit.Timing.RowAttributes +{ + public class AttributeBubbledWord : CompositeDrawable + { + private readonly ControlPoint controlPoint; + + private OsuSpriteText textDrawable; + + private string text; + + public string Text + { + get => text; + set + { + if (value == text) + return; + + text = value; + if (textDrawable != null) + textDrawable.Text = text; + } + } + + public AttributeBubbledWord(ControlPoint controlPoint) + { + this.controlPoint = controlPoint; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours, OverlayColourProvider overlayColours) + { + AutoSizeAxes = Axes.X; + + Anchor = Anchor.CentreLeft; + Origin = Anchor.CentreLeft; + + Height = 12; + + InternalChildren = new Drawable[] + { + new Circle + { + Colour = controlPoint.GetRepresentingColour(colours), + RelativeSizeAxes = Axes.Both, + }, + textDrawable = new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Padding = new MarginPadding(3), + Font = OsuFont.Default.With(weight: FontWeight.SemiBold, size: 12), + Text = text, + Colour = colours.Gray0 + }, + }; + } + } +} From ec249a0edbf40c694f905539ddf03691a463d222 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Apr 2021 16:10:37 +0900 Subject: [PATCH 1628/1791] Add new display for sample row attribute --- .../Screens/Edit/Timing/ControlPointTable.cs | 2 +- .../RowAttributes/SampleRowAttribute.cs | 61 +++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Screens/Edit/Timing/RowAttributes/SampleRowAttribute.cs diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index f71f31acb7..17aa5bbefa 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -147,7 +147,7 @@ namespace osu.Game.Screens.Edit.Timing ).Trim(), colour); case SampleControlPoint sample: - return new EmptyRowAttribute("sample", () => $"{sample.SampleBank} {sample.SampleVolume}%", colour); + return new SampleRowAttribute(sample); } return null; diff --git a/osu.Game/Screens/Edit/Timing/RowAttributes/SampleRowAttribute.cs b/osu.Game/Screens/Edit/Timing/RowAttributes/SampleRowAttribute.cs new file mode 100644 index 0000000000..066b180423 --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/RowAttributes/SampleRowAttribute.cs @@ -0,0 +1,61 @@ +// 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.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Screens.Edit.Timing.RowAttributes +{ + public class SampleRowAttribute : RowAttribute + { + private AttributeBubbledWord sampleText; + private OsuSpriteText volumeText; + + private readonly Bindable sampleBank; + private readonly BindableNumber volume; + + public SampleRowAttribute(SampleControlPoint sample) + : base(sample, "sample") + { + sampleBank = sample.SampleBankBindable.GetBoundCopy(); + volume = sample.SampleVolumeBindable.GetBoundCopy(); + } + + [BackgroundDependencyLoader] + private void load() + { + AttributeProgressBar progress; + + Content.AddRange(new Drawable[] + { + volumeText = new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Width = 30, + Font = OsuFont.GetFont(size: EditorTable.TEXT_SIZE, weight: FontWeight.Regular), + }, + progress = new AttributeProgressBar(Point), + sampleText = new AttributeBubbledWord(Point), + }); + + volume.BindValueChanged(vol => + { + progress.Current.Value = vol.NewValue / 100f; + updateText(); + }, true); + + sampleBank.BindValueChanged(_ => updateText(), true); + } + + private void updateText() + { + volumeText.Text = $"{volume.Value}%"; + sampleText.Text = $"{sampleBank.Value}"; + } + } +} From f8b20ca8aa16a302c4cfaaeea318db7c4201c1aa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Apr 2021 16:28:18 +0900 Subject: [PATCH 1629/1791] Add new display for effect row attribute --- .../Screens/Edit/Timing/ControlPointTable.cs | 8 +--- .../RowAttributes/EffectRowAttribute.cs | 38 +++++++++++++++++++ 2 files changed, 39 insertions(+), 7 deletions(-) create mode 100644 osu.Game/Screens/Edit/Timing/RowAttributes/EffectRowAttribute.cs diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 17aa5bbefa..1f06bc5bc7 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -14,7 +14,6 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Screens.Edit.Timing.RowAttributes; using osuTK; -using osuTK.Graphics; namespace osu.Game.Screens.Edit.Timing { @@ -130,8 +129,6 @@ namespace osu.Game.Screens.Edit.Timing private Drawable createAttribute(ControlPoint controlPoint) { - Color4 colour = controlPoint.GetRepresentingColour(colours); - switch (controlPoint) { case TimingControlPoint timing: @@ -141,10 +138,7 @@ namespace osu.Game.Screens.Edit.Timing return new DifficultyRowAttribute(difficulty); case EffectControlPoint effect: - return new EmptyRowAttribute("effect", () => string.Join(" ", - effect.KiaiMode ? "Kiai" : string.Empty, - effect.OmitFirstBarLine ? "NoBarLine" : string.Empty - ).Trim(), colour); + return new EffectRowAttribute(effect); case SampleControlPoint sample: return new SampleRowAttribute(sample); diff --git a/osu.Game/Screens/Edit/Timing/RowAttributes/EffectRowAttribute.cs b/osu.Game/Screens/Edit/Timing/RowAttributes/EffectRowAttribute.cs new file mode 100644 index 0000000000..e7fc57e2c9 --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/RowAttributes/EffectRowAttribute.cs @@ -0,0 +1,38 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Beatmaps.ControlPoints; + +namespace osu.Game.Screens.Edit.Timing.RowAttributes +{ + public class EffectRowAttribute : RowAttribute + { + private readonly Bindable kiaiMode; + private readonly Bindable omitBarLine; + private AttributeBubbledWord kiaiModeBubble; + private AttributeBubbledWord omitBarLineBubble; + + public EffectRowAttribute(EffectControlPoint effect) + : base(effect, "effect") + { + kiaiMode = effect.KiaiModeBindable.GetBoundCopy(); + omitBarLine = effect.OmitFirstBarLineBindable.GetBoundCopy(); + } + + [BackgroundDependencyLoader] + private void load() + { + Content.AddRange(new Drawable[] + { + kiaiModeBubble = new AttributeBubbledWord(Point) { Text = "kiai" }, + omitBarLineBubble = new AttributeBubbledWord(Point) { Text = "no barline" }, + }); + + kiaiMode.BindValueChanged(enabled => kiaiModeBubble.FadeTo(enabled.NewValue ? 1 : 0), true); + omitBarLine.BindValueChanged(enabled => omitBarLineBubble.FadeTo(enabled.NewValue ? 1 : 0), true); + } + } +} From 8da561a2a637582248b3309f41b63cc0a61b2ca9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Apr 2021 16:35:13 +0900 Subject: [PATCH 1630/1791] Soften colours and adjust padding slightly --- osu.Game/Screens/Edit/Timing/RowAttribute.cs | 2 +- .../Screens/Edit/Timing/RowAttributes/AttributeBubbledWord.cs | 4 ++-- .../Screens/Edit/Timing/RowAttributes/AttributeProgressBar.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/RowAttribute.cs b/osu.Game/Screens/Edit/Timing/RowAttribute.cs index e46459892c..e184907751 100644 --- a/osu.Game/Screens/Edit/Timing/RowAttribute.cs +++ b/osu.Game/Screens/Edit/Timing/RowAttribute.cs @@ -77,7 +77,7 @@ namespace osu.Game.Screens.Edit.Timing Padding = new MarginPadding(3), Font = OsuFont.Default.With(weight: FontWeight.SemiBold, size: 12), Text = label, - Colour = colours.Gray0 + Colour = overlayColours.Background5, }, }, }, diff --git a/osu.Game/Screens/Edit/Timing/RowAttributes/AttributeBubbledWord.cs b/osu.Game/Screens/Edit/Timing/RowAttributes/AttributeBubbledWord.cs index 3e956cbe92..7d7ad61de2 100644 --- a/osu.Game/Screens/Edit/Timing/RowAttributes/AttributeBubbledWord.cs +++ b/osu.Game/Screens/Edit/Timing/RowAttributes/AttributeBubbledWord.cs @@ -60,10 +60,10 @@ namespace osu.Game.Screens.Edit.Timing.RowAttributes { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Padding = new MarginPadding(3), + Padding = new MarginPadding(6), Font = OsuFont.Default.With(weight: FontWeight.SemiBold, size: 12), Text = text, - Colour = colours.Gray0 + Colour = overlayColours.Background5, }, }; } diff --git a/osu.Game/Screens/Edit/Timing/RowAttributes/AttributeProgressBar.cs b/osu.Game/Screens/Edit/Timing/RowAttributes/AttributeProgressBar.cs index 9d7def4c33..6f7e790489 100644 --- a/osu.Game/Screens/Edit/Timing/RowAttributes/AttributeProgressBar.cs +++ b/osu.Game/Screens/Edit/Timing/RowAttributes/AttributeProgressBar.cs @@ -35,7 +35,7 @@ namespace osu.Game.Screens.Edit.Timing.RowAttributes CornerRadius = Height / 2; BackgroundColour = overlayColours.Background6; - Colour = controlPoint.GetRepresentingColour(colours); + FillColour = controlPoint.GetRepresentingColour(colours); } } } From 1ebc5ac5cc2d33abb3233ca60e51f988f133d319 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Apr 2021 16:36:00 +0900 Subject: [PATCH 1631/1791] Remove unused legacy class --- .../Timing/RowAttributes/EmptyRowAttribute.cs | 63 ------------------- 1 file changed, 63 deletions(-) delete mode 100644 osu.Game/Screens/Edit/Timing/RowAttributes/EmptyRowAttribute.cs diff --git a/osu.Game/Screens/Edit/Timing/RowAttributes/EmptyRowAttribute.cs b/osu.Game/Screens/Edit/Timing/RowAttributes/EmptyRowAttribute.cs deleted file mode 100644 index 8a73853eb3..0000000000 --- a/osu.Game/Screens/Edit/Timing/RowAttributes/EmptyRowAttribute.cs +++ /dev/null @@ -1,63 +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 osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osuTK.Graphics; - -namespace osu.Game.Screens.Edit.Timing.RowAttributes -{ - public class EmptyRowAttribute : CompositeDrawable, IHasTooltip - { - private readonly string header; - private readonly Func content; - private readonly Color4 colour; - - public EmptyRowAttribute(string header, Func content, Color4 colour) - { - this.header = header; - this.content = content; - this.colour = colour; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - AutoSizeAxes = Axes.X; - - Height = 20; - - Anchor = Anchor.CentreLeft; - Origin = Anchor.CentreLeft; - - Masking = true; - CornerRadius = 5; - - InternalChildren = new Drawable[] - { - new Box - { - Colour = colour, - RelativeSizeAxes = Axes.Both, - }, - new OsuSpriteText - { - Padding = new MarginPadding(2), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.Default.With(weight: FontWeight.SemiBold, size: 12), - Text = header, - Colour = colours.Gray0 - }, - }; - } - - public string TooltipText => content(); - } -} From c50b526ba09fa2fe98f2d1d43d822a38dc8237f2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 19 Apr 2021 16:48:55 +0900 Subject: [PATCH 1632/1791] Remove local state dictionary from SpectatorScreen --- .../Online/Spectator/SpectatorStreamingClient.cs | 12 ++++++++++++ osu.Game/Screens/Spectate/SpectatorScreen.cs | 14 +++++++++----- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 9def009469..13b12d9add 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -284,6 +284,18 @@ namespace osu.Game.Online.Spectator lastSendTime = Time.Current; } + /// + /// Attempts to retrieve the for a currently-playing user. + /// + /// The user. + /// The current for the user, if they're playing. null if the user is not playing. + /// true if successful (the user is playing), false otherwise. + public bool TryGetPlayingUserState(int userId, out SpectatorState state) + { + lock (userLock) + return playingUserStates.TryGetValue(userId, out state); + } + /// /// Bind an action to with the option of running the bound action once immediately. /// diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index 7be6c6183b..f554b15abf 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -44,7 +44,6 @@ namespace osu.Game.Screens.Spectate private readonly object stateLock = new object(); private readonly Dictionary userMap = new Dictionary(); - private readonly Dictionary spectatorStates = new Dictionary(); private readonly Dictionary gameplayStates = new Dictionary(); private IBindable> managerUpdated; @@ -107,9 +106,12 @@ namespace osu.Game.Screens.Spectate lock (stateLock) { - foreach (var (userId, state) in spectatorStates) + foreach (var (userId, _) in userMap) { - if (beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID == state.BeatmapID)) + if (!spectatorClient.TryGetPlayingUserState(userId, out var userState)) + continue; + + if (beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID == userState.BeatmapID)) updateGameplayState(userId); } } @@ -125,7 +127,6 @@ namespace osu.Game.Screens.Spectate if (!userMap.ContainsKey(userId)) return; - spectatorStates[userId] = state; Schedule(() => OnUserStateChanged(userId, state)); updateGameplayState(userId); @@ -138,7 +139,10 @@ namespace osu.Game.Screens.Spectate { Debug.Assert(userMap.ContainsKey(userId)); - var spectatorState = spectatorStates[userId]; + // The user may have stopped playing. + if (!spectatorClient.TryGetPlayingUserState(userId, out var spectatorState)) + return; + var user = userMap[userId]; var resolvedRuleset = rulesets.AvailableRulesets.FirstOrDefault(r => r.ID == spectatorState.RulesetID)?.CreateInstance(); From c12848ce4dc62749a77cd0e927c0ad43020cfa08 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 19 Apr 2021 17:02:59 +0900 Subject: [PATCH 1633/1791] Apply fixes to tests --- osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs index 802dbf2021..b38f7a998d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs @@ -118,8 +118,8 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestBasic() { AddStep("move to center", () => InputManager.MoveMouseTo(recordingManager.ScreenSpaceDrawQuad.Centre)); - AddUntilStep("one frame recorded", () => replay.Frames.Count == 1); - AddAssert("position matches", () => playbackManager.ChildrenOfType().First().Position == recordingManager.ChildrenOfType().First().Position); + AddUntilStep("at least one frame recorded", () => replay.Frames.Count > 0); + AddUntilStep("position matches", () => playbackManager.ChildrenOfType().First().Position == recordingManager.ChildrenOfType().First().Position); } [Test] @@ -139,14 +139,16 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestLimitedFrameRate() { ScheduledDelegate moveFunction = null; + int initialFrameCount = 0; AddStep("lower rate", () => recorder.RecordFrameRate = 2); + AddStep("count frames", () => initialFrameCount = replay.Frames.Count); AddStep("move to center", () => InputManager.MoveMouseTo(recordingManager.ScreenSpaceDrawQuad.Centre)); AddStep("much move", () => moveFunction = Scheduler.AddDelayed(() => InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(-1, 0)), 10, true)); AddWaitStep("move", 10); AddStep("stop move", () => moveFunction.Cancel()); - AddAssert("less than 10 frames recorded", () => replay.Frames.Count < 10); + AddAssert("less than 10 frames recorded", () => replay.Frames.Count - initialFrameCount < 10); } [Test] From 5bce5d2057eebe74115edb5f1dbc53ceaf6e3738 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Apr 2021 17:57:07 +0900 Subject: [PATCH 1634/1791] Update design logic --- osu.Game/Screens/Edit/Timing/RowAttribute.cs | 36 ++++------ .../RowAttributes/AttributeBubbledWord.cs | 71 ------------------- .../Timing/RowAttributes/AttributeText.cs | 32 +++++++++ .../RowAttributes/DifficultyRowAttribute.cs | 14 ++-- .../RowAttributes/EffectRowAttribute.cs | 8 +-- .../RowAttributes/SampleRowAttribute.cs | 16 ++--- .../RowAttributes/TimingRowAttribute.cs | 9 +-- 7 files changed, 62 insertions(+), 124 deletions(-) delete mode 100644 osu.Game/Screens/Edit/Timing/RowAttributes/AttributeBubbledWord.cs create mode 100644 osu.Game/Screens/Edit/Timing/RowAttributes/AttributeText.cs diff --git a/osu.Game/Screens/Edit/Timing/RowAttribute.cs b/osu.Game/Screens/Edit/Timing/RowAttribute.cs index e184907751..74d43628e1 100644 --- a/osu.Game/Screens/Edit/Timing/RowAttribute.cs +++ b/osu.Game/Screens/Edit/Timing/RowAttribute.cs @@ -39,7 +39,7 @@ namespace osu.Game.Screens.Edit.Timing Origin = Anchor.CentreLeft; Masking = true; - CornerRadius = 5; + CornerRadius = 3; InternalChildren = new Drawable[] { @@ -53,33 +53,25 @@ namespace osu.Game.Screens.Edit.Timing RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, Direction = FillDirection.Horizontal, - Margin = new MarginPadding { Right = 5 }, + Margin = new MarginPadding { Horizontal = 5 }, Spacing = new Vector2(5), Children = new Drawable[] { - new Container + new Circle { - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Children = new Drawable[] - { - new Box - { - Colour = Point.GetRepresentingColour(colours), - RelativeSizeAxes = Axes.Both, - }, - new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Padding = new MarginPadding(3), - Font = OsuFont.Default.With(weight: FontWeight.SemiBold, size: 12), - Text = label, - Colour = overlayColours.Background5, - }, - }, + Colour = Point.GetRepresentingColour(colours), + RelativeSizeAxes = Axes.Y, + Size = new Vector2(4, 0.6f), + }, + new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Padding = new MarginPadding(3), + Font = OsuFont.Default.With(weight: FontWeight.Bold, size: 12), + Text = label, }, }, } diff --git a/osu.Game/Screens/Edit/Timing/RowAttributes/AttributeBubbledWord.cs b/osu.Game/Screens/Edit/Timing/RowAttributes/AttributeBubbledWord.cs deleted file mode 100644 index 7d7ad61de2..0000000000 --- a/osu.Game/Screens/Edit/Timing/RowAttributes/AttributeBubbledWord.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 osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Overlays; - -namespace osu.Game.Screens.Edit.Timing.RowAttributes -{ - public class AttributeBubbledWord : CompositeDrawable - { - private readonly ControlPoint controlPoint; - - private OsuSpriteText textDrawable; - - private string text; - - public string Text - { - get => text; - set - { - if (value == text) - return; - - text = value; - if (textDrawable != null) - textDrawable.Text = text; - } - } - - public AttributeBubbledWord(ControlPoint controlPoint) - { - this.controlPoint = controlPoint; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours, OverlayColourProvider overlayColours) - { - AutoSizeAxes = Axes.X; - - Anchor = Anchor.CentreLeft; - Origin = Anchor.CentreLeft; - - Height = 12; - - InternalChildren = new Drawable[] - { - new Circle - { - Colour = controlPoint.GetRepresentingColour(colours), - RelativeSizeAxes = Axes.Both, - }, - textDrawable = new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Padding = new MarginPadding(6), - Font = OsuFont.Default.With(weight: FontWeight.SemiBold, size: 12), - Text = text, - Colour = overlayColours.Background5, - }, - }; - } - } -} diff --git a/osu.Game/Screens/Edit/Timing/RowAttributes/AttributeText.cs b/osu.Game/Screens/Edit/Timing/RowAttributes/AttributeText.cs new file mode 100644 index 0000000000..d0a51f9faa --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/RowAttributes/AttributeText.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.Graphics; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Screens.Edit.Timing.RowAttributes +{ + public class AttributeText : OsuSpriteText + { + private readonly ControlPoint controlPoint; + + public AttributeText(ControlPoint controlPoint) + { + this.controlPoint = controlPoint; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Anchor = Anchor.CentreLeft; + Origin = Anchor.CentreLeft; + + Padding = new MarginPadding(6); + Font = OsuFont.Default.With(weight: FontWeight.Bold, size: 12); + Colour = controlPoint.GetRepresentingColour(colours); + } + } +} diff --git a/osu.Game/Screens/Edit/Timing/RowAttributes/DifficultyRowAttribute.cs b/osu.Game/Screens/Edit/Timing/RowAttributes/DifficultyRowAttribute.cs index 1fdd8c2708..7b553ac7ad 100644 --- a/osu.Game/Screens/Edit/Timing/RowAttributes/DifficultyRowAttribute.cs +++ b/osu.Game/Screens/Edit/Timing/RowAttributes/DifficultyRowAttribute.cs @@ -5,7 +5,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Graphics; using osu.Game.Graphics.Sprites; namespace osu.Game.Screens.Edit.Timing.RowAttributes @@ -27,17 +26,14 @@ namespace osu.Game.Screens.Edit.Timing.RowAttributes { Content.AddRange(new Drawable[] { - text = new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Width = 40, - Font = OsuFont.GetFont(size: EditorTable.TEXT_SIZE, weight: FontWeight.Regular), - }, new AttributeProgressBar(Point) { Current = speedMultiplier, - } + }, + text = new AttributeText(Point) + { + Width = 40, + }, }); speedMultiplier.BindValueChanged(_ => updateText(), true); diff --git a/osu.Game/Screens/Edit/Timing/RowAttributes/EffectRowAttribute.cs b/osu.Game/Screens/Edit/Timing/RowAttributes/EffectRowAttribute.cs index e7fc57e2c9..812407d6da 100644 --- a/osu.Game/Screens/Edit/Timing/RowAttributes/EffectRowAttribute.cs +++ b/osu.Game/Screens/Edit/Timing/RowAttributes/EffectRowAttribute.cs @@ -12,8 +12,8 @@ namespace osu.Game.Screens.Edit.Timing.RowAttributes { private readonly Bindable kiaiMode; private readonly Bindable omitBarLine; - private AttributeBubbledWord kiaiModeBubble; - private AttributeBubbledWord omitBarLineBubble; + private AttributeText kiaiModeBubble; + private AttributeText omitBarLineBubble; public EffectRowAttribute(EffectControlPoint effect) : base(effect, "effect") @@ -27,8 +27,8 @@ namespace osu.Game.Screens.Edit.Timing.RowAttributes { Content.AddRange(new Drawable[] { - kiaiModeBubble = new AttributeBubbledWord(Point) { Text = "kiai" }, - omitBarLineBubble = new AttributeBubbledWord(Point) { Text = "no barline" }, + kiaiModeBubble = new AttributeText(Point) { Text = "kiai" }, + omitBarLineBubble = new AttributeText(Point) { Text = "no barline" }, }); kiaiMode.BindValueChanged(enabled => kiaiModeBubble.FadeTo(enabled.NewValue ? 1 : 0), true); diff --git a/osu.Game/Screens/Edit/Timing/RowAttributes/SampleRowAttribute.cs b/osu.Game/Screens/Edit/Timing/RowAttributes/SampleRowAttribute.cs index 066b180423..ac0797dba1 100644 --- a/osu.Game/Screens/Edit/Timing/RowAttributes/SampleRowAttribute.cs +++ b/osu.Game/Screens/Edit/Timing/RowAttributes/SampleRowAttribute.cs @@ -5,14 +5,13 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Graphics; using osu.Game.Graphics.Sprites; namespace osu.Game.Screens.Edit.Timing.RowAttributes { public class SampleRowAttribute : RowAttribute { - private AttributeBubbledWord sampleText; + private AttributeText sampleText; private OsuSpriteText volumeText; private readonly Bindable sampleBank; @@ -32,15 +31,12 @@ namespace osu.Game.Screens.Edit.Timing.RowAttributes Content.AddRange(new Drawable[] { - volumeText = new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Width = 30, - Font = OsuFont.GetFont(size: EditorTable.TEXT_SIZE, weight: FontWeight.Regular), - }, + sampleText = new AttributeText(Point), progress = new AttributeProgressBar(Point), - sampleText = new AttributeBubbledWord(Point), + volumeText = new AttributeText(Point) + { + Width = 40, + }, }); volume.BindValueChanged(vol => diff --git a/osu.Game/Screens/Edit/Timing/RowAttributes/TimingRowAttribute.cs b/osu.Game/Screens/Edit/Timing/RowAttributes/TimingRowAttribute.cs index c6e2649f8e..ab840e56a7 100644 --- a/osu.Game/Screens/Edit/Timing/RowAttributes/TimingRowAttribute.cs +++ b/osu.Game/Screens/Edit/Timing/RowAttributes/TimingRowAttribute.cs @@ -4,10 +4,8 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; -using osu.Framework.Graphics; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Timing; -using osu.Game.Graphics; using osu.Game.Graphics.Sprites; namespace osu.Game.Screens.Edit.Timing.RowAttributes @@ -28,12 +26,7 @@ namespace osu.Game.Screens.Edit.Timing.RowAttributes [BackgroundDependencyLoader] private void load() { - Content.Add(text = new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Font = OsuFont.GetFont(size: EditorTable.TEXT_SIZE, weight: FontWeight.Regular), - }); + Content.Add(text = new AttributeText(Point)); timeSignature.BindValueChanged(_ => updateText()); beatLength.BindValueChanged(_ => updateText(), true); From 097a3475337df9ae869bd576886b79c675b8f2c4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Apr 2021 18:25:30 +0900 Subject: [PATCH 1635/1791] Adjust Add different background colour for timing area --- osu.Game/Screens/Edit/EditorTable.cs | 4 +- .../Screens/Edit/Timing/ControlPointTable.cs | 53 +++++++++++++++---- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 11 +++- 3 files changed, 53 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorTable.cs b/osu.Game/Screens/Edit/EditorTable.cs index 5a34a79726..815f3ed0ea 100644 --- a/osu.Game/Screens/Edit/EditorTable.cs +++ b/osu.Game/Screens/Edit/EditorTable.cs @@ -96,8 +96,8 @@ namespace osu.Game.Screens.Edit [BackgroundDependencyLoader] private void load(OverlayColourProvider colours) { - hoveredBackground.Colour = colourHover = colours.Background3; - colourSelected = colours.Background1; + hoveredBackground.Colour = colourHover = colours.Background1; + colourSelected = colours.Colour3; } private bool selected; diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 1f06bc5bc7..26efe0a5ea 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.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.Collections.Generic; using System.Linq; using osu.Framework.Allocation; @@ -25,6 +26,8 @@ namespace osu.Game.Screens.Edit.Timing [Resolved] private EditorClock clock { get; set; } + public const float TIMING_COLUMN_WIDTH = 210; + public IEnumerable ControlGroups { set @@ -66,35 +69,62 @@ namespace osu.Game.Screens.Edit.Timing { var columns = new List { - new TableColumn("Time", Anchor.CentreLeft, new Dimension(GridSizeMode.Absolute, 120)), + new TableColumn("Time", Anchor.CentreLeft, new Dimension(GridSizeMode.Absolute, TIMING_COLUMN_WIDTH)), new TableColumn("Attributes", Anchor.CentreLeft), }; return columns.ToArray(); } - private Drawable[] createContent(int index, ControlPointGroup group) => new Drawable[] + private Drawable[] createContent(int index, ControlPointGroup group) { - new OsuSpriteText + return new Drawable[] { - Text = group.Time.ToEditorFormattedString(), - Font = OsuFont.GetFont(size: TEXT_SIZE, weight: FontWeight.Bold) - }, - new ControlGroupAttributes(group), - }; + new FillFlowContainer + { + RelativeSizeAxes = Axes.Y, + Width = TIMING_COLUMN_WIDTH, + Spacing = new Vector2(5), + Children = new Drawable[] + { + new OsuSpriteText + { + Text = group.Time.ToEditorFormattedString(), + Font = OsuFont.GetFont(size: TEXT_SIZE, weight: FontWeight.Bold), + Width = 60, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + new ControlGroupAttributes(group, c => c is TimingControlPoint) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + } + } + }, + new ControlGroupAttributes(group, c => !(c is TimingControlPoint)) + }; + } private class ControlGroupAttributes : CompositeDrawable { + private readonly Func matchFunction; + private readonly IBindableList controlPoints = new BindableList(); private readonly FillFlowContainer fill; - public ControlGroupAttributes(ControlPointGroup group) + public ControlGroupAttributes(ControlPointGroup group, Func matchFunction) { - RelativeSizeAxes = Axes.Both; + this.matchFunction = matchFunction; + + AutoSizeAxes = Axes.X; + RelativeSizeAxes = Axes.Y; + InternalChild = fill = new FillFlowContainer { - RelativeSizeAxes = Axes.Both, + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, Direction = FillDirection.Horizontal, Spacing = new Vector2(2) }; @@ -120,6 +150,7 @@ namespace osu.Game.Screens.Edit.Timing private void createChildren() { fill.ChildrenEnumerable = controlPoints + .Where(matchFunction) .Select(createAttribute) .Where(c => c != null) // arbitrary ordering to make timing points first. diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 0b446314e8..9f26dece08 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -71,13 +71,20 @@ namespace osu.Game.Screens.Edit.Timing { RelativeSizeAxes = Axes.Both; + const float margins = 10; InternalChildren = new Drawable[] { new Box { - Colour = colours.Background2, + Colour = colours.Background3, RelativeSizeAxes = Axes.Both, }, + new Box + { + Colour = colours.Background2, + RelativeSizeAxes = Axes.Y, + Width = ControlPointTable.TIMING_COLUMN_WIDTH + margins, + }, new OsuScrollContainer { RelativeSizeAxes = Axes.Both, @@ -89,7 +96,7 @@ namespace osu.Game.Screens.Edit.Timing Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, Direction = FillDirection.Horizontal, - Margin = new MarginPadding(10), + Margin = new MarginPadding(margins), Spacing = new Vector2(5), Children = new Drawable[] { From a40dcd4b8dd3a8403e208cc7de62dd66181386d5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Apr 2021 18:53:06 +0900 Subject: [PATCH 1636/1791] Add a touch more space in the timing column --- osu.Game/Screens/Edit/Timing/ControlPointTable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 26efe0a5ea..fe63138d28 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -26,7 +26,7 @@ namespace osu.Game.Screens.Edit.Timing [Resolved] private EditorClock clock { get; set; } - public const float TIMING_COLUMN_WIDTH = 210; + public const float TIMING_COLUMN_WIDTH = 220; public IEnumerable ControlGroups { From 5afdc3ff66f39eb34bc8359e715f6719da8c038a Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 19 Apr 2021 19:56:17 +0900 Subject: [PATCH 1637/1791] Make DHO application logic clearer with Entry/HitObject separation --- .../Objects/Drawables/DrawableHitObject.cs | 57 ++++++++++++++----- 1 file changed, 42 insertions(+), 15 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 669e4cecbe..dfeb87de88 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -204,23 +204,27 @@ namespace osu.Game.Rulesets.Objects.Drawables /// The controlling the lifetime of . public void Apply([NotNull] HitObject hitObject, [CanBeNull] HitObjectLifetimeEntry lifetimeEntry) { - free(); - - HitObject = hitObject ?? throw new InvalidOperationException($"Cannot apply a null {nameof(HitObject)}."); - - this.lifetimeEntry = lifetimeEntry; + if (hitObject == null) + throw new ArgumentNullException($"Cannot apply a null {nameof(HitObject)}."); if (lifetimeEntry != null) { - // Transfer lifetime from the entry. - LifetimeStart = lifetimeEntry.LifetimeStart; - LifetimeEnd = lifetimeEntry.LifetimeEnd; - - // Copy any existing result from the entry (required for rewind / judgement revert). - Result = lifetimeEntry.Result; + applyEntry(lifetimeEntry); } else - LifetimeStart = HitObject.StartTime - InitialLifetimeOffset; + { + applyHitObject(hitObject); + + // Set default lifetime for a non-pooled DHO + LifetimeStart = hitObject.StartTime - InitialLifetimeOffset; + } + } + + private void applyHitObject([NotNull] HitObject hitObject) + { + freeHitObject(); + + HitObject = hitObject; // Ensure this DHO has a result. Result ??= CreateResult(HitObject.CreateJudgement()) @@ -281,10 +285,23 @@ namespace osu.Game.Rulesets.Objects.Drawables hasHitObjectApplied = true; } + private void applyEntry([NotNull] HitObjectLifetimeEntry entry) + { + freeEntry(); + + setLifetime(entry.LifetimeStart, entry.LifetimeEnd); + lifetimeEntry = entry; + + // Copy any existing result from the entry (required for rewind / judgement revert). + Result = entry.Result; + + applyHitObject(entry.HitObject); + } + /// /// Removes the currently applied /// - private void free() + private void freeHitObject() { if (!hasHitObjectApplied) return; @@ -322,13 +339,23 @@ namespace osu.Game.Rulesets.Objects.Drawables HitObject = null; ParentHitObject = null; Result = null; - lifetimeEntry = null; clearExistingStateTransforms(); hasHitObjectApplied = false; } + private void freeEntry() + { + freeHitObject(); + + if (lifetimeEntry == null) return; + + lifetimeEntry = null; + + setLifetime(double.MaxValue, double.MaxValue); + } + protected sealed override void FreeAfterUse() { base.FreeAfterUse(); @@ -337,7 +364,7 @@ namespace osu.Game.Rulesets.Objects.Drawables if (!IsInPool) return; - free(); + freeEntry(); } /// From c7183f92f79efe7855fb4b4defacb9224659e3e3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 19 Apr 2021 19:53:55 +0900 Subject: [PATCH 1638/1791] Rename Restart() -> Reset() --- osu.Game/Screens/Play/GameplayClockContainer.cs | 4 ++-- osu.Game/Screens/Play/MasterGameplayClockContainer.cs | 4 ++-- osu.Game/Screens/Play/Player.cs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 642ede5f1c..ee65384bc9 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -82,9 +82,9 @@ namespace osu.Game.Screens.Play public virtual void Stop() => IsPaused.Value = true; /// - /// Restarts gameplay. + /// Resets this and the source to an initial state ready for gameplay. /// - public virtual void Restart() + public virtual void Reset() { AdjustableSource.Seek(0); AdjustableSource.Stop(); diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index db0aa23001..5ea50cbdc7 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -123,10 +123,10 @@ namespace osu.Game.Screens.Play userOffsetClock.ProcessFrame(); } - public override void Restart() + public override void Reset() { updateRate(); - base.Restart(); + base.Reset(); } /// diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 841f906b05..27a4fcc291 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -811,7 +811,7 @@ namespace osu.Game.Screens.Play if (GameplayClockContainer.GameplayClock.IsRunning) throw new InvalidOperationException($"{nameof(StartGameplay)} should not be called when the gameplay clock is already running"); - GameplayClockContainer.Restart(); + GameplayClockContainer.Reset(); } public override void OnSuspending(IScreen next) From acbf4580a412772b6be6d8e3d60446bbe35b9003 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 19 Apr 2021 19:55:59 +0900 Subject: [PATCH 1639/1791] Only set initial source in Reset() --- osu.Game/Screens/Play/GameplayClockContainer.cs | 17 +++++++++++++++-- .../Play/MasterGameplayClockContainer.cs | 4 ++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index ee65384bc9..3da28f3560 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -29,17 +29,22 @@ namespace osu.Game.Screens.Play /// protected readonly DecoupleableInterpolatingFramedClock AdjustableSource; + /// + /// The source clock. + /// + protected IClock SourceClock { get; private set; } + /// /// Creates a new . /// /// The source used for timing. protected GameplayClockContainer(IClock sourceClock) { + SourceClock = sourceClock; + RelativeSizeAxes = Axes.Both; AdjustableSource = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; - AdjustableSource.ChangeSource(sourceClock); - IsPaused.BindValueChanged(OnIsPausedChanged); } @@ -86,6 +91,8 @@ namespace osu.Game.Screens.Play /// public virtual void Reset() { + ChangeSource(SourceClock); + AdjustableSource.Seek(0); AdjustableSource.Stop(); @@ -93,6 +100,12 @@ namespace osu.Game.Screens.Play Start(); } + /// + /// Changes the source clock. + /// + /// The new source. + protected void ChangeSource(IClock sourceClock) => AdjustableSource.ChangeSource(SourceClock = sourceClock); + protected override void Update() { if (!IsPaused.Value) diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 5ea50cbdc7..f019e50b60 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -33,7 +33,7 @@ namespace osu.Game.Screens.Play /// public const double MINIMUM_SKIP_TIME = 1000; - protected Track Track => (Track)AdjustableSource.Source; + protected Track Track => (Track)SourceClock; public readonly BindableNumber UserPlaybackRate = new BindableDouble(1) { @@ -164,7 +164,7 @@ namespace osu.Game.Screens.Play public void StopUsingBeatmapClock() { removeSourceClockAdjustments(); - AdjustableSource.ChangeSource(new TrackVirtual(beatmap.Track.Length)); + ChangeSource(new TrackVirtual(beatmap.Track.Length)); } private bool speedAdjustmentsApplied; From 2c487ddb70bf4078422fba8e772e5e564e582d36 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 19 Apr 2021 20:37:06 +0900 Subject: [PATCH 1640/1791] Create synthetic LifetimeEntry for a DHO when not supplied Now, a DHO is always associated with a HitObjectLifetimeEntry while used. Result is always stored in the entry, and not in the DHO. --- .../Objects/Drawables/DrawableHitObject.cs | 87 ++++++++----------- .../Objects/UnmanagedHitObjectEntry.cs | 20 +++++ 2 files changed, 55 insertions(+), 52 deletions(-) create mode 100644 osu.Game/Rulesets/Objects/UnmanagedHitObjectEntry.cs diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index dfeb87de88..9b132ea932 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -39,7 +39,12 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// The currently represented by this . /// - public HitObject HitObject { get; private set; } + public HitObject HitObject => lifetimeEntry?.HitObject ?? initialHitObject; + + /// + /// The given in the constructor that will be applied when loaded. + /// + private HitObject initialHitObject; /// /// The parenting , if any. @@ -108,7 +113,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// The scoring result of this . /// - public JudgementResult Result { get; private set; } + public JudgementResult Result => lifetimeEntry?.Result; /// /// The relative X position of this hit object for sample playback balance adjustment. @@ -140,11 +145,6 @@ namespace osu.Game.Rulesets.Objects.Drawables /// public IBindable State => state; - /// - /// Whether is currently applied. - /// - private bool hasHitObjectApplied; - /// /// The controlling the lifetime of the currently-attached . /// @@ -168,7 +168,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// protected DrawableHitObject([CanBeNull] HitObject initialHitObject = null) { - HitObject = initialHitObject; + this.initialHitObject = initialHitObject; } [BackgroundDependencyLoader] @@ -184,8 +184,11 @@ namespace osu.Game.Rulesets.Objects.Drawables { base.LoadAsyncComplete(); - if (HitObject != null) - Apply(HitObject, lifetimeEntry); + if (initialHitObject != null) + { + Apply(initialHitObject, null); + initialHitObject = null; + } } protected override void LoadComplete() @@ -209,26 +212,36 @@ namespace osu.Game.Rulesets.Objects.Drawables if (lifetimeEntry != null) { - applyEntry(lifetimeEntry); + if (lifetimeEntry.HitObject != hitObject) + throw new InvalidOperationException($"{nameof(HitObjectLifetimeEntry)} has different {nameof(HitObject)} from the specified one."); + + apply(lifetimeEntry); } else { - applyHitObject(hitObject); + var unmanagedEntry = new UnmanagedHitObjectEntry(hitObject, this); + apply(unmanagedEntry); // Set default lifetime for a non-pooled DHO LifetimeStart = hitObject.StartTime - InitialLifetimeOffset; } } - private void applyHitObject([NotNull] HitObject hitObject) + /// + /// Applies a new to be represented by this . + /// + private void apply([NotNull] HitObjectLifetimeEntry entry) { - freeHitObject(); + free(); - HitObject = hitObject; + lifetimeEntry = entry; + + LifetimeStart = entry.LifetimeStart; + LifetimeEnd = entry.LifetimeEnd; // Ensure this DHO has a result. - Result ??= CreateResult(HitObject.CreateJudgement()) - ?? throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}."); + entry.Result ??= CreateResult(HitObject.CreateJudgement()) + ?? throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}."); // Copy back the result to the entry for potential future retrieval. if (lifetimeEntry != null) @@ -281,30 +294,14 @@ namespace osu.Game.Rulesets.Objects.Drawables else updateState(ArmedState.Idle, true); } - - hasHitObjectApplied = true; - } - - private void applyEntry([NotNull] HitObjectLifetimeEntry entry) - { - freeEntry(); - - setLifetime(entry.LifetimeStart, entry.LifetimeEnd); - lifetimeEntry = entry; - - // Copy any existing result from the entry (required for rewind / judgement revert). - Result = entry.Result; - - applyHitObject(entry.HitObject); } /// - /// Removes the currently applied + /// Removes the currently applied /// - private void freeHitObject() + private void free() { - if (!hasHitObjectApplied) - return; + if (lifetimeEntry == null) return; StartTimeBindable.UnbindFrom(HitObject.StartTimeBindable); if (HitObject is IHasComboInformation combo) @@ -336,24 +333,10 @@ namespace osu.Game.Rulesets.Objects.Drawables OnFree(); - HitObject = null; ParentHitObject = null; - Result = null; - - clearExistingStateTransforms(); - - hasHitObjectApplied = false; - } - - private void freeEntry() - { - freeHitObject(); - - if (lifetimeEntry == null) return; - lifetimeEntry = null; - setLifetime(double.MaxValue, double.MaxValue); + clearExistingStateTransforms(); } protected sealed override void FreeAfterUse() @@ -364,7 +347,7 @@ namespace osu.Game.Rulesets.Objects.Drawables if (!IsInPool) return; - freeEntry(); + free(); } /// diff --git a/osu.Game/Rulesets/Objects/UnmanagedHitObjectEntry.cs b/osu.Game/Rulesets/Objects/UnmanagedHitObjectEntry.cs new file mode 100644 index 0000000000..507cad15d3 --- /dev/null +++ b/osu.Game/Rulesets/Objects/UnmanagedHitObjectEntry.cs @@ -0,0 +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.Game.Rulesets.Objects.Drawables; + +namespace osu.Game.Rulesets.Objects +{ + internal class UnmanagedHitObjectEntry : HitObjectLifetimeEntry + { + public readonly DrawableHitObject DrawableHitObject; + + public UnmanagedHitObjectEntry(HitObject hitObject, DrawableHitObject drawableHitObject) + : base(hitObject) + { + DrawableHitObject = drawableHitObject; + LifetimeStart = DrawableHitObject.LifetimeStart; + LifetimeEnd = DrawableHitObject.LifetimeEnd; + } + } +} From 510e54ff5405a944344412fbd72b9c3418e4d28e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Apr 2021 23:29:14 +0900 Subject: [PATCH 1641/1791] Update framework --- osu.Android.props | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.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 0bb0bf171c..ed2b27e1c7 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs index bcbb0f4366..aee431284e 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Osu.Mods public void Update(Playfield playfield) { - playfield.Rotation = (Direction.Value == RotationDirection.CounterClockwise ? -1 : 1) * 360 * (float)(playfield.Time.Current / 60000 * SpinSpeed.Value); + playfield.Rotation = (Direction.Value == RotationDirection.Counterclockwise ? -1 : 1) * 360 * (float)(playfield.Time.Current / 60000 * SpinSpeed.Value); } public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index e0a267241d..509cb3ddad 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 bcd953c0bd..4b67bd78a1 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 0825fc57a9f50a86594b4090954366d47aa82725 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 19 Apr 2021 18:24:15 +0200 Subject: [PATCH 1642/1791] Move foreground colour helper into `OsuColour` --- osu.Game/Graphics/OsuColour.cs | 12 ++++++++++ .../Graphics/UserInterfaceV2/ColourDisplay.cs | 3 +-- .../Timeline/TimelineHitObjectBlueprint.cs | 3 +-- osu.Game/Utils/ColourUtils.cs | 23 ------------------- 4 files changed, 14 insertions(+), 27 deletions(-) delete mode 100644 osu.Game/Utils/ColourUtils.cs diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index c3b9b6006c..15967c37c2 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -94,6 +94,18 @@ namespace osu.Game.Graphics } } + /// + /// Returns a foreground text colour that is supposed to contrast well with + /// the supplied . + /// + public static Color4 ForegroundTextColourFor(Color4 backgroundColour) + { + // formula taken from the RGB->YIQ conversions: https://en.wikipedia.org/wiki/YIQ + // brightness here is equivalent to the Y component in the above colour model, which is a rough estimate of lightness. + float brightness = 0.299f * backgroundColour.R + 0.587f * backgroundColour.G + 0.114f * backgroundColour.B; + return Gray(brightness > 0.5f ? 0.2f : 0.9f); + } + // See https://github.com/ppy/osu-web/blob/master/resources/assets/less/colors.less public readonly Color4 PurpleLighter = Color4Extensions.FromHex(@"eeeeff"); public readonly Color4 PurpleLight = Color4Extensions.FromHex(@"aa88ff"); diff --git a/osu.Game/Graphics/UserInterfaceV2/ColourDisplay.cs b/osu.Game/Graphics/UserInterfaceV2/ColourDisplay.cs index c07e5d5bf7..01d91f7cfd 100644 --- a/osu.Game/Graphics/UserInterfaceV2/ColourDisplay.cs +++ b/osu.Game/Graphics/UserInterfaceV2/ColourDisplay.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Localisation; using osu.Game.Graphics.Sprites; -using osu.Game.Utils; using osuTK; using osuTK.Graphics; @@ -102,7 +101,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 { fill.Colour = current.Value; colourHexCode.Text = current.Value.ToHex(); - colourHexCode.Colour = ColourUtils.ForegroundTextColourFor(current.Value); + colourHexCode.Colour = OsuColour.ForegroundTextColourFor(current.Value); } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index dc67009d08..0425370ae5 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -21,7 +21,6 @@ using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Skinning; -using osu.Game.Utils; using osuTK; using osuTK.Graphics; @@ -159,7 +158,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline circle.Colour = comboColour; var col = circle.Colour.TopLeft.Linear; - colouredComponents.Colour = ColourUtils.ForegroundTextColourFor(col); + colouredComponents.Colour = OsuColour.ForegroundTextColourFor(col); } protected override void Update() diff --git a/osu.Game/Utils/ColourUtils.cs b/osu.Game/Utils/ColourUtils.cs deleted file mode 100644 index 33f6f5981f..0000000000 --- a/osu.Game/Utils/ColourUtils.cs +++ /dev/null @@ -1,23 +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.Game.Graphics; -using osuTK.Graphics; - -namespace osu.Game.Utils -{ - public static class ColourUtils - { - /// - /// Returns a foreground text colour that is supposed to contrast well on top of - /// the supplied . - /// - public static Color4 ForegroundTextColourFor(Color4 backgroundColour) - { - // formula taken from the RGB->YIQ conversions: https://en.wikipedia.org/wiki/YIQ - // brightness here is equivalent to the Y component in the above colour model, which is a rough estimate of lightness. - float brightness = 0.299f * backgroundColour.R + 0.587f * backgroundColour.G + 0.114f * backgroundColour.B; - return OsuColour.Gray(brightness > 0.5f ? 0.2f : 0.9f); - } - } -} From 8656176ab8e09b14737212846fdeed787a3f7bc4 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 20 Apr 2021 01:31:51 +0200 Subject: [PATCH 1643/1791] Add the playable beatmap as check argument This is different from the working beatmap's `.Beatmap` property in that it is mutated by the ruleset/editor. So hit objects, for example, are actually of type `Slider` and such instead of the legacy `ConvertSlider`. This should be preferred over `workingBeatmap.Beatmap`. --- .../Editor/Checks/CheckOffscreenObjectsTest.cs | 6 +++--- .../Edit/Checks/CheckOffscreenObjects.cs | 4 ++-- osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs | 5 ++++- .../Editing/Checks/CheckBackgroundQualityTest.cs | 12 ++++++------ .../Editing/Checks/CheckFilePresenceTest.cs | 12 ++++++------ osu.Game/Rulesets/Edit/BeatmapVerifier.cs | 5 ++++- .../Rulesets/Edit/Checks/CheckBackgroundQuality.cs | 6 +++--- osu.Game/Rulesets/Edit/Checks/CheckFilePresence.cs | 4 ++-- osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs | 5 +++-- osu.Game/Rulesets/Edit/IBeatmapVerifier.cs | 2 +- osu.Game/Screens/Edit/Verify/VerifyScreen.cs | 4 ++-- 11 files changed, 36 insertions(+), 29 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs index db347960ef..3a4817398c 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs @@ -224,12 +224,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks private void assertOk(IBeatmap beatmap) { - Assert.That(check.Run(new TestWorkingBeatmap(beatmap)), Is.Empty); + Assert.That(check.Run(beatmap, new TestWorkingBeatmap(beatmap)), Is.Empty); } private void assertOffscreenCircle(IBeatmap beatmap) { - var issues = check.Run(new TestWorkingBeatmap(beatmap)).ToList(); + var issues = check.Run(beatmap, new TestWorkingBeatmap(beatmap)).ToList(); Assert.That(issues, Has.Count.EqualTo(1)); Assert.That(issues.Single().Template is CheckOffscreenObjects.IssueTemplateOffscreenCircle); @@ -237,7 +237,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks private void assertOffscreenSlider(IBeatmap beatmap) { - var issues = check.Run(new TestWorkingBeatmap(beatmap)).ToList(); + var issues = check.Run(beatmap, new TestWorkingBeatmap(beatmap)).ToList(); Assert.That(issues, Has.Count.EqualTo(1)); Assert.That(issues.Single().Template is CheckOffscreenObjects.IssueTemplateOffscreenSlider); diff --git a/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs b/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs index c210f73b5f..4b0a7531a1 100644 --- a/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs +++ b/osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs @@ -31,9 +31,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Checks new IssueTemplateOffscreenSlider(this) }; - public IEnumerable Run(IWorkingBeatmap workingBeatmap) + public IEnumerable Run(IBeatmap playableBeatmap, IWorkingBeatmap workingBeatmap) { - foreach (var hitobject in workingBeatmap.Beatmap.HitObjects) + foreach (var hitobject in playableBeatmap.HitObjects) { switch (hitobject) { diff --git a/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs b/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs index 9b9383d547..dab6483179 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs @@ -17,6 +17,9 @@ namespace osu.Game.Rulesets.Osu.Edit new CheckOffscreenObjects() }; - public IEnumerable Run(WorkingBeatmap workingBeatmap) => checks.SelectMany(check => check.Run(workingBeatmap)); + public IEnumerable Run(IBeatmap playableBeatmap, WorkingBeatmap workingBeatmap) + { + return checks.SelectMany(check => check.Run(playableBeatmap, workingBeatmap)); + } } } diff --git a/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs b/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs index 8dad5bbf34..42d143cc04 100644 --- a/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs @@ -55,7 +55,7 @@ namespace osu.Game.Tests.Editing.Checks beatmap.Metadata.BackgroundFile = null; var mock = getMockWorkingBeatmap(null, System.Array.Empty()); - Assert.That(check.Run(mock.Object), Is.Empty); + Assert.That(check.Run(beatmap, mock.Object), Is.Empty); } [Test] @@ -63,7 +63,7 @@ namespace osu.Game.Tests.Editing.Checks { var mock = getMockWorkingBeatmap(new Texture(1920, 1080)); - Assert.That(check.Run(mock.Object), Is.Empty); + Assert.That(check.Run(beatmap, mock.Object), Is.Empty); } [Test] @@ -71,7 +71,7 @@ namespace osu.Game.Tests.Editing.Checks { var mock = getMockWorkingBeatmap(new Texture(3840, 2160)); - var issues = check.Run(mock.Object).ToList(); + var issues = check.Run(beatmap, mock.Object).ToList(); Assert.That(issues, Has.Count.EqualTo(1)); Assert.That(issues.Single().Template is CheckBackgroundQuality.IssueTemplateTooHighResolution); @@ -82,7 +82,7 @@ namespace osu.Game.Tests.Editing.Checks { var mock = getMockWorkingBeatmap(new Texture(640, 480)); - var issues = check.Run(mock.Object).ToList(); + var issues = check.Run(beatmap, mock.Object).ToList(); Assert.That(issues, Has.Count.EqualTo(1)); Assert.That(issues.Single().Template is CheckBackgroundQuality.IssueTemplateLowResolution); @@ -93,7 +93,7 @@ namespace osu.Game.Tests.Editing.Checks { var mock = getMockWorkingBeatmap(new Texture(100, 100)); - var issues = check.Run(mock.Object).ToList(); + var issues = check.Run(beatmap, mock.Object).ToList(); Assert.That(issues, Has.Count.EqualTo(1)); Assert.That(issues.Single().Template is CheckBackgroundQuality.IssueTemplateTooLowResolution); @@ -104,7 +104,7 @@ namespace osu.Game.Tests.Editing.Checks { var mock = getMockWorkingBeatmap(new Texture(1920, 1080), new byte[1024 * 1024 * 3]); - var issues = check.Run(mock.Object).ToList(); + var issues = check.Run(beatmap, mock.Object).ToList(); Assert.That(issues, Has.Count.EqualTo(1)); Assert.That(issues.Single().Template is CheckBackgroundQuality.IssueTemplateTooUncompressed); diff --git a/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs b/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs index 1149115b55..fe2e16ffd8 100644 --- a/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs @@ -15,13 +15,13 @@ namespace osu.Game.Tests.Editing.Checks public class CheckFilePresenceTest { private CheckBackgroundPresence check; - private WorkingBeatmap beatmap; + private IBeatmap beatmap; [SetUp] public void Setup() { check = new CheckBackgroundPresence(); - beatmap = new TestWorkingBeatmap(new Beatmap + beatmap = new Beatmap { BeatmapInfo = new BeatmapInfo { @@ -34,13 +34,13 @@ namespace osu.Game.Tests.Editing.Checks }) } } - }); + }; } [Test] public void TestBackgroundSetAndInFiles() { - Assert.That(check.Run(beatmap), Is.Empty); + Assert.That(check.Run(beatmap, new TestWorkingBeatmap(beatmap)), Is.Empty); } [Test] @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Editing.Checks { beatmap.BeatmapInfo.BeatmapSet.Files.Clear(); - var issues = check.Run(beatmap).ToList(); + var issues = check.Run(beatmap, new TestWorkingBeatmap(beatmap)).ToList(); Assert.That(issues, Has.Count.EqualTo(1)); Assert.That(issues.Single().Template is CheckFilePresence.IssueTemplateDoesNotExist); @@ -59,7 +59,7 @@ namespace osu.Game.Tests.Editing.Checks { beatmap.Metadata.BackgroundFile = null; - var issues = check.Run(beatmap).ToList(); + var issues = check.Run(beatmap, new TestWorkingBeatmap(beatmap)).ToList(); Assert.That(issues, Has.Count.EqualTo(1)); Assert.That(issues.Single().Template is CheckFilePresence.IssueTemplateNoneSet); diff --git a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs index 90447049f8..9efaa94b40 100644 --- a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs +++ b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs @@ -23,6 +23,9 @@ namespace osu.Game.Rulesets.Edit new CheckAudioPresence(), }; - public IEnumerable Run(WorkingBeatmap workingBeatmap) => checks.SelectMany(check => check.Run(workingBeatmap)); + public IEnumerable Run(IBeatmap playableBeatmap, WorkingBeatmap workingBeatmap) + { + return checks.SelectMany(check => check.Run(playableBeatmap, workingBeatmap)); + } } } diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs b/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs index 1494ae5da4..767c2b94b5 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs @@ -30,9 +30,9 @@ namespace osu.Game.Rulesets.Edit.Checks new IssueTemplateTooUncompressed(this) }; - public IEnumerable Run(IWorkingBeatmap workingBeatmap) + public IEnumerable Run(IBeatmap playableBeatmap, IWorkingBeatmap workingBeatmap) { - var backgroundFile = workingBeatmap.Beatmap.Metadata?.BackgroundFile; + var backgroundFile = playableBeatmap.Metadata?.BackgroundFile; if (backgroundFile == null) yield break; @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Edit.Checks else if (texture.Width < low_width || texture.Height < low_height) yield return new IssueTemplateLowResolution(this).Create(texture.Width, texture.Height); - string storagePath = workingBeatmap.Beatmap.BeatmapInfo.BeatmapSet.GetPathForFile(backgroundFile); + string storagePath = playableBeatmap.BeatmapInfo.BeatmapSet.GetPathForFile(backgroundFile); double filesizeMb = workingBeatmap.GetStream(storagePath).Length / (1024d * 1024d); if (filesizeMb > max_filesize_mb) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckFilePresence.cs b/osu.Game/Rulesets/Edit/Checks/CheckFilePresence.cs index 37fa79568f..340b053fdb 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckFilePresence.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckFilePresence.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Edit.Checks new IssueTemplateDoesNotExist(this) }; - public IEnumerable Run(IWorkingBeatmap workingBeatmap) + public IEnumerable Run(IBeatmap playableBeatmap, IWorkingBeatmap workingBeatmap) { var filename = GetFilename(workingBeatmap); @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Edit.Checks } // If the file is set, also make sure it still exists. - var storagePath = workingBeatmap.Beatmap.BeatmapInfo.BeatmapSet.GetPathForFile(filename); + var storagePath = playableBeatmap.BeatmapInfo.BeatmapSet.GetPathForFile(filename); if (storagePath != null) yield break; diff --git a/osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs b/osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs index c3a64b58e9..31a7583941 100644 --- a/osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs +++ b/osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs @@ -24,7 +24,8 @@ namespace osu.Game.Rulesets.Edit.Checks.Components /// /// Runs this check and returns any issues detected for the provided beatmap. /// - /// The beatmap to run the check on. - public IEnumerable Run(IWorkingBeatmap workingBeatmap); + /// The playable beatmap of the beatmap to run the check on. + /// The working beatmap of the beatmap to run the check on. + public IEnumerable Run(IBeatmap playableBeatmap, IWorkingBeatmap workingBeatmap); } } diff --git a/osu.Game/Rulesets/Edit/IBeatmapVerifier.cs b/osu.Game/Rulesets/Edit/IBeatmapVerifier.cs index 12be8815e1..b598176a35 100644 --- a/osu.Game/Rulesets/Edit/IBeatmapVerifier.cs +++ b/osu.Game/Rulesets/Edit/IBeatmapVerifier.cs @@ -12,6 +12,6 @@ namespace osu.Game.Rulesets.Edit /// public interface IBeatmapVerifier { - public IEnumerable Run(WorkingBeatmap workingBeatmap); + public IEnumerable Run(IBeatmap playableBeatmap, WorkingBeatmap workingBeatmap); } } diff --git a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs index 6aa479b38d..393c89fe52 100644 --- a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs +++ b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs @@ -124,10 +124,10 @@ namespace osu.Game.Screens.Edit.Verify private void refresh() { var workingBeatmap = beatmapManager.GetWorkingBeatmap(beatmap.BeatmapInfo); - var issues = generalVerifier.Run(workingBeatmap); + var issues = generalVerifier.Run(beatmap.PlayableBeatmap, workingBeatmap); if (rulesetVerifier != null) - issues = issues.Concat(rulesetVerifier.Run(workingBeatmap)); + issues = issues.Concat(rulesetVerifier.Run(beatmap.PlayableBeatmap, workingBeatmap)); table.Issues = issues .OrderBy(issue => issue.Template.Type) From be6a02a17e3f5621d7f6942678d278748cd88cec Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 20 Apr 2021 01:32:22 +0200 Subject: [PATCH 1644/1791] Simplify background quality test names --- .../Editing/Checks/CheckBackgroundQualityTest.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs b/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs index 42d143cc04..b6970a99e4 100644 --- a/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs @@ -49,7 +49,7 @@ namespace osu.Game.Tests.Editing.Checks } [Test] - public void TestBackgroundMissing() + public void TestMissing() { // While this is a problem, it is out of scope for this check and is caught by a different one. beatmap.Metadata.BackgroundFile = null; @@ -59,7 +59,7 @@ namespace osu.Game.Tests.Editing.Checks } [Test] - public void TestBackgroundAcceptable() + public void TestAcceptable() { var mock = getMockWorkingBeatmap(new Texture(1920, 1080)); @@ -67,7 +67,7 @@ namespace osu.Game.Tests.Editing.Checks } [Test] - public void TestBackgroundTooHighResolution() + public void TestTooHighResolution() { var mock = getMockWorkingBeatmap(new Texture(3840, 2160)); @@ -78,7 +78,7 @@ namespace osu.Game.Tests.Editing.Checks } [Test] - public void TestBackgroundLowResolution() + public void TestLowResolution() { var mock = getMockWorkingBeatmap(new Texture(640, 480)); @@ -89,7 +89,7 @@ namespace osu.Game.Tests.Editing.Checks } [Test] - public void TestBackgroundTooLowResolution() + public void TestTooLowResolution() { var mock = getMockWorkingBeatmap(new Texture(100, 100)); @@ -100,7 +100,7 @@ namespace osu.Game.Tests.Editing.Checks } [Test] - public void TestBackgroundTooUncompressed() + public void TestTooUncompressed() { var mock = getMockWorkingBeatmap(new Texture(1920, 1080), new byte[1024 * 1024 * 3]); From 14c626ffcbff01f250a7008ebb8469181f5d6f95 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 20 Apr 2021 01:33:19 +0200 Subject: [PATCH 1645/1791] Use the playable beatmap for file presence checks --- osu.Game/Rulesets/Edit/Checks/CheckAudioPresence.cs | 2 +- osu.Game/Rulesets/Edit/Checks/CheckBackgroundPresence.cs | 2 +- osu.Game/Rulesets/Edit/Checks/CheckFilePresence.cs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckAudioPresence.cs b/osu.Game/Rulesets/Edit/Checks/CheckAudioPresence.cs index 55e53ef519..2d572a521e 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckAudioPresence.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckAudioPresence.cs @@ -10,6 +10,6 @@ namespace osu.Game.Rulesets.Edit.Checks { protected override CheckCategory Category => CheckCategory.Audio; protected override string TypeOfFile => "audio"; - protected override string GetFilename(IWorkingBeatmap workingBeatmap) => workingBeatmap.Beatmap.Metadata?.AudioFile; + protected override string GetFilename(IBeatmap playableBeatmap) => playableBeatmap.Metadata?.AudioFile; } } diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBackgroundPresence.cs b/osu.Game/Rulesets/Edit/Checks/CheckBackgroundPresence.cs index 3a229b889b..233c708a25 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckBackgroundPresence.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckBackgroundPresence.cs @@ -10,6 +10,6 @@ namespace osu.Game.Rulesets.Edit.Checks { protected override CheckCategory Category => CheckCategory.Resources; protected override string TypeOfFile => "background"; - protected override string GetFilename(IWorkingBeatmap workingBeatmap) => workingBeatmap.Beatmap.Metadata?.BackgroundFile; + protected override string GetFilename(IBeatmap playableBeatmap) => playableBeatmap.Metadata?.BackgroundFile; } } diff --git a/osu.Game/Rulesets/Edit/Checks/CheckFilePresence.cs b/osu.Game/Rulesets/Edit/Checks/CheckFilePresence.cs index 340b053fdb..006fc57c04 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckFilePresence.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckFilePresence.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Edit.Checks { protected abstract CheckCategory Category { get; } protected abstract string TypeOfFile { get; } - protected abstract string GetFilename(IWorkingBeatmap workingBeatmap); + protected abstract string GetFilename(IBeatmap playableBeatmap); public CheckMetadata Metadata => new CheckMetadata(Category, $"Missing {TypeOfFile}"); @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Edit.Checks public IEnumerable Run(IBeatmap playableBeatmap, IWorkingBeatmap workingBeatmap) { - var filename = GetFilename(workingBeatmap); + var filename = GetFilename(playableBeatmap); if (filename == null) { From 40ae856dfc4cb5006f43f10e3b1363e8434cb5ea Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 20 Apr 2021 01:34:05 +0200 Subject: [PATCH 1646/1791] Show 2 decimals for background filesize --- osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs b/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs index 767c2b94b5..59fee74023 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckBackgroundQuality.cs @@ -88,7 +88,7 @@ namespace osu.Game.Rulesets.Edit.Checks public class IssueTemplateTooUncompressed : IssueTemplate { public IssueTemplateTooUncompressed(ICheck check) - : base(check, IssueType.Problem, "The background filesize ({0:0.#} MB) exceeds {1} MB.") + : base(check, IssueType.Problem, "The background filesize ({0:0.##} MB) exceeds {1} MB.") { } From f168247254424bda53e90680bfc45e83b69e7f22 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 20 Apr 2021 01:35:41 +0200 Subject: [PATCH 1647/1791] Add `Track` as a property to `IWorkingBeatmap` This is implemented by `WorkingBeatmap` already, and is much better to use than loading the track every time we need it. --- osu.Game/Beatmaps/IWorkingBeatmap.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Beatmaps/IWorkingBeatmap.cs b/osu.Game/Beatmaps/IWorkingBeatmap.cs index f1bf6f48ef..b1cf6db6fc 100644 --- a/osu.Game/Beatmaps/IWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/IWorkingBeatmap.cs @@ -42,6 +42,11 @@ namespace osu.Game.Beatmaps /// ISkin Skin { get; } + /// + /// Get the loaded audio track instance. + /// + Track Track { get; } + /// /// Constructs a playable from using the applicable converters for a specific . /// From c633f155653ef109898471b489e2ef466f1a10fc Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 20 Apr 2021 01:36:03 +0200 Subject: [PATCH 1648/1791] Add audio quality check --- osu.Game/Rulesets/Edit/BeatmapVerifier.cs | 2 + .../Rulesets/Edit/Checks/CheckAudioQuality.cs | 75 +++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 osu.Game/Rulesets/Edit/Checks/CheckAudioQuality.cs diff --git a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs index 9efaa94b40..f33feac971 100644 --- a/osu.Game/Rulesets/Edit/BeatmapVerifier.cs +++ b/osu.Game/Rulesets/Edit/BeatmapVerifier.cs @@ -19,8 +19,10 @@ namespace osu.Game.Rulesets.Edit // Resources new CheckBackgroundPresence(), new CheckBackgroundQuality(), + // Audio new CheckAudioPresence(), + new CheckAudioQuality() }; public IEnumerable Run(IBeatmap playableBeatmap, WorkingBeatmap workingBeatmap) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckAudioQuality.cs b/osu.Game/Rulesets/Edit/Checks/CheckAudioQuality.cs new file mode 100644 index 0000000000..b67374c023 --- /dev/null +++ b/osu.Game/Rulesets/Edit/Checks/CheckAudioQuality.cs @@ -0,0 +1,75 @@ +// 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.Rulesets.Edit.Checks.Components; + +namespace osu.Game.Rulesets.Edit.Checks +{ + public class CheckAudioQuality : ICheck + { + // This is a requirement as stated in the Ranking Criteria. + // See https://osu.ppy.sh/wiki/en/Ranking_Criteria#rules.4 + private const int max_bitrate = 192; + + // "A song's audio file /.../ must be of reasonable quality. Try to find the highest quality source file available" + // There not existing a version with a bitrate of 128 kbps or higher is extremely rare. + private const int min_bitrate = 128; + + public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Resources, "Too high or low audio bitrate"); + + public IEnumerable PossibleTemplates => new IssueTemplate[] + { + new IssueTemplateTooHighBitrate(this), + new IssueTemplateTooLowBitrate(this), + new IssueTemplateNoBitrate(this) + }; + + public IEnumerable Run(IBeatmap playableBeatmap, IWorkingBeatmap workingBeatmap) + { + var audioFile = playableBeatmap.Metadata?.AudioFile; + if (audioFile == null) + yield break; + + var track = workingBeatmap.Track; + + if (track?.Bitrate == null || track.Bitrate.Value == 0) + yield return new IssueTemplateNoBitrate(this).Create(); + else if (track.Bitrate.Value > max_bitrate) + yield return new IssueTemplateTooHighBitrate(this).Create(track.Bitrate.Value); + else if (track.Bitrate.Value < min_bitrate) + yield return new IssueTemplateTooLowBitrate(this).Create(track.Bitrate.Value); + } + + public class IssueTemplateTooHighBitrate : IssueTemplate + { + public IssueTemplateTooHighBitrate(ICheck check) + : base(check, IssueType.Problem, "The audio bitrate ({0} kbps) exceeds {1} kbps.") + { + } + + public Issue Create(int bitrate) => new Issue(this, bitrate, max_bitrate); + } + + public class IssueTemplateTooLowBitrate : IssueTemplate + { + public IssueTemplateTooLowBitrate(ICheck check) + : base(check, IssueType.Problem, "The audio bitrate ({0} kbps) is lower than {1} kbps.") + { + } + + public Issue Create(int bitrate) => new Issue(this, bitrate, min_bitrate); + } + + public class IssueTemplateNoBitrate : IssueTemplate + { + public IssueTemplateNoBitrate(ICheck check) + : base(check, IssueType.Error, "The audio bitrate could not be retrieved.") + { + } + + public Issue Create() => new Issue(this); + } + } +} From 2bb079ea1403479e1bf513eb63fdc3f1d98bceb3 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 20 Apr 2021 01:36:15 +0200 Subject: [PATCH 1649/1791] Add audio quality check tests --- .../Editing/Checks/CheckAudioQualityTest.cs | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 osu.Game.Tests/Editing/Checks/CheckAudioQualityTest.cs diff --git a/osu.Game.Tests/Editing/Checks/CheckAudioQualityTest.cs b/osu.Game.Tests/Editing/Checks/CheckAudioQualityTest.cs new file mode 100644 index 0000000000..e73d9adfb0 --- /dev/null +++ b/osu.Game.Tests/Editing/Checks/CheckAudioQualityTest.cs @@ -0,0 +1,114 @@ +// 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 Moq; +using NUnit.Framework; +using osu.Framework.Audio.Track; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit.Checks; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Tests.Editing.Checks +{ + [TestFixture] + public class CheckAudioQualityTest + { + private CheckAudioQuality check; + private IBeatmap beatmap; + + [SetUp] + public void Setup() + { + check = new CheckAudioQuality(); + beatmap = new Beatmap + { + BeatmapInfo = new BeatmapInfo + { + Metadata = new BeatmapMetadata { AudioFile = "abc123.jpg" } + } + }; + } + + [Test] + public void TestMissing() + { + // While this is a problem, it is out of scope for this check and is caught by a different one. + beatmap.Metadata.AudioFile = null; + + var mock = new Mock(); + mock.SetupGet(_ => _.Beatmap).Returns(beatmap); + mock.SetupGet(_ => _.Track).Returns((Track)null); + + Assert.That(check.Run(beatmap, mock.Object), Is.Empty); + } + + [Test] + public void TestAcceptable() + { + var mock = getMockWorkingBeatmap(192); + + Assert.That(check.Run(beatmap, mock.Object), Is.Empty); + } + + [Test] + public void TestNullBitrate() + { + var mock = getMockWorkingBeatmap(null); + + var issues = check.Run(beatmap, mock.Object).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckAudioQuality.IssueTemplateNoBitrate); + } + + [Test] + public void TestZeroBitrate() + { + var mock = getMockWorkingBeatmap(0); + + var issues = check.Run(beatmap, mock.Object).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckAudioQuality.IssueTemplateNoBitrate); + } + + [Test] + public void TestTooHighBitrate() + { + var mock = getMockWorkingBeatmap(320); + + var issues = check.Run(beatmap, mock.Object).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckAudioQuality.IssueTemplateTooHighBitrate); + } + + [Test] + public void TestTooLowBitrate() + { + var mock = getMockWorkingBeatmap(64); + + var issues = check.Run(beatmap, mock.Object).ToList(); + + Assert.That(issues, Has.Count.EqualTo(1)); + Assert.That(issues.Single().Template is CheckAudioQuality.IssueTemplateTooLowBitrate); + } + + /// + /// Returns the mock of the working beatmap with the given audio properties. + /// + /// The bitrate of the audio file the beatmap uses. + private Mock getMockWorkingBeatmap(int? audioBitrate) + { + var mockTrack = new Mock(); + mockTrack.SetupGet(_ => _.Bitrate).Returns(audioBitrate); + + var mockWorkingBeatmap = new Mock(); + mockWorkingBeatmap.SetupGet(_ => _.Beatmap).Returns(beatmap); + mockWorkingBeatmap.SetupGet(_ => _.Track).Returns(mockTrack.Object); + + return mockWorkingBeatmap; + } + } +} From c1b4aaaa03334995f4a96f223b3ce446a8ffb0dc Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 20 Apr 2021 08:38:02 +0900 Subject: [PATCH 1650/1791] Add doc comment --- osu.Game/Rulesets/Objects/UnmanagedHitObjectEntry.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Rulesets/Objects/UnmanagedHitObjectEntry.cs b/osu.Game/Rulesets/Objects/UnmanagedHitObjectEntry.cs index 507cad15d3..86fa59f589 100644 --- a/osu.Game/Rulesets/Objects/UnmanagedHitObjectEntry.cs +++ b/osu.Game/Rulesets/Objects/UnmanagedHitObjectEntry.cs @@ -5,6 +5,10 @@ using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.Objects { + /// + /// Created for a when only is given + /// to make sure a is always associated with a . + /// internal class UnmanagedHitObjectEntry : HitObjectLifetimeEntry { public readonly DrawableHitObject DrawableHitObject; From 4510e795e1326257168b1174a551ecf281a45563 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 20 Apr 2021 02:13:26 +0200 Subject: [PATCH 1651/1791] Fix category of audio quality check --- osu.Game/Rulesets/Edit/Checks/CheckAudioQuality.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/Checks/CheckAudioQuality.cs b/osu.Game/Rulesets/Edit/Checks/CheckAudioQuality.cs index b67374c023..c1074d7c74 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckAudioQuality.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckAudioQuality.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Edit.Checks // There not existing a version with a bitrate of 128 kbps or higher is extremely rare. private const int min_bitrate = 128; - public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Resources, "Too high or low audio bitrate"); + public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Audio, "Too high or low audio bitrate"); public IEnumerable PossibleTemplates => new IssueTemplate[] { From 1bc63a4c611a99c9652743a9ba94eb6acc288e0a Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 20 Apr 2021 09:17:13 +0900 Subject: [PATCH 1652/1791] Now, DHO.lifetimeEntry can be non-null even it is not fully applied --- .../Objects/Drawables/DrawableHitObject.cs | 46 ++++++++----------- .../Objects/UnmanagedHitObjectEntry.cs | 7 +-- 2 files changed, 20 insertions(+), 33 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 9b132ea932..16f5ed9d17 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -39,12 +39,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// The currently represented by this . /// - public HitObject HitObject => lifetimeEntry?.HitObject ?? initialHitObject; - - /// - /// The given in the constructor that will be applied when loaded. - /// - private HitObject initialHitObject; + public HitObject HitObject => lifetimeEntry?.HitObject; /// /// The parenting , if any. @@ -145,9 +140,15 @@ namespace osu.Game.Rulesets.Objects.Drawables /// public IBindable State => state; + /// + /// Whether a is currently applied. + /// + private bool hasEntryApplied; + /// /// The controlling the lifetime of the currently-attached . /// + /// Even if it is not null, it may not be fully applied until loaded ( is false). [CanBeNull] private HitObjectLifetimeEntry lifetimeEntry; @@ -168,7 +169,8 @@ namespace osu.Game.Rulesets.Objects.Drawables /// protected DrawableHitObject([CanBeNull] HitObject initialHitObject = null) { - this.initialHitObject = initialHitObject; + if (initialHitObject != null) + lifetimeEntry = new UnmanagedHitObjectEntry(initialHitObject); } [BackgroundDependencyLoader] @@ -184,11 +186,8 @@ namespace osu.Game.Rulesets.Objects.Drawables { base.LoadAsyncComplete(); - if (initialHitObject != null) - { - Apply(initialHitObject, null); - initialHitObject = null; - } + if (lifetimeEntry != null && !hasEntryApplied) + apply(lifetimeEntry); } protected override void LoadComplete() @@ -210,21 +209,10 @@ namespace osu.Game.Rulesets.Objects.Drawables if (hitObject == null) throw new ArgumentNullException($"Cannot apply a null {nameof(HitObject)}."); - if (lifetimeEntry != null) - { - if (lifetimeEntry.HitObject != hitObject) - throw new InvalidOperationException($"{nameof(HitObjectLifetimeEntry)} has different {nameof(HitObject)} from the specified one."); + if (lifetimeEntry != null && lifetimeEntry.HitObject != hitObject) + throw new InvalidOperationException($"{nameof(HitObjectLifetimeEntry)} has different {nameof(HitObject)} from the specified one."); - apply(lifetimeEntry); - } - else - { - var unmanagedEntry = new UnmanagedHitObjectEntry(hitObject, this); - apply(unmanagedEntry); - - // Set default lifetime for a non-pooled DHO - LifetimeStart = hitObject.StartTime - InitialLifetimeOffset; - } + apply(lifetimeEntry ?? new UnmanagedHitObjectEntry(hitObject)); } /// @@ -294,6 +282,8 @@ namespace osu.Game.Rulesets.Objects.Drawables else updateState(ArmedState.Idle, true); } + + hasEntryApplied = true; } /// @@ -301,7 +291,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// private void free() { - if (lifetimeEntry == null) return; + if (!hasEntryApplied) return; StartTimeBindable.UnbindFrom(HitObject.StartTimeBindable); if (HitObject is IHasComboInformation combo) @@ -337,6 +327,8 @@ namespace osu.Game.Rulesets.Objects.Drawables lifetimeEntry = null; clearExistingStateTransforms(); + + hasEntryApplied = false; } protected sealed override void FreeAfterUse() diff --git a/osu.Game/Rulesets/Objects/UnmanagedHitObjectEntry.cs b/osu.Game/Rulesets/Objects/UnmanagedHitObjectEntry.cs index 86fa59f589..3cfa3c4d3a 100644 --- a/osu.Game/Rulesets/Objects/UnmanagedHitObjectEntry.cs +++ b/osu.Game/Rulesets/Objects/UnmanagedHitObjectEntry.cs @@ -11,14 +11,9 @@ namespace osu.Game.Rulesets.Objects /// internal class UnmanagedHitObjectEntry : HitObjectLifetimeEntry { - public readonly DrawableHitObject DrawableHitObject; - - public UnmanagedHitObjectEntry(HitObject hitObject, DrawableHitObject drawableHitObject) + public UnmanagedHitObjectEntry(HitObject hitObject) : base(hitObject) { - DrawableHitObject = drawableHitObject; - LifetimeStart = DrawableHitObject.LifetimeStart; - LifetimeEnd = DrawableHitObject.LifetimeEnd; } } } From 67e4fe42847fab7f6cc47b422257971b13979e06 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 20 Apr 2021 02:28:38 +0200 Subject: [PATCH 1653/1791] Add xmldoc to `GetStream` --- osu.Game/Beatmaps/IWorkingBeatmap.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Beatmaps/IWorkingBeatmap.cs b/osu.Game/Beatmaps/IWorkingBeatmap.cs index b1cf6db6fc..053c6d373f 100644 --- a/osu.Game/Beatmaps/IWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/IWorkingBeatmap.cs @@ -74,6 +74,10 @@ namespace osu.Game.Beatmaps /// A fresh track instance, which will also be available via . Track LoadTrack(); + /// + /// Returns the stream of the file from the given storage path. + /// + /// The storage path to the file. Stream GetStream(string storagePath); } } From 1478bcfa8e774dcbb3e1b785b94bae10c2d8acbd Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 20 Apr 2021 02:30:27 +0200 Subject: [PATCH 1654/1791] Improve xmldoc consistency --- osu.Game/Beatmaps/IWorkingBeatmap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/IWorkingBeatmap.cs b/osu.Game/Beatmaps/IWorkingBeatmap.cs index 053c6d373f..a916b37b85 100644 --- a/osu.Game/Beatmaps/IWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/IWorkingBeatmap.cs @@ -43,7 +43,7 @@ namespace osu.Game.Beatmaps ISkin Skin { get; } /// - /// Get the loaded audio track instance. + /// Retrieves the which this has loaded. /// Track Track { get; } From 496df411a7e20485adf374233f5f4d93a292d6b7 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 20 Apr 2021 02:39:11 +0200 Subject: [PATCH 1655/1791] Remove now unused import --- osu.Game/Screens/Edit/Verify/VerifyScreen.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs index 2ab529cb27..530f24300b 100644 --- a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs +++ b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; -using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; From 8a8b9084efa04747e8147d7602fc277359c18b43 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 20 Apr 2021 10:11:36 +0900 Subject: [PATCH 1656/1791] Make single-argument overloead of DHO.Apply public --- .../Objects/Drawables/DrawableHitObject.cs | 46 ++++++++++++------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 16f5ed9d17..59088cffda 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; +using System.Diagnostics; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; @@ -165,7 +166,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// /// The to be initially applied to this . - /// If null, a hitobject is expected to be later applied via (or automatically via pooling). + /// If null, a hitobject is expected to be later applied via (or automatically via pooling). /// protected DrawableHitObject([CanBeNull] HitObject initialHitObject = null) { @@ -187,7 +188,7 @@ namespace osu.Game.Rulesets.Objects.Drawables base.LoadAsyncComplete(); if (lifetimeEntry != null && !hasEntryApplied) - apply(lifetimeEntry); + Apply(lifetimeEntry); } protected override void LoadComplete() @@ -200,36 +201,47 @@ namespace osu.Game.Rulesets.Objects.Drawables } /// - /// Applies a new to be represented by this . + /// Applies a hit object to be represented by this . /// - /// The to apply. - /// The controlling the lifetime of . + /// This overload is semi-deprecated. Use either or . public void Apply([NotNull] HitObject hitObject, [CanBeNull] HitObjectLifetimeEntry lifetimeEntry) + { + if (lifetimeEntry != null && lifetimeEntry.HitObject != hitObject) + throw new InvalidOperationException($"{nameof(HitObjectLifetimeEntry)} has different {nameof(HitObject)} from the specified one."); + + if (lifetimeEntry != null) + Apply(lifetimeEntry); + else + Apply(hitObject); + } + + /// + /// Applies a new to be represented by this . + /// A new is automatically created and applied to this . + /// + public void Apply([NotNull] HitObject hitObject) { if (hitObject == null) throw new ArgumentNullException($"Cannot apply a null {nameof(HitObject)}."); - if (lifetimeEntry != null && lifetimeEntry.HitObject != hitObject) - throw new InvalidOperationException($"{nameof(HitObjectLifetimeEntry)} has different {nameof(HitObject)} from the specified one."); - - apply(lifetimeEntry ?? new UnmanagedHitObjectEntry(hitObject)); + Apply(new UnmanagedHitObjectEntry(hitObject)); } /// /// Applies a new to be represented by this . /// - private void apply([NotNull] HitObjectLifetimeEntry entry) + public void Apply([NotNull] HitObjectLifetimeEntry newEntry) { free(); - lifetimeEntry = entry; + lifetimeEntry = newEntry; - LifetimeStart = entry.LifetimeStart; - LifetimeEnd = entry.LifetimeEnd; + LifetimeStart = lifetimeEntry.LifetimeStart; + LifetimeEnd = lifetimeEntry.LifetimeEnd; // Ensure this DHO has a result. - entry.Result ??= CreateResult(HitObject.CreateJudgement()) - ?? throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}."); + lifetimeEntry.Result ??= CreateResult(HitObject.CreateJudgement()) + ?? throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}."); // Copy back the result to the entry for potential future retrieval. if (lifetimeEntry != null) @@ -387,7 +399,9 @@ namespace osu.Game.Rulesets.Objects.Drawables private void onDefaultsApplied(HitObject hitObject) { - Apply(hitObject, lifetimeEntry); + Debug.Assert(lifetimeEntry != null); + Apply(lifetimeEntry); + DefaultsApplied?.Invoke(this); } From a92ae8ce769aea4ff4d2c293ce3e825970985ba3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 20 Apr 2021 13:01:42 +0900 Subject: [PATCH 1657/1791] Fix Reset() potentially not resetting to the intended start position --- osu.Game/Screens/Play/GameplayClockContainer.cs | 10 +++++++++- .../Screens/Play/MasterGameplayClockContainer.cs | 15 +++++++-------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 3da28f3560..e84f34e0d5 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -29,6 +29,11 @@ namespace osu.Game.Screens.Play /// protected readonly DecoupleableInterpolatingFramedClock AdjustableSource; + /// + /// The offset at which to start playing. Affects the time which the clock is reset to via . + /// + protected virtual double StartOffset => 0; + /// /// The source clock. /// @@ -93,9 +98,12 @@ namespace osu.Game.Screens.Play { ChangeSource(SourceClock); - AdjustableSource.Seek(0); + AdjustableSource.Seek(StartOffset); AdjustableSource.Stop(); + // Make sure the gameplay clock takes on the new time, otherwise the adjustable source will be seeked to the gameplay clock time in Start(). + GameplayClock.UnderlyingClock.ProcessFrame(); + if (!IsPaused.Value) Start(); } diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index f019e50b60..05d34fb8d4 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -47,6 +47,9 @@ namespace osu.Game.Screens.Play private readonly BindableDouble pauseFreqAdjust = new BindableDouble(1); + protected override double StartOffset => startOffset; + private double startOffset; + private readonly WorkingBeatmap beatmap; private readonly double gameplayStartTime; private readonly bool startAtGameplayStart; @@ -74,27 +77,23 @@ namespace osu.Game.Screens.Play userAudioOffset.BindValueChanged(offset => userOffsetClock.Offset = offset.NewValue, true); // sane default provided by ruleset. - double startTime = gameplayStartTime; + startOffset = gameplayStartTime; if (!startAtGameplayStart) { - startTime = Math.Min(0, startTime); + startOffset = Math.Min(0, startOffset); // if a storyboard is present, it may dictate the appropriate start time by having events in negative time space. // this is commonly used to display an intro before the audio track start. double? firstStoryboardEvent = beatmap.Storyboard.EarliestEventTime; if (firstStoryboardEvent != null) - startTime = Math.Min(startTime, firstStoryboardEvent.Value); + startOffset = Math.Min(startOffset, firstStoryboardEvent.Value); // some beatmaps specify a current lead-in time which should be used instead of the ruleset-provided value when available. // this is not available as an option in the live editor but can still be applied via .osu editing. if (beatmap.BeatmapInfo.AudioLeadIn > 0) - startTime = Math.Min(startTime, firstHitObjectTime - beatmap.BeatmapInfo.AudioLeadIn); + startOffset = Math.Min(startOffset, firstHitObjectTime - beatmap.BeatmapInfo.AudioLeadIn); } - - Seek(startTime); - - AdjustableSource.ProcessFrame(); } protected override void OnIsPausedChanged(ValueChangedEvent isPaused) From 8dd9134e3d9b6304c3a3ad8d4f869ea73394d42e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 20 Apr 2021 13:09:49 +0900 Subject: [PATCH 1658/1791] Move source clock adjustment application to Start() --- .../Screens/Play/MasterGameplayClockContainer.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 05d34fb8d4..f1d303e245 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -105,6 +105,12 @@ namespace osu.Game.Screens.Play this.TransformBindableTo(pauseFreqAdjust, 1, 200, Easing.In); } + public override void Start() + { + addSourceClockAdjustments(); + base.Start(); + } + /// /// Seek to a specific time in gameplay. /// @@ -122,12 +128,6 @@ namespace osu.Game.Screens.Play userOffsetClock.ProcessFrame(); } - public override void Reset() - { - updateRate(); - base.Reset(); - } - /// /// Skip forward to the next valid skip point. /// @@ -164,11 +164,12 @@ namespace osu.Game.Screens.Play { removeSourceClockAdjustments(); ChangeSource(new TrackVirtual(beatmap.Track.Length)); + addSourceClockAdjustments(); } private bool speedAdjustmentsApplied; - private void updateRate() + private void addSourceClockAdjustments() { if (speedAdjustmentsApplied) return; From 88ded95e7587c6345da8ecd186a46aba26600524 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 20 Apr 2021 13:56:13 +0900 Subject: [PATCH 1659/1791] Ensure clock is set in GCC.Start() --- .../TestSceneMasterGameplayClockContainer.cs | 28 ++++++++++++++++++- .../Screens/Play/GameplayClockContainer.cs | 7 +++-- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs b/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs index 77ada958d7..935bc07733 100644 --- a/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs +++ b/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs @@ -25,8 +25,34 @@ namespace osu.Game.Tests.Gameplay Add(gcc = new MasterGameplayClockContainer(working, 0)); }); - AddStep("start track", () => gcc.Start()); + AddStep("start clock", () => gcc.Start()); AddUntilStep("elapsed greater than zero", () => gcc.GameplayClock.ElapsedFrameTime > 0); } + + [Test] + public void TestElapseThenReset() + { + GameplayClockContainer gcc = null; + + AddStep("create container", () => + { + var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); + working.LoadTrack(); + + Add(gcc = new MasterGameplayClockContainer(working, 0)); + }); + + AddStep("start clock", () => gcc.Start()); + AddUntilStep("current time greater 2000", () => gcc.GameplayClock.CurrentTime > 2000); + + double timeAtReset = 0; + AddStep("reset clock", () => + { + timeAtReset = gcc.GameplayClock.CurrentTime; + gcc.Reset(); + }); + + AddAssert("current time < time at reset", () => gcc.GameplayClock.CurrentTime < timeAtReset); + } } } diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index e84f34e0d5..75b27ed3f3 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -1,10 +1,12 @@ // 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; using osu.Framework.Graphics.Containers; +using osu.Framework.Logging; using osu.Framework.Timing; namespace osu.Game.Screens.Play @@ -68,6 +70,9 @@ namespace osu.Game.Screens.Play /// public virtual void Start() { + // Ensure that the source clock is set. + ChangeSource(SourceClock); + if (!AdjustableSource.IsRunning) { // Seeking the decoupled clock to its current time ensures that its source clock will be seeked to the same time @@ -96,8 +101,6 @@ namespace osu.Game.Screens.Play /// public virtual void Reset() { - ChangeSource(SourceClock); - AdjustableSource.Seek(StartOffset); AdjustableSource.Stop(); From 3d6d26039a0a1040964f532b57ac912fef9716c7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 20 Apr 2021 14:09:54 +0900 Subject: [PATCH 1660/1791] Remove unused usings --- osu.Game/Screens/Play/GameplayClockContainer.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 75b27ed3f3..0ada6613d7 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -1,12 +1,10 @@ // 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; using osu.Framework.Graphics.Containers; -using osu.Framework.Logging; using osu.Framework.Timing; namespace osu.Game.Screens.Play From c6ee4e900e25201389ba194b768832e45322941f Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 20 Apr 2021 15:18:36 +0900 Subject: [PATCH 1661/1791] Ensure a non-null hitobject entry has a non-null Result --- .../Objects/Drawables/DrawableHitObject.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 59088cffda..d798eba4e1 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -171,7 +171,10 @@ namespace osu.Game.Rulesets.Objects.Drawables protected DrawableHitObject([CanBeNull] HitObject initialHitObject = null) { if (initialHitObject != null) + { lifetimeEntry = new UnmanagedHitObjectEntry(initialHitObject); + ensureEntryHasResult(); + } } [BackgroundDependencyLoader] @@ -239,13 +242,7 @@ namespace osu.Game.Rulesets.Objects.Drawables LifetimeStart = lifetimeEntry.LifetimeStart; LifetimeEnd = lifetimeEntry.LifetimeEnd; - // Ensure this DHO has a result. - lifetimeEntry.Result ??= CreateResult(HitObject.CreateJudgement()) - ?? throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}."); - - // Copy back the result to the entry for potential future retrieval. - if (lifetimeEntry != null) - lifetimeEntry.Result = Result; + ensureEntryHasResult(); foreach (var h in HitObject.NestedHitObjects) { @@ -799,6 +796,13 @@ namespace osu.Game.Rulesets.Objects.Drawables /// The that provides the scoring information. protected virtual JudgementResult CreateResult(Judgement judgement) => new JudgementResult(HitObject, judgement); + private void ensureEntryHasResult() + { + Debug.Assert(lifetimeEntry != null); + lifetimeEntry.Result ??= CreateResult(HitObject.CreateJudgement()) + ?? throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}."); + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From 281c2041b23bdbce8859f9027220eb4b06fb908d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 20 Apr 2021 16:51:00 +0900 Subject: [PATCH 1662/1791] Add failing test --- .../Gameplay/TestSceneStoryboardSamples.cs | 28 ++++++++++++++++++- osu.Game/Skinning/PausableSkinnableSound.cs | 2 +- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs index cae5f20332..f2cb5f75d8 100644 --- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs @@ -77,7 +77,33 @@ namespace osu.Game.Tests.Gameplay AddStep("start time", () => gameplayContainer.Start()); - AddUntilStep("sample playback succeeded", () => sample.LifetimeEnd < double.MaxValue); + AddUntilStep("sample played", () => sample.RequestedPlaying); + AddUntilStep("sample has lifetime end", () => sample.LifetimeEnd < double.MaxValue); + } + + [Test] + public void TestSampleHasLifetimeEndWithInitialClockTime() + { + GameplayClockContainer gameplayContainer = null; + DrawableStoryboardSample sample = null; + + AddStep("create container", () => + { + var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); + working.LoadTrack(); + + Add(gameplayContainer = new GameplayClockContainer(working, 1000, true)); + + gameplayContainer.Add(sample = new DrawableStoryboardSample(new StoryboardSampleInfo(string.Empty, 0, 1)) + { + Clock = gameplayContainer.GameplayClock + }); + }); + + AddStep("start time", () => gameplayContainer.Start()); + + AddUntilStep("sample not played", () => !sample.RequestedPlaying); + AddUntilStep("sample has lifetime end", () => sample.LifetimeEnd < double.MaxValue); } [TestCase(typeof(OsuModDoubleTime), 1.5)] diff --git a/osu.Game/Skinning/PausableSkinnableSound.cs b/osu.Game/Skinning/PausableSkinnableSound.cs index b3c7c5d6b2..10b8c47028 100644 --- a/osu.Game/Skinning/PausableSkinnableSound.cs +++ b/osu.Game/Skinning/PausableSkinnableSound.cs @@ -16,7 +16,7 @@ namespace osu.Game.Skinning { public double Length => !DrawableSamples.Any() ? 0 : DrawableSamples.Max(sample => sample.Length); - protected bool RequestedPlaying { get; private set; } + public bool RequestedPlaying { get; private set; } public PausableSkinnableSound() { From d28eb399a4539c776464e99fa322a33ebf1fa654 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 20 Apr 2021 16:51:24 +0900 Subject: [PATCH 1663/1791] Fix storyboard sample lifetimes not set if seeked past --- .../Drawables/DrawableStoryboardSample.cs | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs index 7b16009859..fbdd27e762 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs @@ -61,28 +61,32 @@ namespace osu.Game.Storyboards.Drawables { base.Update(); + // Check if we've yet to pass the sample start time. if (Time.Current < sampleInfo.StartTime) { - // We've rewound before the start time of the sample Stop(); - // In the case that the user fast-forwards to a point far beyond the start time of the sample, - // we want to be able to fall into the if-conditional below (therefore we must not have a life time end) + // Playback has stopped, but if the user fast-forwards to a point after the start time of the sample then + // we must not have a lifetime end in order to continue receiving updates and start the sample below. LifetimeStart = sampleInfo.StartTime; LifetimeEnd = double.MaxValue; + + return; } - else if (Time.Current - Time.Elapsed <= sampleInfo.StartTime) + + // Ensure that we've elapsed from a point before the sample's start time before playing. + if (Time.Current - Time.Elapsed <= sampleInfo.StartTime) { // We've passed the start time of the sample. We only play the sample if we're within an allowable range // from the sample's start, to reduce layering if we've been fast-forwarded far into the future if (!RequestedPlaying && Time.Current - sampleInfo.StartTime < allowable_late_start) Play(); - - // In the case that the user rewinds to a point far behind the start time of the sample, - // we want to be able to fall into the if-conditional above (therefore we must not have a life time start) - LifetimeStart = double.MinValue; - LifetimeEnd = sampleInfo.StartTime; } + + // Playback has started, but if the user rewinds to a point before the start time of the sample then + // we must not have a lifetime start in order to continue receiving updates and stop the sample above. + LifetimeStart = double.MinValue; + LifetimeEnd = sampleInfo.StartTime; } } } From 5da18c51a49bb3669dae12f18b9af89668bf873f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 20 Apr 2021 17:27:37 +0900 Subject: [PATCH 1664/1791] Fix compile error --- 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 57b3687d3a..5b8abddd3f 100644 --- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs @@ -92,7 +92,7 @@ namespace osu.Game.Tests.Gameplay var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); working.LoadTrack(); - Add(gameplayContainer = new GameplayClockContainer(working, 1000, true)); + Add(gameplayContainer = new MasterGameplayClockContainer(working, 1000, true)); gameplayContainer.Add(sample = new DrawableStoryboardSample(new StoryboardSampleInfo(string.Empty, 0, 1)) { From 97fb90d9f45c660e4420fbf05477846e010eea35 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 20 Apr 2021 17:35:59 +0900 Subject: [PATCH 1665/1791] Move clock processing to base.Seek() --- osu.Game/Screens/Play/GameplayClockContainer.cs | 10 +++++++--- osu.Game/Screens/Play/MasterGameplayClockContainer.cs | 3 --- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 0ada6613d7..f6b99c094f 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -87,7 +87,13 @@ namespace osu.Game.Screens.Play /// Seek to a specific time in gameplay. /// /// The destination time to seek to. - public virtual void Seek(double time) => AdjustableSource.Seek(time); + public virtual void Seek(double time) + { + AdjustableSource.Seek(time); + + // Manually process to make sure the gameplay clock is correctly updated after a seek. + GameplayClock.UnderlyingClock.ProcessFrame(); + } /// /// Stops gameplay. @@ -102,8 +108,6 @@ namespace osu.Game.Screens.Play AdjustableSource.Seek(StartOffset); AdjustableSource.Stop(); - // Make sure the gameplay clock takes on the new time, otherwise the adjustable source will be seeked to the gameplay clock time in Start(). - GameplayClock.UnderlyingClock.ProcessFrame(); if (!IsPaused.Value) Start(); diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index f1d303e245..1b893a76c5 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -123,9 +123,6 @@ namespace osu.Game.Screens.Play // remove the offset component here because most of the time we want the seek to be aligned to gameplay, not the audio track. // we may want to consider reversing the application of offsets in the future as it may feel more correct. base.Seek(time - totalOffset); - - // manually process frame to ensure GameplayClock is correctly updated after a seek. - userOffsetClock.ProcessFrame(); } /// From a683e5ec34cbbbf1fe08a4eafc1cbaeac7d0969a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 20 Apr 2021 17:36:24 +0900 Subject: [PATCH 1666/1791] Seek using local method --- osu.Game/Screens/Play/GameplayClockContainer.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index f6b99c094f..e9a9c83119 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -105,9 +105,10 @@ namespace osu.Game.Screens.Play /// public virtual void Reset() { - AdjustableSource.Seek(StartOffset); - AdjustableSource.Stop(); + Seek(StartOffset); + // Manually stop the source in order to not affect the IsPaused state. + AdjustableSource.Stop(); if (!IsPaused.Value) Start(); From ddf1b560f3d4ba22884020838e59c40c50d1b4a6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Apr 2021 18:18:50 +0900 Subject: [PATCH 1667/1791] Remove catcher fade during hyperdash Closes https://github.com/ppy/osu/issues/12472. --- osu.Game.Rulesets.Catch/UI/Catcher.cs | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 5d57e84b75..d045dcf16a 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -384,16 +384,7 @@ namespace osu.Game.Rulesets.Catch.UI { updateTrailVisibility(); - if (hyperDashing) - { - this.FadeColour(hyperDashColour, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint); - this.FadeTo(0.2f, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint); - } - else - { - this.FadeColour(Color4.White, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint); - this.FadeTo(1f, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint); - } + this.FadeColour(hyperDashing ? hyperDashColour : Color4.White, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint); } private void updateTrailVisibility() => trails.DisplayTrail = Dashing || HyperDashing; From f11b068dee2b58a0160c138cfcb811e6f18a2ec0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Apr 2021 18:22:58 +0900 Subject: [PATCH 1668/1791] Allow faster roll speed selection in "Barrel Roll" mod --- osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs index aee431284e..1587f97f46 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Mods public BindableNumber SpinSpeed { get; } = new BindableDouble(0.5) { MinValue = 0.02, - MaxValue = 4, + MaxValue = 12, Precision = 0.01, }; From ec080fcb32fdf2fa1ca7fd4b2d8db1c7a4d2ffc7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 20 Apr 2021 18:25:46 +0900 Subject: [PATCH 1669/1791] Move seekOffset back to MasterGameplayClockContainer --- osu.Game/Screens/Play/GameplayClockContainer.cs | 7 +------ .../Screens/Play/MasterGameplayClockContainer.cs | 12 +++++++++--- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index e9a9c83119..5cd17d92c4 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -29,11 +29,6 @@ namespace osu.Game.Screens.Play /// protected readonly DecoupleableInterpolatingFramedClock AdjustableSource; - /// - /// The offset at which to start playing. Affects the time which the clock is reset to via . - /// - protected virtual double StartOffset => 0; - /// /// The source clock. /// @@ -105,7 +100,7 @@ namespace osu.Game.Screens.Play /// public virtual void Reset() { - Seek(StartOffset); + Seek(0); // Manually stop the source in order to not affect the IsPaused state. AdjustableSource.Stop(); diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 1b893a76c5..affe24069d 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -47,9 +47,6 @@ namespace osu.Game.Screens.Play private readonly BindableDouble pauseFreqAdjust = new BindableDouble(1); - protected override double StartOffset => startOffset; - private double startOffset; - private readonly WorkingBeatmap beatmap; private readonly double gameplayStartTime; private readonly bool startAtGameplayStart; @@ -59,6 +56,7 @@ namespace osu.Game.Screens.Play private FramedOffsetClock platformOffsetClock; private LocalGameplayClock localGameplayClock; private Bindable userAudioOffset; + private double startOffset; public MasterGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStartTime, bool startAtGameplayStart = false) : base(beatmap.Track) @@ -94,6 +92,8 @@ namespace osu.Game.Screens.Play if (beatmap.BeatmapInfo.AudioLeadIn > 0) startOffset = Math.Min(startOffset, firstHitObjectTime - beatmap.BeatmapInfo.AudioLeadIn); } + + Seek(startOffset); } protected override void OnIsPausedChanged(ValueChangedEvent isPaused) @@ -142,6 +142,12 @@ namespace osu.Game.Screens.Play Seek(skipTarget); } + public override void Reset() + { + base.Reset(); + Seek(startOffset); + } + protected override GameplayClock CreateGameplayClock(IFrameBasedClock source) { // Lazer's audio timings in general doesn't match stable. This is the result of user testing, albeit limited. From f144661c31a729b6874fad1b720035e3e95d4d23 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 20 Apr 2021 18:26:30 +0900 Subject: [PATCH 1670/1791] Fix storyboard sample test scene --- .../Gameplay/TestSceneStoryboardSamples.cs | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs index 5b8abddd3f..bbab9ae94d 100644 --- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs @@ -20,6 +20,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; using osu.Game.Skinning; using osu.Game.Storyboards; @@ -67,15 +68,17 @@ namespace osu.Game.Tests.Gameplay var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); working.LoadTrack(); - Add(gameplayContainer = new MasterGameplayClockContainer(working, 0)); - - gameplayContainer.Add(sample = new DrawableStoryboardSample(new StoryboardSampleInfo(string.Empty, 0, 1)) + Add(gameplayContainer = new MasterGameplayClockContainer(working, 0) { - Clock = gameplayContainer.GameplayClock + IsPaused = { Value = true }, + Child = new FrameStabilityContainer + { + Child = sample = new DrawableStoryboardSample(new StoryboardSampleInfo(string.Empty, 0, 1)) + } }); }); - AddStep("start time", () => gameplayContainer.Start()); + AddStep("reset clock", () => gameplayContainer.Start()); AddUntilStep("sample played", () => sample.RequestedPlaying); AddUntilStep("sample has lifetime end", () => sample.LifetimeEnd < double.MaxValue); @@ -92,11 +95,13 @@ namespace osu.Game.Tests.Gameplay var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); working.LoadTrack(); - Add(gameplayContainer = new MasterGameplayClockContainer(working, 1000, true)); - - gameplayContainer.Add(sample = new DrawableStoryboardSample(new StoryboardSampleInfo(string.Empty, 0, 1)) + Add(gameplayContainer = new MasterGameplayClockContainer(working, 1000, true) { - Clock = gameplayContainer.GameplayClock + IsPaused = { Value = true }, + Child = new FrameStabilityContainer + { + Child = sample = new DrawableStoryboardSample(new StoryboardSampleInfo(string.Empty, 0, 1)) + } }); }); From ac0ed72d043d339a856637a0a3675fe06f5280ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Apr 2021 18:36:11 +0900 Subject: [PATCH 1671/1791] Keep hitcircles aligned with view in "Barrel Roll" mod --- .../Mods/OsuModBarrelRoll.cs | 25 +++++++++++++++++-- .../Objects/Drawables/DrawableHitCircle.cs | 6 ++++- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs index aee431284e..64b87f3977 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs @@ -1,20 +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.Collections.Generic; using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Game.Configuration; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.UI; using osuTK; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModBarrelRoll : Mod, IUpdatableByPlayfield, IApplicableToDrawableRuleset + public class OsuModBarrelRoll : Mod, IUpdatableByPlayfield, IApplicableToDrawableRuleset, IApplicableToDrawableHitObjects { + private float currentRotation; + [SettingSource("Roll speed", "Rotations per minute")] public BindableNumber SpinSpeed { get; } = new BindableDouble(0.5) { @@ -35,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Mods public void Update(Playfield playfield) { - playfield.Rotation = (Direction.Value == RotationDirection.Counterclockwise ? -1 : 1) * 360 * (float)(playfield.Time.Current / 60000 * SpinSpeed.Value); + playfield.Rotation = currentRotation = (Direction.Value == RotationDirection.Counterclockwise ? -1 : 1) * 360 * (float)(playfield.Time.Current / 60000 * SpinSpeed.Value); } public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) @@ -43,5 +48,21 @@ namespace osu.Game.Rulesets.Osu.Mods // scale the playfield to allow all hitobjects to stay within the visible region. drawableRuleset.Playfield.Scale = new Vector2(OsuPlayfield.BASE_SIZE.Y / OsuPlayfield.BASE_SIZE.X); } + + public void ApplyToDrawableHitObjects(IEnumerable drawables) + { + foreach (var d in drawables) + { + d.OnUpdate += _ => + { + switch (d) + { + case DrawableHitCircle circle: + circle.CirclePiece.Rotation = -currentRotation; + break; + } + }; + } + } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index 189003875d..df77ec2693 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -66,7 +66,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables return true; }, }, - CirclePiece = new SkinnableDrawable(new OsuSkinComponent(CirclePieceComponent), _ => new MainCirclePiece()), + CirclePiece = new SkinnableDrawable(new OsuSkinComponent(CirclePieceComponent), _ => new MainCirclePiece()) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, ApproachCircle = new ApproachCircle { Alpha = 0, From c5d6b6ea8d05578cfac4ae8697f15e37d8d84da3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 20 Apr 2021 18:41:09 +0900 Subject: [PATCH 1672/1791] Fix tests failing intermittently This was due to this code happening in UpdateAfterChildren(), after the GCC has processed one frame. During this time, the clock could have advanced an arbitrary amount. The cause of this is the removal of the Task.Run() to set the clock in Restart() (now called Reset()) which changed the timing, so it only worked before due to pure luck. --- osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs index dccde366c2..f5f17a0bc1 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs @@ -130,9 +130,9 @@ namespace osu.Game.Tests.Visual.Gameplay public double GameplayClockTime => GameplayClockContainer.GameplayClock.CurrentTime; - protected override void UpdateAfterChildren() + protected override void Update() { - base.UpdateAfterChildren(); + base.Update(); if (!FirstFrameClockTime.HasValue) { From 5262d94e21e630c794c8a6356d12f44f38ce33a0 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 20 Apr 2021 13:21:57 +0200 Subject: [PATCH 1673/1791] Fix wrong assert in offscreen test --- .../Editor/Checks/CheckOffscreenObjectsTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs index 3a4817398c..6139b0e676 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs @@ -138,7 +138,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks [Test] public void TestSliderNearEdgeStackedOffscreen() { - assertOk(new Beatmap + assertOffscreenSlider(new Beatmap { HitObjects = new List { From 6a1e4ff99f41091d04f82bb721525af410eb10b7 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 20 Apr 2021 13:28:32 +0200 Subject: [PATCH 1674/1791] Add file hash to file presence test Necessary because we now find the storage path of the file rather than just the file itself. --- osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs b/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs index fe2e16ffd8..f6e875a8fc 100644 --- a/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Game.Beatmaps; +using osu.Game.IO; using osu.Game.Rulesets.Edit.Checks; using osu.Game.Rulesets.Objects; using osu.Game.Tests.Beatmaps; @@ -30,7 +31,11 @@ namespace osu.Game.Tests.Editing.Checks { Files = new List(new[] { - new BeatmapSetFileInfo { Filename = "abc123.jpg" } + new BeatmapSetFileInfo + { + Filename = "abc123.jpg", + FileInfo = new FileInfo { Hash = "abcdef" } + } }) } } From c0318a4d3ef401f4b3288d14ac4188f995a5f0d1 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 20 Apr 2021 13:29:14 +0200 Subject: [PATCH 1675/1791] Fix usage of _ in Moq lambdas --- osu.Game.Tests/Editing/Checks/CheckAudioQualityTest.cs | 10 +++++----- .../Editing/Checks/CheckBackgroundQualityTest.cs | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Editing/Checks/CheckAudioQualityTest.cs b/osu.Game.Tests/Editing/Checks/CheckAudioQualityTest.cs index e73d9adfb0..7658ca728d 100644 --- a/osu.Game.Tests/Editing/Checks/CheckAudioQualityTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckAudioQualityTest.cs @@ -37,8 +37,8 @@ namespace osu.Game.Tests.Editing.Checks beatmap.Metadata.AudioFile = null; var mock = new Mock(); - mock.SetupGet(_ => _.Beatmap).Returns(beatmap); - mock.SetupGet(_ => _.Track).Returns((Track)null); + mock.SetupGet(w => w.Beatmap).Returns(beatmap); + mock.SetupGet(w => w.Track).Returns((Track)null); Assert.That(check.Run(beatmap, mock.Object), Is.Empty); } @@ -102,11 +102,11 @@ namespace osu.Game.Tests.Editing.Checks private Mock getMockWorkingBeatmap(int? audioBitrate) { var mockTrack = new Mock(); - mockTrack.SetupGet(_ => _.Bitrate).Returns(audioBitrate); + mockTrack.SetupGet(t => t.Bitrate).Returns(audioBitrate); var mockWorkingBeatmap = new Mock(); - mockWorkingBeatmap.SetupGet(_ => _.Beatmap).Returns(beatmap); - mockWorkingBeatmap.SetupGet(_ => _.Track).Returns(mockTrack.Object); + mockWorkingBeatmap.SetupGet(w => w.Beatmap).Returns(beatmap); + mockWorkingBeatmap.SetupGet(w => w.Track).Returns(mockTrack.Object); return mockWorkingBeatmap; } diff --git a/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs b/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs index b6970a99e4..f0f972d2fa 100644 --- a/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs @@ -120,9 +120,9 @@ namespace osu.Game.Tests.Editing.Checks var stream = new MemoryStream(fileBytes ?? new byte[1024 * 1024]); var mock = new Mock(); - mock.SetupGet(_ => _.Beatmap).Returns(beatmap); - mock.SetupGet(_ => _.Background).Returns(background); - mock.Setup(_ => _.GetStream(It.IsAny())).Returns(stream); + mock.SetupGet(w => w.Beatmap).Returns(beatmap); + mock.SetupGet(w => w.Background).Returns(background); + mock.Setup(w => w.GetStream(It.IsAny())).Returns(stream); return mock; } From 3e1b6b3b346ea449a3504eb71961f7446bf84379 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 20 Apr 2021 13:34:12 +0200 Subject: [PATCH 1676/1791] Simplify verifier run call args Uses the resolved working beatmap instead of resolving it every time. Also uses the EditorBeatmap itself as playable beatmap, as it is of type `IBeatmap` already, and `.PlayableBeatmap` forwards everything anyway. --- osu.Game/Screens/Edit/Verify/VerifyScreen.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs index 530f24300b..9de1f04271 100644 --- a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs +++ b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs @@ -61,7 +61,7 @@ namespace osu.Game.Screens.Edit.Verify private EditorClock clock { get; set; } [Resolved] - private BeatmapManager beatmapManager { get; set; } + private IBindable workingBeatmap { get; set; } [Resolved] private EditorBeatmap beatmap { get; set; } @@ -122,11 +122,10 @@ namespace osu.Game.Screens.Edit.Verify private void refresh() { - var workingBeatmap = beatmapManager.GetWorkingBeatmap(beatmap.BeatmapInfo); - var issues = generalVerifier.Run(beatmap.PlayableBeatmap, workingBeatmap); + var issues = generalVerifier.Run(beatmap, workingBeatmap.Value); if (rulesetVerifier != null) - issues = issues.Concat(rulesetVerifier.Run(beatmap.PlayableBeatmap, workingBeatmap)); + issues = issues.Concat(rulesetVerifier.Run(beatmap, workingBeatmap.Value)); table.Issues = issues .OrderBy(issue => issue.Template.Type) From d7a81471c85a4aff21279974092847c4331355aa Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 20 Apr 2021 13:40:38 +0200 Subject: [PATCH 1677/1791] Add xmldoc to `GetPathForFile` --- osu.Game/Beatmaps/BeatmapSetInfo.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index 774bd0bc62..d90ede5d4b 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -59,6 +59,10 @@ namespace osu.Game.Beatmaps public string StoryboardFile => Files?.Find(f => f.Filename.EndsWith(".osb", StringComparison.OrdinalIgnoreCase))?.Filename; + /// + /// Returns the storage path for the file in this beatmapset with the given filename, if any exists, otherwise null. + /// + /// The name of the file to get the storage path of. public string GetPathForFile(string filename) => Files?.SingleOrDefault(f => string.Equals(f.Filename, filename, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; public List Files { get; set; } From e9dfa2860a4fd63f61f0ad2a26d9677d6b4498fb Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Tue, 20 Apr 2021 13:44:06 +0200 Subject: [PATCH 1678/1791] Add xmldoc note about path being relative --- osu.Game/Beatmaps/BeatmapSetInfo.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index d90ede5d4b..1ce42535a0 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -61,6 +61,7 @@ namespace osu.Game.Beatmaps /// /// Returns the storage path for the file in this beatmapset with the given filename, if any exists, otherwise null. + /// The path returned is relative to the user file storage. /// /// The name of the file to get the storage path of. public string GetPathForFile(string filename) => Files?.SingleOrDefault(f => string.Equals(f.Filename, filename, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; From a9e4a0ed50dcfbd81487afbc14ba10fd8cd7463f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 20 Apr 2021 21:17:24 +0900 Subject: [PATCH 1679/1791] Fix potentially starting play when finished The UserFinishedPlaying event may trigger before the event is subscribed to by SpectatorScreen. For such cases, an extra check is done to make sure the user is _actually_ playing. --- osu.Game/Screens/Spectate/SpectatorScreen.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index f554b15abf..ed01d56801 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -127,6 +127,10 @@ namespace osu.Game.Screens.Spectate if (!userMap.ContainsKey(userId)) return; + // The user may have stopped playing. + if (!spectatorClient.TryGetPlayingUserState(userId, out _)) + return; + Schedule(() => OnUserStateChanged(userId, state)); updateGameplayState(userId); From 4cc3321d54b2e2e57ebb09b1cb240135d421ec4d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 20 Apr 2021 21:20:08 +0900 Subject: [PATCH 1680/1791] Fix potential doubling of events --- osu.Game/Online/Spectator/SpectatorStreamingClient.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 13b12d9add..378096c7fb 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -303,13 +303,14 @@ namespace osu.Game.Online.Spectator /// Whether the action provided in should be run once immediately for all users currently playing. public void BindUserBeganPlaying(Action callback, bool runOnceImmediately = false) { - OnUserBeganPlaying += callback; - - if (!runOnceImmediately) - return; - + // The lock is taken before the event is subscribed to to prevent doubling of events. lock (userLock) { + OnUserBeganPlaying += callback; + + if (!runOnceImmediately) + return; + foreach (var (userId, state) in playingUserStates) callback(userId, state); } From 7fc450c620eba103fc96a716edd149c9e15e790a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Apr 2021 23:42:53 +0900 Subject: [PATCH 1681/1791] Fix mod settings blocking input outside its visible area Closes #12502. --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 26b8632d7f..754b260bf0 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -245,15 +245,21 @@ namespace osu.Game.Overlays.Mods }, } }, - ModSettingsContainer = new ModSettingsContainer + new Container { RelativeSizeAxes = Axes.Both, Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, - Width = 0.3f, - Alpha = 0, Padding = new MarginPadding(30), - SelectedMods = { BindTarget = SelectedMods }, + Width = 0.3f, + Children = new Drawable[] + { + ModSettingsContainer = new ModSettingsContainer + { + Alpha = 0, + SelectedMods = { BindTarget = SelectedMods }, + }, + } }, } }, From 4910d8f56cd04339773ec2ae4ca288dcc9324839 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Apr 2021 23:57:12 +0900 Subject: [PATCH 1682/1791] Fix click-to-resume cursor location being incorrect when playfield is transformed Closes #12501. --- osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs index 44ca5e850f..27d48d1296 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Osu.UI base.PopIn(); GameplayCursor.ActiveCursor.Hide(); - cursorScaleContainer.MoveTo(GameplayCursor.ActiveCursor.Position); + cursorScaleContainer.Position = ToLocalSpace(GameplayCursor.ActiveCursor.ScreenSpaceDrawQuad.Centre); clickToResumeCursor.Appear(); if (localCursorContainer == null) From 881043bc5d8d8a76d3fd54c9a389adc83472ee61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Apr 2021 19:05:43 +0200 Subject: [PATCH 1683/1791] Fix failing test after mod settings layout changes The slight hack which was used in the test to ensure that the mod settings overlay covered the entire width of the mod overlay broke after adjustments to the layout in the previous commit. Locally adjust the hack to use the parent of the `ModSettingsContainer` rather than the container itself. --- osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs index 2158cf77e5..bda1973354 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs @@ -167,7 +167,7 @@ namespace osu.Game.Tests.Visual.UserInterface GetModButton(mod).SelectNext(1); public void SetModSettingsWidth(float newWidth) => - ModSettingsContainer.Width = newWidth; + ModSettingsContainer.Parent.Width = newWidth; } public class TestRulesetInfo : RulesetInfo From e80c3c317a34b989dc18a9d904d39da724c828f3 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 21 Apr 2021 09:14:22 +0900 Subject: [PATCH 1684/1791] Rename UnmanagedHitObjectEntry -> SyntheticHitObjectEntry "Unmanaged" was confusing because its lifetime is still managed by the HitObjectContainer. --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 4 ++-- ...{UnmanagedHitObjectEntry.cs => SyntheticHitObjectEntry.cs} | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename osu.Game/Rulesets/Objects/{UnmanagedHitObjectEntry.cs => SyntheticHitObjectEntry.cs} (81%) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index d798eba4e1..ca1c601c1d 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -172,7 +172,7 @@ namespace osu.Game.Rulesets.Objects.Drawables { if (initialHitObject != null) { - lifetimeEntry = new UnmanagedHitObjectEntry(initialHitObject); + lifetimeEntry = new SyntheticHitObjectEntry(initialHitObject); ensureEntryHasResult(); } } @@ -227,7 +227,7 @@ namespace osu.Game.Rulesets.Objects.Drawables if (hitObject == null) throw new ArgumentNullException($"Cannot apply a null {nameof(HitObject)}."); - Apply(new UnmanagedHitObjectEntry(hitObject)); + Apply(new SyntheticHitObjectEntry(hitObject)); } /// diff --git a/osu.Game/Rulesets/Objects/UnmanagedHitObjectEntry.cs b/osu.Game/Rulesets/Objects/SyntheticHitObjectEntry.cs similarity index 81% rename from osu.Game/Rulesets/Objects/UnmanagedHitObjectEntry.cs rename to osu.Game/Rulesets/Objects/SyntheticHitObjectEntry.cs index 3cfa3c4d3a..76f9eaf25a 100644 --- a/osu.Game/Rulesets/Objects/UnmanagedHitObjectEntry.cs +++ b/osu.Game/Rulesets/Objects/SyntheticHitObjectEntry.cs @@ -9,9 +9,9 @@ namespace osu.Game.Rulesets.Objects /// Created for a when only is given /// to make sure a is always associated with a . /// - internal class UnmanagedHitObjectEntry : HitObjectLifetimeEntry + internal class SyntheticHitObjectEntry : HitObjectLifetimeEntry { - public UnmanagedHitObjectEntry(HitObject hitObject) + public SyntheticHitObjectEntry(HitObject hitObject) : base(hitObject) { } From 67fcfd9dbc243a8ffe976410729efeb94c06336d Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 21 Apr 2021 09:48:16 +0900 Subject: [PATCH 1685/1791] Fix wrong InitialLifetimeOffset is used for a non-pooled DHO. HitObjectLifetimeEntry's InitialLifetimeOffset is different from DrawableHitObject's InitialLifetimeOffset. --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 4 ++-- osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs | 9 +++++++-- osu.Game/Rulesets/Objects/SyntheticHitObjectEntry.cs | 6 ++++-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index ca1c601c1d..4eea058163 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -172,7 +172,7 @@ namespace osu.Game.Rulesets.Objects.Drawables { if (initialHitObject != null) { - lifetimeEntry = new SyntheticHitObjectEntry(initialHitObject); + lifetimeEntry = new SyntheticHitObjectEntry(initialHitObject, initialHitObject.StartTime - InitialLifetimeOffset); ensureEntryHasResult(); } } @@ -227,7 +227,7 @@ namespace osu.Game.Rulesets.Objects.Drawables if (hitObject == null) throw new ArgumentNullException($"Cannot apply a null {nameof(HitObject)}."); - Apply(new SyntheticHitObjectEntry(hitObject)); + Apply(new SyntheticHitObjectEntry(hitObject, hitObject.StartTime - InitialLifetimeOffset)); } /// diff --git a/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs b/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs index 1954d7e6d2..d1d459a8f6 100644 --- a/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs +++ b/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs @@ -30,12 +30,17 @@ namespace osu.Game.Rulesets.Objects /// Creates a new . /// /// The to store the lifetime of. - public HitObjectLifetimeEntry(HitObject hitObject) + /// The . + /// The . + public HitObjectLifetimeEntry(HitObject hitObject, double lifetimeStart = double.MinValue, double lifetimeEnd = double.MaxValue) { HitObject = hitObject; startTimeBindable.BindTo(HitObject.StartTimeBindable); - startTimeBindable.BindValueChanged(onStartTimeChanged, true); + // Only set initial lifetime if it is not provided + startTimeBindable.BindValueChanged(onStartTimeChanged, lifetimeStart == double.MinValue); + + setLifetime(lifetimeStart, lifetimeEnd); } // The lifetime start, as set by the hitobject. diff --git a/osu.Game/Rulesets/Objects/SyntheticHitObjectEntry.cs b/osu.Game/Rulesets/Objects/SyntheticHitObjectEntry.cs index 76f9eaf25a..c064f3ff10 100644 --- a/osu.Game/Rulesets/Objects/SyntheticHitObjectEntry.cs +++ b/osu.Game/Rulesets/Objects/SyntheticHitObjectEntry.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. +#nullable enable + using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.Objects @@ -11,8 +13,8 @@ namespace osu.Game.Rulesets.Objects /// internal class SyntheticHitObjectEntry : HitObjectLifetimeEntry { - public SyntheticHitObjectEntry(HitObject hitObject) - : base(hitObject) + public SyntheticHitObjectEntry(HitObject hitObject, double initialLifetimeStart) + : base(hitObject, initialLifetimeStart) { } } From 44ff08cce4675e40492364633a1917608f7c908b Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 21 Apr 2021 10:02:50 +0900 Subject: [PATCH 1686/1791] Revert "Fix wrong InitialLifetimeOffset is used for a non-pooled DHO." This reverts commit 67fcfd9d --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 4 ++-- osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs | 9 ++------- osu.Game/Rulesets/Objects/SyntheticHitObjectEntry.cs | 6 ++---- 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 4eea058163..ca1c601c1d 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -172,7 +172,7 @@ namespace osu.Game.Rulesets.Objects.Drawables { if (initialHitObject != null) { - lifetimeEntry = new SyntheticHitObjectEntry(initialHitObject, initialHitObject.StartTime - InitialLifetimeOffset); + lifetimeEntry = new SyntheticHitObjectEntry(initialHitObject); ensureEntryHasResult(); } } @@ -227,7 +227,7 @@ namespace osu.Game.Rulesets.Objects.Drawables if (hitObject == null) throw new ArgumentNullException($"Cannot apply a null {nameof(HitObject)}."); - Apply(new SyntheticHitObjectEntry(hitObject, hitObject.StartTime - InitialLifetimeOffset)); + Apply(new SyntheticHitObjectEntry(hitObject)); } /// diff --git a/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs b/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs index d1d459a8f6..1954d7e6d2 100644 --- a/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs +++ b/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs @@ -30,17 +30,12 @@ namespace osu.Game.Rulesets.Objects /// Creates a new . /// /// The to store the lifetime of. - /// The . - /// The . - public HitObjectLifetimeEntry(HitObject hitObject, double lifetimeStart = double.MinValue, double lifetimeEnd = double.MaxValue) + public HitObjectLifetimeEntry(HitObject hitObject) { HitObject = hitObject; startTimeBindable.BindTo(HitObject.StartTimeBindable); - // Only set initial lifetime if it is not provided - startTimeBindable.BindValueChanged(onStartTimeChanged, lifetimeStart == double.MinValue); - - setLifetime(lifetimeStart, lifetimeEnd); + startTimeBindable.BindValueChanged(onStartTimeChanged, true); } // The lifetime start, as set by the hitobject. diff --git a/osu.Game/Rulesets/Objects/SyntheticHitObjectEntry.cs b/osu.Game/Rulesets/Objects/SyntheticHitObjectEntry.cs index c064f3ff10..76f9eaf25a 100644 --- a/osu.Game/Rulesets/Objects/SyntheticHitObjectEntry.cs +++ b/osu.Game/Rulesets/Objects/SyntheticHitObjectEntry.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable enable - using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.Objects @@ -13,8 +11,8 @@ namespace osu.Game.Rulesets.Objects /// internal class SyntheticHitObjectEntry : HitObjectLifetimeEntry { - public SyntheticHitObjectEntry(HitObject hitObject, double initialLifetimeStart) - : base(hitObject, initialLifetimeStart) + public SyntheticHitObjectEntry(HitObject hitObject) + : base(hitObject) { } } From 9d423201edb4eebdc23fe141830773db5b2729f8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 21 Apr 2021 10:29:18 +0900 Subject: [PATCH 1687/1791] Fix slider tails wiggling independently --- osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs index 9c5e41f245..123bbe12de 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Mods // Wiggle the repeat points with the slider instead of independently. // Also fixes an issue with repeat points being positioned incorrectly. - if (osuObject is SliderRepeat) + if (osuObject is SliderRepeat || osuObject is SliderTailCircle) return; Random objRand = new Random((int)osuObject.StartTime); From e454037d82e5883e2419203ccb98a983e333cece Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 21 Apr 2021 10:32:08 +0900 Subject: [PATCH 1688/1791] Add to comment --- osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs index 123bbe12de..a01cec4bb3 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Mods var osuObject = (OsuHitObject)drawable.HitObject; Vector2 origin = drawable.Position; - // Wiggle the repeat points with the slider instead of independently. + // Wiggle the repeat points and the tail with the slider instead of independently. // Also fixes an issue with repeat points being positioned incorrectly. if (osuObject is SliderRepeat || osuObject is SliderTailCircle) return; From 73d3da168769bf884ec876cdf888aa6f118a8dcb Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 21 Apr 2021 11:32:01 +0900 Subject: [PATCH 1689/1791] Fix wrong InitialLifetimeOffset is used for a non-pooled DHO. HitObjectLifetimeEntry's InitialLifetimeOffset is different from DrawableHitObject's InitialLifetimeOffset. --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index ca1c601c1d..d0fa7eb22f 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -239,6 +239,11 @@ namespace osu.Game.Rulesets.Objects.Drawables lifetimeEntry = newEntry; + // LifetimeStart is already computed using HitObjectLifetimeEntry's InitialLifetimeOffset. + // We override this with DHO's InitialLifetimeOffset for a non-pooled DHO. + if (newEntry is SyntheticHitObjectEntry) + lifetimeEntry.LifetimeStart = HitObject.StartTime - InitialLifetimeOffset; + LifetimeStart = lifetimeEntry.LifetimeStart; LifetimeEnd = lifetimeEntry.LifetimeEnd; From 3fbeadf31847cd87aef622e7ddba08cbb253f9ec Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 21 Apr 2021 14:32:37 +0900 Subject: [PATCH 1690/1791] Deprecate old overload of Apply --- osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleApplication.cs | 2 +- osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs | 2 +- osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerApplication.cs | 2 +- osu.Game.Rulesets.Taiko.Tests/TestSceneBarLineApplication.cs | 4 ++-- .../TestSceneDrumRollApplication.cs | 4 ++-- osu.Game.Rulesets.Taiko.Tests/TestSceneHitApplication.cs | 4 ++-- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 5 +---- osu.Game/Rulesets/UI/Playfield.cs | 2 +- 8 files changed, 11 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleApplication.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleApplication.cs index 5fc1082743..8b3fead366 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleApplication.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleApplication.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Tests { Position = new Vector2(128, 128), ComboIndex = 1, - }), null)); + }))); } private HitCircle prepareObject(HitCircle circle) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs index aac6db60fe..e698766aac 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs @@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Tests new Vector2(300, 0), }), RepeatCount = 1 - }), null)); + }))); } [Test] diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerApplication.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerApplication.cs index d7fbc7ac48..8c97c02049 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerApplication.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerApplication.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Tests Position = new Vector2(256, 192), ComboIndex = 1, Duration = 1000, - }), null)); + }))); AddAssert("rotation is reset", () => dho.Result.RateAdjustedRotation == 0); } diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneBarLineApplication.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneBarLineApplication.cs index a970965141..f33c738b04 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneBarLineApplication.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneBarLineApplication.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Taiko.Tests { StartTime = 400, Major = true - }), null)); + }))); AddHitObject(barLine); RemoveHitObject(barLine); @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Taiko.Tests { StartTime = 200, Major = false - }), null)); + }))); AddHitObject(barLine); } } diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumRollApplication.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumRollApplication.cs index 54450e27db..c389a05566 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumRollApplication.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumRollApplication.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Taiko.Tests Duration = 500, IsStrong = false, TickRate = 2 - }), null)); + }))); AddHitObject(drumRoll); RemoveHitObject(drumRoll); @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Taiko.Tests Duration = 400, IsStrong = true, TickRate = 16 - }), null)); + }))); AddHitObject(drumRoll); } diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneHitApplication.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneHitApplication.cs index 52fd440857..c2f251fcb6 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneHitApplication.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneHitApplication.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Taiko.Tests Type = HitType.Rim, IsStrong = false, StartTime = 300 - }), null)); + }))); AddHitObject(hit); RemoveHitObject(hit); @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Taiko.Tests Type = HitType.Centre, IsStrong = true, StartTime = 500 - }), null)); + }))); AddHitObject(hit); } diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index d0fa7eb22f..ba2b8423d0 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -206,12 +206,9 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// Applies a hit object to be represented by this . /// - /// This overload is semi-deprecated. Use either or . + [Obsolete("Use either overload of Apply that takes a single argument of type HitObject or HitObjectLifetimeEntry")] public void Apply([NotNull] HitObject hitObject, [CanBeNull] HitObjectLifetimeEntry lifetimeEntry) { - if (lifetimeEntry != null && lifetimeEntry.HitObject != hitObject) - throw new InvalidOperationException($"{nameof(HitObjectLifetimeEntry)} has different {nameof(HitObject)} from the specified one."); - if (lifetimeEntry != null) Apply(lifetimeEntry); else diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index d55005363c..17d3cf01a4 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -362,7 +362,7 @@ namespace osu.Game.Rulesets.UI lifetimeEntryMap[hitObject] = entry = CreateLifetimeEntry(hitObject); dho.ParentHitObject = parent; - dho.Apply(hitObject, entry); + dho.Apply(entry); }); } From e90d791754776b5b5c51d42083145a665b9912c6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 21 Apr 2021 09:14:19 +0300 Subject: [PATCH 1691/1791] Add base "classic" mod --- osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs | 17 +-------------- osu.Game/Rulesets/Mods/ModClassic.cs | 24 +++++++++++++++++++++ 2 files changed, 25 insertions(+), 16 deletions(-) create mode 100644 osu.Game/Rulesets/Mods/ModClassic.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs index 882f848190..77dea5b0dc 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Bindables; -using osu.Framework.Graphics.Sprites; using osu.Game.Configuration; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; @@ -16,22 +15,8 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModClassic : Mod, IApplicableToHitObject, IApplicableToDrawableHitObjects, IApplicableToDrawableRuleset + public class OsuModClassic : ModClassic, IApplicableToHitObject, IApplicableToDrawableHitObjects, IApplicableToDrawableRuleset { - public override string Name => "Classic"; - - public override string Acronym => "CL"; - - public override double ScoreMultiplier => 1; - - public override IconUsage? Icon => FontAwesome.Solid.History; - - public override string Description => "Feeling nostalgic?"; - - public override bool Ranked => false; - - public override ModType Type => ModType.Conversion; - [SettingSource("No slider head accuracy requirement", "Scores sliders proportionally to the number of ticks hit.")] public Bindable NoSliderHeadAccuracy { get; } = new BindableBool(true); diff --git a/osu.Game/Rulesets/Mods/ModClassic.cs b/osu.Game/Rulesets/Mods/ModClassic.cs new file mode 100644 index 0000000000..f1207ec188 --- /dev/null +++ b/osu.Game/Rulesets/Mods/ModClassic.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.Sprites; + +namespace osu.Game.Rulesets.Mods +{ + public abstract class ModClassic : Mod + { + public override string Name => "Classic"; + + public override string Acronym => "CL"; + + public override double ScoreMultiplier => 1; + + public override IconUsage? Icon => FontAwesome.Solid.History; + + public override string Description => "Feeling nostalgic?"; + + public override bool Ranked => false; + + public override ModType Type => ModType.Conversion; + } +} From e3398d8f1fdfbbc57b6a3ae38e4ec9648f8505f6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 21 Apr 2021 09:14:31 +0300 Subject: [PATCH 1692/1791] Implement "classic" mod for all other legacy rulesets Currently empty, automatically handled in game to not be selectable (see `Mod.HasImplementation`) --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 1 + osu.Game.Rulesets.Catch/Mods/CatchModClassic.cs | 11 +++++++++++ osu.Game.Rulesets.Mania/ManiaRuleset.cs | 1 + osu.Game.Rulesets.Mania/Mods/ManiaModClassic.cs | 11 +++++++++++ osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs | 11 +++++++++++ osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 1 + 6 files changed, 36 insertions(+) create mode 100644 osu.Game.Rulesets.Catch/Mods/CatchModClassic.cs create mode 100644 osu.Game.Rulesets.Mania/Mods/ManiaModClassic.cs create mode 100644 osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index f4ddbd3021..ab877c21c1 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -114,6 +114,7 @@ namespace osu.Game.Rulesets.Catch return new Mod[] { new CatchModDifficultyAdjust(), + new CatchModClassic(), }; case ModType.Automation: diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModClassic.cs b/osu.Game.Rulesets.Catch/Mods/CatchModClassic.cs new file mode 100644 index 0000000000..9624e84018 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Mods/CatchModClassic.cs @@ -0,0 +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.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Catch.Mods +{ + public class CatchModClassic : ModClassic + { + } +} diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 88b63606b9..b3889bc7d3 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -239,6 +239,7 @@ namespace osu.Game.Rulesets.Mania new ManiaModDualStages(), new ManiaModMirror(), new ManiaModDifficultyAdjust(), + new ManiaModClassic(), new ManiaModInvert(), new ManiaModConstantSpeed() }; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModClassic.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModClassic.cs new file mode 100644 index 0000000000..073dda9de8 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModClassic.cs @@ -0,0 +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.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Mania.Mods +{ + public class ManiaModClassic : ModClassic + { + } +} diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs new file mode 100644 index 0000000000..5a4d18be98 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs @@ -0,0 +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.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Taiko.Mods +{ + public class TaikoModClassic : ModClassic + { + } +} diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 56f58f404b..f4e158ec32 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -135,6 +135,7 @@ namespace osu.Game.Rulesets.Taiko { new TaikoModRandom(), new TaikoModDifficultyAdjust(), + new TaikoModClassic(), }; case ModType.Automation: From 1a715b2926de6c3a9e3322474c4fe1f20ec11a5e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 21 Apr 2021 09:16:28 +0300 Subject: [PATCH 1693/1791] Append "classic" mod to legacy scores --- osu.Game/Scoring/ScoreInfo.cs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 222f69b025..bf131658ea 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -65,14 +65,19 @@ namespace osu.Game.Scoring { get { - if (mods != null) - return mods; - - if (localAPIMods == null) - return Array.Empty(); + Mod[] scoreMods = Array.Empty(); var rulesetInstance = Ruleset.CreateInstance(); - return apiMods.Select(m => m.ToMod(rulesetInstance)).ToArray(); + + if (mods != null) + scoreMods = mods; + else if (localAPIMods != null) + scoreMods = apiMods.Select(m => m.ToMod(rulesetInstance)).ToArray(); + + if (IsLegacyScore) + scoreMods = scoreMods.Append(rulesetInstance.GetAllMods().OfType().Single()).ToArray(); + + return scoreMods; } set { From eb20865c02898b77c2ad583e80c5108ae662d177 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Apr 2021 15:55:13 +0900 Subject: [PATCH 1694/1791] Show tablet preview with physical tablet counter-rotated for supplied user area selection Closes https://github.com/ppy/osu/issues/12399. Rotation animation is intentionally delayed slightly to give a better sense of what is going on (or maybe just look cool). --- .../Settings/Sections/Input/TabletAreaSelection.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs index f61742093c..b670e3558c 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs @@ -129,6 +129,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input rotation.BindTo(handler.Rotation); rotation.BindValueChanged(val => { + tabletContainer.RotateTo(-val.NewValue, 800, Easing.OutQuint); usableAreaContainer.RotateTo(val.NewValue, 100, Easing.OutQuint) .OnComplete(_ => checkBounds()); // required as we are using SSDQ. }); @@ -183,8 +184,10 @@ namespace osu.Game.Overlays.Settings.Sections.Input if (!(tablet.Value?.Size is Vector2 size)) return; - float fitX = size.X / (DrawWidth - Padding.Left - Padding.Right); - float fitY = size.Y / DrawHeight; + float maxDimension = size.LengthFast; + + float fitX = maxDimension / (DrawWidth - Padding.Left - Padding.Right); + float fitY = maxDimension / DrawHeight; float adjust = MathF.Max(fitX, fitY); From ab2a8b5c898b2f212c670ee6a3f9dd89e8fd1b53 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Apr 2021 16:12:09 +0900 Subject: [PATCH 1695/1791] Fix initial rotation not being set --- .../Overlays/Settings/Sections/Input/TabletAreaSelection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs index b670e3558c..412889d210 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs @@ -132,7 +132,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input tabletContainer.RotateTo(-val.NewValue, 800, Easing.OutQuint); usableAreaContainer.RotateTo(val.NewValue, 100, Easing.OutQuint) .OnComplete(_ => checkBounds()); // required as we are using SSDQ. - }); + }, true); tablet.BindTo(handler.Tablet); tablet.BindValueChanged(_ => Scheduler.AddOnce(updateTabletDetails)); From fb848f7544435470f330aec600b2fc14e5313936 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 21 Apr 2021 16:33:14 +0900 Subject: [PATCH 1696/1791] Rename to MasterGameplayClock --- .../Screens/Play/MasterGameplayClockContainer.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index affe24069d..fcbc6fae15 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -54,7 +54,7 @@ namespace osu.Game.Screens.Play private FramedOffsetClock userOffsetClock; private FramedOffsetClock platformOffsetClock; - private LocalGameplayClock localGameplayClock; + private MasterGameplayClock masterGameplayClock; private Bindable userAudioOffset; private double startOffset; @@ -157,7 +157,7 @@ namespace osu.Game.Screens.Play // the final usable gameplay clock with user-set offsets applied. userOffsetClock = new HardwareCorrectionOffsetClock(platformOffsetClock); - return localGameplayClock = new LocalGameplayClock(userOffsetClock); + return masterGameplayClock = new MasterGameplayClock(userOffsetClock); } /// @@ -180,8 +180,8 @@ namespace osu.Game.Screens.Play Track.AddAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); Track.AddAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); - localGameplayClock.MutableNonGameplayAdjustments.Add(pauseFreqAdjust); - localGameplayClock.MutableNonGameplayAdjustments.Add(UserPlaybackRate); + masterGameplayClock.MutableNonGameplayAdjustments.Add(pauseFreqAdjust); + masterGameplayClock.MutableNonGameplayAdjustments.Add(UserPlaybackRate); speedAdjustmentsApplied = true; } @@ -194,8 +194,8 @@ namespace osu.Game.Screens.Play Track.RemoveAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); Track.RemoveAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); - localGameplayClock.MutableNonGameplayAdjustments.Remove(pauseFreqAdjust); - localGameplayClock.MutableNonGameplayAdjustments.Remove(UserPlaybackRate); + masterGameplayClock.MutableNonGameplayAdjustments.Remove(pauseFreqAdjust); + masterGameplayClock.MutableNonGameplayAdjustments.Remove(UserPlaybackRate); speedAdjustmentsApplied = false; } @@ -218,13 +218,13 @@ namespace osu.Game.Screens.Play } } - private class LocalGameplayClock : GameplayClock + private class MasterGameplayClock : GameplayClock { public readonly List> MutableNonGameplayAdjustments = new List>(); public override IEnumerable> NonGameplayAdjustments => MutableNonGameplayAdjustments; - public LocalGameplayClock(FramedOffsetClock underlyingClock) + public MasterGameplayClock(FramedOffsetClock underlyingClock) : base(underlyingClock) { } From deeb9e3765294cdaa656a664b1eee0f31a73f8f2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Apr 2021 17:27:00 +0900 Subject: [PATCH 1697/1791] 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 ed2b27e1c7..3324af7c51 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 509cb3ddad..fcd1ed6987 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 4b67bd78a1..34810a3106 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From bbf2ec369b748dcb424f370178ec5402a5c46493 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 20 Apr 2021 17:13:59 +0900 Subject: [PATCH 1698/1791] Remove SkinReloadableDrawable inheritance from DHO --- .../Objects/Drawables/DrawableHitObject.cs | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index ba2b8423d0..1369623a62 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -11,6 +11,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Pooling; using osu.Framework.Graphics.Primitives; using osu.Framework.Threading; using osu.Game.Audio; @@ -25,7 +26,7 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Objects.Drawables { [Cached(typeof(DrawableHitObject))] - public abstract class DrawableHitObject : SkinReloadableDrawable + public abstract class DrawableHitObject : PoolableDrawable { /// /// Invoked after this 's applied has had its defaults applied. @@ -178,12 +179,15 @@ namespace osu.Game.Rulesets.Objects.Drawables } [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + private void load(OsuConfigManager config, ISkinSource skinSource) { config.BindWith(OsuSetting.PositionalHitSounds, userPositionalHitSounds); // Explicit non-virtual function call. base.AddInternal(Samples = new PausableSkinnableSound()); + + CurrentSkin = skinSource; + CurrentSkin.SourceChanged += onSkinSourceChanged; } protected override void LoadAsyncComplete() @@ -536,17 +540,19 @@ namespace osu.Game.Rulesets.Objects.Drawables #endregion - protected sealed override void SkinChanged(ISkinSource skin, bool allowFallback) - { - base.SkinChanged(skin, allowFallback); + #region Skinning + protected ISkinSource CurrentSkin { get; private set; } + + private void onSkinSourceChanged() => Scheduler.AddOnce(() => + { UpdateComboColour(); - ApplySkin(skin, allowFallback); + ApplySkin(CurrentSkin, true); if (IsLoaded) updateState(State.Value, true); - } + }); protected void UpdateComboColour() { @@ -616,6 +622,8 @@ namespace osu.Game.Rulesets.Objects.Drawables Samples.Stop(); } + #endregion + protected override void Update() { base.Update(); @@ -811,6 +819,8 @@ namespace osu.Game.Rulesets.Objects.Drawables if (HitObject != null) HitObject.DefaultsApplied -= onDefaultsApplied; + + CurrentSkin.SourceChanged -= onSkinSourceChanged; } } From b877a2973739c4cb8a32393bbb021392f536be82 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 20 Apr 2021 17:55:01 +0900 Subject: [PATCH 1699/1791] Factor out pooling and lifetime management logic of DHO to a base class --- .../Objects/Drawables/DrawableHitObject.cs | 116 +++-------------- .../Objects/Pooling/DrawableObject.cs | 121 ++++++++++++++++++ 2 files changed, 141 insertions(+), 96 deletions(-) create mode 100644 osu.Game/Rulesets/Objects/Pooling/DrawableObject.cs diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 1369623a62..312ed93e45 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -11,7 +11,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Pooling; using osu.Framework.Graphics.Primitives; using osu.Framework.Threading; using osu.Game.Audio; @@ -20,13 +19,14 @@ using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; using osu.Game.Configuration; +using osu.Game.Rulesets.Objects.Pooling; using osu.Game.Rulesets.UI; using osuTK.Graphics; namespace osu.Game.Rulesets.Objects.Drawables { [Cached(typeof(DrawableHitObject))] - public abstract class DrawableHitObject : PoolableDrawable + public abstract class DrawableHitObject : DrawableObject { /// /// Invoked after this 's applied has had its defaults applied. @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// The currently represented by this . /// - public HitObject HitObject => lifetimeEntry?.HitObject; + public HitObject HitObject => Entry?.HitObject; /// /// The parenting , if any. @@ -110,7 +110,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// The scoring result of this . /// - public JudgementResult Result => lifetimeEntry?.Result; + public JudgementResult Result => Entry?.Result; /// /// The relative X position of this hit object for sample playback balance adjustment. @@ -126,8 +126,6 @@ namespace osu.Game.Rulesets.Objects.Drawables private readonly Bindable userPositionalHitSounds = new Bindable(); private readonly Bindable comboIndexBindable = new Bindable(); - public override bool RemoveWhenNotAlive => false; - public override bool RemoveCompletedTransforms => false; protected override bool RequiresChildrenUpdate => true; public override bool IsPresent => base.IsPresent || (State.Value == ArmedState.Idle && Clock?.CurrentTime >= LifetimeStart); @@ -142,18 +140,6 @@ namespace osu.Game.Rulesets.Objects.Drawables /// public IBindable State => state; - /// - /// Whether a is currently applied. - /// - private bool hasEntryApplied; - - /// - /// The controlling the lifetime of the currently-attached . - /// - /// Even if it is not null, it may not be fully applied until loaded ( is false). - [CanBeNull] - private HitObjectLifetimeEntry lifetimeEntry; - [Resolved(CanBeNull = true)] private IPooledHitObjectProvider pooledObjectProvider { get; set; } @@ -167,15 +153,13 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// /// The to be initially applied to this . - /// If null, a hitobject is expected to be later applied via (or automatically via pooling). + /// If null, a hitobject is expected to be later applied via (or automatically via pooling). /// protected DrawableHitObject([CanBeNull] HitObject initialHitObject = null) + : base(initialHitObject != null ? new SyntheticHitObjectEntry(initialHitObject) : null) { - if (initialHitObject != null) - { - lifetimeEntry = new SyntheticHitObjectEntry(initialHitObject); + if (Entry != null) ensureEntryHasResult(); - } } [BackgroundDependencyLoader] @@ -190,14 +174,6 @@ namespace osu.Game.Rulesets.Objects.Drawables CurrentSkin.SourceChanged += onSkinSourceChanged; } - protected override void LoadAsyncComplete() - { - base.LoadAsyncComplete(); - - if (lifetimeEntry != null && !hasEntryApplied) - Apply(lifetimeEntry); - } - protected override void LoadComplete() { base.LoadComplete(); @@ -231,22 +207,15 @@ namespace osu.Game.Rulesets.Objects.Drawables Apply(new SyntheticHitObjectEntry(hitObject)); } - /// - /// Applies a new to be represented by this . - /// - public void Apply([NotNull] HitObjectLifetimeEntry newEntry) + protected sealed override void OnApply(HitObjectLifetimeEntry entry) { - free(); - - lifetimeEntry = newEntry; - // LifetimeStart is already computed using HitObjectLifetimeEntry's InitialLifetimeOffset. // We override this with DHO's InitialLifetimeOffset for a non-pooled DHO. - if (newEntry is SyntheticHitObjectEntry) - lifetimeEntry.LifetimeStart = HitObject.StartTime - InitialLifetimeOffset; + if (entry is SyntheticHitObjectEntry) + entry.LifetimeStart = HitObject.StartTime - InitialLifetimeOffset; - LifetimeStart = lifetimeEntry.LifetimeStart; - LifetimeEnd = lifetimeEntry.LifetimeEnd; + LifetimeStart = entry.LifetimeStart; + LifetimeEnd = entry.LifetimeEnd; ensureEntryHasResult(); @@ -297,17 +266,10 @@ namespace osu.Game.Rulesets.Objects.Drawables else updateState(ArmedState.Idle, true); } - - hasEntryApplied = true; } - /// - /// Removes the currently applied - /// - private void free() + protected sealed override void OnFree(HitObjectLifetimeEntry entry) { - if (!hasEntryApplied) return; - StartTimeBindable.UnbindFrom(HitObject.StartTimeBindable); if (HitObject is IHasComboInformation combo) comboIndexBindable.UnbindFrom(combo.ComboIndexBindable); @@ -339,22 +301,8 @@ namespace osu.Game.Rulesets.Objects.Drawables OnFree(); ParentHitObject = null; - lifetimeEntry = null; clearExistingStateTransforms(); - - hasEntryApplied = false; - } - - protected sealed override void FreeAfterUse() - { - base.FreeAfterUse(); - - // Freeing while not in a pool would cause the DHO to not be usable elsewhere in the hierarchy without being re-applied. - if (!IsInPool) - return; - - free(); } /// @@ -402,8 +350,8 @@ namespace osu.Game.Rulesets.Objects.Drawables private void onDefaultsApplied(HitObject hitObject) { - Debug.Assert(lifetimeEntry != null); - Apply(lifetimeEntry); + Debug.Assert(Entry != null); + Apply(Entry); DefaultsApplied?.Invoke(this); } @@ -486,7 +434,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// Apply (generally fade-in) transforms leading into the start time. - /// The local drawable hierarchy is recursively delayed to for convenience. + /// The local drawable hierarchy is recursively delayed to for convenience. /// /// By default this will fade in the object from zero with no duration. /// @@ -661,30 +609,6 @@ namespace osu.Game.Rulesets.Objects.Drawables /// protected internal new ScheduledDelegate Schedule(Action action) => base.Schedule(action); - public override double LifetimeStart - { - get => base.LifetimeStart; - set => setLifetime(value, LifetimeEnd); - } - - public override double LifetimeEnd - { - get => base.LifetimeEnd; - set => setLifetime(LifetimeStart, value); - } - - private void setLifetime(double lifetimeStart, double lifetimeEnd) - { - base.LifetimeStart = lifetimeStart; - base.LifetimeEnd = lifetimeEnd; - - if (lifetimeEntry != null) - { - lifetimeEntry.LifetimeStart = lifetimeStart; - lifetimeEntry.LifetimeEnd = lifetimeEnd; - } - } - /// /// A safe offset prior to the start time of at which this may begin displaying contents. /// By default, s are assumed to display their contents within 10 seconds prior to the start time of . @@ -692,7 +616,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// This is only used as an optimisation to delay the initial update of this and may be tuned more aggressively if required. /// It is indirectly used to decide the automatic transform offset provided to . - /// A more accurate should be set for further optimisation (in , for example). + /// A more accurate should be set for further optimisation (in , for example). /// /// Only has an effect if this is not being pooled. /// For pooled s, use instead. @@ -808,9 +732,9 @@ namespace osu.Game.Rulesets.Objects.Drawables private void ensureEntryHasResult() { - Debug.Assert(lifetimeEntry != null); - lifetimeEntry.Result ??= CreateResult(HitObject.CreateJudgement()) - ?? throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}."); + Debug.Assert(Entry != null); + Entry.Result ??= CreateResult(HitObject.CreateJudgement()) + ?? throw new InvalidOperationException($"{GetType().ReadableName()} must provide a {nameof(JudgementResult)} through {nameof(CreateResult)}."); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Rulesets/Objects/Pooling/DrawableObject.cs b/osu.Game/Rulesets/Objects/Pooling/DrawableObject.cs new file mode 100644 index 0000000000..b29e6a6c3c --- /dev/null +++ b/osu.Game/Rulesets/Objects/Pooling/DrawableObject.cs @@ -0,0 +1,121 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using System.Diagnostics; +using osu.Framework.Graphics.Performance; +using osu.Framework.Graphics.Pooling; + +namespace osu.Game.Rulesets.Objects.Pooling +{ + /// + /// A that is controlled by to implement drawable pooling and replay rewinding. + /// + /// The type storing state and controlling this drawable. + public abstract class DrawableObject : PoolableDrawable where TEntry : LifetimeEntry + { + /// + /// The entry holding essential state of this . + /// + protected TEntry? Entry { get; private set; } + + /// + /// Whether is applied to this . + /// When an initial entry is specified in the constructor, is set but not applied until loading is completed. + /// + protected bool HasEntryApplied { get; private set; } + + public override double LifetimeStart + { + get => base.LifetimeStart; + set => setLifetime(value, LifetimeEnd); + } + + public override double LifetimeEnd + { + get => base.LifetimeEnd; + set => setLifetime(LifetimeStart, value); + } + + public override bool RemoveWhenNotAlive => false; + public override bool RemoveCompletedTransforms => false; + + protected DrawableObject(TEntry? initialEntry = null) + { + Entry = initialEntry; + } + + protected override void LoadAsyncComplete() + { + base.LoadAsyncComplete(); + + if (Entry != null && !HasEntryApplied) + Apply(Entry); + } + + /// + /// Applies a new entry to be represented by this drawable. + /// If there is an existing entry applied, the entry will be replaced. + /// + public void Apply(TEntry entry) + { + freeIfInUse(); + + setLifetime(entry.LifetimeStart, entry.LifetimeEnd); + Entry = entry; + + OnApply(entry); + + HasEntryApplied = true; + } + + protected sealed override void FreeAfterUse() + { + base.FreeAfterUse(); + + if (IsInPool) + freeIfInUse(); + } + + /// + /// Invoked to apply a new entry to this drawable. + /// + protected virtual void OnApply(TEntry entry) + { + } + + /// + /// Invoked to revert application of the entry to this drawable. + /// + protected virtual void OnFree(TEntry entry) + { + } + + private void setLifetime(double start, double end) + { + base.LifetimeStart = start; + base.LifetimeEnd = end; + + if (Entry != null) + { + Entry.LifetimeStart = start; + Entry.LifetimeEnd = end; + } + } + + private void freeIfInUse() + { + if (!HasEntryApplied) return; + + Debug.Assert(Entry != null); + + OnFree(Entry); + + Entry = null; + setLifetime(double.MaxValue, double.MaxValue); + + HasEntryApplied = false; + } + } +} From c6c91cd9a5ce76554b277b0e7520e0aeb4312cef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Apr 2021 18:05:26 +0900 Subject: [PATCH 1700/1791] Refactor `WaveformOpacityMenuItem` to not receive whole config --- osu.Game/Screens/Edit/Editor.cs | 2 +- .../{WaveformOpacityMenu.cs => WaveformOpacityMenuItem.cs} | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) rename osu.Game/Screens/Edit/{WaveformOpacityMenu.cs => WaveformOpacityMenuItem.cs} (85%) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index fffea65456..360fbb36db 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -226,7 +226,7 @@ namespace osu.Game.Screens.Edit { Items = new[] { - new WaveformOpacityMenu(config) + new WaveformOpacityMenuItem(config.GetBindable(OsuSetting.EditorWaveformOpacity)), } } } diff --git a/osu.Game/Screens/Edit/WaveformOpacityMenu.cs b/osu.Game/Screens/Edit/WaveformOpacityMenuItem.cs similarity index 85% rename from osu.Game/Screens/Edit/WaveformOpacityMenu.cs rename to osu.Game/Screens/Edit/WaveformOpacityMenuItem.cs index 5d209ae141..053c2fa4b0 100644 --- a/osu.Game/Screens/Edit/WaveformOpacityMenu.cs +++ b/osu.Game/Screens/Edit/WaveformOpacityMenuItem.cs @@ -4,18 +4,17 @@ using System.Collections.Generic; using osu.Framework.Bindables; using osu.Framework.Graphics.UserInterface; -using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; namespace osu.Game.Screens.Edit { - internal class WaveformOpacityMenu : MenuItem + internal class WaveformOpacityMenuItem : MenuItem { private readonly Bindable waveformOpacity; private readonly Dictionary menuItemLookup = new Dictionary(); - public WaveformOpacityMenu(OsuConfigManager config) + public WaveformOpacityMenuItem(Bindable waveformOpacity) : base("Waveform opacity") { Items = new[] @@ -26,7 +25,7 @@ namespace osu.Game.Screens.Edit createMenuItem(1f), }; - waveformOpacity = config.GetBindable(OsuSetting.EditorWaveformOpacity); + this.waveformOpacity = waveformOpacity; waveformOpacity.BindValueChanged(opacity => { foreach (var kvp in menuItemLookup) From 9d8f0c854d1bce0763c1e7e4113321a8e0646944 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Apr 2021 18:05:40 +0900 Subject: [PATCH 1701/1791] Setup configuration item for editor hit animations --- osu.Game/Configuration/OsuConfigManager.cs | 2 ++ osu.Game/Screens/Edit/Editor.cs | 3 ++- .../Screens/Edit/HitAnimationsMenuItem.cs | 21 +++++++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Screens/Edit/HitAnimationsMenuItem.cs diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index f9b1c9618b..09412b1f1b 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -143,6 +143,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.DiscordRichPresence, DiscordRichPresenceMode.Full); SetDefault(OsuSetting.EditorWaveformOpacity, 0.25f); + SetDefault(OsuSetting.EditorHitAnimations, false); } public OsuConfigManager(Storage storage) @@ -266,6 +267,7 @@ namespace osu.Game.Configuration GameplayDisableWinKey, SeasonalBackgroundMode, EditorWaveformOpacity, + EditorHitAnimations, DiscordRichPresence, AutomaticallyDownloadWhenSpectating, ShowOnlineExplicitContent, diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 360fbb36db..da0e9ebbaf 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -224,9 +224,10 @@ namespace osu.Game.Screens.Edit }, new MenuItem("View") { - Items = new[] + Items = new MenuItem[] { new WaveformOpacityMenuItem(config.GetBindable(OsuSetting.EditorWaveformOpacity)), + new HitAnimationsMenuItem(config.GetBindable(OsuSetting.EditorHitAnimations)) } } } diff --git a/osu.Game/Screens/Edit/HitAnimationsMenuItem.cs b/osu.Game/Screens/Edit/HitAnimationsMenuItem.cs new file mode 100644 index 0000000000..fb7ab39f7a --- /dev/null +++ b/osu.Game/Screens/Edit/HitAnimationsMenuItem.cs @@ -0,0 +1,21 @@ +// 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.Bindables; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Screens.Edit +{ + internal class HitAnimationsMenuItem : ToggleMenuItem + { + [UsedImplicitly] + private readonly Bindable hitAnimations; + + public HitAnimationsMenuItem(Bindable hitAnimations) + : base("Hit animations") + { + State.BindTo(this.hitAnimations = hitAnimations); + } + } +} From 47a4a07024b6557d475b92f1356ee15c0828376c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Apr 2021 19:15:10 +0900 Subject: [PATCH 1702/1791] Split out animation triggering of `MainCirclePiece` to be interface driven --- .../Objects/Drawables/DrawableHitCircle.cs | 2 ++ .../Skinning/Default/IMainCirclePiece.cs | 17 +++++++++++++++++ .../Skinning/Default/MainCirclePiece.cs | 7 ++----- .../Skinning/Legacy/LegacyMainCirclePiece.cs | 8 +++----- 4 files changed, 24 insertions(+), 10 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Skinning/Default/IMainCirclePiece.cs diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index df77ec2693..fb6c110b3c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -182,6 +182,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables // todo: temporary / arbitrary, used for lifetime optimisation. this.Delay(800).FadeOut(); + (CirclePiece.Drawable as IMainCirclePiece)?.Animate(state); + switch (state) { case ArmedState.Idle: diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/IMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Default/IMainCirclePiece.cs new file mode 100644 index 0000000000..17a1e29094 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/Default/IMainCirclePiece.cs @@ -0,0 +1,17 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables; + +namespace osu.Game.Rulesets.Osu.Skinning.Default +{ + public interface IMainCirclePiece + { + /// + /// Begins animating this . + /// + /// The of the related . + void Animate(ArmedState state); + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/MainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Default/MainCirclePiece.cs index 46aeadc59b..b46baa00ba 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/MainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/MainCirclePiece.cs @@ -13,7 +13,7 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Skinning.Default { - public class MainCirclePiece : CompositeDrawable + public class MainCirclePiece : CompositeDrawable, IMainCirclePiece { private readonly CirclePiece circle; private readonly RingPiece ring; @@ -67,12 +67,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default }, true); indexInCurrentCombo.BindValueChanged(index => number.Text = (index.NewValue + 1).ToString(), true); - - drawableObject.ApplyCustomUpdateState += updateState; - updateState(drawableObject, drawableObject.State.Value); } - private void updateState(DrawableHitObject drawableObject, ArmedState state) + public void Animate(ArmedState state) { using (BeginAbsoluteSequence(drawableObject.StateUpdateTime)) glow.FadeOut(400); diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs index 545e80a709..cf62165929 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs @@ -12,6 +12,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.Skinning.Default; using osu.Game.Skinning; using osuTK; using osuTK.Graphics; @@ -19,7 +20,7 @@ using static osu.Game.Skinning.LegacySkinConfiguration; namespace osu.Game.Rulesets.Osu.Skinning.Legacy { - public class LegacyMainCirclePiece : CompositeDrawable + public class LegacyMainCirclePiece : CompositeDrawable, IMainCirclePiece { private readonly string priorityLookup; private readonly bool hasNumber; @@ -138,12 +139,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy accentColour.BindValueChanged(colour => hitCircleSprite.Colour = LegacyColourCompatibility.DisallowZeroAlpha(colour.NewValue), true); if (hasNumber) indexInCurrentCombo.BindValueChanged(index => hitCircleText.Text = (index.NewValue + 1).ToString(), true); - - drawableObject.ApplyCustomUpdateState += updateState; - updateState(drawableObject, drawableObject.State.Value); } - private void updateState(DrawableHitObject drawableObject, ArmedState state) + public void Animate(ArmedState state) { const double legacy_fade_duration = 240; From f2824a222a13f924df12f4479ccf3b8d68e5f6b9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Apr 2021 19:41:15 +0900 Subject: [PATCH 1703/1791] Adjust existing fades to close match stable editor --- osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs index 5fdb79cbbd..dfb71d1d22 100644 --- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs +++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs @@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Osu.Edit case DrawableHitCircle circle: // also handles slider heads circle.ApproachCircle - .FadeOutFromOne(editor_hit_object_fade_out_extension) + .FadeOutFromOne(editor_hit_object_fade_out_extension * 4) .Expire(); break; } @@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Osu.Edit hitObject.RemoveTransform(existing); - using (hitObject.BeginAbsoluteSequence(existing.StartTime)) + using (hitObject.BeginAbsoluteSequence(hitObject.HitStateUpdateTime)) hitObject.FadeOut(editor_hit_object_fade_out_extension).Expire(); } } From 0f70469d1c33f392def6ae1070f51cdad941643c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Apr 2021 19:44:17 +0900 Subject: [PATCH 1704/1791] Only apply custom editor overrides if hit animations is disabled --- .../Edit/DrawableOsuEditRuleset.cs | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs index dfb71d1d22..b8d0637e90 100644 --- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs +++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs @@ -3,8 +3,11 @@ using System.Collections.Generic; using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; @@ -27,8 +30,16 @@ namespace osu.Game.Rulesets.Osu.Edit private class OsuEditPlayfield : OsuPlayfield { + private Bindable hitAnimations; + protected override GameplayCursorContainer CreateCursor() => null; + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + hitAnimations = config.GetBindable(OsuSetting.EditorHitAnimations); + } + protected override void OnNewDrawableHitObject(DrawableHitObject d) { d.ApplyCustomUpdateState += updateState; @@ -42,7 +53,7 @@ namespace osu.Game.Rulesets.Osu.Edit private void updateState(DrawableHitObject hitObject, ArmedState state) { - if (state == ArmedState.Idle) + if (state == ArmedState.Idle || hitAnimations.Value) return; // adjust the visuals of certain object types to make them stay on screen for longer than usual. @@ -60,6 +71,15 @@ namespace osu.Game.Rulesets.Osu.Edit circle.ApproachCircle .FadeOutFromOne(editor_hit_object_fade_out_extension * 4) .Expire(); + + circle.ApproachCircle.ScaleTo(1.1f, 300, Easing.OutQuint); + + var circlePieceDrawable = circle.CirclePiece.Drawable; + + // clear any explode animation logic. + circlePieceDrawable.ApplyTransformsAt(circle.HitStateUpdateTime, true); + circlePieceDrawable.ClearTransformsAfter(circle.HitStateUpdateTime, true); + break; } From 60b702549dd2bfc42f21a73831396edf9c7dedac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Apr 2021 14:20:49 +0900 Subject: [PATCH 1705/1791] 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 3324af7c51..7b97103851 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 fcd1ed6987..b27e00f59d 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 34810a3106..d07e16ea56 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 21f34be19fe58a3cfaca1f2182dc1054aed0aca4 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 22 Apr 2021 14:42:10 +0900 Subject: [PATCH 1706/1791] Add support for per-ruleset sample playback when switching rulesets (via toolbar) --- .../Overlays/Toolbar/ToolbarRulesetSelector.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs index 905d5b44c6..eb235632e8 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; @@ -13,6 +14,8 @@ using osu.Framework.Input.Events; using osuTK.Input; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; namespace osu.Game.Overlays.Toolbar { @@ -20,6 +23,8 @@ namespace osu.Game.Overlays.Toolbar { protected Drawable ModeButtonLine { get; private set; } + private readonly Dictionary selectionSamples = new Dictionary(); + public ToolbarRulesetSelector() { RelativeSizeAxes = Axes.Y; @@ -27,7 +32,7 @@ namespace osu.Game.Overlays.Toolbar } [BackgroundDependencyLoader] - private void load() + private void load(AudioManager audio) { AddRangeInternal(new[] { @@ -54,6 +59,9 @@ namespace osu.Game.Overlays.Toolbar } } }); + + foreach (var ruleset in Rulesets.AvailableRulesets) + selectionSamples[ruleset.ShortName] = audio.Samples.Get($"UI/ruleset-select-{ruleset.ShortName}"); } protected override void LoadComplete() @@ -72,6 +80,10 @@ namespace osu.Game.Overlays.Toolbar if (SelectedTab != null) { ModeButtonLine.MoveToX(SelectedTab.DrawPosition.X, !hasInitialPosition ? 0 : 200, Easing.OutQuint); + + if (hasInitialPosition) + selectionSamples[SelectedTab.Value.ShortName]?.Play(); + hasInitialPosition = true; } }); From ea3bb07924f6be8126dc8bc1894478db9f00ba82 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Apr 2021 14:51:14 +0900 Subject: [PATCH 1707/1791] Add test that fails on incorrect system/info message ordering --- .../Online/TestSceneStandAloneChatDisplay.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs index 01e67b1681..165fff99dd 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs @@ -83,6 +83,28 @@ namespace osu.Game.Tests.Visual.Online }; }); + [Test] + public void TestSystemMessageOrdering() + { + var standardMessage = new Message(messageIdSequence++) + { + Sender = admin, + Content = "I am a wang!" + }; + + var infoMessage1 = new InfoMessage($"the system is calling {messageIdSequence++}"); + var infoMessage2 = new InfoMessage($"the system is calling {messageIdSequence++}"); + + AddStep("message from admin", () => testChannel.AddNewMessages(standardMessage)); + AddStep("message from system", () => testChannel.AddNewMessages(infoMessage1)); + AddStep("message from system", () => testChannel.AddNewMessages(infoMessage2)); + + AddAssert("message order is correct", () => testChannel.Messages.Count == 3 + && testChannel.Messages[0] == standardMessage + && testChannel.Messages[1] == infoMessage1 + && testChannel.Messages[2] == infoMessage2); + } + [Test] public void TestManyMessages() { From 3befb49ea9e5ed6420c053ba4b07bd58f56fac75 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Apr 2021 14:51:57 +0900 Subject: [PATCH 1708/1791] Fix system messages always being displayed above standard messages Closes https://github.com/ppy/osu/issues/12509. --- osu.Game/Online/Chat/InfoMessage.cs | 4 +--- osu.Game/Online/Chat/Message.cs | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/Chat/InfoMessage.cs b/osu.Game/Online/Chat/InfoMessage.cs index 8dce188804..cea336aae2 100644 --- a/osu.Game/Online/Chat/InfoMessage.cs +++ b/osu.Game/Online/Chat/InfoMessage.cs @@ -8,10 +8,8 @@ namespace osu.Game.Online.Chat { public class InfoMessage : LocalMessage { - private static int infoID = -1; - public InfoMessage(string message) - : base(infoID--) + : base(null) { Timestamp = DateTimeOffset.Now; Content = message; diff --git a/osu.Game/Online/Chat/Message.cs b/osu.Game/Online/Chat/Message.cs index 2e41038a59..30753b3920 100644 --- a/osu.Game/Online/Chat/Message.cs +++ b/osu.Game/Online/Chat/Message.cs @@ -59,7 +59,7 @@ namespace osu.Game.Online.Chat return Id.Value.CompareTo(other.Id.Value); } - public virtual bool Equals(Message other) => Id == other?.Id; + public virtual bool Equals(Message other) => Id.HasValue && Id == other?.Id; // ReSharper disable once ImpureMethodCallOnReadonlyValueField public override int GetHashCode() => Id.GetHashCode(); From b37c5a8749422ccdd93caeeea815aa5013f26353 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Apr 2021 14:59:57 +0900 Subject: [PATCH 1709/1791] Rollback hold note placement when length is zero --- .../Edit/Blueprints/HoldNotePlacementBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs index a13afdfffe..093a8da24f 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints return; base.OnMouseUp(e); - EndPlacement(true); + EndPlacement(HitObject.Duration > 0); } private double originalStartTime; From cf1e3ea98873897de95984f79846ca7282a7581f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Apr 2021 15:41:21 +0900 Subject: [PATCH 1710/1791] Add failing test covering quick shift-rightclick deletion in placement mode --- .../Editing/TestSceneEditorSelection.cs | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSelection.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSelection.cs index 99f31b0c2a..c9b6d2e376 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSelection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSelection.cs @@ -12,6 +12,7 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; +using osu.Game.Rulesets.Osu.UI; using osu.Game.Tests.Beatmaps; using osu.Game.Screens.Edit.Compose.Components; using osuTK; @@ -156,9 +157,35 @@ namespace osu.Game.Tests.Visual.Editing } [Test] - public void TestQuickDeleteRemovesObject() + public void TestQuickDeleteRemovesObjectInPlacement() { - var addedObject = new HitCircle { StartTime = 1000 }; + var addedObject = new HitCircle + { + StartTime = 0, + Position = OsuPlayfield.BASE_SIZE * 0.5f + }; + + AddStep("add hitobject", () => EditorBeatmap.Add(addedObject)); + + AddStep("enter placement mode", () => InputManager.PressKey(Key.Number2)); + + moveMouseToObject(() => addedObject); + + AddStep("hold shift", () => InputManager.PressKey(Key.ShiftLeft)); + AddStep("right click", () => InputManager.Click(MouseButton.Right)); + AddStep("release shift", () => InputManager.ReleaseKey(Key.ShiftLeft)); + + AddAssert("no hitobjects in beatmap", () => EditorBeatmap.HitObjects.Count == 0); + } + + [Test] + public void TestQuickDeleteRemovesObjectInSelection() + { + var addedObject = new HitCircle + { + StartTime = 0, + Position = OsuPlayfield.BASE_SIZE * 0.5f + }; AddStep("add hitobject", () => EditorBeatmap.Add(addedObject)); From 9a7bf8109ff7029ad76e2d60b389d660d062ec57 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Apr 2021 15:27:08 +0900 Subject: [PATCH 1711/1791] Allow certain mouse input to pass through `PlacementBlueprints` to the selection logic --- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index 6c1cd01796..4ad8c815fe 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -14,6 +14,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose; using osuTK; +using osuTK.Input; namespace osu.Game.Rulesets.Edit { @@ -128,8 +129,11 @@ namespace osu.Game.Rulesets.Edit case DoubleClickEvent _: return false; - case MouseButtonEvent _: - return true; + case MouseButtonEvent mouse: + // placement blueprints should generally block mouse from reaching underlying components (ie. performing clicks on interface buttons). + // for now, the one exception we want to allow is when using a non-main mouse button when shift is pressed, which is used to trigger object deletion + // while in placement mode. + return mouse.Button == MouseButton.Left || !mouse.ShiftPressed; default: return false; From 3e1002fbf36650b9a91a6a04df9d15c0d220f8f4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Apr 2021 17:06:08 +0900 Subject: [PATCH 1712/1791] Improve osu!catch caught fruit placement algorithm --- osu.Game.Rulesets.Catch/UI/Catcher.cs | 29 +++++++++++++++++---------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index d045dcf16a..8edc5d0eb2 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -53,6 +53,11 @@ namespace osu.Game.Rulesets.Catch.UI /// public const double BASE_SPEED = 1.0; + /// + /// The amount by which caught fruit should be scaled down to fit on the plate. + /// + private const float caught_fruit_scale_adjust = 0.5f; + [NotNull] private readonly Container trailsTarget; @@ -240,7 +245,7 @@ namespace osu.Game.Rulesets.Catch.UI if (result.IsHit) { - var positionInStack = computePositionInStack(new Vector2(palpableObject.X - X, 0), palpableObject.DisplaySize.X / 2); + var positionInStack = computePositionInStack(new Vector2(palpableObject.X - X, 0), palpableObject.DisplaySize.X); if (CatchFruitOnPlate) placeCaughtObject(palpableObject, positionInStack); @@ -470,7 +475,7 @@ namespace osu.Game.Rulesets.Catch.UI caughtObject.CopyStateFrom(drawableObject); caughtObject.Anchor = Anchor.TopCentre; caughtObject.Position = position; - caughtObject.Scale /= 2; + caughtObject.Scale *= caught_fruit_scale_adjust; caughtObjectContainer.Add(caughtObject); @@ -480,19 +485,21 @@ namespace osu.Game.Rulesets.Catch.UI private Vector2 computePositionInStack(Vector2 position, float displayRadius) { - const float radius_div_2 = CatchHitObject.OBJECT_RADIUS / 2; - const float allowance = 10; + // this is taken from osu-stable (lenience should be 10 * 10 at standard scale). + const float lenience_adjust = 10 / CatchHitObject.OBJECT_RADIUS; - while (caughtObjectContainer.Any(f => Vector2Extensions.Distance(f.Position, position) < (displayRadius + radius_div_2) / (allowance / 2))) + float adjustedRadius = displayRadius * lenience_adjust; + float checkDistance = MathF.Pow(adjustedRadius, 2); + + // offset fruit vertically to better place "above" the plate. + position.Y -= 5; + + while (caughtObjectContainer.Any(f => Vector2Extensions.DistanceSquared(f.Position, position) < checkDistance)) { - float diff = (displayRadius + radius_div_2) / allowance; - - position.X += (RNG.NextSingle() - 0.5f) * diff * 2; - position.Y -= RNG.NextSingle() * diff; + position.X += RNG.NextSingle(-adjustedRadius, adjustedRadius); + position.Y -= RNG.NextSingle(0, 5); } - position.X = Math.Clamp(position.X, -CatcherArea.CATCHER_SIZE / 2, CatcherArea.CATCHER_SIZE / 2); - return position; } From 84a713822306f5a6dd4897b58e3894f6b669190f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Apr 2021 16:56:23 +0900 Subject: [PATCH 1713/1791] Update tests to better support stack regeneration cases --- .../TestSceneCatcher.cs | 33 ++++++++++++++----- osu.Game.Rulesets.Catch/UI/Catcher.cs | 7 +++- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs index 48efd73222..f8e278a486 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs @@ -10,6 +10,7 @@ using osu.Game.Rulesets.Catch.UI; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Configuration; @@ -170,16 +171,25 @@ namespace osu.Game.Rulesets.Catch.Tests } [Test] - public void TestCatcherStacking() + public void TestCatcherRandomStacking() + { + AddStep("catch more fruits", () => attemptCatch(() => new Fruit + { + X = (RNG.NextSingle() - 0.5f) * CatcherArea.CATCHER_SIZE + }, 50)); + } + + [Test] + public void TestCatcherStackingSameCaughtPosition() { AddStep("catch fruit", () => attemptCatch(new Fruit())); checkPlate(1); - AddStep("catch more fruits", () => attemptCatch(new Fruit(), 9)); + AddStep("catch more fruits", () => attemptCatch(() => new Fruit(), 9)); checkPlate(10); AddAssert("caught objects are stacked", () => - catcher.CaughtObjects.All(obj => obj.Y <= 0) && - catcher.CaughtObjects.Any(obj => obj.Y == 0) && - catcher.CaughtObjects.Any(obj => obj.Y < -20)); + catcher.CaughtObjects.All(obj => obj.Y <= Catcher.CAUGHT_FRUIT_VERTICAL_OFFSET) && + catcher.CaughtObjects.Any(obj => obj.Y == Catcher.CAUGHT_FRUIT_VERTICAL_OFFSET) && + catcher.CaughtObjects.Any(obj => obj.Y < -25)); } [Test] @@ -189,11 +199,11 @@ namespace osu.Game.Rulesets.Catch.Tests AddStep("catch tiny droplet", () => attemptCatch(new TinyDroplet())); AddAssert("tiny droplet is exploded", () => catcher.CaughtObjects.Count() == 1 && droppedObjectContainer.Count == 1); AddUntilStep("wait explosion", () => !droppedObjectContainer.Any()); - AddStep("catch more fruits", () => attemptCatch(new Fruit(), 9)); + AddStep("catch more fruits", () => attemptCatch(() => new Fruit(), 9)); AddStep("explode", () => catcher.Explode()); AddAssert("fruits are exploded", () => !catcher.CaughtObjects.Any() && droppedObjectContainer.Count == 10); AddUntilStep("wait explosion", () => !droppedObjectContainer.Any()); - AddStep("catch fruits", () => attemptCatch(new Fruit(), 10)); + AddStep("catch fruits", () => attemptCatch(() => new Fruit(), 10)); AddStep("drop", () => catcher.Drop()); AddAssert("fruits are dropped", () => !catcher.CaughtObjects.Any() && droppedObjectContainer.Count == 10); } @@ -222,10 +232,15 @@ namespace osu.Game.Rulesets.Catch.Tests private void checkHyperDash(bool state) => AddAssert($"catcher is {(state ? "" : "not ")}hyper dashing", () => catcher.HyperDashing == state); - private void attemptCatch(CatchHitObject hitObject, int count = 1) + private void attemptCatch(CatchHitObject hitObject) + { + attemptCatch(() => hitObject, 1); + } + + private void attemptCatch(Func hitObject, int count) { for (var i = 0; i < count; i++) - attemptCatch(hitObject, out _, out _); + attemptCatch(hitObject(), out _, out _); } private void attemptCatch(CatchHitObject hitObject, out DrawableCatchHitObject drawableObject, out JudgementResult result) diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 8edc5d0eb2..4fc9e32a73 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -53,6 +53,11 @@ namespace osu.Game.Rulesets.Catch.UI /// public const double BASE_SPEED = 1.0; + /// + /// The amount by which caught fruit should be offset from the plate surface to make them look visually "caught". + /// + public const float CAUGHT_FRUIT_VERTICAL_OFFSET = -5; + /// /// The amount by which caught fruit should be scaled down to fit on the plate. /// @@ -492,7 +497,7 @@ namespace osu.Game.Rulesets.Catch.UI float checkDistance = MathF.Pow(adjustedRadius, 2); // offset fruit vertically to better place "above" the plate. - position.Y -= 5; + position.Y += CAUGHT_FRUIT_VERTICAL_OFFSET; while (caughtObjectContainer.Any(f => Vector2Extensions.DistanceSquared(f.Position, position) < checkDistance)) { From dc2bc462b8c68b44b6e927ec84bbf87fc0586df3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Apr 2021 17:27:23 +0900 Subject: [PATCH 1714/1791] Expose internal catcher width calculation methods --- osu.Game.Rulesets.Catch/UI/Catcher.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 4fc9e32a73..0d6a577d1e 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -212,13 +212,13 @@ namespace osu.Game.Rulesets.Catch.UI /// Calculates the width of the area used for attempting catches in gameplay. /// /// The scale of the catcher. - internal static float CalculateCatchWidth(Vector2 scale) => CatcherArea.CATCHER_SIZE * Math.Abs(scale.X) * ALLOWED_CATCH_RANGE; + public static float CalculateCatchWidth(Vector2 scale) => CatcherArea.CATCHER_SIZE * Math.Abs(scale.X) * ALLOWED_CATCH_RANGE; /// /// Calculates the width of the area used for attempting catches in gameplay. /// /// The beatmap difficulty. - internal static float CalculateCatchWidth(BeatmapDifficulty difficulty) => CalculateCatchWidth(calculateScale(difficulty)); + public static float CalculateCatchWidth(BeatmapDifficulty difficulty) => CalculateCatchWidth(calculateScale(difficulty)); /// /// Determine if this catcher can catch a in the current position. From 2203552e9e23528e79c63024d80186e4f5034bc2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Apr 2021 17:32:24 +0900 Subject: [PATCH 1715/1791] Add stacking test logic to `TestSceneCatcherArea` for skinned testing --- .../TestSceneCatcher.cs | 3 +- .../TestSceneCatcherArea.cs | 36 +++++++++++++++---- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs index f8e278a486..517027a9fc 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs @@ -21,6 +21,7 @@ using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; using osu.Game.Tests.Visual; +using osuTK; namespace osu.Game.Rulesets.Catch.Tests { @@ -175,7 +176,7 @@ namespace osu.Game.Rulesets.Catch.Tests { AddStep("catch more fruits", () => attemptCatch(() => new Fruit { - X = (RNG.NextSingle() - 0.5f) * CatcherArea.CATCHER_SIZE + X = (RNG.NextSingle() - 0.5f) * Catcher.CalculateCatchWidth(Vector2.One) }, 50)); } diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs index 1cbfa6338e..3e4ee5ffa9 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs @@ -8,6 +8,8 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; +using osu.Framework.Threading; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Configuration; @@ -31,12 +33,32 @@ namespace osu.Game.Rulesets.Catch.Tests private float circleSize; + private ScheduledDelegate addManyFruit; + + private BeatmapDifficulty beatmapDifficulty; + public TestSceneCatcherArea() { AddSliderStep("circle size", 0, 8, 5, createCatcher); AddToggleStep("hyper dash", t => this.ChildrenOfType().ForEach(area => area.ToggleHyperDash(t))); - AddStep("catch fruit", () => attemptCatch(new Fruit())); + AddStep("catch centered fruit", () => attemptCatch(new Fruit())); + AddStep("catch many random fruit", () => + { + int count = 50; + + addManyFruit?.Cancel(); + addManyFruit = Scheduler.AddDelayed(() => + { + attemptCatch(new Fruit + { + X = (RNG.NextSingle() - 0.5f) * Catcher.CalculateCatchWidth(beatmapDifficulty), + }); + + if (count-- == 0) + addManyFruit?.Cancel(); + }, 50, true); + }); AddStep("catch fruit last in combo", () => attemptCatch(new Fruit { LastInCombo = true })); AddStep("catch kiai fruit", () => attemptCatch(new TestSceneCatcher.TestKiaiFruit())); AddStep("miss last in combo", () => attemptCatch(new Fruit { X = 100, LastInCombo = true })); @@ -45,10 +67,7 @@ namespace osu.Game.Rulesets.Catch.Tests private void attemptCatch(Fruit fruit) { fruit.X = fruit.OriginalX + catcher.X; - fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty - { - CircleSize = circleSize - }); + fruit.ApplyDefaults(new ControlPointInfo(), beatmapDifficulty); foreach (var area in this.ChildrenOfType()) { @@ -71,6 +90,11 @@ namespace osu.Game.Rulesets.Catch.Tests { circleSize = size; + beatmapDifficulty = new BeatmapDifficulty + { + CircleSize = circleSize + }; + SetContents(() => { var droppedObjectContainer = new Container @@ -84,7 +108,7 @@ namespace osu.Game.Rulesets.Catch.Tests Children = new Drawable[] { droppedObjectContainer, - new TestCatcherArea(droppedObjectContainer, new BeatmapDifficulty { CircleSize = size }) + new TestCatcherArea(droppedObjectContainer, beatmapDifficulty) { Anchor = Anchor.Centre, Origin = Anchor.TopCentre, From bdf07ad59a7cfc0abb796133001da52bd83f314b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Apr 2021 17:47:03 +0900 Subject: [PATCH 1716/1791] Limit catching towards the centre of the plate (to emulate actual gameplay) --- osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs index 3e4ee5ffa9..ad404e1f63 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs @@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Catch.Tests { attemptCatch(new Fruit { - X = (RNG.NextSingle() - 0.5f) * Catcher.CalculateCatchWidth(beatmapDifficulty), + X = (RNG.NextSingle() - 0.5f) * Catcher.CalculateCatchWidth(beatmapDifficulty) * 0.6f, }); if (count-- == 0) From 1884c18a2cc3d169012f89da64a16d65c7cd267f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Apr 2021 18:12:03 +0900 Subject: [PATCH 1717/1791] Ignore movement operations which have no offset --- 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 b1afbe0d61..f70e063ba9 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -519,7 +519,8 @@ 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; - Beatmap.PerformOnSelection(obj => obj.StartTime += offset); + if (offset != 0) + Beatmap.PerformOnSelection(obj => obj.StartTime += offset); } return true; From 8a6267580a207fc63c88ed3a962037eeff7ad162 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 22 Apr 2021 18:44:14 +0900 Subject: [PATCH 1718/1791] Fix nullref --- osu.Game/Scoring/ScoreInfo.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index bf131658ea..a6faaf6379 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -65,9 +65,11 @@ namespace osu.Game.Scoring { get { - Mod[] scoreMods = Array.Empty(); + var rulesetInstance = Ruleset?.CreateInstance(); + if (rulesetInstance == null) + return mods ?? Array.Empty(); - var rulesetInstance = Ruleset.CreateInstance(); + Mod[] scoreMods = Array.Empty(); if (mods != null) scoreMods = mods; From a5364b224f083be5d55a31c95203f7a65069f3cd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Apr 2021 18:47:04 +0900 Subject: [PATCH 1719/1791] Add simple key based time nudging support to editor --- .../Editing/TestSceneEditorSelection.cs | 22 ++++++++++ .../Input/Bindings/GlobalActionContainer.cs | 8 ++++ .../Timeline/TimelineBlueprintContainer.cs | 42 ++++++++++++++++++- 3 files changed, 71 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSelection.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSelection.cs index 99f31b0c2a..c783ea1448 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSelection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSelection.cs @@ -41,6 +41,28 @@ namespace osu.Game.Tests.Visual.Editing }); } + [Test] + public void TestNudgeSelection() + { + HitCircle[] addedObjects = null; + + AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects = new[] + { + new HitCircle { StartTime = 100 }, + new HitCircle { StartTime = 200, Position = new Vector2(50) }, + new HitCircle { StartTime = 300, Position = new Vector2(100) }, + new HitCircle { StartTime = 400, Position = new Vector2(150) }, + })); + + AddStep("select objects", () => EditorBeatmap.SelectedHitObjects.AddRange(addedObjects)); + + AddStep("nudge forwards", () => InputManager.Key(Key.K)); + AddAssert("objects moved forwards in time", () => addedObjects[0].StartTime > 100); + + AddStep("nudge backwards", () => InputManager.Key(Key.J)); + AddAssert("objects reverted to original position", () => addedObjects[0].StartTime == 100); + } + [Test] public void TestBasicSelect() { diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index e414e12dd1..6717de5658 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -71,6 +71,8 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.F3 }, GlobalAction.EditorTimingMode), new KeyBinding(new[] { InputKey.F4 }, GlobalAction.EditorSetupMode), new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.A }, GlobalAction.EditorVerifyMode), + new KeyBinding(new[] { InputKey.J }, GlobalAction.EditorNudgeLeft), + new KeyBinding(new[] { InputKey.K }, GlobalAction.EditorNudgeRight), }; public IEnumerable InGameKeyBindings => new[] @@ -251,5 +253,11 @@ namespace osu.Game.Input.Bindings [Description("Verify mode")] EditorVerifyMode, + + [Description("Nudge selection left")] + EditorNudgeLeft, + + [Description("Nudge selection right")] + EditorNudgeRight } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index 7a3781a981..3555bc2800 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -12,9 +12,11 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Utils; using osu.Game.Graphics; +using osu.Game.Input.Bindings; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts; @@ -237,10 +239,48 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } } - internal class TimelineSelectionHandler : SelectionHandler + internal class TimelineSelectionHandler : SelectionHandler, IKeyBindingHandler { // for now we always allow movement. snapping is provided by the Timeline's "distance" snap implementation public override bool HandleMovement(MoveSelectionEvent moveEvent) => true; + + public bool OnPressed(GlobalAction action) + { + switch (action) + { + case GlobalAction.EditorNudgeLeft: + nudgeSelection(-1); + return true; + + case GlobalAction.EditorNudgeRight: + nudgeSelection(1); + return true; + } + + return false; + } + + public void OnReleased(GlobalAction action) + { + } + + /// + /// Nudge the current selection by the specified multiple of beat divisor lengths, + /// based on the timing at the first object in the selection. + /// + /// The direction and count of beat divisor lengths to adjust. + private void nudgeSelection(int amount) + { + var selected = EditorBeatmap.SelectedHitObjects; + + if (selected.Count == 0) + return; + + var timingPoint = EditorBeatmap.ControlPointInfo.TimingPointAt(selected.First().StartTime); + double adjustment = timingPoint.BeatLength / EditorBeatmap.BeatDivisor * amount; + + EditorBeatmap.PerformOnSelection(h => h.StartTime += adjustment); + } } private class TimelineDragBox : DragBox From 30e6ea4291dd95fb87108263df1f677e151d32f4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 22 Apr 2021 18:59:57 +0900 Subject: [PATCH 1720/1791] Add failing test --- .../TestSceneHoldNoteInput.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs index 42ea12214f..668487f673 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs @@ -324,6 +324,33 @@ namespace osu.Game.Rulesets.Mania.Tests assertTailJudgement(HitResult.Ok); } + [Test] + public void TestZeroLength() + { + var beatmap = new Beatmap + { + HitObjects = + { + new HoldNote + { + StartTime = 1000, + Duration = 0, + Column = 0, + }, + }, + BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo }, + }; + + performTest(new List + { + new ManiaReplayFrame(beatmap.HitObjects[0].StartTime, ManiaAction.Key1), + new ManiaReplayFrame(beatmap.HitObjects[0].GetEndTime() + 1), + }, beatmap); + + AddAssert("hold note hit", () => judgementResults.Where(j => beatmap.HitObjects[0].NestedHitObjects.Contains(j.HitObject)) + .All(j => j.Type.IsHit())); + } + private void assertHeadJudgement(HitResult result) => AddAssert($"head judged as {result}", () => judgementResults.First(j => j.HitObject is Note).Type == result); From 4148d473e3d4c949b312abcbc915f2fd3e99e925 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 22 Apr 2021 19:51:33 +0900 Subject: [PATCH 1721/1791] Fix hold note crashing with 0 length --- osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 828ee7b03e..02829d87bd 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -221,7 +221,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables // As the note is being held, adjust the size of the sizing container. This has two effects: // 1. The contained masking container will mask the body and ticks. // 2. The head note will move along with the new "head position" in the container. - if (Head.IsHit && releaseTime == null) + if (Head.IsHit && releaseTime == null && DrawHeight > 0) { // How far past the hit target this hold note is. Always a positive value. float yOffset = Math.Max(0, Direction.Value == ScrollingDirection.Up ? -Y : Y); From d20a8694e47264a5d6363550d8f3582d70cb8d10 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Apr 2021 19:55:06 +0900 Subject: [PATCH 1722/1791] Update resources --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 3324af7c51..5b779b803f 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index fcd1ed6987..da8163a971 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -30,7 +30,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 34810a3106..dc7a57f905 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + From b4f492ca4ce6e171bd3ff9cfd02d5a98140f0666 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Apr 2021 23:06:46 +0900 Subject: [PATCH 1723/1791] 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 7b97103851..140057f9cc 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 b27e00f59d..532dea7ace 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 d07e16ea56..edebefa70e 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 0ee73b8e536a37d94d56fcfe4f1878716f5687bf Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 22 Apr 2021 23:22:44 +0900 Subject: [PATCH 1724/1791] Add failing test --- .../StatefulMultiplayerClientTest.cs | 26 +++++++++++++++++++ .../Multiplayer/TestMultiplayerClient.cs | 11 +++++--- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs index a2ad37cf4a..377a33b527 100644 --- a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs +++ b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs @@ -41,6 +41,32 @@ namespace osu.Game.Tests.NonVisual.Multiplayer checkPlayingUserCount(0); } + [Test] + public void TestPlayingUsersUpdatedOnJoin() + { + AddStep("leave room", () => Client.LeaveRoom()); + AddUntilStep("wait for room part", () => Client.Room == null); + + AddStep("create room initially in gameplay", () => + { + Room.RoomID.Value = null; + Client.RoomSetupAction = room => + { + room.State = MultiplayerRoomState.Playing; + room.Users.Add(new MultiplayerRoomUser(55) + { + User = new User { Id = 55 }, + State = MultiplayerUserState.Playing + }); + }; + + RoomManager.CreateRoom(Room); + }); + + AddUntilStep("wait for room join", () => Client.Room != null); + checkPlayingUserCount(1); + } + private void checkPlayingUserCount(int expectedCount) => AddAssert($"{"user".ToQuantity(expectedCount)} playing", () => Client.CurrentMatchPlayingUserIds.Count == expectedCount); diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index b5cd3dad02..de77a15da0 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -25,6 +25,8 @@ namespace osu.Game.Tests.Visual.Multiplayer public override IBindable IsConnected => isConnected; private readonly Bindable isConnected = new Bindable(true); + public Action? RoomSetupAction; + [Resolved] private IAPIProvider api { get; set; } = null!; @@ -112,7 +114,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { var apiRoom = roomManager.Rooms.Single(r => r.RoomID.Value == roomId); - var user = new MultiplayerRoomUser(api.LocalUser.Value.Id) + var localUser = new MultiplayerRoomUser(api.LocalUser.Value.Id) { User = api.LocalUser.Value }; @@ -129,10 +131,13 @@ namespace osu.Game.Tests.Visual.Multiplayer AllowedMods = apiRoom.Playlist.Last().AllowedMods.Select(m => new APIMod(m)).ToArray(), PlaylistItemId = apiRoom.Playlist.Last().ID }, - Users = { user }, - Host = user + Users = { localUser }, + Host = localUser }; + RoomSetupAction?.Invoke(room); + RoomSetupAction = null; + return Task.FromResult(room); } From f593d9e42c3efa144dc07df35f504bd15efc41c7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 22 Apr 2021 23:23:43 +0900 Subject: [PATCH 1725/1791] Fix playing users not being updated on room join --- osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index 2ddc10db0f..c0706b082d 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -144,6 +144,8 @@ namespace osu.Game.Online.Multiplayer Room = joinedRoom; apiRoom = room; defaultPlaylistItemId = apiRoom.Playlist.FirstOrDefault()?.ID ?? 0; + foreach (var user in joinedRoom.Users) + updateUserPlayingState(user.UserID, user.State); }, cancellationSource.Token).ConfigureAwait(false); // Update room settings. From fbb9cb3f6f6d0a9d6fe0d89aa66ca26b545ec982 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 23 Apr 2021 11:01:48 +0900 Subject: [PATCH 1726/1791] Fix broken merge resolution --- osu.Game/osu.Game.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index d3d2de4557..75a3e45941 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -30,7 +30,6 @@ - From bc0e1d8c37272acf7a75c048036c512d42777bcf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 23 Apr 2021 15:06:39 +0900 Subject: [PATCH 1727/1791] Remove dead newline --- osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs index 4c46e24f1b..e4c41bed6d 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs @@ -21,6 +21,5 @@ namespace osu.Game.Rulesets.Catch.Mods drawableRuleset.Origin = Anchor.Centre; drawableRuleset.Scale = new osuTK.Vector2(1, -1); } - } } From ae2fd2f2e1f4315d4c1b90a56d412fa7b54cc9f3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 23 Apr 2021 18:46:59 +0900 Subject: [PATCH 1728/1791] Ensure source is set on reset --- osu.Game/Screens/Play/GameplayClockContainer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 5cd17d92c4..6677116399 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -100,6 +100,7 @@ namespace osu.Game.Screens.Play /// public virtual void Reset() { + ChangeSource(SourceClock); Seek(0); // Manually stop the source in order to not affect the IsPaused state. From fdb5490e51f1edf5b09aed6ecaf428e7ff03e8a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 23 Apr 2021 21:56:08 +0200 Subject: [PATCH 1729/1791] Attempt to explain source initialisation better --- osu.Game/Screens/Play/GameplayClockContainer.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 6677116399..6d60c09521 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -63,8 +63,7 @@ namespace osu.Game.Screens.Play /// public virtual void Start() { - // Ensure that the source clock is set. - ChangeSource(SourceClock); + ensureSourceClockSet(); if (!AdjustableSource.IsRunning) { @@ -100,7 +99,7 @@ namespace osu.Game.Screens.Play /// public virtual void Reset() { - ChangeSource(SourceClock); + ensureSourceClockSet(); Seek(0); // Manually stop the source in order to not affect the IsPaused state. @@ -116,6 +115,15 @@ namespace osu.Game.Screens.Play /// The new source. protected void ChangeSource(IClock sourceClock) => AdjustableSource.ChangeSource(SourceClock = sourceClock); + /// + /// Ensures that the is set to . + /// This is usually done before a seek to avoid accidentally seeking only the adjustable source in decoupled mode, + /// but not the actual source clock. + /// That will pretty much only happen on the very first call of this method, as the source clock is passed in the constructor, + /// but it is not yet set on the adjustable source there. + /// + private void ensureSourceClockSet() => ChangeSource(SourceClock); + protected override void Update() { if (!IsPaused.Value) From 04958a043f8ce1f5e24a65c438f0ad4c4305e4f3 Mon Sep 17 00:00:00 2001 From: subfluid <76847113+subfluid@users.noreply.github.com> Date: Fri, 23 Apr 2021 20:54:06 -0700 Subject: [PATCH 1730/1791] Fix Spelling Error 'passses' line 20 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0d6af2aeba..c539f9f4d8 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ The future of [osu!](https://osu.ppy.sh) and the beginning of an open era! Commo This project is under heavy development, but is in a stable state. Users are encouraged to try it out and keep it installed alongside the stable *osu!* client. It will continue to evolve to the point of eventually replacing the existing stable client as an update. -**IMPORTANT:** Gameplay mechanics (and other features which you may have come to know and love) are in a constant state of flux. Game balance and final quality-of-life passses come at the end of development, preceeded by experimentation and changes which may potentially **reduce playability or usability**. This is done in order to allow us to move forward as developers and designers more efficiently. If this offends you, please consider sticking to the stable releases of osu! (found on the website). We are not yet open to heated discussion over game mechanics and will not be using github as a forum for such discussions just yet. +**IMPORTANT:** Gameplay mechanics (and other features which you may have come to know and love) are in a constant state of flux. Game balance and final quality-of-life passes come at the end of development, preceeded by experimentation and changes which may potentially **reduce playability or usability**. This is done in order to allow us to move forward as developers and designers more efficiently. If this offends you, please consider sticking to the stable releases of osu! (found on the website). We are not yet open to heated discussion over game mechanics and will not be using github as a forum for such discussions just yet. We are accepting bug reports (please report with as much detail as possible and follow the existing issue templates). Feature requests are also welcome, but understand that our focus is on completing the game to feature parity before adding new features. A few resources are available as starting points to getting involved and understanding the project: From 0ccdfeea57f3a983e1735ce1d3c0aca7b7959da2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 24 Apr 2021 14:33:56 +0900 Subject: [PATCH 1731/1791] Fix code quality issues --- osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs index e4c41bed6d..63203dd57c 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs @@ -1,9 +1,12 @@ -using osu.Framework.Graphics; +// 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.Sprites; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; -using osu.Game.Screens.Play; +using osuTK; namespace osu.Game.Rulesets.Catch.Mods { @@ -19,7 +22,8 @@ namespace osu.Game.Rulesets.Catch.Mods { drawableRuleset.Anchor = Anchor.Centre; drawableRuleset.Origin = Anchor.Centre; - drawableRuleset.Scale = new osuTK.Vector2(1, -1); + + drawableRuleset.Scale = new Vector2(1, -1); } } } From 2ae144be8e035801a3131b90c92542f3c075c03f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 24 Apr 2021 14:38:00 +0900 Subject: [PATCH 1732/1791] 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 721e13a759..e0b07549f4 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 75a3e45941..9a43d5f031 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 69676a1aed..2c41b3ef26 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 1ec99577ce48557e47ebcb510ac3894be81cd7db Mon Sep 17 00:00:00 2001 From: PercyDan54 <50285552+PercyDan54@users.noreply.github.com> Date: Sat, 24 Apr 2021 14:05:11 +0800 Subject: [PATCH 1733/1791] Incorrect path on Android --- .github/ISSUE_TEMPLATE/01-bug-issues.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/01-bug-issues.md b/.github/ISSUE_TEMPLATE/01-bug-issues.md index e45893b97a..7026179259 100644 --- a/.github/ISSUE_TEMPLATE/01-bug-issues.md +++ b/.github/ISSUE_TEMPLATE/01-bug-issues.md @@ -25,6 +25,6 @@ Please check: *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)*, +- `Android/data/sh.ppy.osulazer/files/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) --> From 2e2f843e223ead183607062f4fbb43aa9b994a3e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 24 Apr 2021 09:26:49 +0300 Subject: [PATCH 1734/1791] Refine android game logs path in contributing guidelines --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6c327f01b3..e14be20642 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,7 +24,7 @@ Issues, bug reports and feature suggestions are welcomed, though please keep in * the in-game logs, which are located at: * `%AppData%/osu/logs` (on Windows), * `~/.local/share/osu/logs` (on Linux and macOS), - * `Android/Data/sh.ppy.osulazer/logs` (on Android), + * `Android/data/sh.ppy.osulazer/files/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), * your system specifications (including the operating system and platform you are playing on), * a reproduction scenario (list of steps you have performed leading up to the occurrence of the bug), From e937b778f6a5ccb4840e9561467c81edfbfb4028 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 24 Apr 2021 14:19:39 +0200 Subject: [PATCH 1735/1791] Fix potential failure in `ensureSourceClockSet()` `ensureSourceClockSet()` was intended to only run when the adjustable source hasn't been set at all yet. As it turns out permitting it to run unconditionally can break the state of the underlying interpolated clock. This is caused by the following factors: * While the decoupleable clock is running, its `CurrentTime` does not come from either the source clock, or the internal stopwatch; it is instead calculated using the base `InterpolatingFramedClock` logic. * A source change of a decoupleable clock seeks the provided source clock to the decoupleable's current time. * When an interpolating clock is seeked (decoupleable clock is also an interpolating one), its interpolation state (`{Last,Current}InterpolatedTime`) are reset to 0. * If the interpolating clock determines that its current time is too far away from the source's time (which was set when the source is changed), it will ignore the source and instead continue to use its current time until the source clock has caught up. Overall, the source change is not really necessary if a source is already there. The only reason to ensure it was set was to make sure the first seek of the gameplay clock wasn't performed in decoupled mode. Therefore, add a guard to make sure the source is only set if there isn't one already. --- osu.Game/Screens/Play/GameplayClockContainer.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 6d60c09521..1c8a3e51ac 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -116,13 +116,17 @@ namespace osu.Game.Screens.Play protected void ChangeSource(IClock sourceClock) => AdjustableSource.ChangeSource(SourceClock = sourceClock); /// - /// Ensures that the is set to . + /// Ensures that the is set to , if it hasn't been given a source yet. /// This is usually done before a seek to avoid accidentally seeking only the adjustable source in decoupled mode, /// but not the actual source clock. /// That will pretty much only happen on the very first call of this method, as the source clock is passed in the constructor, /// but it is not yet set on the adjustable source there. /// - private void ensureSourceClockSet() => ChangeSource(SourceClock); + private void ensureSourceClockSet() + { + if (AdjustableSource.Source == null) + ChangeSource(SourceClock); + } protected override void Update() { From fa8e8ed36f3b410fc91eb07a49bcb4413e52fbce Mon Sep 17 00:00:00 2001 From: plan-do-break-fix Date: Sat, 24 Apr 2021 22:57:18 -0500 Subject: [PATCH 1736/1791] fix(docs): corrects typo in project README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c539f9f4d8..eb790ca18f 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ The future of [osu!](https://osu.ppy.sh) and the beginning of an open era! Commo This project is under heavy development, but is in a stable state. Users are encouraged to try it out and keep it installed alongside the stable *osu!* client. It will continue to evolve to the point of eventually replacing the existing stable client as an update. -**IMPORTANT:** Gameplay mechanics (and other features which you may have come to know and love) are in a constant state of flux. Game balance and final quality-of-life passes come at the end of development, preceeded by experimentation and changes which may potentially **reduce playability or usability**. This is done in order to allow us to move forward as developers and designers more efficiently. If this offends you, please consider sticking to the stable releases of osu! (found on the website). We are not yet open to heated discussion over game mechanics and will not be using github as a forum for such discussions just yet. +**IMPORTANT:** Gameplay mechanics (and other features which you may have come to know and love) are in a constant state of flux. Game balance and final quality-of-life passes come at the end of development, preceded by experimentation and changes which may potentially **reduce playability or usability**. This is done in order to allow us to move forward as developers and designers more efficiently. If this offends you, please consider sticking to the stable releases of osu! (found on the website). We are not yet open to heated discussion over game mechanics and will not be using github as a forum for such discussions just yet. We are accepting bug reports (please report with as much detail as possible and follow the existing issue templates). Feature requests are also welcome, but understand that our focus is on completing the game to feature parity before adding new features. A few resources are available as starting points to getting involved and understanding the project: From 0b9172a1dc08dfa3e432b93aa48d0a3b7862fd31 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 26 Apr 2021 02:39:18 +0300 Subject: [PATCH 1737/1791] Animate back slider repeat and tail circle pieces --- .../Objects/Drawables/DrawableSliderRepeat.cs | 7 +++++-- .../Objects/Drawables/DrawableSliderTail.cs | 3 +++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index 76490e0de1..5af2a38559 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -26,7 +26,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private double animDuration; - public Drawable CirclePiece { get; private set; } + public SkinnableDrawable CirclePiece { get; private set; } + private Drawable scaleContainer; private ReverseArrowPiece arrow; @@ -53,7 +54,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Children = new[] + Children = new Drawable[] { // no default for this; only visible in legacy skins. CirclePiece = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderTailHitCircle), _ => Empty()), @@ -91,6 +92,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.UpdateHitStateTransforms(state); + (CirclePiece.Drawable as IMainCirclePiece)?.Animate(state); + switch (state) { case ArmedState.Idle: diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs index 87f098dd29..84b9b881a2 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs @@ -7,6 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Skinning.Default; using osu.Game.Skinning; using osuTK; @@ -84,6 +85,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Debug.Assert(HitObject.HitWindows != null); + (circlePiece.Drawable as IMainCirclePiece)?.Animate(state); + switch (state) { case ArmedState.Idle: From e6474e6ff73b968970dc0c9e943130a6f865af06 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 26 Apr 2021 11:47:38 +0900 Subject: [PATCH 1738/1791] Remove redundant statement (lifetime is set in base) --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 312ed93e45..6ab6a4b984 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -212,10 +212,7 @@ namespace osu.Game.Rulesets.Objects.Drawables // LifetimeStart is already computed using HitObjectLifetimeEntry's InitialLifetimeOffset. // We override this with DHO's InitialLifetimeOffset for a non-pooled DHO. if (entry is SyntheticHitObjectEntry) - entry.LifetimeStart = HitObject.StartTime - InitialLifetimeOffset; - - LifetimeStart = entry.LifetimeStart; - LifetimeEnd = entry.LifetimeEnd; + LifetimeStart = HitObject.StartTime - InitialLifetimeOffset; ensureEntryHasResult(); From 20e3cadd30e6c2e9e49b0f880915646e7caa9624 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 26 Apr 2021 12:04:59 +0900 Subject: [PATCH 1739/1791] freeIfInUse -> free, and add comments --- .../Rulesets/Objects/Pooling/DrawableObject.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Pooling/DrawableObject.cs b/osu.Game/Rulesets/Objects/Pooling/DrawableObject.cs index b29e6a6c3c..27ed4c04f2 100644 --- a/osu.Game/Rulesets/Objects/Pooling/DrawableObject.cs +++ b/osu.Game/Rulesets/Objects/Pooling/DrawableObject.cs @@ -50,6 +50,7 @@ namespace osu.Game.Rulesets.Objects.Pooling { base.LoadAsyncComplete(); + // Apply the initial entry given in the constructor. if (Entry != null && !HasEntryApplied) Apply(Entry); } @@ -60,7 +61,8 @@ namespace osu.Game.Rulesets.Objects.Pooling /// public void Apply(TEntry entry) { - freeIfInUse(); + if (HasEntryApplied) + free(); setLifetime(entry.LifetimeStart, entry.LifetimeEnd); Entry = entry; @@ -74,8 +76,9 @@ namespace osu.Game.Rulesets.Objects.Pooling { base.FreeAfterUse(); - if (IsInPool) - freeIfInUse(); + // We preserve the existing entry in case we want to move a non-pooled drawable between different parent drawables. + if (HasEntryApplied && IsInPool) + free(); } /// @@ -104,11 +107,9 @@ namespace osu.Game.Rulesets.Objects.Pooling } } - private void freeIfInUse() + private void free() { - if (!HasEntryApplied) return; - - Debug.Assert(Entry != null); + Debug.Assert(Entry != null && HasEntryApplied); OnFree(Entry); From 6561a7c7d697dafd15d3f35b82d7ccd86552ca0b Mon Sep 17 00:00:00 2001 From: ekrctb Date: Mon, 26 Apr 2021 12:06:21 +0900 Subject: [PATCH 1740/1791] Rename DrawableObject -> PoolableDrawableWithLifetime --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 4 ++-- ...{DrawableObject.cs => PoolableDrawableWithLifetime.cs} | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) rename osu.Game/Rulesets/Objects/Pooling/{DrawableObject.cs => PoolableDrawableWithLifetime.cs} (92%) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 6ab6a4b984..7739994527 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -26,7 +26,7 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Objects.Drawables { [Cached(typeof(DrawableHitObject))] - public abstract class DrawableHitObject : DrawableObject + public abstract class DrawableHitObject : PoolableDrawableWithLifetime { /// /// Invoked after this 's applied has had its defaults applied. @@ -153,7 +153,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// /// The to be initially applied to this . - /// If null, a hitobject is expected to be later applied via (or automatically via pooling). + /// If null, a hitobject is expected to be later applied via (or automatically via pooling). /// protected DrawableHitObject([CanBeNull] HitObject initialHitObject = null) : base(initialHitObject != null ? new SyntheticHitObjectEntry(initialHitObject) : null) diff --git a/osu.Game/Rulesets/Objects/Pooling/DrawableObject.cs b/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs similarity index 92% rename from osu.Game/Rulesets/Objects/Pooling/DrawableObject.cs rename to osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs index 27ed4c04f2..93e476be76 100644 --- a/osu.Game/Rulesets/Objects/Pooling/DrawableObject.cs +++ b/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs @@ -13,15 +13,15 @@ namespace osu.Game.Rulesets.Objects.Pooling /// A that is controlled by to implement drawable pooling and replay rewinding. /// /// The type storing state and controlling this drawable. - public abstract class DrawableObject : PoolableDrawable where TEntry : LifetimeEntry + public abstract class PoolableDrawableWithLifetime : PoolableDrawable where TEntry : LifetimeEntry { /// - /// The entry holding essential state of this . + /// The entry holding essential state of this . /// protected TEntry? Entry { get; private set; } /// - /// Whether is applied to this . + /// Whether is applied to this . /// When an initial entry is specified in the constructor, is set but not applied until loading is completed. /// protected bool HasEntryApplied { get; private set; } @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Objects.Pooling public override bool RemoveWhenNotAlive => false; public override bool RemoveCompletedTransforms => false; - protected DrawableObject(TEntry? initialEntry = null) + protected PoolableDrawableWithLifetime(TEntry? initialEntry = null) { Entry = initialEntry; } From e8d83f2f99887423d9be29ac5eea0cdb673e24b7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Apr 2021 14:33:30 +0900 Subject: [PATCH 1741/1791] Rename "EditRuleset" and "EditPlayfield" to use full "Editor" keyword --- ...eManiaEditRuleset.cs => DrawableManiaEditorRuleset.cs} | 6 +++--- .../{ManiaEditPlayfield.cs => ManiaEditorPlayfield.cs} | 4 ++-- osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs | 4 ++-- ...wableOsuEditRuleset.cs => DrawableOsuEditorRuleset.cs} | 8 ++++---- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) rename osu.Game.Rulesets.Mania/Edit/{DrawableManiaEditRuleset.cs => DrawableManiaEditorRuleset.cs} (78%) rename osu.Game.Rulesets.Mania/Edit/{ManiaEditPlayfield.cs => ManiaEditorPlayfield.cs} (75%) rename osu.Game.Rulesets.Osu/Edit/{DrawableOsuEditRuleset.cs => DrawableOsuEditorRuleset.cs} (93%) diff --git a/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs b/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditorRuleset.cs similarity index 78% rename from osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs rename to osu.Game.Rulesets.Mania/Edit/DrawableManiaEditorRuleset.cs index 445df79f6f..b0af8c503b 100644 --- a/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs +++ b/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditorRuleset.cs @@ -12,16 +12,16 @@ using osu.Game.Rulesets.UI.Scrolling; namespace osu.Game.Rulesets.Mania.Edit { - public class DrawableManiaEditRuleset : DrawableManiaRuleset + public class DrawableManiaEditorRuleset : DrawableManiaRuleset { public new IScrollingInfo ScrollingInfo => base.ScrollingInfo; - public DrawableManiaEditRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods) + public DrawableManiaEditorRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods) : base(ruleset, beatmap, mods) { } - protected override Playfield CreatePlayfield() => new ManiaEditPlayfield(Beatmap.Stages) + protected override Playfield CreatePlayfield() => new ManiaEditorPlayfield(Beatmap.Stages) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaEditPlayfield.cs b/osu.Game.Rulesets.Mania/Edit/ManiaEditorPlayfield.cs similarity index 75% rename from osu.Game.Rulesets.Mania/Edit/ManiaEditPlayfield.cs rename to osu.Game.Rulesets.Mania/Edit/ManiaEditorPlayfield.cs index a42f793a77..186d50716e 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaEditPlayfield.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaEditorPlayfield.cs @@ -7,9 +7,9 @@ using System.Collections.Generic; namespace osu.Game.Rulesets.Mania.Edit { - public class ManiaEditPlayfield : ManiaPlayfield + public class ManiaEditorPlayfield : ManiaPlayfield { - public ManiaEditPlayfield(List stages) + public ManiaEditorPlayfield(List stages) : base(stages) { } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index d9570bf8be..2baec95c94 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Mania.Edit { public class ManiaHitObjectComposer : HitObjectComposer { - private DrawableManiaEditRuleset drawableRuleset; + private DrawableManiaEditorRuleset drawableRuleset; private ManiaBeatSnapGrid beatSnapGrid; private InputManager inputManager; @@ -80,7 +80,7 @@ namespace osu.Game.Rulesets.Mania.Edit protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) { - drawableRuleset = new DrawableManiaEditRuleset(ruleset, beatmap, mods); + drawableRuleset = new DrawableManiaEditorRuleset(ruleset, beatmap, mods); // This is the earliest we can cache the scrolling info to ourselves, before masks are added to the hierarchy and inject it dependencies.CacheAs(drawableRuleset.ScrollingInfo); diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs similarity index 93% rename from osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs rename to osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs index b8d0637e90..7227e2b511 100644 --- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs +++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs @@ -17,18 +17,18 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Edit { - public class DrawableOsuEditRuleset : DrawableOsuRuleset + public class DrawableOsuEditorRuleset : DrawableOsuRuleset { - public DrawableOsuEditRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods) + public DrawableOsuEditorRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods) : base(ruleset, beatmap, mods) { } - protected override Playfield CreatePlayfield() => new OsuEditPlayfield(); + protected override Playfield CreatePlayfield() => new OsuEditorPlayfield(); public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new OsuPlayfieldAdjustmentContainer { Size = Vector2.One }; - private class OsuEditPlayfield : OsuPlayfield + private class OsuEditorPlayfield : OsuPlayfield { private Bindable hitAnimations; diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 396fd41377..7b67d7aaf1 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Edit } protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) - => new DrawableOsuEditRuleset(ruleset, beatmap, mods); + => new DrawableOsuEditorRuleset(ruleset, beatmap, mods); protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] { From bda8f68da45a8645a07abd6c5fc1d7e5a2478156 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 26 Apr 2021 15:03:43 +0900 Subject: [PATCH 1742/1791] Add failing test --- osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs index 2cc031405e..590d159300 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs @@ -34,6 +34,18 @@ namespace osu.Game.Rulesets.Osu.Tests private List judgementResults; + [Test] + public void TestPressBothKeysSimultaneouslyAndReleaseOne() + { + performTest(new List + { + new OsuReplayFrame { Position = Vector2.Zero, Actions = { OsuAction.LeftButton, OsuAction.RightButton }, Time = time_slider_start }, + new OsuReplayFrame { Position = Vector2.Zero, Actions = { OsuAction.RightButton }, Time = time_during_slide_1 }, + }); + + AddAssert("Tracking retained", assertMaxJudge); + } + /// /// Scenario: /// - Press a key before a slider starts From 6182181ea1d3f1a6809d8631341ef2addbfbd3d4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 26 Apr 2021 15:20:29 +0900 Subject: [PATCH 1743/1791] Fix simultaneous slider input not allowing both keys --- .../Skinning/Default/SliderBall.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs index 82b677e12c..b85610491c 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs @@ -2,6 +2,7 @@ // 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.Bindables; @@ -134,6 +135,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default /// private double? timeToAcceptAnyKeyAfter; + /// + /// The actions that were pressed in the previous frame. + /// + private readonly List lastPressedActions = new List(); + protected override void Update() { base.Update(); @@ -152,8 +158,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default { var otherKey = headCircleHitAction == OsuAction.RightButton ? OsuAction.LeftButton : OsuAction.RightButton; - // we can return to accepting all keys if the initial head circle key is the *only* key pressed, or all keys have been released. - if (actions?.Contains(otherKey) != true) + // we can return to accepting all other keys if they were released in the previous frame. + if (!lastPressedActions.Contains(otherKey)) timeToAcceptAnyKeyAfter = Time.Current; } @@ -164,6 +170,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default lastScreenSpaceMousePosition.HasValue && followCircle.ReceivePositionalInputAt(lastScreenSpaceMousePosition.Value) && // valid action (actions?.Any(isValidTrackingAction) ?? false); + + lastPressedActions.Clear(); + lastPressedActions.AddRange(actions); } /// From aa7ade8186df4033412bf93db2b970c5ae18a8fc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Apr 2021 15:22:17 +0900 Subject: [PATCH 1744/1791] Expose presence of `MainCirclePiece` via an interface --- .../Objects/Drawables/DrawableHitCircle.cs | 2 +- .../Objects/Drawables/DrawableSliderRepeat.cs | 2 +- .../Objects/Drawables/DrawableSliderTail.cs | 11 ++++++----- .../Skinning/Default/IHasMainCirclePiece.cs | 12 ++++++++++++ 4 files changed, 20 insertions(+), 7 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Skinning/Default/IHasMainCirclePiece.cs diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index fb6c110b3c..1bf9e76d7d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -19,7 +19,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables { - public class DrawableHitCircle : DrawableOsuHitObject + public class DrawableHitCircle : DrawableOsuHitObject, IHasMainCirclePiece { public OsuAction? HitAction => HitArea.HitAction; protected virtual OsuSkinComponents CirclePieceComponent => OsuSkinComponents.HitCircle; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index 5af2a38559..cc7d9d1b23 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -15,7 +15,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables { - public class DrawableSliderRepeat : DrawableOsuHitObject, ITrackSnaking + public class DrawableSliderRepeat : DrawableOsuHitObject, ITrackSnaking, IHasMainCirclePiece { public new SliderRepeat HitObject => (SliderRepeat)base.HitObject; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs index 84b9b881a2..d81af053d1 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs @@ -13,7 +13,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables { - public class DrawableSliderTail : DrawableOsuHitObject, IRequireTracking, ITrackSnaking + public class DrawableSliderTail : DrawableOsuHitObject, IRequireTracking, ITrackSnaking, IHasMainCirclePiece { public new SliderTailCircle HitObject => (SliderTailCircle)base.HitObject; @@ -35,7 +35,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public bool Tracking { get; set; } - private SkinnableDrawable circlePiece; + public SkinnableDrawable CirclePiece { get; private set; } + private Container scaleContainer; public DrawableSliderTail() @@ -64,7 +65,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Children = new Drawable[] { // no default for this; only visible in legacy skins. - circlePiece = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderTailHitCircle), _ => Empty()) + CirclePiece = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderTailHitCircle), _ => Empty()) } }, }; @@ -76,7 +77,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.UpdateInitialTransforms(); - circlePiece.FadeInFromZero(HitObject.TimeFadeIn); + CirclePiece.FadeInFromZero(HitObject.TimeFadeIn); } protected override void UpdateHitStateTransforms(ArmedState state) @@ -85,7 +86,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Debug.Assert(HitObject.HitWindows != null); - (circlePiece.Drawable as IMainCirclePiece)?.Animate(state); + (CirclePiece.Drawable as IMainCirclePiece)?.Animate(state); switch (state) { diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/IHasMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Default/IHasMainCirclePiece.cs new file mode 100644 index 0000000000..8bb7629542 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/Default/IHasMainCirclePiece.cs @@ -0,0 +1,12 @@ +// 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.Skinning; + +namespace osu.Game.Rulesets.Osu.Skinning.Default +{ + public interface IHasMainCirclePiece + { + SkinnableDrawable CirclePiece { get; } + } +} From 4da964c3f3235661b8f4b9c9d7c32cb60f3e711b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Apr 2021 15:22:42 +0900 Subject: [PATCH 1745/1791] Expose `DrawableSliderRepeat`'s arrow and move transforms to children --- .../Objects/Drawables/DrawableSliderRepeat.cs | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index cc7d9d1b23..5db012c4f8 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -28,8 +28,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public SkinnableDrawable CirclePiece { get; private set; } + public ReverseArrowPiece Arrow { get; private set; } + private Drawable scaleContainer; - private ReverseArrowPiece arrow; public override bool DisplayResult => false; @@ -57,8 +58,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Children = new Drawable[] { // no default for this; only visible in legacy skins. - CirclePiece = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderTailHitCircle), _ => Empty()), - arrow = new ReverseArrowPiece(), + CirclePiece = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderTailHitCircle), _ => Empty()) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + Arrow = new ReverseArrowPiece(), } }; @@ -105,8 +110,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables break; case ArmedState.Hit: - this.FadeOut(animDuration, Easing.Out) - .ScaleTo(Scale * 1.5f, animDuration, Easing.Out); + this.FadeOut(animDuration, Easing.Out); + + Arrow.ScaleTo(Scale * 1.5f, animDuration, Easing.Out); + CirclePiece.ScaleTo(Scale * 1.5f, animDuration, Easing.Out); break; } } @@ -142,18 +149,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } float aimRotation = MathUtils.RadiansToDegrees(MathF.Atan2(aimRotationVector.Y - Position.Y, aimRotationVector.X - Position.X)); - while (Math.Abs(aimRotation - arrow.Rotation) > 180) - aimRotation += aimRotation < arrow.Rotation ? 360 : -360; + while (Math.Abs(aimRotation - Arrow.Rotation) > 180) + aimRotation += aimRotation < Arrow.Rotation ? 360 : -360; if (!hasRotation) { - arrow.Rotation = aimRotation; + Arrow.Rotation = aimRotation; hasRotation = true; } else { // If we're already snaking, interpolate to smooth out sharp curves (linear sliders, mainly). - arrow.Rotation = Interpolation.ValueAt(Math.Clamp(Clock.ElapsedFrameTime, 0, 100), arrow.Rotation, aimRotation, 0, 50, Easing.OutQuint); + Arrow.Rotation = Interpolation.ValueAt(Math.Clamp(Clock.ElapsedFrameTime, 0, 100), Arrow.Rotation, aimRotation, 0, 50, Easing.OutQuint); } } } From 8795c5f0824e1f1cd9b1aae00d429ac2968713f8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Apr 2021 15:23:21 +0900 Subject: [PATCH 1746/1791] Update osu! editor transform logic to allow adjustments to `DrawableSliderRepeat` and `DrawableSliderTail` --- .../Edit/DrawableOsuEditorRuleset.cs | 63 ++++++++++--------- 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs index 7227e2b511..aeeae84d14 100644 --- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs +++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs @@ -11,6 +11,7 @@ using osu.Game.Configuration; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.Skinning.Default; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.UI; using osuTK; @@ -56,43 +57,45 @@ namespace osu.Game.Rulesets.Osu.Edit if (state == ArmedState.Idle || hitAnimations.Value) return; - // adjust the visuals of certain object types to make them stay on screen for longer than usual. - switch (hitObject) + if (hitObject is DrawableHitCircle circle) { - default: - // there are quite a few drawable hit types we don't want to extend (spinners, ticks etc.) - return; + circle.ApproachCircle + .FadeOutFromOne(editor_hit_object_fade_out_extension * 4) + .Expire(); - case DrawableSlider _: - // no specifics to sliders but let them fade slower below. - break; - - case DrawableHitCircle circle: // also handles slider heads - circle.ApproachCircle - .FadeOutFromOne(editor_hit_object_fade_out_extension * 4) - .Expire(); - - circle.ApproachCircle.ScaleTo(1.1f, 300, Easing.OutQuint); - - var circlePieceDrawable = circle.CirclePiece.Drawable; - - // clear any explode animation logic. - circlePieceDrawable.ApplyTransformsAt(circle.HitStateUpdateTime, true); - circlePieceDrawable.ClearTransformsAfter(circle.HitStateUpdateTime, true); - - break; + circle.ApproachCircle.ScaleTo(1.1f, 300, Easing.OutQuint); } - // Get the existing fade out transform - var existing = hitObject.Transforms.LastOrDefault(t => t.TargetMember == nameof(Alpha)); + if (hitObject is IHasMainCirclePiece mainPieceContainer) + { + // clear any explode animation logic. + mainPieceContainer.CirclePiece.ApplyTransformsAt(hitObject.HitStateUpdateTime, true); + mainPieceContainer.CirclePiece.ClearTransformsAfter(hitObject.HitStateUpdateTime, true); + } - if (existing == null) - return; + if (hitObject is DrawableSliderRepeat repeat) + { + repeat.Arrow.ApplyTransformsAt(hitObject.HitStateUpdateTime, true); + repeat.Arrow.ClearTransformsAfter(hitObject.HitStateUpdateTime, true); + } - hitObject.RemoveTransform(existing); + // adjust the visuals of top-level object types to make them stay on screen for longer than usual. + switch (hitObject) + { + case DrawableSlider _: + case DrawableHitCircle _: + // Get the existing fade out transform + var existing = hitObject.Transforms.LastOrDefault(t => t.TargetMember == nameof(Alpha)); - using (hitObject.BeginAbsoluteSequence(hitObject.HitStateUpdateTime)) - hitObject.FadeOut(editor_hit_object_fade_out_extension).Expire(); + if (existing == null) + return; + + hitObject.RemoveTransform(existing); + + using (hitObject.BeginAbsoluteSequence(hitObject.HitStateUpdateTime)) + hitObject.FadeOut(editor_hit_object_fade_out_extension).Expire(); + break; + } } } } From d10aac851debb1a7b185a855f76c491833e193ea Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Apr 2021 15:30:22 +0900 Subject: [PATCH 1747/1791] Extract scale constant --- .../Objects/Drawables/DrawableSliderRepeat.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index 5db012c4f8..7b4188edab 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -112,8 +112,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables case ArmedState.Hit: this.FadeOut(animDuration, Easing.Out); - Arrow.ScaleTo(Scale * 1.5f, animDuration, Easing.Out); - CirclePiece.ScaleTo(Scale * 1.5f, animDuration, Easing.Out); + const float final_scale = 1.5f; + + Arrow.ScaleTo(Scale * final_scale, animDuration, Easing.Out); + CirclePiece.ScaleTo(Scale * final_scale, animDuration, Easing.Out); break; } } From f70e45b1998a97dd8421f0f046c7efcada54d40b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 26 Apr 2021 15:35:08 +0900 Subject: [PATCH 1748/1791] Prevent adding null enumerable --- osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs index b85610491c..c5c95557e6 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs @@ -172,7 +172,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default (actions?.Any(isValidTrackingAction) ?? false); lastPressedActions.Clear(); - lastPressedActions.AddRange(actions); + if (actions != null) + lastPressedActions.AddRange(actions); } /// From fd5fbaf0dbc5dcf7d5fe475f4ac7c88fe80edbb7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 26 Apr 2021 15:37:42 +0900 Subject: [PATCH 1749/1791] Rename ruleset wrapper class --- ...eEditRulesetWrapper.cs => DrawableEditorRulesetWrapper.cs} | 4 ++-- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename osu.Game/Rulesets/Edit/{DrawableEditRulesetWrapper.cs => DrawableEditorRulesetWrapper.cs} (94%) diff --git a/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs b/osu.Game/Rulesets/Edit/DrawableEditorRulesetWrapper.cs similarity index 94% rename from osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs rename to osu.Game/Rulesets/Edit/DrawableEditorRulesetWrapper.cs index c60d4c7834..62e2539c2a 100644 --- a/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs +++ b/osu.Game/Rulesets/Edit/DrawableEditorRulesetWrapper.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Edit /// /// A wrapper for a . Handles adding visual representations of s to the underlying . /// - internal class DrawableEditRulesetWrapper : CompositeDrawable + internal class DrawableEditorRulesetWrapper : CompositeDrawable where TObject : HitObject { public Playfield Playfield => drawableRuleset.Playfield; @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Edit [Resolved] private EditorBeatmap beatmap { get; set; } - public DrawableEditRulesetWrapper(DrawableRuleset drawableRuleset) + public DrawableEditorRulesetWrapper(DrawableRuleset drawableRuleset) { this.drawableRuleset = drawableRuleset; diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 736fc47dee..35896d4982 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -54,7 +54,7 @@ namespace osu.Game.Rulesets.Edit protected ComposeBlueprintContainer BlueprintContainer { get; private set; } - private DrawableEditRulesetWrapper drawableRulesetWrapper; + private DrawableEditorRulesetWrapper drawableRulesetWrapper; protected readonly Container LayerBelowRuleset = new Container { RelativeSizeAxes = Axes.Both }; @@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Edit try { - drawableRulesetWrapper = new DrawableEditRulesetWrapper(CreateDrawableRuleset(Ruleset, EditorBeatmap.PlayableBeatmap, new[] { Ruleset.GetAutoplayMod() })) + drawableRulesetWrapper = new DrawableEditorRulesetWrapper(CreateDrawableRuleset(Ruleset, EditorBeatmap.PlayableBeatmap, new[] { Ruleset.GetAutoplayMod() })) { Clock = EditorClock, ProcessCustomClock = false From 0d0b4ea78a8a07be1ac472641f8123e26f1a4bdf Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 26 Apr 2021 15:47:37 +0900 Subject: [PATCH 1750/1791] Rewrite comment to hopefully be more readable --- osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs index c5c95557e6..8feeca56e8 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs @@ -158,7 +158,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default { var otherKey = headCircleHitAction == OsuAction.RightButton ? OsuAction.LeftButton : OsuAction.RightButton; - // we can return to accepting all other keys if they were released in the previous frame. + // we can start accepting any key once all other keys have been released in the previous frame. if (!lastPressedActions.Contains(otherKey)) timeToAcceptAnyKeyAfter = Time.Current; } From 6560dc2d1f579947218d1fa88853775efb0d1792 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 26 Apr 2021 20:46:44 +0900 Subject: [PATCH 1751/1791] Fix exported replays being wrapped in zip packages --- osu.Game/Database/ArchiveModelManager.cs | 20 +++++++++++++++----- osu.Game/Scoring/ScoreManager.cs | 10 ++++++++++ 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index d809dbcb01..6719351530 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -422,15 +422,25 @@ namespace osu.Game.Database if (retrievedItem == null) throw new ArgumentException("Specified model could not be found", nameof(item)); + using (var outputStream = exportStorage.GetStream($"{getValidFilename(item.ToString())}{HandledExtensions.First()}", FileAccess.Write, FileMode.Create)) + ExportModelTo(retrievedItem, outputStream); + + exportStorage.OpenInNativeExplorer(); + } + + /// + /// Exports an item to the given output stream. + /// + /// The item to export. + /// The output stream to export to. + protected virtual void ExportModelTo(TModel model, Stream outputStream) + { using (var archive = ZipArchive.Create()) { - foreach (var file in retrievedItem.Files) + foreach (var file in model.Files) archive.AddEntry(file.Filename, Files.Storage.GetStream(file.FileInfo.StoragePath)); - using (var outputStream = exportStorage.GetStream($"{getValidFilename(item.ToString())}{HandledExtensions.First()}", FileAccess.Write, FileMode.Create)) - archive.SaveTo(outputStream); - - exportStorage.OpenInNativeExplorer(); + archive.SaveTo(outputStream); } } diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index c7ee26c248..9d3b952ada 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -72,6 +72,16 @@ namespace osu.Game.Scoring } } + protected override void ExportModelTo(ScoreInfo model, Stream outputStream) + { + var file = model.Files.SingleOrDefault(); + if (file == null) + return; + + using (var inputStream = Files.Storage.GetStream(file.FileInfo.StoragePath)) + inputStream.CopyTo(outputStream); + } + protected override IEnumerable GetStableImportPaths(Storage storage) => storage.GetFiles(ImportFromStablePath).Where(p => HandledExtensions.Any(ext => Path.GetExtension(p)?.Equals(ext, StringComparison.OrdinalIgnoreCase) ?? false)) .Select(path => storage.GetFullPath(path)); From 213ac88a8b34b7d1caa57183945b501b3c828e1a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 26 Apr 2021 20:52:20 +0900 Subject: [PATCH 1752/1791] Fix exported scores not being compatible with osu-stable --- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index db7e51e833..56c4e75864 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -95,7 +95,7 @@ namespace osu.Game.Scoring.Legacy foreach (var f in score.Replay.Frames.OfType().Select(f => f.ToLegacy(beatmap))) { - replayData.Append(FormattableString.Invariant($"{f.Time - lastF.Time}|{f.MouseX ?? 0}|{f.MouseY ?? 0}|{(int)f.ButtonState},")); + replayData.Append(FormattableString.Invariant($"{(int)Math.Round(f.Time - lastF.Time)}|{f.MouseX ?? 0}|{f.MouseY ?? 0}|{(int)f.ButtonState},")); lastF = f; } } From 08a232f7fad662384426f21b3131e36828b9de62 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Mon, 26 Apr 2021 20:08:40 +0200 Subject: [PATCH 1753/1791] Add method to safely refresh DrawableHitObject transforms --- .../Rulesets/Objects/Drawables/DrawableHitObject.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 7739994527..679c86072b 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -14,13 +14,13 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Framework.Threading; using osu.Game.Audio; +using osu.Game.Configuration; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects.Pooling; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; -using osu.Game.Skinning; -using osu.Game.Configuration; -using osu.Game.Rulesets.Objects.Pooling; using osu.Game.Rulesets.UI; +using osu.Game.Skinning; using osuTK.Graphics; namespace osu.Game.Rulesets.Objects.Drawables @@ -429,6 +429,13 @@ namespace osu.Game.Rulesets.Objects.Drawables base.ClearTransformsAfter(double.MinValue, true); } + /// + /// Removes all previously applied transforms, then reapplies a new set of transforms with potentially different parameters. + /// The transforms will use the current , and they will use the appropriate start times. + /// This also takes in account potential overrides defined in . + /// + protected void RefreshStateTransforms() => updateState(State.Value, true); + /// /// Apply (generally fade-in) transforms leading into the start time. /// The local drawable hierarchy is recursively delayed to for convenience. From 9ad30da72943d4bae44dc82e7438d8bb84e14ce0 Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Mon, 26 Apr 2021 16:41:26 -0400 Subject: [PATCH 1754/1791] Show a notification if game is run as administrator --- osu.Desktop/OsuGameDesktop.cs | 9 ++++++ osu.Desktop/osu.Desktop.csproj | 1 + osu.Game/Admin/AdminChecker.cs | 57 ++++++++++++++++++++++++++++++++++ osu.Game/OsuGame.cs | 5 +++ 4 files changed, 72 insertions(+) create mode 100644 osu.Game/Admin/AdminChecker.cs diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 0c21c75290..1ce79b3b49 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using System.Reflection; using System.Runtime.Versioning; +using System.Security.Principal; using System.Threading.Tasks; using Microsoft.Win32; using osu.Desktop.Overlays; @@ -20,6 +21,7 @@ using osu.Game.Screens.Menu; using osu.Game.Updater; using osu.Desktop.Windows; using osu.Framework.Threading; +using osu.Game.Admin; using osu.Game.IO; namespace osu.Desktop @@ -102,6 +104,8 @@ namespace osu.Desktop } } + protected override AdminChecker CreateAdminChecker() => new DesktopAdminChecker(); + protected override void LoadComplete() { base.LoadComplete(); @@ -180,5 +184,10 @@ namespace osu.Desktop Task.Factory.StartNew(() => Import(paths), TaskCreationOptions.LongRunning); } } + + private class DesktopAdminChecker : AdminChecker + { + protected override bool IsAdmin() => OperatingSystem.IsWindows() ? new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator) : Mono.Unix.Native.Syscall.geteuid() == 0; + } } } diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 3e0f0cb7f6..6bd0f64218 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -25,6 +25,7 @@ + diff --git a/osu.Game/Admin/AdminChecker.cs b/osu.Game/Admin/AdminChecker.cs new file mode 100644 index 0000000000..b81e34653e --- /dev/null +++ b/osu.Game/Admin/AdminChecker.cs @@ -0,0 +1,57 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Overlays; +using osu.Game.Overlays.Notifications; + +namespace osu.Game.Admin +{ + /// + /// Checks if the game is running with elevated privileges (as admin in Windows, root in Unix) and displays a warning notification if so. + /// + public class AdminChecker : CompositeDrawable + { + [Resolved] + protected NotificationOverlay Notifications { get; private set; } + + protected override void LoadComplete() + { + base.LoadComplete(); + if (IsAdmin()) + Notifications.Post(new AdminNotification()); + } + + protected virtual bool IsAdmin() => false; + + private class AdminNotification : SimpleNotification + { + public override bool IsImportant => true; + + public AdminNotification() + { + bool isUnix = RuntimeInfo.IsUnix; + Text = $"Running osu! as {(isUnix ? "root" : "administrator")} does not improve performance and poses a security risk. Please run the game normally."; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours, NotificationOverlay notificationOverlay) + { + Icon = FontAwesome.Solid.ShieldAlt; + IconBackgound.Colour = colours.YellowDark; + + Activated = delegate + { + notificationOverlay.Hide(); + return true; + }; + } + } + } + + +} diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 28f32ba455..358e85b247 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -28,6 +28,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Threading; +using osu.Game.Admin; using osu.Game.Beatmaps; using osu.Game.Collections; using osu.Game.Graphics; @@ -450,6 +451,8 @@ namespace osu.Game protected virtual UpdateManager CreateUpdateManager() => new UpdateManager(); + protected virtual AdminChecker CreateAdminChecker() => new AdminChecker(); + protected override Container CreateScalingContainer() => new ScalingContainer(ScalingMode.Everything); #region Beatmap progression @@ -673,6 +676,8 @@ namespace osu.Game // dependency on notification overlay, dependent by settings overlay loadComponentSingleFile(CreateUpdateManager(), Add, true); + loadComponentSingleFile(CreateAdminChecker(), Add, false); + // overlay elements loadComponentSingleFile(new ManageCollectionsDialog(), overlayContent.Add, true); loadComponentSingleFile(beatmapListing = new BeatmapListingOverlay(), overlayContent.Add, true); From 260dd06f4707f378ae2fd7a6fda4852d6160105a Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Mon, 26 Apr 2021 17:41:04 -0400 Subject: [PATCH 1755/1791] Move AdminChecker to osu.Desktop.Admin --- {osu.Game => osu.Desktop}/Admin/AdminChecker.cs | 13 ++++++------- osu.Desktop/OsuGameDesktop.cs | 12 +++--------- osu.Game/OsuGame.cs | 5 ----- 3 files changed, 9 insertions(+), 21 deletions(-) rename {osu.Game => osu.Desktop}/Admin/AdminChecker.cs (75%) diff --git a/osu.Game/Admin/AdminChecker.cs b/osu.Desktop/Admin/AdminChecker.cs similarity index 75% rename from osu.Game/Admin/AdminChecker.cs rename to osu.Desktop/Admin/AdminChecker.cs index b81e34653e..b9f0d68694 100644 --- a/osu.Game/Admin/AdminChecker.cs +++ b/osu.Desktop/Admin/AdminChecker.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.Security.Principal; using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; @@ -9,7 +11,7 @@ using osu.Game.Graphics; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; -namespace osu.Game.Admin +namespace osu.Desktop.Admin { /// /// Checks if the game is running with elevated privileges (as admin in Windows, root in Unix) and displays a warning notification if so. @@ -22,11 +24,11 @@ namespace osu.Game.Admin protected override void LoadComplete() { base.LoadComplete(); - if (IsAdmin()) + if (isAdmin()) Notifications.Post(new AdminNotification()); } - protected virtual bool IsAdmin() => false; + private bool isAdmin() => OperatingSystem.IsWindows() ? new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator) : Mono.Unix.Native.Syscall.geteuid() == 0; private class AdminNotification : SimpleNotification { @@ -34,8 +36,7 @@ namespace osu.Game.Admin public AdminNotification() { - bool isUnix = RuntimeInfo.IsUnix; - Text = $"Running osu! as {(isUnix ? "root" : "administrator")} does not improve performance and poses a security risk. Please run the game normally."; + Text = $"Running osu! as {(RuntimeInfo.IsUnix ? "root" : "administrator")} does not improve performance and poses a security risk. Please run the game normally."; } [BackgroundDependencyLoader] @@ -52,6 +53,4 @@ namespace osu.Game.Admin } } } - - } diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 1ce79b3b49..77f968e1b6 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -7,9 +7,9 @@ using System.IO; using System.Linq; using System.Reflection; using System.Runtime.Versioning; -using System.Security.Principal; using System.Threading.Tasks; using Microsoft.Win32; +using osu.Desktop.Admin; using osu.Desktop.Overlays; using osu.Framework.Platform; using osu.Game; @@ -21,7 +21,6 @@ using osu.Game.Screens.Menu; using osu.Game.Updater; using osu.Desktop.Windows; using osu.Framework.Threading; -using osu.Game.Admin; using osu.Game.IO; namespace osu.Desktop @@ -104,8 +103,6 @@ namespace osu.Desktop } } - protected override AdminChecker CreateAdminChecker() => new DesktopAdminChecker(); - protected override void LoadComplete() { base.LoadComplete(); @@ -117,6 +114,8 @@ namespace osu.Desktop if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows) LoadComponentAsync(new GameplayWinKeyBlocker(), Add); + + LoadComponentAsync(new AdminChecker(), Add); } protected override void ScreenChanged(IScreen lastScreen, IScreen newScreen) @@ -184,10 +183,5 @@ namespace osu.Desktop Task.Factory.StartNew(() => Import(paths), TaskCreationOptions.LongRunning); } } - - private class DesktopAdminChecker : AdminChecker - { - protected override bool IsAdmin() => OperatingSystem.IsWindows() ? new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator) : Mono.Unix.Native.Syscall.geteuid() == 0; - } } } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 358e85b247..28f32ba455 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -28,7 +28,6 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Threading; -using osu.Game.Admin; using osu.Game.Beatmaps; using osu.Game.Collections; using osu.Game.Graphics; @@ -451,8 +450,6 @@ namespace osu.Game protected virtual UpdateManager CreateUpdateManager() => new UpdateManager(); - protected virtual AdminChecker CreateAdminChecker() => new AdminChecker(); - protected override Container CreateScalingContainer() => new ScalingContainer(ScalingMode.Everything); #region Beatmap progression @@ -676,8 +673,6 @@ namespace osu.Game // dependency on notification overlay, dependent by settings overlay loadComponentSingleFile(CreateUpdateManager(), Add, true); - loadComponentSingleFile(CreateAdminChecker(), Add, false); - // overlay elements loadComponentSingleFile(new ManageCollectionsDialog(), overlayContent.Add, true); loadComponentSingleFile(beatmapListing = new BeatmapListingOverlay(), overlayContent.Add, true); From c3bad1d4c599e43846c8f0e596b370bc082ef5ed Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Mon, 26 Apr 2021 21:05:18 -0400 Subject: [PATCH 1756/1791] Rename AdminChecker to ElevatedPrivilegesChecker, refactor elevated check --- ...hecker.cs => ElevatedPrivilegesChecker.cs} | 30 ++++++++++++++----- osu.Desktop/OsuGameDesktop.cs | 2 +- 2 files changed, 23 insertions(+), 9 deletions(-) rename osu.Desktop/Admin/{AdminChecker.cs => ElevatedPrivilegesChecker.cs} (64%) diff --git a/osu.Desktop/Admin/AdminChecker.cs b/osu.Desktop/Admin/ElevatedPrivilegesChecker.cs similarity index 64% rename from osu.Desktop/Admin/AdminChecker.cs rename to osu.Desktop/Admin/ElevatedPrivilegesChecker.cs index b9f0d68694..ea4aab0f85 100644 --- a/osu.Desktop/Admin/AdminChecker.cs +++ b/osu.Desktop/Admin/ElevatedPrivilegesChecker.cs @@ -5,7 +5,7 @@ using System; using System.Security.Principal; using osu.Framework; using osu.Framework.Allocation; -using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Overlays; @@ -16,7 +16,7 @@ namespace osu.Desktop.Admin /// /// Checks if the game is running with elevated privileges (as admin in Windows, root in Unix) and displays a warning notification if so. /// - public class AdminChecker : CompositeDrawable + public class ElevatedPrivilegesChecker : Component { [Resolved] protected NotificationOverlay Notifications { get; private set; } @@ -24,17 +24,31 @@ namespace osu.Desktop.Admin protected override void LoadComplete() { base.LoadComplete(); - if (isAdmin()) - Notifications.Post(new AdminNotification()); + + bool elevated = false; + + if (OperatingSystem.IsWindows()) + { + var windowsIdentity = WindowsIdentity.GetCurrent(); + var windowsPrincipal = new WindowsPrincipal(windowsIdentity); + elevated = windowsPrincipal.IsInRole(WindowsBuiltInRole.Administrator); + } + else if (RuntimeInfo.IsUnix) + { + elevated = Mono.Unix.Native.Syscall.geteuid() == 0; + } + + if (!elevated) + return; + + Notifications.Post(new ElevatedPrivilegesNotification()); } - private bool isAdmin() => OperatingSystem.IsWindows() ? new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator) : Mono.Unix.Native.Syscall.geteuid() == 0; - - private class AdminNotification : SimpleNotification + private class ElevatedPrivilegesNotification : SimpleNotification { public override bool IsImportant => true; - public AdminNotification() + public ElevatedPrivilegesNotification() { Text = $"Running osu! as {(RuntimeInfo.IsUnix ? "root" : "administrator")} does not improve performance and poses a security risk. Please run the game normally."; } diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 77f968e1b6..9f7854779e 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -115,7 +115,7 @@ namespace osu.Desktop if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows) LoadComponentAsync(new GameplayWinKeyBlocker(), Add); - LoadComponentAsync(new AdminChecker(), Add); + LoadComponentAsync(new ElevatedPrivilegesChecker(), Add); } protected override void ScreenChanged(IScreen lastScreen, IScreen newScreen) From a2723f3f579565b2aca0e1c6159362838c9c559b Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Mon, 26 Apr 2021 22:37:08 -0400 Subject: [PATCH 1757/1791] Perform elevated check asynchronously, use a separate function w/ switch statement --- osu.Desktop/OsuGameDesktop.cs | 2 +- .../ElevatedPrivilegesChecker.cs | 48 +++++++++++++------ osu.Desktop/osu.Desktop.csproj | 2 +- 3 files changed, 35 insertions(+), 17 deletions(-) rename osu.Desktop/{Admin => Security}/ElevatedPrivilegesChecker.cs (66%) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 9f7854779e..4a28ab3722 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -9,7 +9,7 @@ using System.Reflection; using System.Runtime.Versioning; using System.Threading.Tasks; using Microsoft.Win32; -using osu.Desktop.Admin; +using osu.Desktop.Security; using osu.Desktop.Overlays; using osu.Framework.Platform; using osu.Game; diff --git a/osu.Desktop/Admin/ElevatedPrivilegesChecker.cs b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs similarity index 66% rename from osu.Desktop/Admin/ElevatedPrivilegesChecker.cs rename to osu.Desktop/Security/ElevatedPrivilegesChecker.cs index ea4aab0f85..a719dbf952 100644 --- a/osu.Desktop/Admin/ElevatedPrivilegesChecker.cs +++ b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs @@ -11,7 +11,7 @@ using osu.Game.Graphics; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; -namespace osu.Desktop.Admin +namespace osu.Desktop.Security { /// /// Checks if the game is running with elevated privileges (as admin in Windows, root in Unix) and displays a warning notification if so. @@ -21,36 +21,54 @@ namespace osu.Desktop.Admin [Resolved] protected NotificationOverlay Notifications { get; private set; } + private bool elevated; + protected override void LoadComplete() { base.LoadComplete(); - bool elevated = false; - - if (OperatingSystem.IsWindows()) - { - var windowsIdentity = WindowsIdentity.GetCurrent(); - var windowsPrincipal = new WindowsPrincipal(windowsIdentity); - elevated = windowsPrincipal.IsInRole(WindowsBuiltInRole.Administrator); - } - else if (RuntimeInfo.IsUnix) - { - elevated = Mono.Unix.Native.Syscall.geteuid() == 0; - } - if (!elevated) return; Notifications.Post(new ElevatedPrivilegesNotification()); } + [BackgroundDependencyLoader] + private void load() + { + elevated = isElevated(); + } + + private bool isElevated() + { + switch (RuntimeInfo.OS) + { + case RuntimeInfo.Platform.Windows: + { + if (!OperatingSystem.IsWindows()) return false; + + var windowsIdentity = WindowsIdentity.GetCurrent(); + var windowsPrincipal = new WindowsPrincipal(windowsIdentity); + + return windowsPrincipal.IsInRole(WindowsBuiltInRole.Administrator); + } + + case RuntimeInfo.Platform.macOS: + case RuntimeInfo.Platform.Linux: + return Mono.Unix.Native.Syscall.geteuid() == 0; + + default: + return false; + } + } + private class ElevatedPrivilegesNotification : SimpleNotification { public override bool IsImportant => true; public ElevatedPrivilegesNotification() { - Text = $"Running osu! as {(RuntimeInfo.IsUnix ? "root" : "administrator")} does not improve performance and poses a security risk. Please run the game normally."; + Text = $"Running osu! as {(RuntimeInfo.IsUnix ? "root" : "administrator")} does not improve performance and poses a security risk. Please run the game as a normal user."; } [BackgroundDependencyLoader] diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 6bd0f64218..ad5c323e9b 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -25,7 +25,7 @@ - + From e0f54f58424f6fc77d79fe12ef6f6a511e4b6f71 Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Mon, 26 Apr 2021 22:51:03 -0400 Subject: [PATCH 1758/1791] Move load() before LoadComplete() --- osu.Desktop/Security/ElevatedPrivilegesChecker.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs index a719dbf952..5fb8263516 100644 --- a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs +++ b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs @@ -23,6 +23,12 @@ namespace osu.Desktop.Security private bool elevated; + [BackgroundDependencyLoader] + private void load() + { + elevated = isElevated(); + } + protected override void LoadComplete() { base.LoadComplete(); @@ -33,12 +39,6 @@ namespace osu.Desktop.Security Notifications.Post(new ElevatedPrivilegesNotification()); } - [BackgroundDependencyLoader] - private void load() - { - elevated = isElevated(); - } - private bool isElevated() { switch (RuntimeInfo.OS) From 5a3fbef5ace66e56b2f5312aacf74a4e2ff26793 Mon Sep 17 00:00:00 2001 From: Christine Chen Date: Tue, 27 Apr 2021 00:23:08 -0400 Subject: [PATCH 1759/1791] Use a try-catch, notification activation does nothing --- .../Security/ElevatedPrivilegesChecker.cs | 45 +++++++++---------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs index 5fb8263516..47705eb929 100644 --- a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs +++ b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs @@ -19,7 +19,7 @@ namespace osu.Desktop.Security public class ElevatedPrivilegesChecker : Component { [Resolved] - protected NotificationOverlay Notifications { get; private set; } + private NotificationOverlay notifications { get; set; } private bool elevated; @@ -33,32 +33,35 @@ namespace osu.Desktop.Security { base.LoadComplete(); - if (!elevated) - return; - - Notifications.Post(new ElevatedPrivilegesNotification()); + if (elevated) + notifications.Post(new ElevatedPrivilegesNotification()); } private bool isElevated() { - switch (RuntimeInfo.OS) + try { - case RuntimeInfo.Platform.Windows: + switch (RuntimeInfo.OS) { - if (!OperatingSystem.IsWindows()) return false; + case RuntimeInfo.Platform.Windows: + if (!OperatingSystem.IsWindows()) return false; - var windowsIdentity = WindowsIdentity.GetCurrent(); - var windowsPrincipal = new WindowsPrincipal(windowsIdentity); + var windowsIdentity = WindowsIdentity.GetCurrent(); + var windowsPrincipal = new WindowsPrincipal(windowsIdentity); - return windowsPrincipal.IsInRole(WindowsBuiltInRole.Administrator); + return windowsPrincipal.IsInRole(WindowsBuiltInRole.Administrator); + + case RuntimeInfo.Platform.macOS: + case RuntimeInfo.Platform.Linux: + return Mono.Unix.Native.Syscall.geteuid() == 0; + + default: + return false; } - - case RuntimeInfo.Platform.macOS: - case RuntimeInfo.Platform.Linux: - return Mono.Unix.Native.Syscall.geteuid() == 0; - - default: - return false; + } + catch + { + return false; } } @@ -77,11 +80,7 @@ namespace osu.Desktop.Security Icon = FontAwesome.Solid.ShieldAlt; IconBackgound.Colour = colours.YellowDark; - Activated = delegate - { - notificationOverlay.Hide(); - return true; - }; + Activated = () => true; } } } From ec1c336b0aca43376e853e0a4d0826368924a95d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Apr 2021 13:23:14 +0900 Subject: [PATCH 1760/1791] Fix a couple of inspections --- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 2 +- osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 77ea3b05dc..8b20df9a68 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -207,7 +207,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders var lastPiece = controlPointVisualiser.Pieces.Single(p => p.ControlPoint == last); lastPoint = last; - return lastPiece?.IsHovered != true; + return lastPiece.IsHovered != true; } private void updateSlider() diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index 99cdca045b..337a806b6e 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -3,7 +3,6 @@ using System; using osu.Framework; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; @@ -35,9 +34,6 @@ namespace osu.Game.Rulesets.Edit public override bool HandlePositionalInput => ShouldBeAlive; public override bool RemoveWhenNotAlive => false; - [Resolved(CanBeNull = true)] - private HitObjectComposer composer { get; set; } - protected SelectionBlueprint(HitObject hitObject) { HitObject = hitObject; From 7980d16b4c202bcb063c0569112ad76463e8a9c6 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 27 Apr 2021 14:26:12 +0900 Subject: [PATCH 1761/1791] Add failing test showing the issue of DHO lifetime --- .../Gameplay/TestSceneDrawableHitObject.cs | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs diff --git a/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs b/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs new file mode 100644 index 0000000000..d42802db91 --- /dev/null +++ b/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs @@ -0,0 +1,62 @@ +// 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.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Tests.Visual; + +namespace osu.Game.Tests.Gameplay +{ + [HeadlessTest] + public class TestSceneDrawableHitObject : OsuTestScene + { + [Test] + public void TestEntryLifetime() + { + TestDrawableHitObject dho = null; + var initialHitObject = new HitObject + { + StartTime = 1000 + }; + var entry = new TestLifetimeEntry(new HitObject + { + StartTime = 2000 + }); + + AddStep("Create DHO", () => Child = dho = new TestDrawableHitObject(initialHitObject)); + + AddAssert("Correct initial lifetime", () => dho.LifetimeStart == initialHitObject.StartTime - TestDrawableHitObject.INITIAL_LIFETIME_OFFSET); + + AddStep("Apply entry", () => dho.Apply(entry)); + + AddAssert("Correct initial lifetime", () => dho.LifetimeStart == entry.HitObject.StartTime - TestLifetimeEntry.INITIAL_LIFETIME_OFFSET); + + AddStep("Set lifetime", () => dho.LifetimeEnd = 3000); + AddAssert("Entry lifetime is updated", () => entry.LifetimeEnd == 3000); + } + + private class TestDrawableHitObject : DrawableHitObject + { + public const double INITIAL_LIFETIME_OFFSET = 100; + protected override double InitialLifetimeOffset => INITIAL_LIFETIME_OFFSET; + + public TestDrawableHitObject(HitObject hitObject) + : base(hitObject) + { + } + } + + private class TestLifetimeEntry : HitObjectLifetimeEntry + { + public const double INITIAL_LIFETIME_OFFSET = 200; + protected override double InitialLifetimeOffset => INITIAL_LIFETIME_OFFSET; + + public TestLifetimeEntry(HitObject hitObject) + : base(hitObject) + { + } + } + } +} From 2303d108bb56b8214c788c18b59424d3fe69858e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Apr 2021 14:35:14 +0900 Subject: [PATCH 1762/1791] Simplify false return path --- osu.Desktop/Security/ElevatedPrivilegesChecker.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs index 47705eb929..92885ad855 100644 --- a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs +++ b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs @@ -54,15 +54,13 @@ namespace osu.Desktop.Security case RuntimeInfo.Platform.macOS: case RuntimeInfo.Platform.Linux: return Mono.Unix.Native.Syscall.geteuid() == 0; - - default: - return false; } } catch { - return false; } + + return false; } private class ElevatedPrivilegesNotification : SimpleNotification From 13de571b3c690b2def91c4331b3b70bff00c4719 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Apr 2021 14:35:57 +0900 Subject: [PATCH 1763/1791] Rename private method --- osu.Desktop/Security/ElevatedPrivilegesChecker.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs index 92885ad855..82e7d2a141 100644 --- a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs +++ b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs @@ -26,7 +26,7 @@ namespace osu.Desktop.Security [BackgroundDependencyLoader] private void load() { - elevated = isElevated(); + elevated = checkElevated(); } protected override void LoadComplete() @@ -37,7 +37,7 @@ namespace osu.Desktop.Security notifications.Post(new ElevatedPrivilegesNotification()); } - private bool isElevated() + private bool checkElevated() { try { From 2673cd3d9935025232453d9b371d516bc6b7de8b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Apr 2021 14:36:11 +0900 Subject: [PATCH 1764/1791] Remove unnecessary noop action --- osu.Desktop/Security/ElevatedPrivilegesChecker.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs index 82e7d2a141..edd4906421 100644 --- a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs +++ b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs @@ -77,8 +77,6 @@ namespace osu.Desktop.Security { Icon = FontAwesome.Solid.ShieldAlt; IconBackgound.Colour = colours.YellowDark; - - Activated = () => true; } } } From dbcb1259e2c07a726b8be0b1d12aca362c43df7f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Apr 2021 14:38:19 +0900 Subject: [PATCH 1765/1791] Add a note about elevated privileges also breaking integrations --- osu.Desktop/Security/ElevatedPrivilegesChecker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs index edd4906421..01458b4c37 100644 --- a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs +++ b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs @@ -69,7 +69,7 @@ namespace osu.Desktop.Security public ElevatedPrivilegesNotification() { - Text = $"Running osu! as {(RuntimeInfo.IsUnix ? "root" : "administrator")} does not improve performance and poses a security risk. Please run the game as a normal user."; + Text = $"Running osu! as {(RuntimeInfo.IsUnix ? "root" : "administrator")} does not improve performance, may break integrations and poses a security risk. Please run the game as a normal user."; } [BackgroundDependencyLoader] From a2c0951d94102cee2660e65412ffb4c4850114f1 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 27 Apr 2021 15:23:33 +0900 Subject: [PATCH 1766/1791] Use overriding instead of hiding in HitObjectLifetimeEntry Hidden properties are used when the type is the base class. It caused issues when `DrawableHitObject` logic is factored out to `PoolableDrawableWithLifetime` because it is using the base `LifetimeEntry`, not `HitObjectLifetimeEntry`. --- .../Objects/Drawables/DrawableHitObject.cs | 5 ++- .../Objects/HitObjectLifetimeEntry.cs | 41 +++++-------------- 2 files changed, 13 insertions(+), 33 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 7739994527..6431ec8980 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -11,6 +11,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Performance; using osu.Framework.Graphics.Primitives; using osu.Framework.Threading; using osu.Game.Audio; @@ -431,7 +432,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// Apply (generally fade-in) transforms leading into the start time. - /// The local drawable hierarchy is recursively delayed to for convenience. + /// The local drawable hierarchy is recursively delayed to for convenience. /// /// By default this will fade in the object from zero with no duration. /// @@ -613,7 +614,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// This is only used as an optimisation to delay the initial update of this and may be tuned more aggressively if required. /// It is indirectly used to decide the automatic transform offset provided to . - /// A more accurate should be set for further optimisation (in , for example). + /// A more accurate should be set for further optimisation (in , for example). /// /// Only has an effect if this is not being pooled. /// For pooled s, use instead. diff --git a/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs b/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs index 1954d7e6d2..23e991d00e 100644 --- a/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs +++ b/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs @@ -38,40 +38,19 @@ namespace osu.Game.Rulesets.Objects startTimeBindable.BindValueChanged(onStartTimeChanged, true); } - // The lifetime start, as set by the hitobject. + // The lifetime, as set by the hitobject. private double realLifetimeStart = double.MinValue; - - /// - /// The time at which the should become alive. - /// - public new double LifetimeStart - { - get => realLifetimeStart; - set => setLifetime(realLifetimeStart = value, LifetimeEnd); - } - - // The lifetime end, as set by the hitobject. private double realLifetimeEnd = double.MaxValue; - /// - /// The time at which the should become dead. - /// - public new double LifetimeEnd + public override void SetLifetime(double start, double end) { - get => realLifetimeEnd; - set => setLifetime(LifetimeStart, realLifetimeEnd = value); - } + realLifetimeStart = start; + realLifetimeEnd = end; - private void setLifetime(double start, double end) - { if (keepAlive) - { - start = double.MinValue; - end = double.MaxValue; - } - - base.LifetimeStart = start; - base.LifetimeEnd = end; + base.SetLifetime(double.MinValue, double.MaxValue); + else + base.SetLifetime(start, end); } private bool keepAlive; @@ -87,7 +66,7 @@ namespace osu.Game.Rulesets.Objects return; keepAlive = value; - setLifetime(realLifetimeStart, realLifetimeEnd); + SetLifetime(realLifetimeStart, realLifetimeEnd); } } @@ -98,12 +77,12 @@ namespace osu.Game.Rulesets.Objects /// /// This is only used as an optimisation to delay the initial update of the and may be tuned more aggressively if required. /// It is indirectly used to decide the automatic transform offset provided to . - /// A more accurate should be set for further optimisation (in , for example). + /// A more accurate should be set for further optimisation (in , for example). /// protected virtual double InitialLifetimeOffset => 10000; /// - /// Resets according to the change in start time of the . + /// Resets according to the change in start time of the . /// private void onStartTimeChanged(ValueChangedEvent startTime) => LifetimeStart = HitObject.StartTime - InitialLifetimeOffset; } From c9e6ca537896fd7a099d2b4a4338b1ecfcda9001 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 27 Apr 2021 15:24:52 +0900 Subject: [PATCH 1767/1791] Use now-public Entry.SetLifetime method --- .../Objects/Pooling/PoolableDrawableWithLifetime.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs b/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs index 93e476be76..049ff9d446 100644 --- a/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs +++ b/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs @@ -100,11 +100,7 @@ namespace osu.Game.Rulesets.Objects.Pooling base.LifetimeStart = start; base.LifetimeEnd = end; - if (Entry != null) - { - Entry.LifetimeStart = start; - Entry.LifetimeEnd = end; - } + Entry?.SetLifetime(start, end); } private void free() From 3899e500d3edf2b15622ed4e5ace339ab68fbdc0 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 27 Apr 2021 17:54:18 +0900 Subject: [PATCH 1768/1791] Adopt framework change of LifetimeEntry Override SetLifetimeStart/SetLifetimeEnd separately to track individual assignment. It is necessary to ensure real lifetime is not lost when lifetime is partially updated. --- .../Rulesets/Objects/HitObjectLifetimeEntry.cs | 15 +++++++++++++-- .../Pooling/PoolableDrawableWithLifetime.cs | 6 +++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs b/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs index 23e991d00e..0181d6241b 100644 --- a/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs +++ b/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs @@ -42,11 +42,22 @@ namespace osu.Game.Rulesets.Objects private double realLifetimeStart = double.MinValue; private double realLifetimeEnd = double.MaxValue; - public override void SetLifetime(double start, double end) + // This method is called even if `start == LifetimeStart` when `KeepAlive` is true (necessary to update `realLifetimeStart`). + protected override void SetLifetimeStart(double start) { + // This assignment cannot be done in `SetLifetime` because otherwise setting only `LifetimeStart` will make `realLifetimeEnd` to be lost. realLifetimeStart = start; - realLifetimeEnd = end; + base.SetLifetimeStart(start); + } + protected override void SetLifetimeEnd(double end) + { + realLifetimeEnd = end; + base.SetLifetimeEnd(end); + } + + protected override void SetLifetime(double start, double end) + { if (keepAlive) base.SetLifetime(double.MinValue, double.MaxValue); else diff --git a/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs b/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs index 049ff9d446..93e476be76 100644 --- a/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs +++ b/osu.Game/Rulesets/Objects/Pooling/PoolableDrawableWithLifetime.cs @@ -100,7 +100,11 @@ namespace osu.Game.Rulesets.Objects.Pooling base.LifetimeStart = start; base.LifetimeEnd = end; - Entry?.SetLifetime(start, end); + if (Entry != null) + { + Entry.LifetimeStart = start; + Entry.LifetimeEnd = end; + } } private void free() From 003553aba32d5af6a9decf2a7f2b8c307857ac9b Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 27 Apr 2021 18:10:44 +0900 Subject: [PATCH 1769/1791] Add test of HitObjectLifetimeEntry.KeepAlive behavior --- .../Gameplay/TestSceneDrawableHitObject.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs b/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs index d42802db91..2e3f192f1b 100644 --- a/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs +++ b/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs @@ -37,6 +37,38 @@ namespace osu.Game.Tests.Gameplay AddAssert("Entry lifetime is updated", () => entry.LifetimeEnd == 3000); } + [Test] + public void TestKeepAlive() + { + TestDrawableHitObject dho = null; + TestLifetimeEntry entry = null; + AddStep("Create DHO", () => + { + dho = new TestDrawableHitObject(null); + dho.Apply(entry = new TestLifetimeEntry(new HitObject()) + { + LifetimeStart = 0, + LifetimeEnd = 1000, + }); + Child = dho; + }); + + AddStep("KeepAlive = true", () => entry.KeepAlive = true); + AddAssert("Lifetime is overriden", () => entry.LifetimeStart == double.MinValue && entry.LifetimeEnd == double.MaxValue); + + AddStep("Set LifetimeStart", () => dho.LifetimeStart = 500); + AddStep("KeepAlive = false", () => entry.KeepAlive = false); + AddAssert("Lifetime is correct", () => entry.LifetimeStart == 500 && entry.LifetimeEnd == 1000); + + AddStep("Set LifetimeStart while KeepAlive", () => + { + entry.KeepAlive = true; + dho.LifetimeStart = double.MinValue; + entry.KeepAlive = false; + }); + AddAssert("Lifetime is changed", () => entry.LifetimeStart == double.MinValue && entry.LifetimeEnd == 1000); + } + private class TestDrawableHitObject : DrawableHitObject { public const double INITIAL_LIFETIME_OFFSET = 100; From 3ea55314f2f11c53dc2232e4152beacf7dc7f779 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Tue, 27 Apr 2021 11:29:16 +0200 Subject: [PATCH 1770/1791] Update osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs Co-authored-by: Dan Balasescu --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 679c86072b..e715e4aac6 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -430,9 +430,7 @@ namespace osu.Game.Rulesets.Objects.Drawables } /// - /// Removes all previously applied transforms, then reapplies a new set of transforms with potentially different parameters. - /// The transforms will use the current , and they will use the appropriate start times. - /// This also takes in account potential overrides defined in . + /// Reapplies the current . /// protected void RefreshStateTransforms() => updateState(State.Value, true); From b87446a5778ee76c96f02cd163beebdc44476e24 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Tue, 27 Apr 2021 19:37:01 +0900 Subject: [PATCH 1771/1791] Simplify HitObjectLifetimeEntry logic a bit --- .../Objects/HitObjectLifetimeEntry.cs | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs b/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs index 0181d6241b..0d1eb68f07 100644 --- a/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs +++ b/osu.Game/Rulesets/Objects/HitObjectLifetimeEntry.cs @@ -45,23 +45,16 @@ namespace osu.Game.Rulesets.Objects // This method is called even if `start == LifetimeStart` when `KeepAlive` is true (necessary to update `realLifetimeStart`). protected override void SetLifetimeStart(double start) { - // This assignment cannot be done in `SetLifetime` because otherwise setting only `LifetimeStart` will make `realLifetimeEnd` to be lost. realLifetimeStart = start; - base.SetLifetimeStart(start); + if (!keepAlive) + base.SetLifetimeStart(start); } protected override void SetLifetimeEnd(double end) { realLifetimeEnd = end; - base.SetLifetimeEnd(end); - } - - protected override void SetLifetime(double start, double end) - { - if (keepAlive) - base.SetLifetime(double.MinValue, double.MaxValue); - else - base.SetLifetime(start, end); + if (!keepAlive) + base.SetLifetimeEnd(end); } private bool keepAlive; @@ -77,7 +70,10 @@ namespace osu.Game.Rulesets.Objects return; keepAlive = value; - SetLifetime(realLifetimeStart, realLifetimeEnd); + if (keepAlive) + SetLifetime(double.MinValue, double.MaxValue); + else + SetLifetime(realLifetimeStart, realLifetimeEnd); } } From 5dadfd04e7c11f0e69982401fe5647ba67621067 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Apr 2021 22:36:25 +0900 Subject: [PATCH 1772/1791] 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 e0b07549f4..5aee9e15cc 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 9a43d5f031..986bd8e7ba 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 2c41b3ef26..c32109d6db 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 3b04aed491abef2278c34f497ead995a839c6f52 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 27 Apr 2021 22:31:23 +0900 Subject: [PATCH 1773/1791] Add failing test --- .../Rulesets/Scoring/ScoreProcessorTest.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index 20fa0732b9..2c862fe8a8 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -287,6 +287,23 @@ namespace osu.Game.Tests.Rulesets.Scoring Assert.AreEqual(expectedReturnValue, hitResult.IsScorable()); } + [TestCase(HitResult.Perfect, 1_000_000)] + [TestCase(HitResult.SmallTickHit, 1_000_000)] + [TestCase(HitResult.LargeTickHit, 1_000_000)] + [TestCase(HitResult.SmallBonus, 700_000 + Judgement.SMALL_BONUS_SCORE)] + [TestCase(HitResult.LargeBonus, 700_000 + Judgement.LARGE_BONUS_SCORE)] + public void TestGetScoreWithExternalStatistics(HitResult result, int expectedScore) + { + var statistic = new Dictionary { { result, 1 } }; + + scoreProcessor.ApplyBeatmap(new Beatmap + { + HitObjects = { new TestHitObject(result) } + }); + + Assert.That(scoreProcessor.GetImmediateScore(ScoringMode.Standardised, result.AffectsCombo() ? 1 : 0, statistic), Is.EqualTo(expectedScore).Within(0.5d)); + } + private class TestJudgement : Judgement { public override HitResult MaxResult { get; } From 1281993f1f9b106c6e77c05cf59b96e85590feda Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 27 Apr 2021 22:35:31 +0900 Subject: [PATCH 1774/1791] Fix bonus score not calculated from the correct statistics --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 0fb5c2f4b5..a16a6a6c9b 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -252,7 +252,7 @@ namespace osu.Game.Rulesets.Scoring computedBaseScore += Judgement.ToNumericResult(pair.Key) * pair.Value; } - return GetScore(mode, maxAchievableCombo, calculateAccuracyRatio(computedBaseScore), calculateComboRatio(maxCombo), scoreResultCounts); + return GetScore(mode, maxAchievableCombo, calculateAccuracyRatio(computedBaseScore), calculateComboRatio(maxCombo), statistics); } /// From baa6e845aa812d91a15c655b459de4fbaa203c09 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 27 Apr 2021 22:38:37 +0900 Subject: [PATCH 1775/1791] Change to fluent assertions --- osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index 2c862fe8a8..ac26f4723e 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; -using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Judgements; @@ -51,7 +50,7 @@ namespace osu.Game.Tests.Rulesets.Scoring }; scoreProcessor.ApplyResult(judgementResult); - Assert.IsTrue(Precision.AlmostEquals(expectedScore, scoreProcessor.TotalScore.Value)); + Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(expectedScore).Within(0.5d)); } /// @@ -118,7 +117,7 @@ namespace osu.Game.Tests.Rulesets.Scoring scoreProcessor.ApplyResult(judgementResult); } - Assert.IsTrue(Precision.AlmostEquals(expectedScore, scoreProcessor.TotalScore.Value, 0.5)); + Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(expectedScore).Within(0.5d)); } /// @@ -158,7 +157,7 @@ namespace osu.Game.Tests.Rulesets.Scoring }; scoreProcessor.ApplyResult(lastJudgementResult); - Assert.IsTrue(Precision.AlmostEquals(expectedScore, scoreProcessor.TotalScore.Value, 0.5)); + Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(expectedScore).Within(0.5d)); } [Test] @@ -169,7 +168,7 @@ namespace osu.Game.Tests.Rulesets.Scoring scoreProcessor.Mode.Value = scoringMode; scoreProcessor.ApplyBeatmap(new TestBeatmap(new RulesetInfo())); - Assert.IsTrue(Precision.AlmostEquals(0, scoreProcessor.TotalScore.Value)); + Assert.That(scoreProcessor.TotalScore.Value, Is.Zero); } [TestCase(HitResult.IgnoreHit, HitResult.IgnoreMiss)] From 4e3ee773968e52788ccf73bca74fe883d72da113 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 28 Apr 2021 02:46:34 +0900 Subject: [PATCH 1776/1791] Add support for custom controls to SettingSourceAttribute --- .../Mods/SettingsSourceAttributeTest.cs | 29 +++++++++++++++++++ .../Configuration/SettingSourceAttribute.cs | 29 ++++++++++++++++++- 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Mods/SettingsSourceAttributeTest.cs b/osu.Game.Tests/Mods/SettingsSourceAttributeTest.cs index 883c9d1ac2..dd105787fa 100644 --- a/osu.Game.Tests/Mods/SettingsSourceAttributeTest.cs +++ b/osu.Game.Tests/Mods/SettingsSourceAttributeTest.cs @@ -4,7 +4,10 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.UserInterface; using osu.Game.Configuration; +using osu.Game.Overlays.Settings; namespace osu.Game.Tests.Mods { @@ -26,6 +29,16 @@ namespace osu.Game.Tests.Mods Assert.That(orderedSettings[3].Item2.Name, Is.EqualTo(nameof(ClassWithSettings.UnorderedSetting))); } + [Test] + public void TestCustomControl() + { + var objectWithCustomSettingControl = new ClassWithCustomSettingControl(); + var settings = objectWithCustomSettingControl.CreateSettingsControls().ToArray(); + + Assert.That(settings, Has.Length.EqualTo(1)); + Assert.That(settings[0], Is.TypeOf()); + } + private class ClassWithSettings { [SettingSource("Unordered setting", "Should be last")] @@ -40,5 +53,21 @@ namespace osu.Game.Tests.Mods [SettingSource("Third setting", "Yet another description", 3)] public BindableInt ThirdSetting { get; set; } = new BindableInt(); } + + private class ClassWithCustomSettingControl + { + [SettingSource("Custom setting", "Should be a custom control", SettingControlType = typeof(CustomSettingsControl))] + public BindableInt UnorderedSetting { get; set; } = new BindableInt(); + } + + private class CustomSettingsControl : SettingsItem + { + protected override Drawable CreateControl() => new CustomControl(); + + private class CustomControl : Drawable, IHasCurrentValue + { + public Bindable Current { get; set; } = new Bindable(); + } + } } } diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index cfce615130..13cf0a9f0c 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -1,12 +1,15 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + using System; using System.Collections.Generic; 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; @@ -31,7 +34,15 @@ namespace osu.Game.Configuration public int? OrderPosition { get; } - public SettingSourceAttribute(string label, string description = null) + /// + /// The type of the settings control which handles this setting source. + /// + /// + /// Must be a type deriving with a public constructor. + /// + public Type? SettingControlType { get; set; } + + public SettingSourceAttribute(string? label, string? description = null) { Label = label ?? string.Empty; Description = description ?? string.Empty; @@ -67,6 +78,22 @@ namespace osu.Game.Configuration { object value = property.GetValue(obj); + if (attr.SettingControlType != null) + { + var controlType = attr.SettingControlType; + if (controlType.EnumerateBaseTypes().All(t => !t.IsGenericType || t.GetGenericTypeDefinition() != typeof(SettingsItem<>))) + throw new InvalidOperationException($"{nameof(SettingSourceAttribute)} had an unsupported custom control type ({controlType.ReadableName()})"); + + var control = (Drawable)Activator.CreateInstance(controlType); + controlType.GetProperty(nameof(SettingsItem.LabelText))?.SetValue(control, attr.Label); + controlType.GetProperty(nameof(SettingsItem.TooltipText))?.SetValue(control, attr.Description); + controlType.GetProperty(nameof(SettingsItem.Current))?.SetValue(control, value); + + yield return control; + + continue; + } + switch (value) { case BindableNumber bNumber: From 61b7dc1e0601feea555c2841249714a9bc684f68 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 28 Apr 2021 03:42:29 +0900 Subject: [PATCH 1777/1791] Fix bonus-only maps having 700K base score --- .../Rulesets/Scoring/ScoreProcessorTest.cs | 12 ++++++------ osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index ac26f4723e..baa10663d5 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -83,8 +83,8 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(ScoringMode.Standardised, HitResult.SmallTickHit, HitResult.SmallTickHit, 925_000)] // (3 * 10) / (4 * 10) * 300_000 + 700_000 (max combo 0) [TestCase(ScoringMode.Standardised, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] // (3 * 0) / (4 * 30) * 300_000 + (0 / 4) * 700_000 [TestCase(ScoringMode.Standardised, HitResult.LargeTickHit, HitResult.LargeTickHit, 575_000)] // (3 * 30) / (4 * 30) * 300_000 + (0 / 4) * 700_000 - [TestCase(ScoringMode.Standardised, HitResult.SmallBonus, HitResult.SmallBonus, 700_030)] // 0 * 300_000 + 700_000 (max combo 0) + 3 * 10 (bonus points) - [TestCase(ScoringMode.Standardised, HitResult.LargeBonus, HitResult.LargeBonus, 700_150)] // 0 * 300_000 + 700_000 (max combo 0) + 3 * 50 (bonus points) + [TestCase(ScoringMode.Standardised, HitResult.SmallBonus, HitResult.SmallBonus, 1_000_030)] // 0 * 300_000 + 700_000 (max combo 0) + 3 * 10 (bonus points) + [TestCase(ScoringMode.Standardised, HitResult.LargeBonus, HitResult.LargeBonus, 1_000_150)] // 0 * 300_000 + 700_000 (max combo 0) + 3 * 50 (bonus points) [TestCase(ScoringMode.Classic, HitResult.Miss, HitResult.Great, 0)] // (0 * 4 * 300) * (1 + 0 / 25) [TestCase(ScoringMode.Classic, HitResult.Meh, HitResult.Great, 156)] // (((3 * 50) / (4 * 300)) * 4 * 300) * (1 + 1 / 25) [TestCase(ScoringMode.Classic, HitResult.Ok, HitResult.Great, 312)] // (((3 * 100) / (4 * 300)) * 4 * 300) * (1 + 1 / 25) @@ -95,8 +95,8 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, HitResult.SmallTickHit, 225)] // (((3 * 10) / (4 * 10)) * 1 * 300) * (1 + 0 / 25) [TestCase(ScoringMode.Classic, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] // (0 * 4 * 300) * (1 + 0 / 25) [TestCase(ScoringMode.Classic, HitResult.LargeTickHit, HitResult.LargeTickHit, 936)] // (((3 * 50) / (4 * 50)) * 4 * 300) * (1 + 1 / 25) - [TestCase(ScoringMode.Classic, HitResult.SmallBonus, HitResult.SmallBonus, 30)] // (0 * 1 * 300) * (1 + 0 / 25) + 3 * 10 (bonus points) - [TestCase(ScoringMode.Classic, HitResult.LargeBonus, HitResult.LargeBonus, 150)] // (0 * 1 * 300) * (1 + 0 / 25) * 3 * 50 (bonus points) + [TestCase(ScoringMode.Classic, HitResult.SmallBonus, HitResult.SmallBonus, 330)] // (0 * 1 * 300) * (1 + 0 / 25) + 3 * 10 (bonus points) + [TestCase(ScoringMode.Classic, HitResult.LargeBonus, HitResult.LargeBonus, 450)] // (1 * 1 * 300) * (1 + 0 / 25) * 3 * 50 (bonus points) public void TestFourVariousResultsOneMiss(ScoringMode scoringMode, HitResult hitResult, HitResult maxResult, int expectedScore) { var minResult = new TestJudgement(hitResult).MinResult; @@ -289,8 +289,8 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(HitResult.Perfect, 1_000_000)] [TestCase(HitResult.SmallTickHit, 1_000_000)] [TestCase(HitResult.LargeTickHit, 1_000_000)] - [TestCase(HitResult.SmallBonus, 700_000 + Judgement.SMALL_BONUS_SCORE)] - [TestCase(HitResult.LargeBonus, 700_000 + Judgement.LARGE_BONUS_SCORE)] + [TestCase(HitResult.SmallBonus, 1_000_000 + Judgement.SMALL_BONUS_SCORE)] + [TestCase(HitResult.LargeBonus, 1_000_000 + Judgement.LARGE_BONUS_SCORE)] public void TestGetScoreWithExternalStatistics(HitResult result, int expectedScore) { var statistic = new Dictionary { { result, 1 } }; diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index a16a6a6c9b..f32f70d4ba 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -266,7 +266,7 @@ namespace osu.Game.Rulesets.Scoring if (preferRolling && rollingMaxBaseScore != 0) return baseScore / rollingMaxBaseScore; - return maxBaseScore > 0 ? baseScore / maxBaseScore : 0; + return maxBaseScore > 0 ? baseScore / maxBaseScore : 1; } private double calculateComboRatio(int maxCombo) => maxAchievableCombo > 0 ? (double)maxCombo / maxAchievableCombo : 1; From 04454062b7dad5cd47b6bd066c23a018e5290f06 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 28 Apr 2021 03:52:59 +0900 Subject: [PATCH 1778/1791] Fix up xmldoc --- osu.Game/Configuration/SettingSourceAttribute.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index 13cf0a9f0c..a9436aac89 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -38,7 +38,7 @@ namespace osu.Game.Configuration /// The type of the settings control which handles this setting source. /// /// - /// Must be a type deriving with a public constructor. + /// Must be a type deriving . /// public Type? SettingControlType { get; set; } From 05bd6ee50cc20a014c958c86752152ca80e3e553 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 28 Apr 2021 03:54:42 +0900 Subject: [PATCH 1779/1791] Add back ctor doc --- osu.Game/Configuration/SettingSourceAttribute.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index a9436aac89..3e50613093 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -38,7 +38,7 @@ namespace osu.Game.Configuration /// The type of the settings control which handles this setting source. /// /// - /// Must be a type deriving . + /// Must be a type deriving with a public parameterless constructor. /// public Type? SettingControlType { get; set; } From ac1534cda2fb51afad43bcc4e4f4fe945a232cde Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Apr 2021 15:54:40 +0900 Subject: [PATCH 1780/1791] Add test covering existing button actually changing to `LocallyAvailable` state --- osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs b/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs index 0c199bfb62..3fc894da0d 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs @@ -43,7 +43,10 @@ namespace osu.Game.Tests.Visual.Online createButtonWithBeatmap(createSoleily()); AddAssert("button state not downloaded", () => downloadButton.DownloadState == DownloadState.NotDownloaded); AddStep("import soleily", () => beatmaps.Import(TestResources.GetQuickTestBeatmapForImport())); + AddUntilStep("wait for beatmap import", () => beatmaps.GetAllUsableBeatmapSets().Any(b => b.OnlineBeatmapSetID == 241526)); + AddAssert("button state downloaded", () => downloadButton.DownloadState == DownloadState.LocallyAvailable); + createButtonWithBeatmap(createSoleily()); AddAssert("button state downloaded", () => downloadButton.DownloadState == DownloadState.LocallyAvailable); ensureSoleilyRemoved(); From 05e3a73a7d24acf00431405b96234498f830ddcb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Apr 2021 15:54:58 +0900 Subject: [PATCH 1781/1791] Fix import cancellation not correctly being forwarded to import notification --- osu.Game/Database/ArchiveModelManager.cs | 49 +++++++++++++++--------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 6719351530..dbeaebb1cd 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -156,33 +156,44 @@ namespace osu.Game.Database bool isLowPriorityImport = tasks.Length > low_priority_import_batch_size; - await Task.WhenAll(tasks.Select(async task => + try { - notification.CancellationToken.ThrowIfCancellationRequested(); - - try + await Task.WhenAll(tasks.Select(async task => { - var model = await Import(task, isLowPriorityImport, notification.CancellationToken).ConfigureAwait(false); + notification.CancellationToken.ThrowIfCancellationRequested(); - lock (imported) + try { - if (model != null) - imported.Add(model); - current++; + var model = await Import(task, isLowPriorityImport, notification.CancellationToken).ConfigureAwait(false); - notification.Text = $"Imported {current} of {tasks.Length} {HumanisedModelName}s"; - notification.Progress = (float)current / tasks.Length; + lock (imported) + { + if (model != null) + imported.Add(model); + current++; + + notification.Text = $"Imported {current} of {tasks.Length} {HumanisedModelName}s"; + notification.Progress = (float)current / tasks.Length; + } } - } - catch (TaskCanceledException) + catch (TaskCanceledException) + { + throw; + } + catch (Exception e) + { + Logger.Error(e, $@"Could not import ({task})", LoggingTarget.Database); + } + })).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + if (imported.Count == 0) { - throw; + notification.State = ProgressNotificationState.Cancelled; + return imported; } - catch (Exception e) - { - Logger.Error(e, $@"Could not import ({task})", LoggingTarget.Database); - } - })).ConfigureAwait(false); + } if (imported.Count == 0) { From 8598a0968f17c0733ec38e030abceeddc8dca37a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Apr 2021 16:29:23 +0900 Subject: [PATCH 1782/1791] Update calculations in comments to match new logic Mostly look to be errors that existed before this PR. Co-authored-by: Endrik --- osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index baa10663d5..41e16ebeaf 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -83,8 +83,8 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(ScoringMode.Standardised, HitResult.SmallTickHit, HitResult.SmallTickHit, 925_000)] // (3 * 10) / (4 * 10) * 300_000 + 700_000 (max combo 0) [TestCase(ScoringMode.Standardised, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] // (3 * 0) / (4 * 30) * 300_000 + (0 / 4) * 700_000 [TestCase(ScoringMode.Standardised, HitResult.LargeTickHit, HitResult.LargeTickHit, 575_000)] // (3 * 30) / (4 * 30) * 300_000 + (0 / 4) * 700_000 - [TestCase(ScoringMode.Standardised, HitResult.SmallBonus, HitResult.SmallBonus, 1_000_030)] // 0 * 300_000 + 700_000 (max combo 0) + 3 * 10 (bonus points) - [TestCase(ScoringMode.Standardised, HitResult.LargeBonus, HitResult.LargeBonus, 1_000_150)] // 0 * 300_000 + 700_000 (max combo 0) + 3 * 50 (bonus points) + [TestCase(ScoringMode.Standardised, HitResult.SmallBonus, HitResult.SmallBonus, 1_000_030)] // 1 * 300_000 + 700_000 (max combo 0) + 3 * 10 (bonus points) + [TestCase(ScoringMode.Standardised, HitResult.LargeBonus, HitResult.LargeBonus, 1_000_150)] // 1 * 300_000 + 700_000 (max combo 0) + 3 * 50 (bonus points) [TestCase(ScoringMode.Classic, HitResult.Miss, HitResult.Great, 0)] // (0 * 4 * 300) * (1 + 0 / 25) [TestCase(ScoringMode.Classic, HitResult.Meh, HitResult.Great, 156)] // (((3 * 50) / (4 * 300)) * 4 * 300) * (1 + 1 / 25) [TestCase(ScoringMode.Classic, HitResult.Ok, HitResult.Great, 312)] // (((3 * 100) / (4 * 300)) * 4 * 300) * (1 + 1 / 25) @@ -95,8 +95,8 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, HitResult.SmallTickHit, 225)] // (((3 * 10) / (4 * 10)) * 1 * 300) * (1 + 0 / 25) [TestCase(ScoringMode.Classic, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] // (0 * 4 * 300) * (1 + 0 / 25) [TestCase(ScoringMode.Classic, HitResult.LargeTickHit, HitResult.LargeTickHit, 936)] // (((3 * 50) / (4 * 50)) * 4 * 300) * (1 + 1 / 25) - [TestCase(ScoringMode.Classic, HitResult.SmallBonus, HitResult.SmallBonus, 330)] // (0 * 1 * 300) * (1 + 0 / 25) + 3 * 10 (bonus points) - [TestCase(ScoringMode.Classic, HitResult.LargeBonus, HitResult.LargeBonus, 450)] // (1 * 1 * 300) * (1 + 0 / 25) * 3 * 50 (bonus points) + [TestCase(ScoringMode.Classic, HitResult.SmallBonus, HitResult.SmallBonus, 330)] // (1 * 1 * 300) * (1 + 0 / 25) + 3 * 10 (bonus points) + [TestCase(ScoringMode.Classic, HitResult.LargeBonus, HitResult.LargeBonus, 450)] // (1 * 1 * 300) * (1 + 0 / 25) + 3 * 50 (bonus points) public void TestFourVariousResultsOneMiss(ScoringMode scoringMode, HitResult hitResult, HitResult maxResult, int expectedScore) { var minResult = new TestJudgement(hitResult).MinResult; From e71dbfd730df6a2909deb9b3b53ba027f9f45e29 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Apr 2021 16:37:48 +0900 Subject: [PATCH 1783/1791] Add inline comment regarding remaining issues with classic scoring --- osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index 41e16ebeaf..184a94912a 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -95,6 +95,7 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, HitResult.SmallTickHit, 225)] // (((3 * 10) / (4 * 10)) * 1 * 300) * (1 + 0 / 25) [TestCase(ScoringMode.Classic, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] // (0 * 4 * 300) * (1 + 0 / 25) [TestCase(ScoringMode.Classic, HitResult.LargeTickHit, HitResult.LargeTickHit, 936)] // (((3 * 50) / (4 * 50)) * 4 * 300) * (1 + 1 / 25) + // TODO: The following two cases don't match expectations currently (a single hit is registered in acc portion when it shouldn't be). See https://github.com/ppy/osu/issues/12604. [TestCase(ScoringMode.Classic, HitResult.SmallBonus, HitResult.SmallBonus, 330)] // (1 * 1 * 300) * (1 + 0 / 25) + 3 * 10 (bonus points) [TestCase(ScoringMode.Classic, HitResult.LargeBonus, HitResult.LargeBonus, 450)] // (1 * 1 * 300) * (1 + 0 / 25) + 3 * 50 (bonus points) public void TestFourVariousResultsOneMiss(ScoringMode scoringMode, HitResult hitResult, HitResult maxResult, int expectedScore) From 6d7eef3f5a437e1886908f3440138700e0dcd9e2 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 28 Apr 2021 16:45:57 +0000 Subject: [PATCH 1784/1791] Upgrade to GitHub-native Dependabot --- .github/dependabot.yml | 46 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..9e9af23b27 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,46 @@ +version: 2 +updates: +- package-ecosystem: nuget + directory: "/" + schedule: + interval: monthly + time: "17:00" + open-pull-requests-limit: 99 + ignore: + - dependency-name: Microsoft.EntityFrameworkCore.Design + versions: + - "> 2.2.6" + - dependency-name: Microsoft.EntityFrameworkCore.Sqlite + versions: + - "> 2.2.6" + - dependency-name: Microsoft.EntityFrameworkCore.Sqlite.Core + versions: + - "> 2.2.6" + - dependency-name: Microsoft.Extensions.DependencyInjection + versions: + - ">= 5.a, < 6" + - dependency-name: NUnit3TestAdapter + versions: + - ">= 3.16.a, < 3.17" + - dependency-name: Microsoft.NET.Test.Sdk + versions: + - 16.9.1 + - dependency-name: Microsoft.Extensions.DependencyInjection + versions: + - 3.1.11 + - 3.1.12 + - dependency-name: Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson + versions: + - 3.1.11 + - dependency-name: Microsoft.NETCore.Targets + versions: + - 5.0.0 + - dependency-name: Microsoft.AspNetCore.SignalR.Protocols.MessagePack + versions: + - 5.0.2 + - dependency-name: NUnit + versions: + - 3.13.1 + - dependency-name: Microsoft.AspNetCore.SignalR.Client + versions: + - 3.1.11 From 921d4510478943f5b6c195044d30102f2a9f2fe2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Apr 2021 03:50:49 +0000 Subject: [PATCH 1785/1791] Bump Microsoft.AspNetCore.SignalR.Client from 5.0.4 to 5.0.5 Bumps [Microsoft.AspNetCore.SignalR.Client](https://github.com/dotnet/aspnetcore) from 5.0.4 to 5.0.5. - [Release notes](https://github.com/dotnet/aspnetcore/releases) - [Commits](https://github.com/dotnet/aspnetcore/compare/v5.0.4...v5.0.5) Signed-off-by: dependabot[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 986bd8e7ba..c61c59e589 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -21,7 +21,7 @@ - + From 5720e255c9b3ed3c1d466db57ca4eac65fae0a09 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Apr 2021 03:50:53 +0000 Subject: [PATCH 1786/1791] Bump Sentry from 3.2.0 to 3.3.4 Bumps [Sentry](https://github.com/getsentry/sentry-dotnet) from 3.2.0 to 3.3.4. - [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.2.0...3.3.4) Signed-off-by: dependabot[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 986bd8e7ba..abb1e40b64 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -31,7 +31,7 @@ - + From 6c51bf523ca0740feae42ade648b7bbffa49192d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Apr 2021 03:50:56 +0000 Subject: [PATCH 1787/1791] Bump SharpCompress from 0.28.1 to 0.28.2 Bumps [SharpCompress](https://github.com/adamhathcock/sharpcompress) from 0.28.1 to 0.28.2. - [Release notes](https://github.com/adamhathcock/sharpcompress/releases) - [Commits](https://github.com/adamhathcock/sharpcompress/compare/0.28.1...0.28.2) Signed-off-by: dependabot[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 986bd8e7ba..189bc724fc 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 c32109d6db..1a9f945ec8 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -94,7 +94,7 @@ - + From 437e9201ba844fb6d7baac65ea679647d439b370 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Apr 2021 03:51:00 +0000 Subject: [PATCH 1788/1791] Bump Microsoft.AspNetCore.SignalR.Protocols.MessagePack Bumps [Microsoft.AspNetCore.SignalR.Protocols.MessagePack](https://github.com/dotnet/aspnetcore) from 5.0.4 to 5.0.5. - [Release notes](https://github.com/dotnet/aspnetcore/releases) - [Commits](https://github.com/dotnet/aspnetcore/compare/v5.0.4...v5.0.5) Signed-off-by: dependabot[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 986bd8e7ba..e2c8e2cf1b 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -22,7 +22,7 @@ - + From 5b071c68e7ab85f7e7faab2e2ae79762140ba6ca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Apr 2021 03:51:04 +0000 Subject: [PATCH 1789/1791] Bump Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson Bumps [Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson](https://github.com/dotnet/aspnetcore) from 5.0.4 to 5.0.5. - [Release notes](https://github.com/dotnet/aspnetcore/releases) - [Commits](https://github.com/dotnet/aspnetcore/compare/v5.0.4...v5.0.5) Signed-off-by: dependabot[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 986bd8e7ba..c9de4dd28a 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -23,7 +23,7 @@ - + From 1e7feff49d57fc2f0575a8a423083e0ff9a44d65 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Apr 2021 03:51:08 +0000 Subject: [PATCH 1790/1791] Bump Humanizer from 2.8.26 to 2.9.9 Bumps [Humanizer](https://github.com/Humanizr/Humanizer) from 2.8.26 to 2.9.9. - [Release notes](https://github.com/Humanizr/Humanizer/releases) - [Changelog](https://github.com/Humanizr/Humanizer/blob/main/release_notes.md) - [Commits](https://github.com/Humanizr/Humanizer/compare/v2.8.26...v2.9.9) Signed-off-by: dependabot[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 986bd8e7ba..aa1599e69a 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -19,7 +19,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index c32109d6db..7061bb5e84 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -89,7 +89,7 @@ - + From 1b3b07d6a9f0ccb9e595a1c6ae69456254356b96 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Apr 2021 03:51:12 +0000 Subject: [PATCH 1791/1791] Bump NUnit from 3.13.1 to 3.13.2 Bumps [NUnit](https://github.com/nunit/nunit) from 3.13.1 to 3.13.2. - [Release notes](https://github.com/nunit/nunit/releases) - [Changelog](https://github.com/nunit/nunit/blob/v3.13.2/CHANGES.md) - [Commits](https://github.com/nunit/nunit/compare/v3.13.1...v3.13.2) Signed-off-by: dependabot[bot] --- .../osu.Game.Rulesets.EmptyFreeform.Tests.csproj | 2 +- .../osu.Game.Rulesets.Pippidon.Tests.csproj | 2 +- .../osu.Game.Rulesets.EmptyScrolling.Tests.csproj | 2 +- .../osu.Game.Rulesets.Pippidon.Tests.csproj | 2 +- osu.Game.Benchmarks/osu.Game.Benchmarks.csproj | 2 +- .../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 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj index 98a32f9b3a..992f954a3a 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj @@ -11,7 +11,7 @@ - + diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index afa7b03536..7571d1827a 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -11,7 +11,7 @@ - + diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj index c9f87a8551..1c8ed54440 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj @@ -11,7 +11,7 @@ - + diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index afa7b03536..7571d1827a 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -11,7 +11,7 @@ - + diff --git a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj index ea43d9a54c..bfcf4ef35e 100644 --- a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj +++ b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj @@ -8,7 +8,7 @@ - + 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 c2d9a923d9..77e9d672e3 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 @@ -3,7 +3,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 64e934efd2..8f8b99b092 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 @@ -3,7 +3,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 f743d65db3..e01e858873 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 @@ -3,7 +3,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 eab144592f..2dfa1dfbb7 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 @@ -3,7 +3,7 @@ - + diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index df6d17f615..895518e1b9 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj index a4e52f8cd4..d5dda39aa5 100644 --- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj +++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj @@ -6,7 +6,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 986bd8e7ba..b242811939 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -33,7 +33,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index c32109d6db..423f87923b 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -95,7 +95,7 @@ - +

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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"); + ///