From 030b55ae85460f8c1346c5d0574d7886e78bf3c9 Mon Sep 17 00:00:00 2001 From: "Jack Boswell (boswelja)" Date: Wed, 3 Jun 2020 17:55:15 +1200 Subject: [PATCH 001/289] 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 002/289] 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 003/289] 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 004/289] 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 005/289] 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 006/289] 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 007/289] 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 008/289] 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 009/289] 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 010/289] 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 011/289] 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 012/289] 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 013/289] 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 014/289] 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 015/289] 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 016/289] 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 017/289] 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 018/289] 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 019/289] 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 020/289] 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 021/289] 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 022/289] 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 023/289] 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 024/289] 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 025/289] 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 026/289] 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 442347df8eee74c903a4564ab8ec92a8b20c7964 Mon Sep 17 00:00:00 2001 From: Samuel Cattini-Schultz Date: Fri, 19 Feb 2021 18:04:25 +1100 Subject: [PATCH 027/289] 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 028/289] 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 cc4c5f72d893bc014d4172b1e2ba1c0930165aa4 Mon Sep 17 00:00:00 2001 From: Leon Gebler Date: Sat, 20 Feb 2021 21:48:31 +0100 Subject: [PATCH 029/289] 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 030/289] 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 031/289] 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 032/289] 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 033/289] 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 3491021f72a00eb5f4471dd6828678d2e6017315 Mon Sep 17 00:00:00 2001 From: Leon Gebler Date: Tue, 23 Feb 2021 20:58:46 +0100 Subject: [PATCH 034/289] 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 035/289] 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 036/289] 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 037/289] 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 8046b5a818f8d2a69d7199e6cff6fa2a1db024d8 Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Wed, 17 Mar 2021 17:35:49 +0800 Subject: [PATCH 038/289] 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 133ff085a53711229834c0c3b3944f1c29d91b73 Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Wed, 17 Mar 2021 18:06:40 +0800 Subject: [PATCH 039/289] 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 51e0304c54a604bab6d6c8007c59ec755b115b2d Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Wed, 17 Mar 2021 18:31:09 +0800 Subject: [PATCH 040/289] 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 563a0584d589bf0dce7c29fffc03268b097db485 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Mar 2021 18:48:51 +0900 Subject: [PATCH 041/289] 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 71a361337da8ee2ddac2feeb8af3e9644248c3d7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Mar 2021 21:57:48 +0900 Subject: [PATCH 042/289] 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 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 043/289] 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 044/289] 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 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 045/289] 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 046/289] 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 047/289] 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 048/289] 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 049/289] 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 050/289] 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 051/289] 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 052/289] 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 053/289] 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 054/289] 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 055/289] 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 056/289] 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 057/289] 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 058/289] 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 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 059/289] 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 060/289] 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 061/289] 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 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 062/289] 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 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 063/289] 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 064/289] 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 065/289] 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 066/289] 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 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 067/289] 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 068/289] 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 069/289] 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 def0e5c42e90e5e8eb984c794a31eee0567caee4 Mon Sep 17 00:00:00 2001 From: Leon Gebler Date: Tue, 23 Mar 2021 17:21:42 +0100 Subject: [PATCH 070/289] 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 071/289] 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 072/289] 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 073/289] 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 074/289] 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 075/289] 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 7b3336783fb3e4864680fa69d2ea8240d3ac93b1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Mar 2021 15:24:59 +0900 Subject: [PATCH 076/289] Stabilise ordering instead of simple reversing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- .../Compose/Components/Timeline/TimelineBlueprintContainer.cs | 3 +-- 1 file changed, 1 insertion(+), 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 3526f264a7..09da613a04 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -138,8 +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()) + foreach (var b in SelectionBlueprints.OrderBy(b => b.HitObject.StartTime).ThenBy(b => b.HitObject.GetEndTime())) { while (currentConcurrentObjects.TryPeek(out double stackEndTime)) { From 9fdd23b134744140c66a5e8595fb1d655c038f83 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Mar 2021 16:28:30 +0900 Subject: [PATCH 077/289] Fix various issues with stacking --- .../Timeline/TimelineBlueprintContainer.cs | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index 09da613a04..cec851b5bb 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -136,41 +136,42 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline // 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(); + Stack currentConcurrentObjects = new Stack(); foreach (var b in SelectionBlueprints.OrderBy(b => b.HitObject.StartTime).ThenBy(b => b.HitObject.GetEndTime())) { - while (currentConcurrentObjects.TryPeek(out double stackEndTime)) + // remove objects from the stack as long as their end time is in the past. + while (currentConcurrentObjects.TryPeek(out HitObject hitObject)) { - if (Precision.AlmostBigger(stackEndTime, b.HitObject.StartTime, 1)) + if (Precision.AlmostBigger(hitObject.GetEndTime(), 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.TryPeek(out HitObject h) && !Precision.AlmostEquals(h.StartTime, b.HitObject.StartTime, 1)) { if (currentConcurrentObjects.Count >= stack_reset_count) currentConcurrentObjects.Clear(); } - currentConcurrentObjects.Push(bEndTime); + b.Y = -(stack_offset * currentConcurrentObjects.Count); + + currentConcurrentObjects.Push(b.HitObject); } } protected override SelectionHandler CreateSelectionHandler() => new TimelineSelectionHandler(); - protected override SelectionBlueprint CreateBlueprintFor(HitObject hitObject) => new TimelineHitObjectBlueprint(hitObject) + protected override SelectionBlueprint CreateBlueprintFor(HitObject hitObject) { - OnDragHandled = handleScrollViaDrag - }; + return new TimelineHitObjectBlueprint(hitObject) + { + OnDragHandled = handleScrollViaDrag + }; + } protected override DragBox CreateDragBox(Action performSelect) => new TimelineDragBox(performSelect); From 013ddc734c09c110b9590b71cd1f04aa1a5bf021 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Mar 2021 17:04:35 +0900 Subject: [PATCH 078/289] Fix osu!catch fruit showing on plate when hidden mod is enabled Closes https://github.com/ppy/osu/issues/12065. --- osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs | 13 ++++++++++++- osu.Game.Rulesets.Catch/UI/Catcher.cs | 5 +++-- osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 7 ++++++- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs index 4b008d2734..5c5e41234d 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs @@ -3,13 +3,16 @@ using System.Linq; using osu.Framework.Graphics; +using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawables; +using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Catch.Mods { - public class CatchModHidden : ModHidden + public class CatchModHidden : ModHidden, IApplicableToDrawableRuleset { public override string Description => @"Play with fading fruits."; public override double ScoreMultiplier => 1.06; @@ -17,6 +20,14 @@ namespace osu.Game.Rulesets.Catch.Mods private const double fade_out_offset_multiplier = 0.6; private const double fade_out_duration_multiplier = 0.44; + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + { + var drawableCatchRuleset = (DrawableCatchRuleset)drawableRuleset; + var catchPlayfield = (CatchPlayfield)drawableCatchRuleset.Playfield; + + catchPlayfield.CatcherArea.CatchFruitOnPlate = false; + } + protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) { base.ApplyNormalVisibilityState(hitObject, state); diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index ed875e7002..42be745c2e 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -223,7 +223,7 @@ namespace osu.Game.Rulesets.Catch.UI catchObjectPosition <= catcherPosition + halfCatchWidth; } - public void OnNewResult(DrawableCatchHitObject drawableObject, JudgementResult result) + public void OnNewResult(DrawableCatchHitObject drawableObject, JudgementResult result, bool placeOnPlate) { var catchResult = (CatchJudgementResult)result; catchResult.CatcherAnimationState = CurrentState; @@ -237,7 +237,8 @@ namespace osu.Game.Rulesets.Catch.UI { var positionInStack = computePositionInStack(new Vector2(palpableObject.X - X, 0), palpableObject.DisplaySize.X / 2); - placeCaughtObject(palpableObject, positionInStack); + if (placeOnPlate) + placeCaughtObject(palpableObject, positionInStack); if (hitLighting.Value) addLighting(hitObject, positionInStack.X, drawableObject.AccentColour.Value); diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index 44adbd5512..29c95ec61c 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -21,6 +21,11 @@ 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); @@ -41,7 +46,7 @@ namespace osu.Game.Rulesets.Catch.UI public void OnNewResult(DrawableCatchHitObject hitObject, JudgementResult result) { - MovableCatcher.OnNewResult(hitObject, result); + MovableCatcher.OnNewResult(hitObject, result, CatchFruitOnPlate); if (!result.Type.IsScorable()) return; From 6808719efabe9cd5997e51f916ecd9f56535b1f5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Mar 2021 17:05:28 +0900 Subject: [PATCH 079/289] Update test scene --- 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 48efd73222..ddb6194899 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); + catcher.OnNewResult(drawableObject, result, true); drawableObject.Expire(); }; } From f6647de7693de11ad68b8316eb17483bd9bb3126 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Mar 2021 19:56:26 +0900 Subject: [PATCH 080/289] Add support for nudging objects in the editor using ctrl+arrow keys Closes #12042. --- .../Components/ComposeBlueprintContainer.cs | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 79f457c050..5ab557804e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Input; +using osu.Framework.Input.Events; using osu.Game.Audio; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; @@ -19,6 +20,7 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osu.Game.Screens.Edit.Components.TernaryButtons; using osuTK; +using osuTK.Input; namespace osu.Game.Screens.Edit.Compose.Components { @@ -70,6 +72,50 @@ namespace osu.Game.Screens.Edit.Compose.Components } } + protected override bool OnKeyDown(KeyDownEvent e) + { + if (e.ControlPressed) + { + switch (e.Key) + { + case Key.Left: + moveSelection(new Vector2(-1, 0)); + return true; + + case Key.Right: + moveSelection(new Vector2(1, 0)); + return true; + + case Key.Up: + moveSelection(new Vector2(0, -1)); + return true; + + case Key.Down: + moveSelection(new Vector2(0, 1)); + return true; + } + } + + return false; + } + + /// + /// Move the current selection spatially by the specified delta, in gamefield coordinates (ie. the same coordinates as the blueprints). + /// + /// + private void moveSelection(Vector2 delta) + { + var firstBlueprint = SelectionHandler.SelectedBlueprints.FirstOrDefault(); + + if (firstBlueprint == null) + return; + + // convert to game space coordinates + delta = firstBlueprint.ToScreenSpace(delta) - firstBlueprint.ToScreenSpace(Vector2.Zero); + + SelectionHandler.HandleMovement(new MoveSelectionEvent(firstBlueprint, firstBlueprint.ScreenSpaceSelectionPoint + delta)); + } + private void updatePlacementNewCombo() { if (currentPlacement?.HitObject is IHasComboInformation c) From ce9130ca5065f4278af498e3949049c03d43dbbe Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Thu, 25 Mar 2021 17:38:55 +0100 Subject: [PATCH 081/289] Remove determinant limit This has since been added into the framework through https://github.com/ppy/osu-framework/pull/4302 --- .../Components/PathControlPointVisualiser.cs | 39 +------------------ 1 file changed, 1 insertion(+), 38 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 0270485f31..348ec30266 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -158,48 +158,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components continue; Vector2[] points = slider.Path.PointsInSegment(segmentStartPoint).Select(p => p.Position.Value).ToArray(); - if (points.Length == 3 && !validCircularArcSegment(points)) + if (points.Length == 3 && circularArcBoundingBox(points).Area >= 640 * 480) 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, - /// 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]; - 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); - - return (normA.X - normB.X) * (normB.Y - normC.Y) - (normB.X - normC.X) * (normA.Y - normB.Y); - } - /// /// Computes the bounding box of the circular arc segment defined by the given points. /// From 1c1af981443df917fe27ace265c41c9f6a515760 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 26 Mar 2021 11:47:41 +0900 Subject: [PATCH 082/289] Update values --- osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs index ab9b7f4847..afd94f4570 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.7212283220504574d, "diffcalc-test")] - [TestCase(1.3212137310562277d, "zero-length-sliders")] + [TestCase(8.7212283220412345d, "diffcalc-test")] + [TestCase(1.3212137158641493d, "zero-length-sliders")] public void TestClockRateAdjusted(double expected, string name) => Test(expected, name, new OsuModDoubleTime()); From 51f0477df4744b6702dff18740974e3a17913be3 Mon Sep 17 00:00:00 2001 From: Naxess <30292137+Naxesss@users.noreply.github.com> Date: Fri, 26 Mar 2021 04:42:46 +0100 Subject: [PATCH 083/289] Move bounding box logic to framework --- .../Components/PathControlPointVisualiser.cs | 81 +------------------ 1 file changed, 4 insertions(+), 77 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 348ec30266..dd39014bb6 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -16,6 +16,7 @@ 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; @@ -157,87 +158,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components if (segmentStartPoint.Type.Value != PathType.PerfectCurve) continue; - Vector2[] points = slider.Path.PointsInSegment(segmentStartPoint).Select(p => p.Position.Value).ToArray(); - if (points.Length == 3 && circularArcBoundingBox(points).Area >= 640 * 480) + 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; } } - /// - /// 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]; - - // 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; - - 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) { if (e.Button == MouseButton.Left && inputManager.CurrentState.Keyboard.ControlPressed) From 2bea69456e8bfbcdf780bd6e8adab5002913f3e3 Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Fri, 26 Mar 2021 15:24:33 +0800 Subject: [PATCH 084/289] remove implementations --- .../Objects/CatchHitObject.cs | 2 -- .../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 | 23 +++---------------- 5 files changed, 3 insertions(+), 28 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs index 631b50d686..ae45182960 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs @@ -120,7 +120,5 @@ 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 c43d223335..27bf50493d 100644 --- a/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs @@ -22,8 +22,6 @@ 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 e784d13084..22b64af3df 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -130,7 +130,5 @@ 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 fa7b2811cc..826d411822 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -168,8 +168,6 @@ 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 a6e84d59a7..c2a9fd49b1 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -63,9 +63,6 @@ namespace osu.Game.Screens.Edit protected bool HasUnsavedChanges => lastSavedHash != changeHandler.CurrentStateHash; - [Resolved] - private GameHost host { get; set; } - [Resolved] private BeatmapManager beatmapManager { get; set; } @@ -110,7 +107,7 @@ namespace osu.Game.Screens.Edit private MusicController music { get; set; } [BackgroundDependencyLoader] - private void load(OsuColour colours, OsuConfigManager config) + private void load(OsuColour colours, GameHost host, OsuConfigManager config) { if (Beatmap.Value is DummyWorkingBeatmap) { @@ -547,24 +544,10 @@ namespace osu.Game.Screens.Edit protected void Copy() { - var builder = new StringBuilder(); - const string suffix = " - "; - if (editorBeatmap.SelectedHitObjects.Count == 0) - { - 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.Select(h => h.ToEditorString()))})"); + return; - clipboard.Value = new ClipboardContent(editorBeatmap).Serialize(); - } - - builder.Append(suffix); - host.GetClipboard()?.SetText(builder.ToString()); + clipboard.Value = new ClipboardContent(editorBeatmap).Serialize(); } protected void Paste() From b8b7eb4c4b8e0af156ebdad46a83ecbe3427b0b2 Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Fri, 26 Mar 2021 15:25:20 +0800 Subject: [PATCH 085/289] refactor logic to its own component and handle hit object to string conversion to its ruleset-specific composers --- .../Edit/ManiaHitObjectComposer.cs | 3 ++ .../Edit/OsuHitObjectComposer.cs | 3 ++ osu.Game/Rulesets/Edit/HitObjectComposer.cs | 2 + .../Screens/Edit/Compose/ComposeScreen.cs | 23 +++++++++- osu.Game/Screens/Edit/SelectionHelper.cs | 46 +++++++++++++++++++ 5 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Screens/Edit/SelectionHelper.cs diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 324670c4b2..4cb34a217c 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -119,5 +119,8 @@ namespace osu.Game.Rulesets.Mania.Edit beatSnapGrid.SelectionTimeRange = null; } } + + public override IEnumerable ConvertSelectionToString() + => EditorBeatmap.SelectedHitObjects.Cast().Select(h => $"{h.StartTime}|{h.Column}"); } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 0490e8b8ce..1943f52c73 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -82,6 +82,9 @@ namespace osu.Game.Rulesets.Osu.Edit protected override ComposeBlueprintContainer CreateBlueprintContainer() => new OsuBlueprintContainer(this); + public override IEnumerable ConvertSelectionToString() + => selectedHitObjects.Cast().Select(h => (h.IndexInCurrentCombo + 1).ToString()); + private DistanceSnapGrid distanceSnapGrid; private Container distanceSnapGridContainer; diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index e927951d0a..eee16043e4 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -438,6 +438,8 @@ namespace osu.Game.Rulesets.Edit /// public abstract bool CursorInPlacementArea { get; } + public virtual IEnumerable ConvertSelectionToString() => Array.Empty(); + #region IPositionSnapProvider public abstract SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition); diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index 81b1195a40..c63ef11c74 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -6,6 +6,8 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Input; +using osu.Framework.Input.Bindings; using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Edit; @@ -14,16 +16,19 @@ using osu.Game.Skinning; namespace osu.Game.Screens.Edit.Compose { - public class ComposeScreen : EditorScreenWithTimeline + public class ComposeScreen : EditorScreenWithTimeline, IKeyBindingHandler { [Resolved] private IBindable beatmap { get; set; } private HitObjectComposer composer; + private SelectionHelper helper; + public ComposeScreen() : base(EditorScreenMode.Compose) { + Add(helper = new SelectionHelper()); } private Ruleset ruleset; @@ -72,5 +77,21 @@ namespace osu.Game.Screens.Edit.Compose // this is intentionally done in two stages to ensure things are in a loaded state before exposing the ruleset to skin sources. return beatmapSkinProvider.WithChild(rulesetSkinProvider.WithChild(content)); } + + public bool OnPressed(PlatformAction action) + { + switch (action.ActionType) + { + case PlatformActionType.Copy: + helper.CopySelectionToClipboard(); + return false; + default: + return false; + }; + } + + public void OnReleased(PlatformAction action) + { + } } } diff --git a/osu.Game/Screens/Edit/SelectionHelper.cs b/osu.Game/Screens/Edit/SelectionHelper.cs new file mode 100644 index 0000000000..e0eb868c9f --- /dev/null +++ b/osu.Game/Screens/Edit/SelectionHelper.cs @@ -0,0 +1,46 @@ +using System.Linq; +using System.Text; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Platform; +using osu.Game.Extensions; +using osu.Game.Rulesets.Edit; + +namespace osu.Game.Screens.Edit +{ + public class SelectionHelper : Component + { + [Resolved] + private GameHost host { get; set; } + + [Resolved] + private EditorClock clock { get; set; } + + [Resolved] + private EditorBeatmap editorBeatmap { get; set; } + + [Resolved(CanBeNull = true)] + private HitObjectComposer composer { get; set; } + + public void CopySelectionToClipboard() + { + host.GetClipboard().SetText(formatSelectionAsString()); + } + + private string formatSelectionAsString() + { + const string separator = " - "; + var builder = new StringBuilder(); + + if (!editorBeatmap.SelectedHitObjects.Any()) + { + builder.Append($"{clock.CurrentTime.ToEditorFormattedString()}{separator}"); + return builder.ToString(); + }; + + builder.Append(editorBeatmap.SelectedHitObjects.First().StartTime.ToEditorFormattedString()); + builder.Append($" ({string.Join(',', composer.ConvertSelectionToString())}){separator}"); + return builder.ToString(); + } + } +} From cb48e5f15822a7aab53317bc8a1b938c777f8544 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Mar 2021 16:33:16 +0900 Subject: [PATCH 086/289] Fix timeline not visually ordering hitobjects in a stable way --- .../Components/HitObjectOrderedSelectionContainer.cs | 8 +++++++- .../Components/Timeline/TimelineBlueprintContainer.cs | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs b/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs index 9e95fe4fa1..d612cf3fe0 100644 --- a/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs @@ -71,7 +71,13 @@ namespace osu.Game.Screens.Edit.Compose.Components // Put earlier blueprints towards the end of the list, so they handle input first int i = yObj.HitObject.StartTime.CompareTo(xObj.HitObject.StartTime); - return i == 0 ? CompareReverseChildID(x, y) : i; + + if (i != 0) return i; + + // Fall back to end time if the start time is equal. + i = yObj.HitObject.GetEndTime().CompareTo(xObj.HitObject.GetEndTime()); + + return i == 0 ? CompareReverseChildID(y, x) : i; } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index cec851b5bb..3623f8ad8e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -138,7 +138,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Stack currentConcurrentObjects = new Stack(); - foreach (var b in SelectionBlueprints.OrderBy(b => b.HitObject.StartTime).ThenBy(b => b.HitObject.GetEndTime())) + foreach (var b in SelectionBlueprints.Reverse()) { // remove objects from the stack as long as their end time is in the past. while (currentConcurrentObjects.TryPeek(out HitObject hitObject)) From 374f8c5e229b314a2f7cf1de9b29285cccbff0d2 Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Fri, 26 Mar 2021 15:33:28 +0800 Subject: [PATCH 087/289] move to compose namespace and add license header --- osu.Game/Screens/Edit/{ => Compose}/SelectionHelper.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) rename osu.Game/Screens/Edit/{ => Compose}/SelectionHelper.cs (85%) diff --git a/osu.Game/Screens/Edit/SelectionHelper.cs b/osu.Game/Screens/Edit/Compose/SelectionHelper.cs similarity index 85% rename from osu.Game/Screens/Edit/SelectionHelper.cs rename to osu.Game/Screens/Edit/Compose/SelectionHelper.cs index e0eb868c9f..39d6d57a2a 100644 --- a/osu.Game/Screens/Edit/SelectionHelper.cs +++ b/osu.Game/Screens/Edit/Compose/SelectionHelper.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 System.Text; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -6,7 +9,7 @@ using osu.Framework.Platform; using osu.Game.Extensions; using osu.Game.Rulesets.Edit; -namespace osu.Game.Screens.Edit +namespace osu.Game.Screens.Edit.Compose { public class SelectionHelper : Component { From 71a0616861916129456e0a301d0386269c51cd6c Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Fri, 26 Mar 2021 15:34:45 +0800 Subject: [PATCH 088/289] remove extra semi colons --- osu.Game/Screens/Edit/Compose/ComposeScreen.cs | 2 +- osu.Game/Screens/Edit/Compose/SelectionHelper.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index c63ef11c74..b249ae9bcd 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -87,7 +87,7 @@ namespace osu.Game.Screens.Edit.Compose return false; default: return false; - }; + } } public void OnReleased(PlatformAction action) diff --git a/osu.Game/Screens/Edit/Compose/SelectionHelper.cs b/osu.Game/Screens/Edit/Compose/SelectionHelper.cs index 39d6d57a2a..2e172c12dc 100644 --- a/osu.Game/Screens/Edit/Compose/SelectionHelper.cs +++ b/osu.Game/Screens/Edit/Compose/SelectionHelper.cs @@ -39,7 +39,7 @@ namespace osu.Game.Screens.Edit.Compose { builder.Append($"{clock.CurrentTime.ToEditorFormattedString()}{separator}"); return builder.ToString(); - }; + } builder.Append(editorBeatmap.SelectedHitObjects.First().StartTime.ToEditorFormattedString()); builder.Append($" ({string.Join(',', composer.ConvertSelectionToString())}){separator}"); From c96321206a0e5157d8864e5d84204706f918ce30 Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Fri, 26 Mar 2021 16:17:24 +0800 Subject: [PATCH 089/289] fix appveyor complaints --- osu.Game/Screens/Edit/Compose/ComposeScreen.cs | 3 ++- osu.Game/Screens/Edit/Editor.cs | 6 +----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index b249ae9bcd..16043ff64b 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -23,7 +23,7 @@ namespace osu.Game.Screens.Edit.Compose private HitObjectComposer composer; - private SelectionHelper helper; + private readonly SelectionHelper helper; public ComposeScreen() : base(EditorScreenMode.Compose) @@ -85,6 +85,7 @@ namespace osu.Game.Screens.Edit.Compose case PlatformActionType.Copy: helper.CopySelectionToClipboard(); return false; + default: return false; } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index c2a9fd49b1..d9ba12d331 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.Text; using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -17,12 +16,10 @@ using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Logging; -using osu.Framework.Platform; 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; @@ -31,7 +28,6 @@ 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; @@ -107,7 +103,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) { From 5d272bef9778d590b84e1e97edfde3d64a8b68de Mon Sep 17 00:00:00 2001 From: Leon Gebler Date: Fri, 26 Mar 2021 16:28:04 +0100 Subject: [PATCH 090/289] Remember ContolPoint positions instead of recalculating them --- osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index ae92b12fd2..b9578b4ae9 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -197,8 +197,13 @@ 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); + Queue oldControlPoints = new Queue(); + foreach (var point in slider.Path.ControlPoints) + { + oldControlPoints.Enqueue(point.Position.Value); point.Position.Value *= pathRelativeDeltaScale; + } //if sliderhead or sliderend end up outside playfield, revert scaling. Quad scaledQuad = getSurroundingQuad(new OsuHitObject[] { slider }); @@ -207,8 +212,8 @@ namespace osu.Game.Rulesets.Osu.Edit if (xInBounds && yInBounds) return true; - foreach (var point in slider.Path.ControlPoints) - point.Position.Value *= new Vector2(1 / pathRelativeDeltaScale.X, 1 / pathRelativeDeltaScale.Y); + foreach(var point in slider.Path.ControlPoints) + point.Position.Value = oldControlPoints.Dequeue(); return true; } From 25ea60cb92c6d4e40b7c442d68d2fd17b4a7056d Mon Sep 17 00:00:00 2001 From: Leon Gebler Date: Fri, 26 Mar 2021 16:39:13 +0100 Subject: [PATCH 091/289] Remove return values from HandleScale submethods --- .../Edit/OsuSelectionHandler.cs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index b9578b4ae9..8a657d0f9b 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -143,19 +143,18 @@ namespace osu.Game.Rulesets.Osu.Edit adjustScaleFromAnchor(ref scale, reference); var hitObjects = selectedMovableObjects; - 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) - result = scaleSlider(slider, scale); + scaleSlider(slider, scale); else - result = scaleHitObjects(hitObjects, reference, scale); + scaleHitObjects(hitObjects, reference, scale); moveSelectionInBounds(); - return result; + return true; } private static void adjustScaleFromAnchor(ref Vector2 scale, Anchor reference) @@ -192,7 +191,7 @@ namespace osu.Game.Rulesets.Osu.Edit return true; } - private bool scaleSlider(Slider slider, Vector2 scale) + private void scaleSlider(Slider slider, Vector2 scale) { 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); @@ -210,15 +209,13 @@ namespace osu.Game.Rulesets.Osu.Edit (bool xInBounds, bool yInBounds) = isQuadInBounds(scaledQuad); if (xInBounds && yInBounds) - return true; + return; foreach(var point in slider.Path.ControlPoints) point.Position.Value = oldControlPoints.Dequeue(); - - return true; } - private bool scaleHitObjects(OsuHitObject[] hitObjects, Anchor reference, Vector2 scale) + private void scaleHitObjects(OsuHitObject[] hitObjects, Anchor reference, Vector2 scale) { scale = getClampedScale(hitObjects, reference, scale); @@ -241,8 +238,6 @@ namespace osu.Game.Rulesets.Osu.Edit h.Position = newPosition; } - - return true; } private (bool X, bool Y) isQuadInBounds(Quad quad) From 305c2e31cfc7f7e6e30f94d561415619b37c87e0 Mon Sep 17 00:00:00 2001 From: Leon Gebler Date: Fri, 26 Mar 2021 16:45:05 +0100 Subject: [PATCH 092/289] Clarify todo comment --- 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 8a657d0f9b..24b0d22c1a 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -283,7 +283,7 @@ namespace osu.Game.Rulesets.Osu.Edit Quad selectionQuad = getSurroundingQuad(hitObjects); - //todo: this is not always correct for selections involving sliders + //todo: this is not always correct for selections involving sliders. This approximation assumes each point is scaled independently, but sliderends move with the sliderhead. Quad scaledQuad = new Quad(selectionQuad.TopLeft.X + xOffset, selectionQuad.TopLeft.Y + yOffset, selectionQuad.Width + scale.X, selectionQuad.Height + scale.Y); //max Size -> playfield bounds From a50c4be8ab806e4f8875cd286a01d6cf5a3a2a7f Mon Sep 17 00:00:00 2001 From: Leon Gebler Date: Fri, 26 Mar 2021 17:41:36 +0100 Subject: [PATCH 093/289] Add missing space --- 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 24b0d22c1a..3a4a37c8cc 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -211,7 +211,7 @@ namespace osu.Game.Rulesets.Osu.Edit if (xInBounds && yInBounds) return; - foreach(var point in slider.Path.ControlPoints) + foreach (var point in slider.Path.ControlPoints) point.Position.Value = oldControlPoints.Dequeue(); } From 21398e25b5b939ae40e15be42bb6ffb93365d315 Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Sat, 27 Mar 2021 10:02:21 +0800 Subject: [PATCH 094/289] null check composer and ensure the correct start time from selected hit objects --- osu.Game/Screens/Edit/Compose/SelectionHelper.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/SelectionHelper.cs b/osu.Game/Screens/Edit/Compose/SelectionHelper.cs index 2e172c12dc..a01a9e0144 100644 --- a/osu.Game/Screens/Edit/Compose/SelectionHelper.cs +++ b/osu.Game/Screens/Edit/Compose/SelectionHelper.cs @@ -41,8 +41,10 @@ namespace osu.Game.Screens.Edit.Compose return builder.ToString(); } - builder.Append(editorBeatmap.SelectedHitObjects.First().StartTime.ToEditorFormattedString()); - builder.Append($" ({string.Join(',', composer.ConvertSelectionToString())}){separator}"); + string hitObjects = composer != null ? string.Join(',', composer.ConvertSelectionToString()) : string.Empty; + + builder.Append(editorBeatmap.SelectedHitObjects.Min(h => h.StartTime).ToEditorFormattedString()); + builder.Append($" ({hitObjects}){separator}"); return builder.ToString(); } } From 010db8968fa664cb7d0a44ec0da727c3a9776773 Mon Sep 17 00:00:00 2001 From: Samuel Cattini-Schultz Date: Sat, 27 Mar 2021 18:38:23 +1100 Subject: [PATCH 095/289] Adjust wording of xmldoc --- .../Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs b/osu.Game/Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs index fa578d55f0..1bf0d8a222 100644 --- a/osu.Game/Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs +++ b/osu.Game/Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Difficulty.Preprocessing public readonly double DeltaTime; /// - /// Start time of . + /// Clockrate adjusted start time of . /// public readonly double StartTime; From 068f00d8a035834b7e21bec15318b6aafd48f9fc Mon Sep 17 00:00:00 2001 From: Samuel Cattini-Schultz Date: Sat, 27 Mar 2021 18:38:43 +1100 Subject: [PATCH 096/289] Add EndTime to DifficultyHitObject for future convenience --- .../Difficulty/Preprocessing/DifficultyHitObject.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs b/osu.Game/Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs index 1bf0d8a222..ece8219071 100644 --- a/osu.Game/Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs +++ b/osu.Game/Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs @@ -30,6 +30,11 @@ namespace osu.Game.Rulesets.Difficulty.Preprocessing /// public readonly double StartTime; + /// + /// Clockrate adjusted start time of . + /// + public readonly double EndTime; + /// /// Creates a new . /// @@ -42,6 +47,7 @@ namespace osu.Game.Rulesets.Difficulty.Preprocessing LastObject = lastObject; DeltaTime = (hitObject.StartTime - lastObject.StartTime) / clockRate; StartTime = hitObject.StartTime / clockRate; + EndTime = hitObject.GetEndTime() / clockRate; } } } From ecb66ad2e2213e52f3460964be7a2c8f21c69a08 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 29 Mar 2021 15:33:54 +0900 Subject: [PATCH 097/289] Fix up xmldoc --- .../Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs b/osu.Game/Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs index ece8219071..576fbb2af0 100644 --- a/osu.Game/Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs +++ b/osu.Game/Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Difficulty.Preprocessing public readonly double StartTime; /// - /// Clockrate adjusted start time of . + /// Clockrate adjusted end time of . /// public readonly double EndTime; From 699a317b445ff557163d91143682ab6f6c5faf8d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Mar 2021 18:07:47 +0900 Subject: [PATCH 098/289] Fix chat scroll sticking visually when scrolling beyond bottom extent --- osu.Game/Overlays/Chat/DrawableChannel.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 86ce724390..5f9c00b36a 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -275,7 +275,8 @@ namespace osu.Game.Overlays.Chat { if (!UserScrolling) { - ScrollToEnd(); + if (Current < ScrollableExtent) + ScrollToEnd(); lastExtent = ScrollableExtent; } }); From 9a02f3868c2105e51dbd7e64ce4443d08938d02e Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Mon, 29 Mar 2021 17:29:05 +0800 Subject: [PATCH 099/289] return a string instead --- osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs | 4 ++-- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 4 ++-- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 4cb34a217c..d9570bf8be 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -120,7 +120,7 @@ namespace osu.Game.Rulesets.Mania.Edit } } - public override IEnumerable ConvertSelectionToString() - => EditorBeatmap.SelectedHitObjects.Cast().Select(h => $"{h.StartTime}|{h.Column}"); + public override string ConvertSelectionToString() + => string.Join(',', EditorBeatmap.SelectedHitObjects.Cast().OrderBy(h => h.StartTime).Select(h => $"{h.StartTime}|{h.Column}")); } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 1943f52c73..396fd41377 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -82,8 +82,8 @@ namespace osu.Game.Rulesets.Osu.Edit protected override ComposeBlueprintContainer CreateBlueprintContainer() => new OsuBlueprintContainer(this); - public override IEnumerable ConvertSelectionToString() - => selectedHitObjects.Cast().Select(h => (h.IndexInCurrentCombo + 1).ToString()); + public override string ConvertSelectionToString() + => string.Join(',', selectedHitObjects.Cast().OrderBy(h => h.StartTime).Select(h => (h.IndexInCurrentCombo + 1).ToString())); private DistanceSnapGrid distanceSnapGrid; private Container distanceSnapGridContainer; diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index eee16043e4..736fc47dee 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -438,7 +438,7 @@ namespace osu.Game.Rulesets.Edit /// public abstract bool CursorInPlacementArea { get; } - public virtual IEnumerable ConvertSelectionToString() => Array.Empty(); + public virtual string ConvertSelectionToString() => string.Empty; #region IPositionSnapProvider From cdb779f764bb09dcba1784f66fc87aab9debfd8b Mon Sep 17 00:00:00 2001 From: Nathan Alo Date: Mon, 29 Mar 2021 17:30:23 +0800 Subject: [PATCH 100/289] move copy logic inside ComposeScreen --- .../Screens/Edit/Compose/ComposeScreen.cs | 37 ++++++++++---- .../Screens/Edit/Compose/SelectionHelper.cs | 51 ------------------- 2 files changed, 26 insertions(+), 62 deletions(-) delete mode 100644 osu.Game/Screens/Edit/Compose/SelectionHelper.cs diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index 16043ff64b..f6ce5a4e3d 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -2,13 +2,17 @@ // See the LICENCE file in the repository root for full licence text. using System.Diagnostics; +using System.Linq; +using System.Text; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input; using osu.Framework.Input.Bindings; +using osu.Framework.Platform; using osu.Game.Beatmaps; +using osu.Game.Extensions; using osu.Game.Rulesets; using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit.Compose.Components.Timeline; @@ -21,14 +25,17 @@ namespace osu.Game.Screens.Edit.Compose [Resolved] private IBindable beatmap { get; set; } - private HitObjectComposer composer; + [Resolved] + private GameHost host { get; set; } - private readonly SelectionHelper helper; + [Resolved] + private EditorClock clock { get; set; } + + private HitObjectComposer composer; public ComposeScreen() : base(EditorScreenMode.Compose) { - Add(helper = new SelectionHelper()); } private Ruleset ruleset; @@ -78,17 +85,25 @@ namespace osu.Game.Screens.Edit.Compose return beatmapSkinProvider.WithChild(rulesetSkinProvider.WithChild(content)); } + private string formatSelectionAsString() + { + var builder = new StringBuilder(); + builder.Append(EditorBeatmap.SelectedHitObjects.OrderBy(h => h.StartTime).FirstOrDefault()?.StartTime.ToEditorFormattedString() ?? clock.CurrentTime.ToEditorFormattedString()); + + if (EditorBeatmap.SelectedHitObjects.Any() && composer != null) + builder.Append($" ({composer.ConvertSelectionToString()})"); + + builder.Append(" - "); + + return builder.ToString(); + } + public bool OnPressed(PlatformAction action) { - switch (action.ActionType) - { - case PlatformActionType.Copy: - helper.CopySelectionToClipboard(); - return false; + if (action.ActionType == PlatformActionType.Copy) + host.GetClipboard().SetText(formatSelectionAsString()); - default: - return false; - } + return false; } public void OnReleased(PlatformAction action) diff --git a/osu.Game/Screens/Edit/Compose/SelectionHelper.cs b/osu.Game/Screens/Edit/Compose/SelectionHelper.cs deleted file mode 100644 index a01a9e0144..0000000000 --- a/osu.Game/Screens/Edit/Compose/SelectionHelper.cs +++ /dev/null @@ -1,51 +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.Linq; -using System.Text; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Platform; -using osu.Game.Extensions; -using osu.Game.Rulesets.Edit; - -namespace osu.Game.Screens.Edit.Compose -{ - public class SelectionHelper : Component - { - [Resolved] - private GameHost host { get; set; } - - [Resolved] - private EditorClock clock { get; set; } - - [Resolved] - private EditorBeatmap editorBeatmap { get; set; } - - [Resolved(CanBeNull = true)] - private HitObjectComposer composer { get; set; } - - public void CopySelectionToClipboard() - { - host.GetClipboard().SetText(formatSelectionAsString()); - } - - private string formatSelectionAsString() - { - const string separator = " - "; - var builder = new StringBuilder(); - - if (!editorBeatmap.SelectedHitObjects.Any()) - { - builder.Append($"{clock.CurrentTime.ToEditorFormattedString()}{separator}"); - return builder.ToString(); - } - - string hitObjects = composer != null ? string.Join(',', composer.ConvertSelectionToString()) : string.Empty; - - builder.Append(editorBeatmap.SelectedHitObjects.Min(h => h.StartTime).ToEditorFormattedString()); - builder.Append($" ({hitObjects}){separator}"); - return builder.ToString(); - } - } -} From 90ab765cf519be8be956175f492f3c7aff4e0f0e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Mar 2021 18:46:32 +0900 Subject: [PATCH 101/289] Reorder methods and surround with region --- .../Screens/Edit/Compose/ComposeScreen.cs | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index f6ce5a4e3d..1d52cc60fd 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -85,6 +85,20 @@ namespace osu.Game.Screens.Edit.Compose return beatmapSkinProvider.WithChild(rulesetSkinProvider.WithChild(content)); } + #region Input Handling + + public bool OnPressed(PlatformAction action) + { + if (action.ActionType == PlatformActionType.Copy) + host.GetClipboard().SetText(formatSelectionAsString()); + + return false; + } + + public void OnReleased(PlatformAction action) + { + } + private string formatSelectionAsString() { var builder = new StringBuilder(); @@ -98,16 +112,6 @@ namespace osu.Game.Screens.Edit.Compose return builder.ToString(); } - public bool OnPressed(PlatformAction action) - { - if (action.ActionType == PlatformActionType.Copy) - host.GetClipboard().SetText(formatSelectionAsString()); - - return false; - } - - public void OnReleased(PlatformAction action) - { - } + #endregion } } From 3909eda095ce5f6fcfdc7afa7e3ea4fdaf826540 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Mar 2021 18:51:28 +0900 Subject: [PATCH 102/289] Avoid using a StringBuilder --- osu.Game/Screens/Edit/Compose/ComposeScreen.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index 1d52cc60fd..61056aeced 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -3,7 +3,6 @@ using System.Diagnostics; using System.Linq; -using System.Text; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -101,15 +100,15 @@ namespace osu.Game.Screens.Edit.Compose private string formatSelectionAsString() { - var builder = new StringBuilder(); - builder.Append(EditorBeatmap.SelectedHitObjects.OrderBy(h => h.StartTime).FirstOrDefault()?.StartTime.ToEditorFormattedString() ?? clock.CurrentTime.ToEditorFormattedString()); + if (composer == null) + return string.Empty; - if (EditorBeatmap.SelectedHitObjects.Any() && composer != null) - builder.Append($" ({composer.ConvertSelectionToString()})"); + double displayTime = EditorBeatmap.SelectedHitObjects.OrderBy(h => h.StartTime).FirstOrDefault()?.StartTime ?? clock.CurrentTime; + string selectionAsString = composer.ConvertSelectionToString(); - builder.Append(" - "); - - return builder.ToString(); + return !string.IsNullOrEmpty(selectionAsString) + ? $"{displayTime.ToEditorFormattedString()} ({selectionAsString}) - " + : $"{displayTime.ToEditorFormattedString()} - "; } #endregion From 1d99a63f17f8e339097552dc51fbc27bef00b5c7 Mon Sep 17 00:00:00 2001 From: Leon Gebler Date: Mon, 29 Mar 2021 14:02:53 +0200 Subject: [PATCH 103/289] Limit minimum size for single slider scaling --- osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 3a4a37c8cc..6350bd7190 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -194,6 +194,10 @@ namespace osu.Game.Rulesets.Osu.Edit private void scaleSlider(Slider slider, Vector2 scale) { 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. + 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); Queue oldControlPoints = new Queue(); From 17b16d4f8914b9989b2f116e334e26fd0a4c39fe Mon Sep 17 00:00:00 2001 From: Leon Gebler Date: Mon, 29 Mar 2021 14:17:30 +0200 Subject: [PATCH 104/289] Clarify purpose of getClampedScale() --- 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 6350bd7190..07643bd75e 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -274,7 +274,7 @@ namespace osu.Game.Rulesets.Osu.Edit } /// - /// Clamp scale where selection does not exceed playfield bounds or flip. + /// Clamp scale for multi-object-scaling where selection does not exceed playfield bounds or flip. /// /// The hitobjects to be scaled /// The anchor from which the scale operation is performed From badf5ee4a2403998b4508907425630b21ea9e392 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 29 Mar 2021 15:03:10 +0200 Subject: [PATCH 105/289] Fix stable.json file directory location due to the change of how TournamentStorage works --- .../NonVisual/IPCLocationTest.cs | 68 +++++++++++++++++++ osu.Game.Tournament/IO/TournamentStorage.cs | 21 +++--- osu.Game.Tournament/Models/StableInfo.cs | 6 +- 3 files changed, 85 insertions(+), 10 deletions(-) create mode 100644 osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs diff --git a/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs b/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs new file mode 100644 index 0000000000..29586dfdec --- /dev/null +++ b/osu.Game.Tournament.Tests/NonVisual/IPCLocationTest.cs @@ -0,0 +1,68 @@ +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using NUnit.Framework; +using osu.Framework; +using osu.Framework.Allocation; +using osu.Framework.Platform; +using osu.Game.Tournament.IO; +using osu.Game.Tournament.IPC; + +namespace osu.Game.Tournament.Tests.NonVisual +{ + [TestFixture] + public class IPCLocationTest + { + [Test] + public void CheckIPCLocation() + { + // don't use clean run because files are being written before osu! launches. + using (HeadlessGameHost host = new HeadlessGameHost(nameof(CheckIPCLocation))) + { + 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 ipcFile = Path.Combine(testCeDir, "ipc.txt"); + File.WriteAllText(ipcFile, string.Empty); + + try + { + var osu = loadOsu(host); + TournamentStorage storage = (TournamentStorage)osu.Dependencies.Get(); + FileBasedIPC ipc = (FileBasedIPC)osu.Dependencies.Get(); + + Assert.True(ipc.SetIPCLocation(testCeDir)); + Assert.True(storage.AllTournaments.Exists("stable.json")); + } + finally + { + host.Storage.DeleteDirectory(testCeDir); + host.Storage.DeleteDirectory("tournaments"); + host.Exit(); + } + } + } + + private TournamentGameBase loadOsu(GameHost host) + { + var osu = new TournamentGameBase(); + Task.Run(() => host.Run(osu)); + waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time"); + return osu; + } + + private static void waitForOrAssert(Func result, string failureMessage, int timeout = 90000) + { + Task task = Task.Run(() => + { + while (!result()) Thread.Sleep(200); + }); + + Assert.IsTrue(task.Wait(timeout), failureMessage); + } + } +} diff --git a/osu.Game.Tournament/IO/TournamentStorage.cs b/osu.Game.Tournament/IO/TournamentStorage.cs index 5d9fed6288..044b60bbd5 100644 --- a/osu.Game.Tournament/IO/TournamentStorage.cs +++ b/osu.Game.Tournament/IO/TournamentStorage.cs @@ -1,12 +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.Collections.Generic; +using System.IO; using osu.Framework.Bindables; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.IO; -using System.IO; -using System.Collections.Generic; using osu.Game.Tournament.Configuration; namespace osu.Game.Tournament.IO @@ -15,7 +15,12 @@ namespace osu.Game.Tournament.IO { private const string default_tournament = "default"; private readonly Storage storage; - private readonly Storage allTournaments; + + /// + /// The storage where all tournaments are located. + /// + public readonly Storage AllTournaments; + private readonly TournamentStorageManager storageConfig; public readonly Bindable CurrentTournament; @@ -23,16 +28,16 @@ namespace osu.Game.Tournament.IO : base(storage.GetStorageForDirectory("tournaments"), string.Empty) { this.storage = storage; - allTournaments = UnderlyingStorage; + AllTournaments = UnderlyingStorage; storageConfig = new TournamentStorageManager(storage); if (storage.Exists("tournament.ini")) { - ChangeTargetStorage(allTournaments.GetStorageForDirectory(storageConfig.Get(StorageConfig.CurrentTournament))); + ChangeTargetStorage(AllTournaments.GetStorageForDirectory(storageConfig.Get(StorageConfig.CurrentTournament))); } else - Migrate(allTournaments.GetStorageForDirectory(default_tournament)); + Migrate(AllTournaments.GetStorageForDirectory(default_tournament)); CurrentTournament = storageConfig.GetBindable(StorageConfig.CurrentTournament); Logger.Log("Using tournament storage: " + GetFullPath(string.Empty)); @@ -42,11 +47,11 @@ namespace osu.Game.Tournament.IO private void updateTournament(ValueChangedEvent newTournament) { - ChangeTargetStorage(allTournaments.GetStorageForDirectory(newTournament.NewValue)); + ChangeTargetStorage(AllTournaments.GetStorageForDirectory(newTournament.NewValue)); Logger.Log("Changing tournament storage: " + GetFullPath(string.Empty)); } - public IEnumerable ListTournaments() => allTournaments.GetDirectories(string.Empty); + public IEnumerable ListTournaments() => AllTournaments.GetDirectories(string.Empty); public override void Migrate(Storage newStorage) { diff --git a/osu.Game.Tournament/Models/StableInfo.cs b/osu.Game.Tournament/Models/StableInfo.cs index 0b0050a245..d390f88d59 100644 --- a/osu.Game.Tournament/Models/StableInfo.cs +++ b/osu.Game.Tournament/Models/StableInfo.cs @@ -5,6 +5,7 @@ using System; using System.IO; using Newtonsoft.Json; using osu.Framework.Platform; +using osu.Game.Tournament.IO; namespace osu.Game.Tournament.Models { @@ -24,13 +25,14 @@ namespace osu.Game.Tournament.Models /// public event Action OnStableInfoSaved; - private const string config_path = "tournament/stable.json"; + private const string config_path = "stable.json"; private readonly Storage storage; public StableInfo(Storage storage) { - this.storage = storage; + TournamentStorage tStorage = (TournamentStorage)storage; + this.storage = tStorage.AllTournaments; if (!storage.Exists(config_path)) return; From 36364a449249fb42289e82e1e8d6c05105a43885 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Mar 2021 22:12:49 +0900 Subject: [PATCH 106/289] Update framework --- osu.Android.props | 2 +- osu.Game/Input/Handlers/ReplayInputHandler.cs | 2 -- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 75ac298626..3682a44b9f 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/Input/Handlers/ReplayInputHandler.cs b/osu.Game/Input/Handlers/ReplayInputHandler.cs index 93ed3ca884..fba1bee0b8 100644 --- a/osu.Game/Input/Handlers/ReplayInputHandler.cs +++ b/osu.Game/Input/Handlers/ReplayInputHandler.cs @@ -34,8 +34,6 @@ namespace osu.Game.Input.Handlers public override bool IsActive => true; - public override int Priority => 0; - public class ReplayState : IInput where T : struct { diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index b90c938a8b..35b0827715 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 ce182a3054..ceb46eae87 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -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 107/289] 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 108/289] 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 109/289] 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 110/289] 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 111/289] 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 112/289] 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 113/289] 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 8a0fcf20ede3a63a074072899b33cb02cd83fdec Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Mar 2021 12:32:42 +0900 Subject: [PATCH 114/289] 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 115/289] 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 116/289] 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 b82247aabec2ee10c12105701d1c0e073a775e6a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Mar 2021 14:13:16 +0900 Subject: [PATCH 117/289] 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 118/289] 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 119/289] 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 120/289] 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 121/289] 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 122/289] 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 123/289] 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 124/289] 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 125/289] 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 126/289] 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 127/289] 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 128/289] 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 129/289] 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 130/289] 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 131/289] 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 132/289] 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 133/289] 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 134/289] 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 135/289] 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 136/289] 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 137/289] 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 138/289] 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 139/289] 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 140/289] 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 141/289] 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 142/289] 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 143/289] 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 144/289] 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 145/289] 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 146/289] 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 147/289] 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 148/289] 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 149/289] 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 150/289] 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 151/289] 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 152/289] 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 153/289] 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 154/289] 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 155/289] 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 156/289] 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 157/289] 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 158/289] 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 159/289] 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 160/289] 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 161/289] 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 162/289] 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 163/289] 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 164/289] 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 165/289] 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 166/289] 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 167/289] 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 168/289] 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 169/289] 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 170/289] 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 171/289] 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 172/289] 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 173/289] 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 174/289] 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 175/289] 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 176/289] 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 177/289] 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 178/289] 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 179/289] 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 180/289] 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 181/289] 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 182/289] 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 183/289] 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 184/289] 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 185/289] 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 186/289] 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 187/289] 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 188/289] 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 189/289] 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 190/289] 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 191/289] 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 192/289] 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 193/289] 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 194/289] 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 195/289] 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 196/289] 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 197/289] 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 198/289] 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 199/289] 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 200/289] 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 201/289] 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 202/289] 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 203/289] 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 204/289] 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 205/289] 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 206/289] 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 207/289] 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 208/289] 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 209/289] 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 210/289] 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 211/289] 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 212/289] 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 213/289] 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 214/289] 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 215/289] 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 216/289] 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 217/289] 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 218/289] 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 219/289] 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 220/289] 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 221/289] 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 222/289] 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 223/289] 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 224/289] 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 225/289] 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 226/289] 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 227/289] 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 228/289] 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 229/289] 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 230/289] 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 231/289] 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 232/289] 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 233/289] 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 234/289] 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 235/289] 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 236/289] 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 237/289] 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 238/289] 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 239/289] 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 240/289] 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 241/289] 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 242/289] 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 243/289] 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 244/289] 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 245/289] 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 246/289] 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 247/289] 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 2791d454d22a8d61e254531635d7a6d7c8bb746e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 7 Apr 2021 22:21:22 +0900 Subject: [PATCH 248/289] 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 249/289] 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 250/289] 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 251/289] 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 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 252/289] 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 253/289] 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 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 254/289] 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 255/289] 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 256/289] 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 257/289] 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 258/289] 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 259/289] 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 260/289] 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 261/289] 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 262/289] 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 263/289] 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 264/289] 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 265/289] 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 266/289] 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 267/289] 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 268/289] 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 269/289] 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 270/289] 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 271/289] 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 272/289] 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 273/289] 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 274/289] 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 8293b06c0afe9dfe16e1340f9ffab8ca1e737fd5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 9 Apr 2021 13:56:55 +0900 Subject: [PATCH 275/289] 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 276/289] 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 277/289] 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 278/289] 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 279/289] 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 280/289] 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 281/289] 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 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 282/289] 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 283/289] 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 8a0da06e89eed9d93c32db7b045e6bd3c021853b Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 9 Apr 2021 22:40:21 +0900 Subject: [PATCH 284/289] 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 285/289] 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 286/289] 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 287/289] 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 288/289] 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 a42714540b6c24385042725540bd6b85a53ebdda Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 10 Apr 2021 23:04:15 -0700 Subject: [PATCH 289/289] 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); }