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/129] 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/129] 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/129] 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/129] 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/129] 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/129] 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/129] 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/129] 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/129] 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/129] 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/129] 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/129] 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/129] 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/129] 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/129] 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/129] 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/129] 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/129] 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/129] 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/129] 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/129] 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/129] 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/129] 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/129] 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/129] 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/129] 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/129] 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/129] 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 1c1af981443df917fe27ace265c41c9f6a515760 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 26 Mar 2021 11:47:41 +0900 Subject: [PATCH 029/129] 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 010db8968fa664cb7d0a44ec0da727c3a9776773 Mon Sep 17 00:00:00 2001 From: Samuel Cattini-Schultz Date: Sat, 27 Mar 2021 18:38:23 +1100 Subject: [PATCH 030/129] 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 031/129] 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 032/129] 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 05961e98d5e3ac0b36f936d0f09ed62896b19093 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Mar 2021 19:03:15 +0900 Subject: [PATCH 033/129] 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 034/129] 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 af478fb2eb6052ddfcc0b99137d9605d2ff06681 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Apr 2021 22:02:32 +0900 Subject: [PATCH 035/129] 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 036/129] 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 037/129] 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 038/129] 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 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 039/129] 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 fcd56dba44a84e3e752e97f0eb3ed6409407f5ac Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 2 Apr 2021 01:38:10 +0300 Subject: [PATCH 040/129] 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 041/129] 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 042/129] 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 043/129] 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 044/129] 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 045/129] 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 046/129] 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 047/129] 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 048/129] 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 049/129] 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 050/129] 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 051/129] 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 052/129] 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 053/129] 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 054/129] 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 055/129] 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 056/129] 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 057/129] 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 5b2dcea8a85b9655fc24f5419c5c19c7b93eb61c Mon Sep 17 00:00:00 2001 From: Samuel Cattini-Schultz Date: Sat, 3 Apr 2021 20:47:39 +1100 Subject: [PATCH 058/129] 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 059/129] 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 060/129] 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 061/129] 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 062/129] 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 063/129] 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 064/129] 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 065/129] 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 066/129] 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 067/129] 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 068/129] 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 069/129] 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 070/129] 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 071/129] 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 072/129] 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 073/129] 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 074/129] 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 075/129] 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 076/129] 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 077/129] 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 078/129] 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 079/129] 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 080/129] 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 081/129] 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 082/129] 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 083/129] 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 084/129] 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 085/129] 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 086/129] 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 087/129] 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 088/129] 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 089/129] 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 090/129] 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 091/129] 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 092/129] 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 093/129] 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 094/129] 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 095/129] 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 096/129] 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 097/129] 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 098/129] 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 099/129] 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 100/129] 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 101/129] 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 a2544100d418cdbe3b7de383d9dfb2a2e2a95f38 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 6 Apr 2021 14:10:59 +0900 Subject: [PATCH 102/129] 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 103/129] 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 104/129] 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 105/129] 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 106/129] 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 107/129] 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 108/129] 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 109/129] 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 110/129] 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 111/129] 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 112/129] 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 113/129] 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 f08b340e811ba6fd7ec5cfcc3f8b854002ab62bf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Apr 2021 16:49:13 +0900 Subject: [PATCH 114/129] 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 115/129] 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 116/129] 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 117/129] 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 118/129] 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 ef658e9597484f008119240601b5921e6713b563 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 7 Apr 2021 15:54:16 +0900 Subject: [PATCH 119/129] 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 56c13148f1861b38b71cd87fdcaf5619b6527c1d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 7 Apr 2021 16:45:06 +0900 Subject: [PATCH 120/129] 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 08858e6426c520bb038ca774da64902dc21e70e9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Apr 2021 17:41:05 +0900 Subject: [PATCH 121/129] 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 122/129] 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 123/129] 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 124/129] 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 125/129] 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 126/129] 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 1f4c17b8f856a1d010fd2e45345d199c51dfba14 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 7 Apr 2021 21:20:44 +0900 Subject: [PATCH 127/129] 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 8cc1e8b8b0605e981b1d0be53cb9a0590383e532 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Apr 2021 23:11:01 +0900 Subject: [PATCH 128/129] 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 129/129] 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 @@ - +