From fea4e1ff686a2475d1251114d4acf011db9118db Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 31 Dec 2017 04:09:51 +0900 Subject: [PATCH 01/14] Add a base interface for applicable mods --- osu.Game/Rulesets/Mods/IApplicableMod.cs | 10 ++++++++++ osu.Game/Rulesets/Mods/IApplicableToClock.cs | 4 ++-- osu.Game/Rulesets/Mods/IApplicableToDifficulty.cs | 4 ++-- .../Rulesets/Mods/IApplicableToDrawableHitObject.cs | 2 +- osu.Game/Rulesets/Mods/IApplicableToHitObject.cs | 2 +- .../Rulesets/Mods/IApplicableToRulesetContainer.cs | 2 +- osu.Game/Rulesets/Mods/IApplicableToScoreProcessor.cs | 2 +- osu.Game/Rulesets/Mods/Mod.cs | 5 +++++ osu.Game/osu.Game.csproj | 1 + 9 files changed, 24 insertions(+), 8 deletions(-) create mode 100644 osu.Game/Rulesets/Mods/IApplicableMod.cs diff --git a/osu.Game/Rulesets/Mods/IApplicableMod.cs b/osu.Game/Rulesets/Mods/IApplicableMod.cs new file mode 100644 index 0000000000..0602b90b69 --- /dev/null +++ b/osu.Game/Rulesets/Mods/IApplicableMod.cs @@ -0,0 +1,10 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Mods +{ + // The base interface for a mod which can be applied in some way. + public interface IApplicableMod + { + } +} diff --git a/osu.Game/Rulesets/Mods/IApplicableToClock.cs b/osu.Game/Rulesets/Mods/IApplicableToClock.cs index f0502cf346..8bb4e2b97d 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToClock.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToClock.cs @@ -8,8 +8,8 @@ namespace osu.Game.Rulesets.Mods /// /// An interface for mods that make adjustments to the track. /// - public interface IApplicableToClock + public interface IApplicableToClock : IApplicableMod { void ApplyToClock(IAdjustableClock clock); } -} \ No newline at end of file +} diff --git a/osu.Game/Rulesets/Mods/IApplicableToDifficulty.cs b/osu.Game/Rulesets/Mods/IApplicableToDifficulty.cs index 58f5defb5e..a95aa4370c 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToDifficulty.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToDifficulty.cs @@ -8,8 +8,8 @@ namespace osu.Game.Rulesets.Mods /// /// An interface for mods that make general adjustments to difficulty. /// - public interface IApplicableToDifficulty + public interface IApplicableToDifficulty : IApplicableMod { void ApplyToDifficulty(BeatmapDifficulty difficulty); } -} \ No newline at end of file +} diff --git a/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObject.cs b/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObject.cs index 1024d5686d..66dbc85095 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObject.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObject.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Mods /// /// An interface for s that can be applied to s. /// - public interface IApplicableToDrawableHitObjects + public interface IApplicableToDrawableHitObjects : IApplicableMod { /// /// Applies this to a list of s. diff --git a/osu.Game/Rulesets/Mods/IApplicableToHitObject.cs b/osu.Game/Rulesets/Mods/IApplicableToHitObject.cs index 7f39def343..1964ad728f 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToHitObject.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToHitObject.cs @@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Mods /// /// An interface for s that can be applied to s. /// - public interface IApplicableToHitObject + public interface IApplicableToHitObject : IApplicableMod where TObject : HitObject { /// diff --git a/osu.Game/Rulesets/Mods/IApplicableToRulesetContainer.cs b/osu.Game/Rulesets/Mods/IApplicableToRulesetContainer.cs index 9b23dd58f9..eae8c9d15c 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToRulesetContainer.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToRulesetContainer.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Mods /// /// An interface for s that can be applied to s. /// - public interface IApplicableToRulesetContainer + public interface IApplicableToRulesetContainer : IApplicableMod where TObject : HitObject { /// diff --git a/osu.Game/Rulesets/Mods/IApplicableToScoreProcessor.cs b/osu.Game/Rulesets/Mods/IApplicableToScoreProcessor.cs index db9b713c59..2314999d4f 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToScoreProcessor.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToScoreProcessor.cs @@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Mods /// /// An interface for mods that make general adjustments to score processor. /// - public interface IApplicableToScoreProcessor + public interface IApplicableToScoreProcessor : IApplicableMod { void ApplyToScoreProcessor(ScoreProcessor scoreProcessor); } diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 7b0034863e..0e477bbb4a 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -41,6 +41,11 @@ namespace osu.Game.Rulesets.Mods /// public abstract double ScoreMultiplier { get; } + /// + /// Returns true if this mod is implemented (and playable). + /// + public virtual bool HasImplementation => this is IApplicableMod; + /// /// Returns if this mod is ranked. /// diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 6cb12430c3..f14b010bce 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -310,6 +310,7 @@ + From d9a80dae5de1f8e13c7677c6015b50fb01297852 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 31 Dec 2017 04:10:25 +0900 Subject: [PATCH 02/14] Male NoFail use an applicable interface --- .../Rulesets/Mods/IApplicableFailOverride.cs | 16 ++++++++++++++++ osu.Game/Rulesets/Mods/Mod.cs | 5 ----- osu.Game/Rulesets/Mods/ModNoFail.cs | 6 +++--- osu.Game/Screens/Play/Player.cs | 2 +- osu.Game/osu.Game.csproj | 1 + 5 files changed, 21 insertions(+), 9 deletions(-) create mode 100644 osu.Game/Rulesets/Mods/IApplicableFailOverride.cs diff --git a/osu.Game/Rulesets/Mods/IApplicableFailOverride.cs b/osu.Game/Rulesets/Mods/IApplicableFailOverride.cs new file mode 100644 index 0000000000..2d7cda5f1f --- /dev/null +++ b/osu.Game/Rulesets/Mods/IApplicableFailOverride.cs @@ -0,0 +1,16 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Mods +{ + /// + /// Represents a mod which can override (and block) a fail. + /// + public interface IApplicableFailOverride : IApplicableMod + { + /// + /// Whether we should allow failing at the current point in time. + /// + bool AllowFail { get; } + } +} diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 0e477bbb4a..68ed545701 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -55,10 +55,5 @@ namespace osu.Game.Rulesets.Mods /// The mods this mod cannot be enabled with. /// public virtual Type[] IncompatibleMods => new Type[] { }; - - /// - /// Whether we should allow failing at the current point in time. - /// - public virtual bool AllowFail => true; } } diff --git a/osu.Game/Rulesets/Mods/ModNoFail.cs b/osu.Game/Rulesets/Mods/ModNoFail.cs index 3a3878d77e..8aefd1b88e 100644 --- a/osu.Game/Rulesets/Mods/ModNoFail.cs +++ b/osu.Game/Rulesets/Mods/ModNoFail.cs @@ -6,7 +6,7 @@ using osu.Game.Graphics; namespace osu.Game.Rulesets.Mods { - public abstract class ModNoFail : Mod + public abstract class ModNoFail : Mod, IApplicableFailOverride { public override string Name => "NoFail"; public override string ShortenedName => "NF"; @@ -20,6 +20,6 @@ namespace osu.Game.Rulesets.Mods /// /// We never fail, 'yo. /// - public override bool AllowFail => false; + public bool AllowFail => false; } -} \ No newline at end of file +} diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 35f39e940f..d4cfa0b3d4 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -298,7 +298,7 @@ namespace osu.Game.Screens.Play private bool onFail() { - if (Beatmap.Value.Mods.Value.Any(m => !m.AllowFail)) + if (Beatmap.Value.Mods.Value.OfType().Any(m => !m.AllowFail)) return false; decoupledClock.Stop(); diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index f14b010bce..cb375139c3 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -310,6 +310,7 @@ + From 28cd72a12b25d258cbf348796cc383ad1e0ddba0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 31 Dec 2017 04:27:15 +0900 Subject: [PATCH 03/14] Add note about mania xKey mods for future implementation --- osu.Game.Rulesets.Mania/Mods/ManiaMod.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaMod.cs b/osu.Game.Rulesets.Mania/Mods/ManiaMod.cs index dfc9993bde..61e11f7610 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaMod.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaMod.cs @@ -105,6 +105,8 @@ namespace osu.Game.Rulesets.Mania.Mods public abstract class ManiaKeyMod : Mod { + // TODO: implement using the IApplicable interface. Haven't done so yet because KeyCount isn't even hooked up at the moment. + public override string ShortenedName => Name; public abstract int KeyCount { get; } public override double ScoreMultiplier => 1; // TODO: Implement the mania key mod score multiplier From 1c80f3e91839004e2c7f02b5647b2e337c6b183f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 31 Dec 2017 04:28:51 +0900 Subject: [PATCH 04/14] Disallow selection of non-implemented mods --- osu.Game/Overlays/Mods/ModButton.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs index 35c2e9234d..2afd34438c 100644 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ b/osu.Game/Overlays/Mods/ModButton.cs @@ -66,6 +66,13 @@ namespace osu.Game.Overlays.Mods Mod modAfter = SelectedMod ?? Mods[0]; + if (!modAfter.HasImplementation) + { + if (modAfter != modBefore) + SelectedIndex += direction; + return; + } + if (beforeSelected != Selected) { iconsContainer.RotateTo(Selected ? 5f : 0f, 300, Easing.OutElastic); @@ -195,6 +202,7 @@ namespace osu.Game.Overlays.Mods backgroundIcon.Icon = foregroundIcon.Icon; foregroundIcon.Icon = mod.Icon; text.Text = mod.Name; + Colour = mod.HasImplementation ? Color4.White : Color4.Gray; } private void createIcons() From f2d302f8dcb77f23021286738c2c2d0dd5176d70 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 31 Dec 2017 06:40:28 +0900 Subject: [PATCH 05/14] Create a LargeTextureStore for cached (but not atlased) global textures Also - Fixes first transition depth being incorrect. - Improves smoothness of transitions (and adds a slight delay to offset from screen switches). --- osu.Game/Beatmaps/BeatmapManager.cs | 3 ++- osu.Game/Graphics/Backgrounds/Background.cs | 5 ++--- .../Graphics/Textures/LargeTextureStore.cs | 18 ++++++++++++++++++ osu.Game/OsuGameBase.cs | 6 ++++++ .../Backgrounds/BackgroundScreenDefault.cs | 13 ++++++++++--- osu.Game/osu.Game.csproj | 1 + 6 files changed, 39 insertions(+), 7 deletions(-) create mode 100644 osu.Game/Graphics/Textures/LargeTextureStore.cs diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index c86860f7b0..ceeac516e1 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -19,6 +19,7 @@ using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.IO; using osu.Game.Database; using osu.Game.Graphics; +using osu.Game.Graphics.Textures; using osu.Game.IO; using osu.Game.IPC; using osu.Game.Online.API; @@ -651,7 +652,7 @@ namespace osu.Game.Beatmaps try { - return new TextureStore(new RawTextureLoaderStore(store), false).Get(getPathForFile(Metadata.BackgroundFile)); + return new LargeTextureStore(new RawTextureLoaderStore(store)).Get(getPathForFile(Metadata.BackgroundFile)); } catch { diff --git a/osu.Game/Graphics/Backgrounds/Background.cs b/osu.Game/Graphics/Backgrounds/Background.cs index 8eb2ddc0ab..4fb08a41f3 100644 --- a/osu.Game/Graphics/Backgrounds/Background.cs +++ b/osu.Game/Graphics/Backgrounds/Background.cs @@ -5,8 +5,8 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; using OpenTK.Graphics; +using osu.Game.Graphics.Textures; namespace osu.Game.Graphics.Backgrounds { @@ -22,7 +22,6 @@ namespace osu.Game.Graphics.Backgrounds this.textureName = textureName; RelativeSizeAxes = Axes.Both; - Depth = float.MaxValue; Add(Sprite = new Sprite { @@ -35,7 +34,7 @@ namespace osu.Game.Graphics.Backgrounds } [BackgroundDependencyLoader] - private void load(TextureStore textures) + private void load(LargeTextureStore textures) { if (!string.IsNullOrEmpty(textureName)) Sprite.Texture = textures.Get(textureName); diff --git a/osu.Game/Graphics/Textures/LargeTextureStore.cs b/osu.Game/Graphics/Textures/LargeTextureStore.cs new file mode 100644 index 0000000000..166364c8dd --- /dev/null +++ b/osu.Game/Graphics/Textures/LargeTextureStore.cs @@ -0,0 +1,18 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics.Textures; +using osu.Framework.IO.Stores; + +namespace osu.Game.Graphics.Textures +{ + /// + /// A texture store that bypasses atlasing. + /// + public class LargeTextureStore : TextureStore + { + public LargeTextureStore(IResourceStore store = null) : base(store, false) + { + } + } +} diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index ea0bf22112..bba09f3257 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -18,8 +18,10 @@ using osu.Game.Graphics; using osu.Game.Graphics.Cursor; using osu.Game.Online.API; using osu.Framework.Graphics.Performance; +using osu.Framework.Graphics.Textures; using osu.Framework.Logging; using osu.Game.Database; +using osu.Game.Graphics.Textures; using osu.Game.Input; using osu.Game.Input.Bindings; using osu.Game.IO; @@ -84,11 +86,15 @@ namespace osu.Game private DatabaseContextFactory contextFactory; + private LargeTextureStore largeTextureStore; + [BackgroundDependencyLoader] private void load() { dependencies.Cache(contextFactory = new DatabaseContextFactory(Host)); + dependencies.Cache(largeTextureStore = new LargeTextureStore(new RawTextureLoaderStore(new NamespacedResourceStore(Resources, @"Textures")))); + dependencies.Cache(this); dependencies.Cache(LocalConfig); diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index de84e90baf..36867a84d5 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Threading; using osu.Game.Graphics.Backgrounds; namespace osu.Game.Screens.Backgrounds @@ -24,16 +25,22 @@ namespace osu.Game.Screens.Backgrounds private void display(Background newBackground) { - current?.FadeOut(800, Easing.OutQuint); + current?.FadeOut(800, Easing.InOutSine); current?.Expire(); Add(current = newBackground); + currentDisplay++; } + private ScheduledDelegate nextTask; + public void Next() { - currentDisplay++; - LoadComponentAsync(new Background(backgroundName) { Depth = currentDisplay }, display); + nextTask?.Cancel(); + nextTask = Scheduler.AddDelayed(() => + { + LoadComponentAsync(new Background(backgroundName) { Depth = currentDisplay }, display); + }, 100); } } } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 82b9f41567..bbdc8b8b76 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -267,6 +267,7 @@ + From 94b761e63e9b935914f94d1c18b0f438d30e3567 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 31 Dec 2017 12:22:17 +0900 Subject: [PATCH 06/14] Remove local storage for now --- osu.Game/OsuGameBase.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index bba09f3257..e311aea8e4 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -86,14 +86,12 @@ namespace osu.Game private DatabaseContextFactory contextFactory; - private LargeTextureStore largeTextureStore; - [BackgroundDependencyLoader] private void load() { dependencies.Cache(contextFactory = new DatabaseContextFactory(Host)); - dependencies.Cache(largeTextureStore = new LargeTextureStore(new RawTextureLoaderStore(new NamespacedResourceStore(Resources, @"Textures")))); + dependencies.Cache(new LargeTextureStore(new RawTextureLoaderStore(new NamespacedResourceStore(Resources, @"Textures")))); dependencies.Cache(this); dependencies.Cache(LocalConfig); From f419518887b2d840cb06f80f04bc2393985fc903 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Jan 2018 15:33:00 +0900 Subject: [PATCH 07/14] Make comment xmldoc --- osu.Game/Rulesets/Mods/IApplicableMod.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/IApplicableMod.cs b/osu.Game/Rulesets/Mods/IApplicableMod.cs index 0602b90b69..ed2b652598 100644 --- a/osu.Game/Rulesets/Mods/IApplicableMod.cs +++ b/osu.Game/Rulesets/Mods/IApplicableMod.cs @@ -3,7 +3,10 @@ namespace osu.Game.Rulesets.Mods { - // The base interface for a mod which can be applied in some way. + /// + /// The base interface for a mod which can be applied in some way. + /// If this is not implemented by a mod, it will not be available for use in-game. + /// public interface IApplicableMod { } From 71bcebe7e11f2f3d1b4f3ae14118b55cee8321a7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Jan 2018 16:08:48 +0900 Subject: [PATCH 08/14] Fix selections occurring even though buttons aren't selected --- osu.Game/Overlays/Mods/ModButton.cs | 158 +++++++++++++--------------- 1 file changed, 74 insertions(+), 84 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs index 2afd34438c..90ed89a1ea 100644 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ b/osu.Game/Overlays/Mods/ModButton.cs @@ -32,7 +32,10 @@ namespace osu.Game.Overlays.Mods private readonly Container iconsContainer; private SampleChannel sampleOn, sampleOff; - public Action Action; // Passed the selected mod or null if none + /// + /// Fired when the selection changes. + /// + public Action SelectionChanged; public string TooltipText => (SelectedMod?.Description ?? Mods.FirstOrDefault()?.Description) ?? string.Empty; @@ -42,78 +45,78 @@ namespace osu.Game.Overlays.Mods // A selected index of -1 means not selected. private int selectedIndex = -1; - protected int SelectedIndex + /// + /// Change the selected mod index of this button. + /// + /// The new index + /// Whether the selection changed. + private bool changeSelectedIndex(int value) { - get + if (value == selectedIndex) return false; + + int direction = value < selectedIndex ? -1 : 1; + bool beforeSelected = Selected; + + Mod modBefore = SelectedMod ?? Mods[0]; + + if (value >= Mods.Length) + selectedIndex = -1; + else if (value < -1) + selectedIndex = Mods.Length - 1; + else + selectedIndex = value; + + Mod modAfter = SelectedMod ?? Mods[0]; + + if (!modAfter.HasImplementation) { - return selectedIndex; + if (modAfter != modBefore) + return changeSelectedIndex(selectedIndex + direction); + return false; } - set + + if (beforeSelected != Selected) { - if (value == selectedIndex) return; - - int direction = value < selectedIndex ? -1 : 1; - bool beforeSelected = Selected; - - Mod modBefore = SelectedMod ?? Mods[0]; - - if (value >= Mods.Length) - selectedIndex = -1; - else if (value < -1) - selectedIndex = Mods.Length - 1; - else - selectedIndex = value; - - Mod modAfter = SelectedMod ?? Mods[0]; - - if (!modAfter.HasImplementation) - { - if (modAfter != modBefore) - SelectedIndex += direction; - return; - } - - if (beforeSelected != Selected) - { - iconsContainer.RotateTo(Selected ? 5f : 0f, 300, Easing.OutElastic); - iconsContainer.ScaleTo(Selected ? 1.1f : 1f, 300, Easing.OutElastic); - } - - if (modBefore != modAfter) - { - const float rotate_angle = 16; - - foregroundIcon.RotateTo(rotate_angle * direction, mod_switch_duration, mod_switch_easing); - backgroundIcon.RotateTo(-rotate_angle * direction, mod_switch_duration, mod_switch_easing); - - backgroundIcon.Icon = modAfter.Icon; - using (BeginDelayedSequence(mod_switch_duration, true)) - { - foregroundIcon - .RotateTo(-rotate_angle * direction) - .RotateTo(0f, mod_switch_duration, mod_switch_easing); - - backgroundIcon - .RotateTo(rotate_angle * direction) - .RotateTo(0f, mod_switch_duration, mod_switch_easing); - - Schedule(() => displayMod(modAfter)); - } - } - - foregroundIcon.Highlighted = Selected; + iconsContainer.RotateTo(Selected ? 5f : 0f, 300, Easing.OutElastic); + iconsContainer.ScaleTo(Selected ? 1.1f : 1f, 300, Easing.OutElastic); } + + if (modBefore != modAfter) + { + const float rotate_angle = 16; + + foregroundIcon.RotateTo(rotate_angle * direction, mod_switch_duration, mod_switch_easing); + backgroundIcon.RotateTo(-rotate_angle * direction, mod_switch_duration, mod_switch_easing); + + backgroundIcon.Icon = modAfter.Icon; + using (BeginDelayedSequence(mod_switch_duration, true)) + { + foregroundIcon + .RotateTo(-rotate_angle * direction) + .RotateTo(0f, mod_switch_duration, mod_switch_easing); + + backgroundIcon + .RotateTo(rotate_angle * direction) + .RotateTo(0f, mod_switch_duration, mod_switch_easing); + + Schedule(() => displayMod(modAfter)); + } + } + + foregroundIcon.Highlighted = Selected; + + (selectedIndex == -1 ? sampleOff : sampleOn).Play(); + SelectionChanged?.Invoke(SelectedMod); + return true; } - public bool Selected => SelectedIndex != -1; + public bool Selected => selectedIndex != -1; private Color4 selectedColour; + public Color4 SelectedColour { - get - { - return selectedColour; - } + get { return selectedColour; } set { if (value == selectedColour) return; @@ -123,12 +126,10 @@ namespace osu.Game.Overlays.Mods } private Mod mod; + public Mod Mod { - get - { - return mod; - } + get { return mod; } set { mod = value; @@ -154,9 +155,7 @@ namespace osu.Game.Overlays.Mods public Mod[] Mods { get; private set; } - // the mods from Mod, only multiple if Mod is a MultiMod - - public virtual Mod SelectedMod => Mods.ElementAtOrDefault(SelectedIndex); + public virtual Mod SelectedMod => Mods.ElementAtOrDefault(selectedIndex); [BackgroundDependencyLoader] private void load(AudioManager audio) @@ -176,25 +175,15 @@ namespace osu.Game.Overlays.Mods SelectPrevious(); break; } + return true; } - public void SelectNext() - { - (++SelectedIndex == Mods.Length ? sampleOff : sampleOn).Play(); - Action?.Invoke(SelectedMod); - } + public void SelectNext() => changeSelectedIndex(selectedIndex + 1); - public void SelectPrevious() - { - (--SelectedIndex == -1 ? sampleOff : sampleOn).Play(); - Action?.Invoke(SelectedMod); - } + public void SelectPrevious() => changeSelectedIndex(selectedIndex - 1); - public void Deselect() - { - SelectedIndex = -1; - } + public void Deselect() => changeSelectedIndex(-1); private void displayMod(Mod mod) { @@ -272,7 +261,8 @@ namespace osu.Game.Overlays.Mods { public override string TooltipText => null; - public PassThroughTooltipModIcon(Mod mod) : base(mod) + public PassThroughTooltipModIcon(Mod mod) + : base(mod) { } } From f72239ef7aa55ae6759a167722837c55082e5ee0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Jan 2018 16:09:22 +0900 Subject: [PATCH 09/14] Centralise deselect logic and add animation to deselection --- osu.Game/Overlays/Mods/ModSection.cs | 23 ++++++++++++++-------- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 15 +++++++------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index aac8a72dd7..ba183abc3f 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -51,7 +51,7 @@ namespace osu.Game.Overlays.Mods return new ModButton(m) { SelectedColour = selectedColour, - Action = Action, + SelectionChanged = Action, }; }).ToArray(); @@ -88,21 +88,28 @@ namespace osu.Game.Overlays.Mods return base.OnKeyDown(state, args); } - public void DeselectAll() - { - foreach (ModButton button in buttons) - button.Deselect(); - } + public void DeselectAll() => DeselectTypes(buttons.Select(b => b.SelectedMod?.GetType()).Where(t => t != null)); - public void DeselectTypes(Type[] modTypes) + /// + /// Deselect one or more mods in this section. + /// + /// The types of s which should be deselected. + /// Set to true to bypass animations and update selections immediately. + public void DeselectTypes(IEnumerable modTypes, bool immediate = false) { + int delay = 0; foreach (var button in buttons) { Mod selected = button.SelectedMod; if (selected == null) continue; foreach (Type type in modTypes) if (type.IsInstanceOfType(selected)) - button.Deselect(); + { + if (immediate) + button.Deselect(); + else + Scheduler.AddDelayed(() => button.Deselect(), delay += 50); + } } } diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 9639907914..cc5a17358d 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -100,17 +100,22 @@ namespace osu.Game.Overlays.Mods refreshSelectedMods(); } - public void DeselectTypes(Type[] modTypes) + /// + /// Deselect one or more mods. + /// + /// The types of s which should be deselected. + /// Set to true to bypass animations and update selections immediately. + public void DeselectTypes(Type[] modTypes, bool immediate = false) { if (modTypes.Length == 0) return; foreach (ModSection section in ModSectionsContainer.Children) - section.DeselectTypes(modTypes); + section.DeselectTypes(modTypes, immediate); } private void modButtonPressed(Mod selectedMod) { if (selectedMod != null) - DeselectTypes(selectedMod.IncompatibleMods); + DeselectTypes(selectedMod.IncompatibleMods, true); refreshSelectedMods(); } @@ -127,10 +132,6 @@ namespace osu.Game.Overlays.Mods ranked &= mod.Ranked; } - // 1.00x - // 1.05x - // 1.20x - MultiplierLabel.Text = $"{multiplier:N2}x"; if (!ranked) MultiplierLabel.Text += " (Unranked)"; From 0e1b03300844e334650c157065d545c3e3fecb70 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Jan 2018 16:55:03 +0900 Subject: [PATCH 10/14] Improve mod cycling logic further --- osu.Game/Overlays/Mods/ModButton.cs | 58 ++++++++++++++++++---------- osu.Game/Overlays/Mods/ModSection.cs | 2 +- 2 files changed, 38 insertions(+), 22 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs index 90ed89a1ea..c41000af15 100644 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ b/osu.Game/Overlays/Mods/ModButton.cs @@ -48,32 +48,27 @@ namespace osu.Game.Overlays.Mods /// /// Change the selected mod index of this button. /// - /// The new index + /// 1 for forwards, -1 for backwards. + public void SelectNext(int direction) + { + int start = selectedIndex + direction; + // wrap around if we are at an extremity. + if (start >= Mods.Length) + start = -1; + else if (start < -1) + start = Mods.Length - 1; - public void SelectPrevious() => changeSelectedIndex(selectedIndex - 1); + for (int i = start; i < Mods.Length && i >= 0; i += direction) + { + if (Mods[i].HasImplementation) + { + changeSelectedIndex(i); + return; + } + } + + Deselect(); + } public void Deselect() => changeSelectedIndex(-1); diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index ba183abc3f..50310d1b27 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -83,7 +83,7 @@ namespace osu.Game.Overlays.Mods { var index = Array.IndexOf(ToggleKeys, args.Key); if (index > -1 && index < buttons.Length) - buttons[index].SelectNext(); + buttons[index].SelectNext(state.Keyboard.ShiftPressed ? -1 : 1); return base.OnKeyDown(state, args); } From f10a19ada13c1a3b6f46bb2636a7c476b9314714 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Jan 2018 16:55:15 +0900 Subject: [PATCH 11/14] Update tests to only use implemented mods --- osu.Game.Tests/Visual/TestCaseMods.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseMods.cs b/osu.Game.Tests/Visual/TestCaseMods.cs index e535da3fcc..ee12c402f2 100644 --- a/osu.Game.Tests/Visual/TestCaseMods.cs +++ b/osu.Game.Tests/Visual/TestCaseMods.cs @@ -80,16 +80,21 @@ namespace osu.Game.Tests.Visual var noFailMod = easierMods.FirstOrDefault(m => m is OsuModNoFail); var hiddenMod = harderMods.FirstOrDefault(m => m is OsuModHidden); + var doubleTimeMod = harderMods.OfType().FirstOrDefault(m => m.Mods.Any(a => a is OsuModDoubleTime)); - var autoPilotMod = assistMods.FirstOrDefault(m => m is OsuModAutopilot); + + var easy = easierMods.FirstOrDefault(m => m is OsuModEasy); + var hardRock = harderMods.FirstOrDefault(m => m is OsuModHardRock); testSingleMod(noFailMod); testMultiMod(doubleTimeMod); - testIncompatibleMods(noFailMod, autoPilotMod); + testIncompatibleMods(easy, hardRock); testDeselectAll(easierMods.Where(m => !(m is MultiMod))); testMultiplierTextColour(noFailMod, modSelect.LowMultiplierColour); testMultiplierTextColour(hiddenMod, modSelect.HighMultiplierColour); - testMultiplierTextUnranked(autoPilotMod); + + // TODO: add back once we have an implemented unranked mod. + // testMultiplierTextUnranked(autoPilotMod); } private void testSingleMod(Mod mod) @@ -169,9 +174,9 @@ namespace osu.Game.Tests.Visual AddAssert("check for ranked", () => !modSelect.MultiplierLabel.Text.EndsWith(unranked_suffix)); } - private void selectNext(Mod mod) => AddStep($"left click {mod.Name}", () => modSelect.GetModButton(mod)?.SelectNext()); + private void selectNext(Mod mod) => AddStep($"left click {mod.Name}", () => modSelect.GetModButton(mod)?.SelectNext(1)); - private void selectPrevious(Mod mod) => AddStep($"right click {mod.Name}", () => modSelect.GetModButton(mod)?.SelectPrevious()); + private void selectPrevious(Mod mod) => AddStep($"right click {mod.Name}", () => modSelect.GetModButton(mod)?.SelectNext(-1)); private void checkSelected(Mod mod) { From 3a7098340a27a0bce61cb52ed9a6f8237725739a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Jan 2018 16:58:11 +0900 Subject: [PATCH 12/14] Add test for unimplemented mod --- osu.Game.Tests/Visual/TestCaseMods.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game.Tests/Visual/TestCaseMods.cs b/osu.Game.Tests/Visual/TestCaseMods.cs index ee12c402f2..5535dd38c6 100644 --- a/osu.Game.Tests/Visual/TestCaseMods.cs +++ b/osu.Game.Tests/Visual/TestCaseMods.cs @@ -83,6 +83,8 @@ namespace osu.Game.Tests.Visual var doubleTimeMod = harderMods.OfType().FirstOrDefault(m => m.Mods.Any(a => a is OsuModDoubleTime)); + var autoPilotMod = assistMods.FirstOrDefault(m => m is OsuModAutopilot); + var easy = easierMods.FirstOrDefault(m => m is OsuModEasy); var hardRock = harderMods.FirstOrDefault(m => m is OsuModHardRock); @@ -93,6 +95,8 @@ namespace osu.Game.Tests.Visual testMultiplierTextColour(noFailMod, modSelect.LowMultiplierColour); testMultiplierTextColour(hiddenMod, modSelect.HighMultiplierColour); + testUnimplmentedMod(autoPilotMod); + // TODO: add back once we have an implemented unranked mod. // testMultiplierTextUnranked(autoPilotMod); } @@ -129,6 +133,12 @@ namespace osu.Game.Tests.Visual checkNotSelected(mod); } + private void testUnimplmentedMod(Mod mod) + { + selectNext(mod); + checkNotSelected(mod); + } + private void testIncompatibleMods(Mod modA, Mod modB) { selectNext(modA); From fa6db30d30b8f799ddcc63b56e7d62666e0132cb Mon Sep 17 00:00:00 2001 From: Dan Balasescu <1329837+smoogipoo@users.noreply.github.com> Date: Wed, 3 Jan 2018 13:12:49 +0900 Subject: [PATCH 13/14] Fix broken xmldoc param --- osu.Game/Overlays/Mods/ModButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs index c41000af15..21f07bb0a4 100644 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ b/osu.Game/Overlays/Mods/ModButton.cs @@ -48,7 +48,7 @@ namespace osu.Game.Overlays.Mods /// /// Change the selected mod index of this button. /// - /// The new index. /// Whether the selection changed. private bool changeSelectedIndex(int newIndex) { From 31b875b7cd61ef8845005f43ad939674fd4c9d08 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Jan 2018 13:42:09 +0900 Subject: [PATCH 14/14] Add test for unranked mod using mania random --- osu.Game.Tests/Visual/TestCaseMods.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseMods.cs b/osu.Game.Tests/Visual/TestCaseMods.cs index 5535dd38c6..c78d3b1f9f 100644 --- a/osu.Game.Tests/Visual/TestCaseMods.cs +++ b/osu.Game.Tests/Visual/TestCaseMods.cs @@ -15,6 +15,8 @@ using System.Collections.Generic; using osu.Game.Rulesets.Osu; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets.Mania.Mods; using OpenTK.Graphics; namespace osu.Game.Tests.Visual @@ -68,6 +70,9 @@ namespace osu.Game.Tests.Visual case OsuRuleset or: testOsuMods(or); break; + case ManiaRuleset mr: + testManiaMods(mr); + break; } } } @@ -96,9 +101,11 @@ namespace osu.Game.Tests.Visual testMultiplierTextColour(hiddenMod, modSelect.HighMultiplierColour); testUnimplmentedMod(autoPilotMod); + } - // TODO: add back once we have an implemented unranked mod. - // testMultiplierTextUnranked(autoPilotMod); + private void testManiaMods(ManiaRuleset ruleset) + { + testMultiplierTextUnranked(ruleset.GetModsFor(ModType.Special).First(m => m is ManiaModRandom)); } private void testSingleMod(Mod mod)