From 20edaf4ba6a04adf9836ef4c3f2b2b61991e6a21 Mon Sep 17 00:00:00 2001 From: Albie Spriddell Date: Sat, 23 Nov 2019 17:32:16 +0000 Subject: [PATCH 001/103] add cinema mod support --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 6 ++++-- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 6 ++++-- osu.Game.Rulesets.Osu/OsuRuleset.cs | 6 ++++-- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 6 ++++-- osu.Game/Rulesets/Mods/ModCinema.cs | 24 ++++++++++++++++++++++-- 5 files changed, 38 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 71d68ace94..bf2b1c0def 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -51,7 +51,9 @@ namespace osu.Game.Rulesets.Catch else if (mods.HasFlag(LegacyMods.SuddenDeath)) yield return new CatchModSuddenDeath(); - if (mods.HasFlag(LegacyMods.Autoplay)) + if (mods.HasFlag(LegacyMods.Cinema)) + yield return new CatchModCinema(); + else if (mods.HasFlag(LegacyMods.Autoplay)) yield return new CatchModAutoplay(); if (mods.HasFlag(LegacyMods.Easy)) @@ -101,7 +103,7 @@ namespace osu.Game.Rulesets.Catch case ModType.Automation: return new Mod[] { - new MultiMod(new CatchModAutoplay(), new ModCinema()), + new MultiMod(new CatchModAutoplay(), new CatchModCinema()), new CatchModRelax(), }; diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index c74a292331..c632cc0b7b 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -51,7 +51,9 @@ namespace osu.Game.Rulesets.Mania else if (mods.HasFlag(LegacyMods.SuddenDeath)) yield return new ManiaModSuddenDeath(); - if (mods.HasFlag(LegacyMods.Autoplay)) + if (mods.HasFlag(LegacyMods.Cinema)) + yield return new ManiaModCinema(); + else if (mods.HasFlag(LegacyMods.Autoplay)) yield return new ManiaModAutoplay(); if (mods.HasFlag(LegacyMods.Easy)) @@ -148,7 +150,7 @@ namespace osu.Game.Rulesets.Mania case ModType.Automation: return new Mod[] { - new MultiMod(new ManiaModAutoplay(), new ModCinema()), + new MultiMod(new ManiaModAutoplay(), new ManiaModCinema()), }; case ModType.Fun: diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index fa69cec78d..2b5a0df3ed 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -60,7 +60,9 @@ namespace osu.Game.Rulesets.Osu if (mods.HasFlag(LegacyMods.Autopilot)) yield return new OsuModAutopilot(); - if (mods.HasFlag(LegacyMods.Autoplay)) + if (mods.HasFlag(LegacyMods.Cinema)) + yield return new OsuModCinema(); + else if (mods.HasFlag(LegacyMods.Autoplay)) yield return new OsuModAutoplay(); if (mods.HasFlag(LegacyMods.Easy)) @@ -126,7 +128,7 @@ namespace osu.Game.Rulesets.Osu case ModType.Automation: return new Mod[] { - new MultiMod(new OsuModAutoplay(), new ModCinema()), + new MultiMod(new OsuModAutoplay(), new OsuModCinema()), new OsuModRelax(), new OsuModAutopilot(), }; diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index b2655f592c..3f3a198f4a 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -50,7 +50,9 @@ namespace osu.Game.Rulesets.Taiko else if (mods.HasFlag(LegacyMods.SuddenDeath)) yield return new TaikoModSuddenDeath(); - if (mods.HasFlag(LegacyMods.Autoplay)) + if (mods.HasFlag(LegacyMods.Cinema)) + yield return new TaikoModCinema(); + else if (mods.HasFlag(LegacyMods.Autoplay)) yield return new TaikoModAutoplay(); if (mods.HasFlag(LegacyMods.Easy)) @@ -100,7 +102,7 @@ namespace osu.Game.Rulesets.Taiko case ModType.Automation: return new Mod[] { - new MultiMod(new TaikoModAutoplay(), new ModCinema()), + new MultiMod(new TaikoModAutoplay(), new TaikoModCinema()), new TaikoModRelax(), }; diff --git a/osu.Game/Rulesets/Mods/ModCinema.cs b/osu.Game/Rulesets/Mods/ModCinema.cs index 3c6a3a54aa..4396c3384b 100644 --- a/osu.Game/Rulesets/Mods/ModCinema.cs +++ b/osu.Game/Rulesets/Mods/ModCinema.cs @@ -3,15 +3,35 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.UI; +using osu.Game.Screens.Play; namespace osu.Game.Rulesets.Mods { - public class ModCinema : ModAutoplay + public abstract class ModCinema : ModCinema, IApplicableToDrawableRuleset + where T : HitObject + { + public virtual void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + { + drawableRuleset.SetReplayScore(CreateReplayScore(drawableRuleset.Beatmap)); + + drawableRuleset.Playfield.AlwaysPresent = true; + drawableRuleset.Playfield.Hide(); + } + } + + public class ModCinema : ModAutoplay, IApplicableToHUD { public override string Name => "Cinema"; public override string Acronym => "CN"; - public override bool HasImplementation => false; public override IconUsage Icon => OsuIcon.ModCinema; public override string Description => "Watch the video without visual distractions."; + + public void ApplyToHUD(HUDOverlay overlay) + { + overlay.AlwaysPresent = true; + overlay.Hide(); + } } } From 3b9f59cb335bb84964737fca27138668127cd371 Mon Sep 17 00:00:00 2001 From: Albie Spriddell Date: Sat, 23 Nov 2019 17:34:53 +0000 Subject: [PATCH 002/103] add cinema mod support --- .../Mods/CatchModCinema.cs | 21 ++++++++++++++++ .../Mods/ManiaModCinema.cs | 22 ++++++++++++++++ osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs | 25 +++++++++++++++++++ .../Mods/TaikoModCinema.cs | 21 ++++++++++++++++ 4 files changed, 89 insertions(+) create mode 100644 osu.Game.Rulesets.Catch/Mods/CatchModCinema.cs create mode 100644 osu.Game.Rulesets.Mania/Mods/ManiaModCinema.cs create mode 100644 osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs create mode 100644 osu.Game.Rulesets.Taiko/Mods/TaikoModCinema.cs diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModCinema.cs b/osu.Game.Rulesets.Catch/Mods/CatchModCinema.cs new file mode 100644 index 0000000000..3bc1ee5bf5 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Mods/CatchModCinema.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 osu.Game.Beatmaps; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.Replays; +using osu.Game.Rulesets.Mods; +using osu.Game.Scoring; +using osu.Game.Users; + +namespace osu.Game.Rulesets.Catch.Mods +{ + public class CatchModCinema : ModCinema + { + public override Score CreateReplayScore(IBeatmap beatmap) => new Score + { + ScoreInfo = new ScoreInfo { User = new User { Username = "osu!salad!" } }, + Replay = new CatchAutoGenerator(beatmap).Generate(), + }; + } +} diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModCinema.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModCinema.cs new file mode 100644 index 0000000000..02c1fc1b79 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModCinema.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.Game.Beatmaps; +using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.Replays; +using osu.Game.Rulesets.Mods; +using osu.Game.Scoring; +using osu.Game.Users; + +namespace osu.Game.Rulesets.Mania.Mods +{ + public class ManiaModCinema : ModCinema + { + public override Score CreateReplayScore(IBeatmap beatmap) => new Score + { + ScoreInfo = new ScoreInfo { User = new User { Username = "osu!topus!" } }, + Replay = new ManiaAutoGenerator((ManiaBeatmap)beatmap).Generate(), + }; + } +} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs b/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs new file mode 100644 index 0000000000..5d9a524577 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs @@ -0,0 +1,25 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Replays; +using osu.Game.Scoring; +using osu.Game.Users; + +namespace osu.Game.Rulesets.Osu.Mods +{ + public class OsuModCinema : ModCinema + { + public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).Append(typeof(OsuModSpunOut)).ToArray(); + + public override Score CreateReplayScore(IBeatmap beatmap) => new Score + { + ScoreInfo = new ScoreInfo { User = new User { Username = "Autoplay" } }, + Replay = new OsuAutoGenerator(beatmap).Generate() + }; + } +} diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModCinema.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModCinema.cs new file mode 100644 index 0000000000..71aa007d3b --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModCinema.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 osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.Replays; +using osu.Game.Scoring; +using osu.Game.Users; + +namespace osu.Game.Rulesets.Taiko.Mods +{ + public class TaikoModCinema : ModCinema + { + public override Score CreateReplayScore(IBeatmap beatmap) => new Score + { + ScoreInfo = new ScoreInfo { User = new User { Username = "mekkadosu!" } }, + Replay = new TaikoAutoGenerator(beatmap).Generate(), + }; + } +} From b8e5796af51eb47f8f897906b7da72b3b1cbc9d5 Mon Sep 17 00:00:00 2001 From: Albie Date: Sun, 24 Nov 2019 07:37:06 +0000 Subject: [PATCH 003/103] add forced video/storyboard and disabled dim for mod inside new interface --- .../Graphics/Containers/UserDimContainer.cs | 6 +++++ osu.Game/Rulesets/Mods/IApplicableToScreen.cs | 26 +++++++++++++++++++ osu.Game/Rulesets/Mods/ModCinema.cs | 9 ++++++- osu.Game/Screens/Play/DimmableStoryboard.cs | 4 +-- osu.Game/Screens/Play/DimmableVideo.cs | 5 ++-- osu.Game/Screens/Play/Player.cs | 20 ++++++++++++-- 6 files changed, 63 insertions(+), 7 deletions(-) create mode 100644 osu.Game/Rulesets/Mods/IApplicableToScreen.cs diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs index 7683bbcd63..42a25a79b1 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -21,6 +21,11 @@ namespace osu.Game.Graphics.Containers /// public readonly Bindable EnableUserDim = new Bindable(true); + /// + /// Whether or not user-configured settings relating to visibility of elements should be ignored + /// + public readonly Bindable IgnoreUserSettings = new Bindable(); + /// /// Whether or not the storyboard loaded should completely hide the background behind it. /// @@ -63,6 +68,7 @@ namespace osu.Game.Graphics.Containers ShowStoryboard.ValueChanged += _ => UpdateVisuals(); ShowVideo.ValueChanged += _ => UpdateVisuals(); StoryboardReplacesBackground.ValueChanged += _ => UpdateVisuals(); + IgnoreUserSettings.ValueChanged += _ => UpdateVisuals(); } protected override void LoadComplete() diff --git a/osu.Game/Rulesets/Mods/IApplicableToScreen.cs b/osu.Game/Rulesets/Mods/IApplicableToScreen.cs new file mode 100644 index 0000000000..f1a631ccba --- /dev/null +++ b/osu.Game/Rulesets/Mods/IApplicableToScreen.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. + +namespace osu.Game.Rulesets.Mods +{ + /// + /// An interface for a mod which can temporarily override screen settings. + /// + public interface IApplicableToScreen : IApplicableMod + { + /// + /// Whether to enable image, video and storyboard dimming + /// + bool EnableDim { get; } + + /// + /// Weather to force the video (if present) + /// + bool ForceVideo { get; } + + /// + /// Weather to force the storyboard (if present) + /// + bool ForceStoryboard { get; } + } +} diff --git a/osu.Game/Rulesets/Mods/ModCinema.cs b/osu.Game/Rulesets/Mods/ModCinema.cs index 4396c3384b..8a777b364a 100644 --- a/osu.Game/Rulesets/Mods/ModCinema.cs +++ b/osu.Game/Rulesets/Mods/ModCinema.cs @@ -1,7 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using osu.Framework.Graphics.Sprites; +using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI; @@ -21,7 +24,7 @@ namespace osu.Game.Rulesets.Mods } } - public class ModCinema : ModAutoplay, IApplicableToHUD + public class ModCinema : ModAutoplay, IApplicableToHUD, IApplicableToScreen { public override string Name => "Cinema"; public override string Acronym => "CN"; @@ -33,5 +36,9 @@ namespace osu.Game.Rulesets.Mods overlay.AlwaysPresent = true; overlay.Hide(); } + + public bool EnableDim => false; + public bool ForceVideo => true; + public bool ForceStoryboard => true; } } diff --git a/osu.Game/Screens/Play/DimmableStoryboard.cs b/osu.Game/Screens/Play/DimmableStoryboard.cs index 2154526e54..4c7bb272cc 100644 --- a/osu.Game/Screens/Play/DimmableStoryboard.cs +++ b/osu.Game/Screens/Play/DimmableStoryboard.cs @@ -33,14 +33,14 @@ namespace osu.Game.Screens.Play base.LoadComplete(); } - protected override bool ShowDimContent => ShowStoryboard.Value && DimLevel < 1; + protected override bool ShowDimContent => IgnoreUserSettings.Value || ShowStoryboard.Value && DimLevel < 1; private void initializeStoryboard(bool async) { if (drawableStoryboard != null) return; - if (!ShowStoryboard.Value) + if (!ShowStoryboard.Value && !IgnoreUserSettings.Value) return; drawableStoryboard = storyboard.CreateDrawable(); diff --git a/osu.Game/Screens/Play/DimmableVideo.cs b/osu.Game/Screens/Play/DimmableVideo.cs index 4d6c10d69d..09ec5a3f5d 100644 --- a/osu.Game/Screens/Play/DimmableVideo.cs +++ b/osu.Game/Screens/Play/DimmableVideo.cs @@ -2,6 +2,7 @@ // 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; @@ -33,7 +34,7 @@ namespace osu.Game.Screens.Play base.LoadComplete(); } - protected override bool ShowDimContent => ShowVideo.Value && DimLevel < 1; + protected override bool ShowDimContent => IgnoreUserSettings.Value || ShowVideo.Value && DimLevel < 1; private void initializeVideo(bool async) { @@ -43,7 +44,7 @@ namespace osu.Game.Screens.Play if (drawableVideo != null) return; - if (!ShowVideo.Value) + if (!ShowVideo.Value && !IgnoreUserSettings.Value) return; drawableVideo = new DrawableVideo(video); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index d6488dc209..3d6a9fe78f 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -498,8 +498,24 @@ namespace osu.Game.Screens.Play .Delay(250) .FadeIn(250); - Background.EnableUserDim.Value = true; - Background.BlurAmount.Value = 0; + var screenOverride = Mods.Value.OfType(); + + if (screenOverride.Count() == 1) + { + var setting = screenOverride.Single(); + + Background.EnableUserDim.Value = setting.EnableDim; + DimmableVideo.EnableUserDim.Value = setting.EnableDim; + DimmableStoryboard.EnableUserDim.Value = setting.EnableDim; + + DimmableVideo.IgnoreUserSettings.Value = setting.ForceVideo; + DimmableStoryboard.IgnoreUserSettings.Value = setting.ForceStoryboard; + } + else + { + Background.EnableUserDim.Value = true; + Background.BlurAmount.Value = 0; + } Background.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground); DimmableStoryboard.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground); From 1d6665fe57510c4dc950881b15728c5e64e7fa74 Mon Sep 17 00:00:00 2001 From: Albie Date: Sun, 24 Nov 2019 07:42:39 +0000 Subject: [PATCH 004/103] improve code quality using resharper and codefactor advice --- osu.Game/Rulesets/Mods/ModCinema.cs | 3 --- osu.Game/Screens/Play/DimmableStoryboard.cs | 2 +- osu.Game/Screens/Play/DimmableVideo.cs | 3 +-- osu.Game/Screens/Play/Player.cs | 2 +- 4 files changed, 3 insertions(+), 7 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModCinema.cs b/osu.Game/Rulesets/Mods/ModCinema.cs index 8a777b364a..5a876dbf51 100644 --- a/osu.Game/Rulesets/Mods/ModCinema.cs +++ b/osu.Game/Rulesets/Mods/ModCinema.cs @@ -1,10 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Linq; using osu.Framework.Graphics.Sprites; -using osu.Game.Beatmaps; -using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI; diff --git a/osu.Game/Screens/Play/DimmableStoryboard.cs b/osu.Game/Screens/Play/DimmableStoryboard.cs index 4c7bb272cc..0fe315fbab 100644 --- a/osu.Game/Screens/Play/DimmableStoryboard.cs +++ b/osu.Game/Screens/Play/DimmableStoryboard.cs @@ -33,7 +33,7 @@ namespace osu.Game.Screens.Play base.LoadComplete(); } - protected override bool ShowDimContent => IgnoreUserSettings.Value || ShowStoryboard.Value && DimLevel < 1; + protected override bool ShowDimContent => IgnoreUserSettings.Value || (ShowStoryboard.Value && DimLevel < 1); private void initializeStoryboard(bool async) { diff --git a/osu.Game/Screens/Play/DimmableVideo.cs b/osu.Game/Screens/Play/DimmableVideo.cs index 09ec5a3f5d..1a01cace17 100644 --- a/osu.Game/Screens/Play/DimmableVideo.cs +++ b/osu.Game/Screens/Play/DimmableVideo.cs @@ -2,7 +2,6 @@ // 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; @@ -34,7 +33,7 @@ namespace osu.Game.Screens.Play base.LoadComplete(); } - protected override bool ShowDimContent => IgnoreUserSettings.Value || ShowVideo.Value && DimLevel < 1; + protected override bool ShowDimContent => IgnoreUserSettings.Value || (ShowVideo.Value && DimLevel < 1); private void initializeVideo(bool async) { diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 3d6a9fe78f..dc4612a525 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -432,7 +432,7 @@ namespace osu.Game.Screens.Play // cannot pause if we are already in a fail state && !HasFailed // cannot pause if already paused (or in a cooldown state) unless we are in a resuming state. - && (IsResuming || (GameplayClockContainer.IsPaused.Value == false && !pauseCooldownActive)); + && (IsResuming || GameplayClockContainer.IsPaused.Value == false && !pauseCooldownActive); private bool pauseCooldownActive => lastPauseActionTime.HasValue && GameplayClockContainer.GameplayClock.CurrentTime < lastPauseActionTime + pause_cooldown; From 9a8e3fe1da79ea53328b2f293b7996eaa43bb138 Mon Sep 17 00:00:00 2001 From: Albie Date: Sun, 24 Nov 2019 07:44:35 +0000 Subject: [PATCH 005/103] add brackets --- osu.Game/Screens/Play/Player.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index dc4612a525..3d6a9fe78f 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -432,7 +432,7 @@ namespace osu.Game.Screens.Play // cannot pause if we are already in a fail state && !HasFailed // cannot pause if already paused (or in a cooldown state) unless we are in a resuming state. - && (IsResuming || GameplayClockContainer.IsPaused.Value == false && !pauseCooldownActive); + && (IsResuming || (GameplayClockContainer.IsPaused.Value == false && !pauseCooldownActive)); private bool pauseCooldownActive => lastPauseActionTime.HasValue && GameplayClockContainer.GameplayClock.CurrentTime < lastPauseActionTime + pause_cooldown; From 9fdbb2a58e8aea3e7232d9853170d96502c3e2bb Mon Sep 17 00:00:00 2001 From: Albie Date: Mon, 25 Nov 2019 07:24:29 +0000 Subject: [PATCH 006/103] change name of interface and expose method instead of seperate values --- osu.Game/Rulesets/Mods/IApplicableToPlayer.cs | 15 +++++++++++ osu.Game/Rulesets/Mods/IApplicableToScreen.cs | 26 ------------------ osu.Game/Rulesets/Mods/ModCinema.cs | 14 +++++++--- osu.Game/Screens/Play/Player.cs | 27 +++++-------------- .../Play/ScreenWithBeatmapBackground.cs | 2 +- 5 files changed, 33 insertions(+), 51 deletions(-) create mode 100644 osu.Game/Rulesets/Mods/IApplicableToPlayer.cs delete mode 100644 osu.Game/Rulesets/Mods/IApplicableToScreen.cs diff --git a/osu.Game/Rulesets/Mods/IApplicableToPlayer.cs b/osu.Game/Rulesets/Mods/IApplicableToPlayer.cs new file mode 100644 index 0000000000..bf78428470 --- /dev/null +++ b/osu.Game/Rulesets/Mods/IApplicableToPlayer.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Screens.Play; + +namespace osu.Game.Rulesets.Mods +{ + /// + /// An interface for a mod which can temporarily override the settings. + /// + public interface IApplicableToPlayer : IApplicableMod + { + void ApplyToPlayer(Player player); + } +} diff --git a/osu.Game/Rulesets/Mods/IApplicableToScreen.cs b/osu.Game/Rulesets/Mods/IApplicableToScreen.cs deleted file mode 100644 index f1a631ccba..0000000000 --- a/osu.Game/Rulesets/Mods/IApplicableToScreen.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -namespace osu.Game.Rulesets.Mods -{ - /// - /// An interface for a mod which can temporarily override screen settings. - /// - public interface IApplicableToScreen : IApplicableMod - { - /// - /// Whether to enable image, video and storyboard dimming - /// - bool EnableDim { get; } - - /// - /// Weather to force the video (if present) - /// - bool ForceVideo { get; } - - /// - /// Weather to force the storyboard (if present) - /// - bool ForceStoryboard { get; } - } -} diff --git a/osu.Game/Rulesets/Mods/ModCinema.cs b/osu.Game/Rulesets/Mods/ModCinema.cs index 5a876dbf51..77bf80b149 100644 --- a/osu.Game/Rulesets/Mods/ModCinema.cs +++ b/osu.Game/Rulesets/Mods/ModCinema.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mods } } - public class ModCinema : ModAutoplay, IApplicableToHUD, IApplicableToScreen + public class ModCinema : ModAutoplay, IApplicableToHUD, IApplicableToPlayer { public override string Name => "Cinema"; public override string Acronym => "CN"; @@ -34,8 +34,14 @@ namespace osu.Game.Rulesets.Mods overlay.Hide(); } - public bool EnableDim => false; - public bool ForceVideo => true; - public bool ForceStoryboard => true; + public void ApplyToPlayer(Player player) + { + player.Background.EnableUserDim.Value = false; + player.DimmableVideo.EnableUserDim.Value = false; + player.DimmableStoryboard.EnableUserDim.Value = false; + + player.DimmableVideo.IgnoreUserSettings.Value = true; + player.DimmableStoryboard.IgnoreUserSettings.Value = true; + } } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 3d6a9fe78f..90171da747 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -80,8 +80,8 @@ namespace osu.Game.Screens.Play protected GameplayClockContainer GameplayClockContainer { get; private set; } - protected DimmableStoryboard DimmableStoryboard { get; private set; } - protected DimmableVideo DimmableVideo { get; private set; } + public DimmableStoryboard DimmableStoryboard { get; private set; } + public DimmableVideo DimmableVideo { get; private set; } [Cached] [Cached(Type = typeof(IBindable>))] @@ -498,24 +498,8 @@ namespace osu.Game.Screens.Play .Delay(250) .FadeIn(250); - var screenOverride = Mods.Value.OfType(); - - if (screenOverride.Count() == 1) - { - var setting = screenOverride.Single(); - - Background.EnableUserDim.Value = setting.EnableDim; - DimmableVideo.EnableUserDim.Value = setting.EnableDim; - DimmableStoryboard.EnableUserDim.Value = setting.EnableDim; - - DimmableVideo.IgnoreUserSettings.Value = setting.ForceVideo; - DimmableStoryboard.IgnoreUserSettings.Value = setting.ForceStoryboard; - } - else - { - Background.EnableUserDim.Value = true; - Background.BlurAmount.Value = 0; - } + Background.EnableUserDim.Value = true; + Background.BlurAmount.Value = 0; Background.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground); DimmableStoryboard.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground); @@ -525,6 +509,9 @@ namespace osu.Game.Screens.Play GameplayClockContainer.Restart(); GameplayClockContainer.FadeInFromZero(750, Easing.OutQuint); + foreach (var mod in Mods.Value.OfType()) + mod.ApplyToPlayer(this); + foreach (var mod in Mods.Value.OfType()) mod.ApplyToHUD(HUDOverlay); } diff --git a/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs b/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs index d7d2c97598..8eb253608b 100644 --- a/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs +++ b/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs @@ -9,6 +9,6 @@ namespace osu.Game.Screens.Play { protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(Beatmap.Value); - protected new BackgroundScreenBeatmap Background => (BackgroundScreenBeatmap)base.Background; + public new BackgroundScreenBeatmap Background => (BackgroundScreenBeatmap)base.Background; } } From 0c7e5a2e3b0cf886eae6f2828c33b793c2501cc0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Dec 2019 19:15:49 +0900 Subject: [PATCH 007/103] Add bindable adjustments for DT/HT rate --- osu.Game/Rulesets/Mods/ModDoubleTime.cs | 14 +++++++++++++- osu.Game/Rulesets/Mods/ModHalfTime.cs | 14 +++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModDoubleTime.cs b/osu.Game/Rulesets/Mods/ModDoubleTime.cs index a5e76e32b1..1a6831d974 100644 --- a/osu.Game/Rulesets/Mods/ModDoubleTime.cs +++ b/osu.Game/Rulesets/Mods/ModDoubleTime.cs @@ -3,7 +3,9 @@ using System; using System.Linq; +using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; +using osu.Game.Configuration; using osu.Game.Graphics; namespace osu.Game.Rulesets.Mods @@ -19,6 +21,16 @@ namespace osu.Game.Rulesets.Mods public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModHalfTime)).ToArray(); - protected override double RateAdjust => 1.5; + [SettingSource("Speed increase", "The actual increase to apply")] + public BindableNumber SpeedChange { get; } = new BindableDouble + { + MinValue = 1.01, + MaxValue = 2, + Default = 1.5, + Value = 1.5, + Precision = 0.01, + }; + + protected override double RateAdjust => SpeedChange.Value; } } diff --git a/osu.Game/Rulesets/Mods/ModHalfTime.cs b/osu.Game/Rulesets/Mods/ModHalfTime.cs index 27369f4c30..4acf771fa8 100644 --- a/osu.Game/Rulesets/Mods/ModHalfTime.cs +++ b/osu.Game/Rulesets/Mods/ModHalfTime.cs @@ -3,7 +3,9 @@ using System; using System.Linq; +using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; +using osu.Game.Configuration; using osu.Game.Graphics; namespace osu.Game.Rulesets.Mods @@ -19,6 +21,16 @@ namespace osu.Game.Rulesets.Mods public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModDoubleTime)).ToArray(); - protected override double RateAdjust => 0.75; + [SettingSource("Speed decrease", "The actual decrease to apply")] + public BindableNumber SpeedChange { get; } = new BindableDouble + { + MinValue = 0.5, + MaxValue = 0.99, + Default = 0.75, + Value = 0.75, + Precision = 0.01, + }; + + protected override double RateAdjust => SpeedChange.Value; } } From 6b667daf0965ec6386632bca78415cfe8bca1365 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 9 Dec 2019 19:41:18 +0900 Subject: [PATCH 008/103] Update bindable types in line with framework --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 14 +++++++++----- .../Tests/Visual/RateAdjustedBeatmapTestScene.cs | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 96275c1274..c15b5d4786 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -511,15 +511,17 @@ namespace osu.Game.Rulesets.UI public IEnumerable GetAvailableResources() => throw new NotImplementedException(); - public void AddAdjustment(AdjustableProperty type, BindableDouble adjustBindable) => throw new NotImplementedException(); + public void AddAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => throw new NotImplementedException(); - public void RemoveAdjustment(AdjustableProperty type, BindableDouble adjustBindable) => throw new NotImplementedException(); + public void RemoveAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => throw new NotImplementedException(); - public BindableDouble Volume => throw new NotImplementedException(); + public BindableNumber Volume => throw new NotImplementedException(); - public BindableDouble Balance => throw new NotImplementedException(); + public BindableNumber Balance => throw new NotImplementedException(); - public BindableDouble Frequency => throw new NotImplementedException(); + public BindableNumber Frequency => throw new NotImplementedException(); + + public BindableNumber Tempo => throw new NotImplementedException(); public IBindable AggregateVolume => throw new NotImplementedException(); @@ -527,6 +529,8 @@ namespace osu.Game.Rulesets.UI public IBindable AggregateFrequency => throw new NotImplementedException(); + public IBindable AggregateTempo => throw new NotImplementedException(); + public int PlaybackConcurrency { get => throw new NotImplementedException(); diff --git a/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs b/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs index 921a1d9789..ad24ffc7b8 100644 --- a/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs +++ b/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs @@ -13,7 +13,7 @@ namespace osu.Game.Tests.Visual base.Update(); // note that this will override any mod rate application - Beatmap.Value.Track.TempoAdjust = Clock.Rate; + Beatmap.Value.Track.Tempo.Value = Clock.Rate; } } } From 7fd52c21f56cdf6fdc2f9004e6b9f449e26b9bba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 9 Dec 2019 19:41:31 +0900 Subject: [PATCH 009/103] Update mods and user adjust to use adjustments --- osu.Game/Rulesets/Mods/ModDaycore.cs | 3 +- osu.Game/Rulesets/Mods/ModDoubleTime.cs | 6 ++-- osu.Game/Rulesets/Mods/ModHalfTime.cs | 6 ++-- osu.Game/Rulesets/Mods/ModNightcore.cs | 3 +- .../{ModTimeAdjust.cs => ModRateAdjust.cs} | 8 +++-- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 35 +++++++++---------- .../Screens/Play/GameplayClockContainer.cs | 9 ++--- 7 files changed, 32 insertions(+), 38 deletions(-) rename osu.Game/Rulesets/Mods/{ModTimeAdjust.cs => ModRateAdjust.cs} (60%) diff --git a/osu.Game/Rulesets/Mods/ModDaycore.cs b/osu.Game/Rulesets/Mods/ModDaycore.cs index dcb3cb5597..f7da1e86db 100644 --- a/osu.Game/Rulesets/Mods/ModDaycore.cs +++ b/osu.Game/Rulesets/Mods/ModDaycore.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Graphics.Sprites; @@ -15,7 +16,7 @@ namespace osu.Game.Rulesets.Mods public override void ApplyToTrack(Track track) { - track.Frequency.Value *= RateAdjust; + track.AddAdjustment(AdjustableProperty.Frequency, SpeedChange); } } } diff --git a/osu.Game/Rulesets/Mods/ModDoubleTime.cs b/osu.Game/Rulesets/Mods/ModDoubleTime.cs index b9faad8cbd..7015460c51 100644 --- a/osu.Game/Rulesets/Mods/ModDoubleTime.cs +++ b/osu.Game/Rulesets/Mods/ModDoubleTime.cs @@ -10,7 +10,7 @@ using osu.Game.Graphics; namespace osu.Game.Rulesets.Mods { - public abstract class ModDoubleTime : ModTimeAdjust + public abstract class ModDoubleTime : ModRateAdjust { public override string Name => "Double Time"; public override string Acronym => "DT"; @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Mods public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModHalfTime)).ToArray(); [SettingSource("Speed increase", "The actual increase to apply")] - public BindableNumber SpeedChange { get; } = new BindableDouble + public override BindableNumber SpeedChange { get; } = new BindableDouble { MinValue = 1.01, MaxValue = 2, @@ -30,7 +30,5 @@ namespace osu.Game.Rulesets.Mods Value = 1.5, Precision = 0.01, }; - - protected override double RateAdjust => SpeedChange.Value; } } diff --git a/osu.Game/Rulesets/Mods/ModHalfTime.cs b/osu.Game/Rulesets/Mods/ModHalfTime.cs index 255c5b7065..15f7afa312 100644 --- a/osu.Game/Rulesets/Mods/ModHalfTime.cs +++ b/osu.Game/Rulesets/Mods/ModHalfTime.cs @@ -10,7 +10,7 @@ using osu.Game.Graphics; namespace osu.Game.Rulesets.Mods { - public abstract class ModHalfTime : ModTimeAdjust + public abstract class ModHalfTime : ModRateAdjust { public override string Name => "Half Time"; public override string Acronym => "HT"; @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Mods public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModDoubleTime)).ToArray(); [SettingSource("Speed decrease", "The actual decrease to apply")] - public BindableNumber SpeedChange { get; } = new BindableDouble + public override BindableNumber SpeedChange { get; } = new BindableDouble { MinValue = 0.5, MaxValue = 0.99, @@ -30,7 +30,5 @@ namespace osu.Game.Rulesets.Mods Value = 0.75, Precision = 0.01, }; - - protected override double RateAdjust => SpeedChange.Value; } } diff --git a/osu.Game/Rulesets/Mods/ModNightcore.cs b/osu.Game/Rulesets/Mods/ModNightcore.cs index a4f1ef5a72..e41f1415ab 100644 --- a/osu.Game/Rulesets/Mods/ModNightcore.cs +++ b/osu.Game/Rulesets/Mods/ModNightcore.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; @@ -16,7 +17,7 @@ namespace osu.Game.Rulesets.Mods public override void ApplyToTrack(Track track) { - track.Frequency.Value *= RateAdjust; + track.AddAdjustment(AdjustableProperty.Frequency, SpeedChange); } } } diff --git a/osu.Game/Rulesets/Mods/ModTimeAdjust.cs b/osu.Game/Rulesets/Mods/ModRateAdjust.cs similarity index 60% rename from osu.Game/Rulesets/Mods/ModTimeAdjust.cs rename to osu.Game/Rulesets/Mods/ModRateAdjust.cs index f137a75ed8..5aa3e09fee 100644 --- a/osu.Game/Rulesets/Mods/ModTimeAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModRateAdjust.cs @@ -2,19 +2,21 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Audio; using osu.Framework.Audio.Track; +using osu.Framework.Bindables; namespace osu.Game.Rulesets.Mods { - public abstract class ModTimeAdjust : Mod, IApplicableToTrack + public abstract class ModRateAdjust : Mod, IApplicableToTrack { public override Type[] IncompatibleMods => new[] { typeof(ModTimeRamp) }; - protected abstract double RateAdjust { get; } + public abstract BindableNumber SpeedChange { get; } public virtual void ApplyToTrack(Track track) { - track.TempoAdjust *= RateAdjust; + track.AddAdjustment(AdjustableProperty.Tempo, SpeedChange); } } } diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index d95d354487..36de546707 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -3,36 +3,42 @@ using System; using System.Linq; +using osu.Framework.Audio; using osu.Framework.Audio.Track; +using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Mods { - public abstract class ModTimeRamp : Mod, IUpdatableByPlayfield, IApplicableToTrack, IApplicableToBeatmap + public abstract class ModTimeRamp : Mod, IUpdatableByPlayfield, IApplicableToBeatmap, IApplicableToTrack { /// /// The point in the beatmap at which the final ramping rate should be reached. /// private const double final_rate_progress = 0.75f; - public override Type[] IncompatibleMods => new[] { typeof(ModTimeAdjust) }; + public override Type[] IncompatibleMods => new[] { typeof(ModRateAdjust) }; protected abstract double FinalRateAdjustment { get; } private double finalRateTime; private double beginRampTime; + + public BindableNumber SpeedChange { get; } = new BindableDouble + { + Default = 1, + Value = 1, + Precision = 0.01, + }; + private Track track; - public virtual void ApplyToTrack(Track track) + public void ApplyToTrack(Track track) { this.track = track; - - lastAdjust = 1; - - // for preview purposes. during gameplay, Update will overwrite this setting. - applyAdjustment(1); + track.AddAdjustment(AdjustableProperty.Frequency, SpeedChange); } public virtual void ApplyToBeatmap(IBeatmap beatmap) @@ -48,20 +54,11 @@ namespace osu.Game.Rulesets.Mods applyAdjustment((track.CurrentTime - beginRampTime) / finalRateTime); } - private double lastAdjust = 1; - /// /// Adjust the rate along the specified ramp /// /// The amount of adjustment to apply (from 0..1). - private void applyAdjustment(double amount) - { - double adjust = 1 + (Math.Sign(FinalRateAdjustment) * Math.Clamp(amount, 0, 1) * Math.Abs(FinalRateAdjustment)); - - track.Tempo.Value /= lastAdjust; - track.Tempo.Value *= lastAdjust; - - lastAdjust = adjust; - } + private void applyAdjustment(double amount) => + SpeedChange.Value = 1 + (Math.Sign(FinalRateAdjustment) * Math.Clamp(amount, 0, 1) * Math.Abs(FinalRateAdjustment)); } } diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 2cc03ae453..9f46fddc5e 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -43,7 +43,7 @@ namespace osu.Game.Screens.Play private readonly double firstHitObjectTime; - public readonly Bindable UserPlaybackRate = new BindableDouble(1) + public readonly BindableNumber UserPlaybackRate = new BindableDouble(1) { Default = 1, MinValue = 0.5, @@ -73,7 +73,6 @@ namespace osu.Game.Screens.Play RelativeSizeAxes = Axes.Both; track = beatmap.Track; - track.AddAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); adjustableClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; @@ -120,7 +119,6 @@ namespace osu.Game.Screens.Play Seek(startTime); adjustableClock.ProcessFrame(); - UserPlaybackRate.ValueChanged += _ => updateRate(); } public void Restart() @@ -223,7 +221,8 @@ namespace osu.Game.Screens.Play speedAdjustmentsApplied = true; track.ResetSpeedAdjustments(); - track.Tempo.Value = UserPlaybackRate.Value; + track.AddAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); + track.AddAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); foreach (var mod in mods.OfType()) mod.ApplyToTrack(track); @@ -244,8 +243,6 @@ namespace osu.Game.Screens.Play track.ResetSpeedAdjustments(); speedAdjustmentsApplied = false; } - - track.RemoveAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); } } } From edc82205543106251bba5a9b8a2c92ea3b81e7ad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 9 Dec 2019 19:58:51 +0900 Subject: [PATCH 010/103] Add time ramp settings --- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 8 ++++++-- osu.Game/Rulesets/Mods/ModWindDown.cs | 12 +++++++++++- osu.Game/Rulesets/Mods/ModWindUp.cs | 12 +++++++++++- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index 36de546707..bffe4f7b70 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -7,6 +7,7 @@ using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.Objects; @@ -21,7 +22,8 @@ namespace osu.Game.Rulesets.Mods public override Type[] IncompatibleMods => new[] { typeof(ModRateAdjust) }; - protected abstract double FinalRateAdjustment { get; } + [SettingSource("Final rate", "The final speed to ramp to")] + public abstract BindableNumber FinalRate { get; } private double finalRateTime; private double beginRampTime; @@ -45,6 +47,8 @@ namespace osu.Game.Rulesets.Mods { HitObject lastObject = beatmap.HitObjects.LastOrDefault(); + SpeedChange.SetDefault(); + beginRampTime = beatmap.HitObjects.FirstOrDefault()?.StartTime ?? 0; finalRateTime = final_rate_progress * (lastObject?.GetEndTime() ?? 0); } @@ -59,6 +63,6 @@ namespace osu.Game.Rulesets.Mods /// /// The amount of adjustment to apply (from 0..1). private void applyAdjustment(double amount) => - SpeedChange.Value = 1 + (Math.Sign(FinalRateAdjustment) * Math.Clamp(amount, 0, 1) * Math.Abs(FinalRateAdjustment)); + SpeedChange.Value = 1 + (Math.Sign(FinalRate.Value) * Math.Clamp(amount, 0, 1) * Math.Abs(FinalRate.Value)); } } diff --git a/osu.Game/Rulesets/Mods/ModWindDown.cs b/osu.Game/Rulesets/Mods/ModWindDown.cs index b2e3abb59d..680c1a10fb 100644 --- a/osu.Game/Rulesets/Mods/ModWindDown.cs +++ b/osu.Game/Rulesets/Mods/ModWindDown.cs @@ -3,7 +3,9 @@ using System; using System.Linq; +using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; +using osu.Game.Configuration; namespace osu.Game.Rulesets.Mods { @@ -15,7 +17,15 @@ namespace osu.Game.Rulesets.Mods public override IconUsage Icon => FontAwesome.Solid.ChevronCircleDown; public override double ScoreMultiplier => 1.0; - protected override double FinalRateAdjustment => -0.25; + [SettingSource("Final rate", "The speed increase to ramp towards")] + public override BindableNumber FinalRate { get; } = new BindableDouble + { + MinValue = -0.5, + MaxValue = -0.01, + Default = -0.25, + Value = -0.25, + Precision = 0.01, + }; public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModWindUp)).ToArray(); } diff --git a/osu.Game/Rulesets/Mods/ModWindUp.cs b/osu.Game/Rulesets/Mods/ModWindUp.cs index 8df35a1de2..ca9ce0ea3e 100644 --- a/osu.Game/Rulesets/Mods/ModWindUp.cs +++ b/osu.Game/Rulesets/Mods/ModWindUp.cs @@ -3,7 +3,9 @@ using System; using System.Linq; +using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; +using osu.Game.Configuration; namespace osu.Game.Rulesets.Mods { @@ -15,7 +17,15 @@ namespace osu.Game.Rulesets.Mods public override IconUsage Icon => FontAwesome.Solid.ChevronCircleUp; public override double ScoreMultiplier => 1.0; - protected override double FinalRateAdjustment => 0.5; + [SettingSource("Final rate", "The speed increase to ramp towards")] + public override BindableNumber FinalRate { get; } = new BindableDouble + { + MinValue = 0.01, + MaxValue = 2, + Default = 1.5, + Value = 1.5, + Precision = 0.01, + }; public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModWindDown)).ToArray(); } From 12bdb1dafd98d09db5af10de7961b8ee17c94a05 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 9 Dec 2019 20:40:38 +0900 Subject: [PATCH 011/103] Pin DC/NC pitch --- osu.Game/Rulesets/Mods/ModDaycore.cs | 13 ++++++++++++- osu.Game/Rulesets/Mods/ModNightcore.cs | 13 ++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModDaycore.cs b/osu.Game/Rulesets/Mods/ModDaycore.cs index f7da1e86db..474e793dd1 100644 --- a/osu.Game/Rulesets/Mods/ModDaycore.cs +++ b/osu.Game/Rulesets/Mods/ModDaycore.cs @@ -3,6 +3,7 @@ using osu.Framework.Audio; using osu.Framework.Audio.Track; +using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; namespace osu.Game.Rulesets.Mods @@ -14,9 +15,19 @@ namespace osu.Game.Rulesets.Mods public override IconUsage Icon => FontAwesome.Solid.Question; public override string Description => "Whoaaaaa..."; + private readonly BindableNumber tempoAdjust = new BindableDouble(1); + private readonly BindableNumber freqAdjust = new BindableDouble(1); + public override void ApplyToTrack(Track track) { - track.AddAdjustment(AdjustableProperty.Frequency, SpeedChange); + track.AddAdjustment(AdjustableProperty.Frequency, freqAdjust); + track.AddAdjustment(AdjustableProperty.Tempo, tempoAdjust); + + SpeedChange.BindValueChanged(val => + { + freqAdjust.Value = SpeedChange.Default; + tempoAdjust.Value = val.NewValue / SpeedChange.Default; + }, true); } } } diff --git a/osu.Game/Rulesets/Mods/ModNightcore.cs b/osu.Game/Rulesets/Mods/ModNightcore.cs index e41f1415ab..401814d18b 100644 --- a/osu.Game/Rulesets/Mods/ModNightcore.cs +++ b/osu.Game/Rulesets/Mods/ModNightcore.cs @@ -3,6 +3,7 @@ using osu.Framework.Audio; using osu.Framework.Audio.Track; +using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; @@ -15,9 +16,19 @@ namespace osu.Game.Rulesets.Mods public override IconUsage Icon => OsuIcon.ModNightcore; public override string Description => "Uguuuuuuuu..."; + private readonly BindableNumber tempoAdjust = new BindableDouble(1); + private readonly BindableNumber freqAdjust = new BindableDouble(1); + public override void ApplyToTrack(Track track) { - track.AddAdjustment(AdjustableProperty.Frequency, SpeedChange); + track.AddAdjustment(AdjustableProperty.Frequency, freqAdjust); + track.AddAdjustment(AdjustableProperty.Tempo, tempoAdjust); + + SpeedChange.BindValueChanged(val => + { + freqAdjust.Value = SpeedChange.Default; + tempoAdjust.Value = val.NewValue / SpeedChange.Default; + }, true); } } } From e9ec6591a907209bda53c4dc8fce87805b7d3e67 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 10 Dec 2019 11:20:08 +0900 Subject: [PATCH 012/103] Separate path connections from control points --- .../Components/PathControlPointConnection.cs | 72 +++++++++++++++++++ .../Components/PathControlPointPiece.cs | 43 ++--------- .../Components/PathControlPointVisualiser.cs | 22 ++++-- 3 files changed, 92 insertions(+), 45 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnection.cs diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnection.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnection.cs new file mode 100644 index 0000000000..f57299c5a9 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnection.cs @@ -0,0 +1,72 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Lines; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Objects; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components +{ + public class PathControlPointConnection : CompositeDrawable + { + public PathControlPoint ControlPoint; + + private readonly Path path; + private readonly Slider slider; + + private IBindable sliderPosition; + private IBindable pathVersion; + + public PathControlPointConnection(Slider slider, PathControlPoint controlPoint) + { + this.slider = slider; + ControlPoint = controlPoint; + + Origin = Anchor.Centre; + AutoSizeAxes = Axes.Both; + + InternalChild = path = new SmoothPath + { + Anchor = Anchor.Centre, + PathRadius = 1 + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + sliderPosition = slider.PositionBindable.GetBoundCopy(); + sliderPosition.BindValueChanged(_ => updateConnectingPath()); + + pathVersion = slider.Path.Version.GetBoundCopy(); + pathVersion.BindValueChanged(_ => updateConnectingPath()); + + updateConnectingPath(); + } + + /// + /// Updates the path connecting this control point to the next one. + /// + private void updateConnectingPath() + { + Position = slider.StackedPosition + ControlPoint.Position.Value; + + path.ClearVertices(); + + int index = slider.Path.ControlPoints.IndexOf(ControlPoint) + 1; + + if (index == 0 || index == slider.Path.ControlPoints.Count) + return; + + path.AddVertex(Vector2.Zero); + path.AddVertex(slider.Path.ControlPoints[index].Position.Value - ControlPoint.Position.Value); + + path.OriginPosition = path.PositionInBoundingBox(Vector2.Zero); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index c2aefac587..42812ff934 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -6,7 +6,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Lines; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Graphics; @@ -28,7 +27,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components public readonly PathControlPoint ControlPoint; private readonly Slider slider; - private readonly Path path; private readonly Container marker; private readonly Drawable markerRing; @@ -39,12 +37,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private OsuColour colours { get; set; } private IBindable sliderPosition; - private IBindable pathVersion; + private IBindable controlPointPosition; public PathControlPointPiece(Slider slider, PathControlPoint controlPoint) { this.slider = slider; - ControlPoint = controlPoint; Origin = Anchor.Centre; @@ -52,11 +49,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components InternalChildren = new Drawable[] { - path = new SmoothPath - { - Anchor = Anchor.Centre, - PathRadius = 1 - }, marker = new Container { Anchor = Anchor.Centre, @@ -96,20 +88,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components base.LoadComplete(); sliderPosition = slider.PositionBindable.GetBoundCopy(); - sliderPosition.BindValueChanged(_ => updateDisplay()); + sliderPosition.BindValueChanged(_ => updateMarkerDisplay()); - pathVersion = slider.Path.Version.GetBoundCopy(); - pathVersion.BindValueChanged(_ => updateDisplay()); + controlPointPosition = ControlPoint.Position.GetBoundCopy(); + controlPointPosition.BindValueChanged(_ => updateMarkerDisplay()); IsSelected.BindValueChanged(_ => updateMarkerDisplay()); - updateDisplay(); - } - - private void updateDisplay() - { updateMarkerDisplay(); - updateConnectingPath(); } // The connecting path is excluded from positional input @@ -189,26 +175,5 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components colour = Color4.White; marker.Colour = colour; } - - /// - /// Updates the path connecting this control point to the previous one. - /// - private void updateConnectingPath() - { - path.ClearVertices(); - - int index = slider.Path.ControlPoints.IndexOf(ControlPoint); - - if (index == -1) - return; - - if (++index != slider.Path.ControlPoints.Count) - { - path.AddVertex(Vector2.Zero); - path.AddVertex(slider.Path.ControlPoints[index].Position.Value - ControlPoint.Position.Value); - } - - path.OriginPosition = path.PositionInBoundingBox(Vector2.Zero); - } } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index eb6e3c01e1..e45dc1d47a 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -25,6 +25,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components public class PathControlPointVisualiser : CompositeDrawable, IKeyBindingHandler, IHasContextMenu { internal readonly Container Pieces; + + private readonly Container connections; private readonly Slider slider; private readonly bool allowSelection; @@ -42,7 +44,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components RelativeSizeAxes = Axes.Both; - InternalChild = Pieces = new Container { RelativeSizeAxes = Axes.Both }; + InternalChildren = new Drawable[] + { + connections = new Container { RelativeSizeAxes = Axes.Both }, + Pieces = new Container { RelativeSizeAxes = Axes.Both } + }; } protected override void LoadComplete() @@ -62,19 +68,23 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { foreach (var point in controlPoints) { - var piece = new PathControlPointPiece(slider, point); + Pieces.Add(new PathControlPointPiece(slider, point).With(d => + { + if (allowSelection) + d.RequestSelection = selectPiece; + })); - if (allowSelection) - piece.RequestSelection = selectPiece; - - Pieces.Add(piece); + connections.Add(new PathControlPointConnection(slider, point)); } } private void removeControlPoints(IEnumerable controlPoints) { foreach (var point in controlPoints) + { Pieces.RemoveAll(p => p.ControlPoint == point); + connections.RemoveAll(c => c.ControlPoint == point); + } } protected override bool OnClick(ClickEvent e) From 6c1ae3bc8ac77bd1babbd0f0395071fd583a905a Mon Sep 17 00:00:00 2001 From: Albie Date: Tue, 10 Dec 2019 16:59:31 +0000 Subject: [PATCH 013/103] add tests --- .../Background/TestSceneUserDimContainer.cs | 17 +++++++++++++++++ .../Graphics/Containers/UserDimContainer.cs | 9 +++++---- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs index f858174ff2..ecea80d6cc 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs @@ -165,6 +165,21 @@ namespace osu.Game.Tests.Visual.Background AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); } + /// + /// Ensure is able to disable user-defined display settings. + /// + [Test] + public void DisableUserDisplaySettingsTest() + { + performFullSetup(); + AddStep("Enable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = true); + AddStep("Set user dim to max", () => player.DimmableStoryboard.) + waitForDim(); + AddStep("Turn on IgnoreUserSettings", () => player.DimmableStoryboard.IgnoreUserSettings.Value = true); + waitForDim(); + AddAssert("Check the background is undimmed", () => player.IsBackgroundUndimmed()); + } + /// /// Ensure is properly accepting user-defined visual changes for a storyboard. /// @@ -352,6 +367,8 @@ namespace osu.Game.Tests.Visual.Background public new DimmableStoryboard DimmableStoryboard => base.DimmableStoryboard; + public bool IsBackgroundUndimmed() => ((FadeAccessibleBackground)Background).Colour == Color4.White; + // Whether or not the player should be allowed to load. public bool BlockLoad; diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs index 42a25a79b1..74d922704e 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Configuration; +using osuTK.Graphics; namespace osu.Game.Graphics.Containers { @@ -22,7 +23,7 @@ namespace osu.Game.Graphics.Containers public readonly Bindable EnableUserDim = new Bindable(true); /// - /// Whether or not user-configured settings relating to visibility of elements should be ignored + /// Whether or not user-configured settings relating to brightness of elements should be ignored /// public readonly Bindable IgnoreUserSettings = new Bindable(); @@ -36,14 +37,14 @@ namespace osu.Game.Graphics.Containers /// public bool ContentDisplayed { get; private set; } + public double DimLevel => EnableUserDim.Value && !IgnoreUserSettings.Value ? UserDimLevel.Value : 0; + protected Bindable UserDimLevel { get; private set; } protected Bindable ShowStoryboard { get; private set; } protected Bindable ShowVideo { get; private set; } - protected double DimLevel => EnableUserDim.Value ? UserDimLevel.Value : 0; - protected override Container Content => dimContent; private Container dimContent { get; } @@ -90,7 +91,7 @@ namespace osu.Game.Graphics.Containers ContentDisplayed = ShowDimContent; dimContent.FadeTo(ContentDisplayed ? 1 : 0, BACKGROUND_FADE_DURATION, Easing.OutQuint); - dimContent.FadeColour(OsuColour.Gray(1 - (float)DimLevel), BACKGROUND_FADE_DURATION, Easing.OutQuint); + dimContent.FadeColour(IgnoreUserSettings.Value ? OsuColour.Gray(1 - (float)DimLevel) : Color4.White, BACKGROUND_FADE_DURATION, Easing.OutQuint); } } } From 479acdcb5b670b0644a2755cd3b93396b3a95764 Mon Sep 17 00:00:00 2001 From: Albie Date: Tue, 10 Dec 2019 17:06:34 +0000 Subject: [PATCH 014/103] fix build bug --- osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs index ecea80d6cc..8c7948ebef 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs @@ -173,7 +173,6 @@ namespace osu.Game.Tests.Visual.Background { performFullSetup(); AddStep("Enable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = true); - AddStep("Set user dim to max", () => player.DimmableStoryboard.) waitForDim(); AddStep("Turn on IgnoreUserSettings", () => player.DimmableStoryboard.IgnoreUserSettings.Value = true); waitForDim(); From 12fb17572c1ce4ad6cb8f1e37b6755c11c13dc8f Mon Sep 17 00:00:00 2001 From: Albie Date: Tue, 10 Dec 2019 18:16:34 +0000 Subject: [PATCH 015/103] cleanup test logic --- .../Background/TestSceneUserDimContainer.cs | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs index 5b7900bad4..28cc7a8532 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs @@ -148,6 +148,20 @@ namespace osu.Game.Tests.Visual.Background AddAssert("Background is visible", () => songSelect.IsBackgroundVisible()); } + /// + /// Ensure is able to disable user-defined display settings. + /// + [Test] + public void DisableUserDisplaySettingsTest() + { + performFullSetup(); + AddStep("Enable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = true); + waitForDim(); + AddStep("Turn on IgnoreUserSettings", () => player.DimmableStoryboard.IgnoreUserSettings.Value = true); + waitForDim(); + AddAssert("Check the background is undimmed", () => player.IsBackgroundUndimmed()); + } + /// /// Ensure is properly accepting user-defined visual changes for a background. /// @@ -165,20 +179,6 @@ namespace osu.Game.Tests.Visual.Background AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); } - /// - /// Ensure is able to disable user-defined display settings. - /// - [Test] - public void DisableUserDisplaySettingsTest() - { - performFullSetup(); - AddStep("Enable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = true); - waitForDim(); - AddStep("Turn on IgnoreUserSettings", () => player.DimmableStoryboard.IgnoreUserSettings.Value = true); - waitForDim(); - AddAssert("Check the background is undimmed", () => player.IsBackgroundUndimmed()); - } - /// /// Ensure is properly accepting user-defined visual changes for a storyboard. /// @@ -367,7 +367,7 @@ namespace osu.Game.Tests.Visual.Background public new DimmableStoryboard DimmableStoryboard => base.DimmableStoryboard; - public bool IsBackgroundUndimmed() => ((FadeAccessibleBackground)Background).Colour == Color4.White; + public bool IsBackgroundUndimmed() => ((FadeAccessibleBackground)Background).CurrentColour == Color4.White; // Whether or not the player should be allowed to load. public bool BlockLoad; From 83b2e0525e9055dae18b7eb7396a5804dbd21d39 Mon Sep 17 00:00:00 2001 From: Albie Date: Wed, 11 Dec 2019 07:02:51 +0000 Subject: [PATCH 016/103] further fixes, not perfect yet --- .../Visual/Background/TestSceneUserDimContainer.cs | 6 ++---- osu.Game/Graphics/Containers/UserDimContainer.cs | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs index 28cc7a8532..f867b98fda 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs @@ -155,9 +155,7 @@ namespace osu.Game.Tests.Visual.Background public void DisableUserDisplaySettingsTest() { performFullSetup(); - AddStep("Enable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = true); - waitForDim(); - AddStep("Turn on IgnoreUserSettings", () => player.DimmableStoryboard.IgnoreUserSettings.Value = true); + AddStep("Start ignoring user settings", () => player.DimmableStoryboard.IgnoreUserSettings.Value = true); waitForDim(); AddAssert("Check the background is undimmed", () => player.IsBackgroundUndimmed()); } @@ -367,7 +365,7 @@ namespace osu.Game.Tests.Visual.Background public new DimmableStoryboard DimmableStoryboard => base.DimmableStoryboard; - public bool IsBackgroundUndimmed() => ((FadeAccessibleBackground)Background).CurrentColour == Color4.White; + public bool IsBackgroundUndimmed() => ((FadeAccessibleBackground)Background).CurrentColour == OsuColour.Gray(1); // Whether or not the player should be allowed to load. public bool BlockLoad; diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs index 74d922704e..bddbbca0ea 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -91,7 +91,7 @@ namespace osu.Game.Graphics.Containers ContentDisplayed = ShowDimContent; dimContent.FadeTo(ContentDisplayed ? 1 : 0, BACKGROUND_FADE_DURATION, Easing.OutQuint); - dimContent.FadeColour(IgnoreUserSettings.Value ? OsuColour.Gray(1 - (float)DimLevel) : Color4.White, BACKGROUND_FADE_DURATION, Easing.OutQuint); + dimContent.FadeColour(IgnoreUserSettings.Value ? Color4.White : OsuColour.Gray(1 - (float)DimLevel), BACKGROUND_FADE_DURATION, Easing.OutQuint); } } } From 404d3207ffc0c963dedde39d10b27d7de5f890fd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 11 Dec 2019 19:43:32 +0900 Subject: [PATCH 017/103] Refactor ModNightcore/ModDaycore --- osu.Game/Rulesets/Mods/ModDaycore.cs | 12 ++++++++---- osu.Game/Rulesets/Mods/ModNightcore.cs | 12 ++++++++---- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModDaycore.cs b/osu.Game/Rulesets/Mods/ModDaycore.cs index 474e793dd1..9445895fb0 100644 --- a/osu.Game/Rulesets/Mods/ModDaycore.cs +++ b/osu.Game/Rulesets/Mods/ModDaycore.cs @@ -18,16 +18,20 @@ namespace osu.Game.Rulesets.Mods private readonly BindableNumber tempoAdjust = new BindableDouble(1); private readonly BindableNumber freqAdjust = new BindableDouble(1); - public override void ApplyToTrack(Track track) + public ModDaycore() { - track.AddAdjustment(AdjustableProperty.Frequency, freqAdjust); - track.AddAdjustment(AdjustableProperty.Tempo, tempoAdjust); - SpeedChange.BindValueChanged(val => { freqAdjust.Value = SpeedChange.Default; tempoAdjust.Value = val.NewValue / SpeedChange.Default; }, true); } + + public override void ApplyToTrack(Track track) + { + // base.ApplyToTrack() intentionally not called (different tempo adjustment is applied) + track.AddAdjustment(AdjustableProperty.Frequency, freqAdjust); + track.AddAdjustment(AdjustableProperty.Tempo, tempoAdjust); + } } } diff --git a/osu.Game/Rulesets/Mods/ModNightcore.cs b/osu.Game/Rulesets/Mods/ModNightcore.cs index 401814d18b..9b27925693 100644 --- a/osu.Game/Rulesets/Mods/ModNightcore.cs +++ b/osu.Game/Rulesets/Mods/ModNightcore.cs @@ -19,16 +19,20 @@ namespace osu.Game.Rulesets.Mods private readonly BindableNumber tempoAdjust = new BindableDouble(1); private readonly BindableNumber freqAdjust = new BindableDouble(1); - public override void ApplyToTrack(Track track) + public ModNightcore() { - track.AddAdjustment(AdjustableProperty.Frequency, freqAdjust); - track.AddAdjustment(AdjustableProperty.Tempo, tempoAdjust); - SpeedChange.BindValueChanged(val => { freqAdjust.Value = SpeedChange.Default; tempoAdjust.Value = val.NewValue / SpeedChange.Default; }, true); } + + public override void ApplyToTrack(Track track) + { + // base.ApplyToTrack() intentionally not called (different tempo adjustment is applied) + track.AddAdjustment(AdjustableProperty.Frequency, freqAdjust); + track.AddAdjustment(AdjustableProperty.Tempo, tempoAdjust); + } } } From c34b6b59eda2e6a62c5e1b1aca36b9e9c45f88c1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 11 Dec 2019 19:48:57 +0900 Subject: [PATCH 018/103] Remove time ramp and rate adjust mod incompatibility --- osu.Game/Rulesets/Mods/ModRateAdjust.cs | 2 -- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 2 -- 2 files changed, 4 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModRateAdjust.cs b/osu.Game/Rulesets/Mods/ModRateAdjust.cs index 5aa3e09fee..9143836fb7 100644 --- a/osu.Game/Rulesets/Mods/ModRateAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModRateAdjust.cs @@ -10,8 +10,6 @@ namespace osu.Game.Rulesets.Mods { public abstract class ModRateAdjust : Mod, IApplicableToTrack { - public override Type[] IncompatibleMods => new[] { typeof(ModTimeRamp) }; - public abstract BindableNumber SpeedChange { get; } public virtual void ApplyToTrack(Track track) diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index bffe4f7b70..e10afa7d7c 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -20,8 +20,6 @@ namespace osu.Game.Rulesets.Mods /// private const double final_rate_progress = 0.75f; - public override Type[] IncompatibleMods => new[] { typeof(ModRateAdjust) }; - [SettingSource("Final rate", "The final speed to ramp to")] public abstract BindableNumber FinalRate { get; } From 40f918dce6a65a77589bd6a5800f510f82d1ff53 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 11 Dec 2019 19:49:32 +0900 Subject: [PATCH 019/103] Remove unused using --- osu.Game/Rulesets/Mods/ModRateAdjust.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModRateAdjust.cs b/osu.Game/Rulesets/Mods/ModRateAdjust.cs index 9143836fb7..1739524bcd 100644 --- a/osu.Game/Rulesets/Mods/ModRateAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModRateAdjust.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 osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Bindables; From 47b6b0173913ad78ac3beaf702676b0e929c8515 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Dec 2019 20:11:48 +0900 Subject: [PATCH 020/103] Rename class to signify it is a drawable --- ...ointConnection.cs => PathControlPointConnectionPiece.cs} | 4 ++-- .../Sliders/Components/PathControlPointVisualiser.cs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) rename osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/{PathControlPointConnection.cs => PathControlPointConnectionPiece.cs} (92%) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnection.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs similarity index 92% rename from osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnection.cs rename to osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs index f57299c5a9..4dfe7834fd 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnection.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs @@ -11,7 +11,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { - public class PathControlPointConnection : CompositeDrawable + public class PathControlPointConnectionPiece : CompositeDrawable { public PathControlPoint ControlPoint; @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private IBindable sliderPosition; private IBindable pathVersion; - public PathControlPointConnection(Slider slider, PathControlPoint controlPoint) + public PathControlPointConnectionPiece(Slider slider, PathControlPoint controlPoint) { this.slider = slider; ControlPoint = controlPoint; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 64a8faa02a..a97c0b4a72 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { internal readonly Container Pieces; - private readonly Container connections; + private readonly Container connections; private readonly Slider slider; @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components InternalChildren = new Drawable[] { - connections = new Container { RelativeSizeAxes = Axes.Both }, + connections = new Container { RelativeSizeAxes = Axes.Both }, Pieces = new Container { RelativeSizeAxes = Axes.Both } }; } @@ -78,7 +78,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components d.RequestSelection = selectPiece; })); - connections.Add(new PathControlPointConnection(slider, point)); + connections.Add(new PathControlPointConnectionPiece(slider, point)); } } From 50377e728637b48c96769e1917cc628e4e70a1c9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Dec 2019 20:14:16 +0900 Subject: [PATCH 021/103] Add summary xmldoc --- .../Sliders/Components/PathControlPointConnectionPiece.cs | 3 +++ .../Blueprints/Sliders/Components/PathControlPointPiece.cs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs index 4dfe7834fd..0fc441fec6 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs @@ -11,6 +11,9 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { + /// + /// A visualisation of the line between two s. + /// public class PathControlPointConnectionPiece : CompositeDrawable { public PathControlPoint ControlPoint; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index 42812ff934..6a0730db91 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -18,6 +18,9 @@ using osuTK.Input; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { + /// + /// A visualisation of a single in a . + /// public class PathControlPointPiece : BlueprintPiece { public Action RequestSelection; From caa9286a9091f2482ba4ba5b6879f4894cc675ad Mon Sep 17 00:00:00 2001 From: Albie Date: Wed, 11 Dec 2019 17:39:40 +0000 Subject: [PATCH 022/103] update tests, change binding and reduce lines in cinema mod --- .../Background/TestSceneUserDimContainer.cs | 15 +++++++++++---- osu.Game/Graphics/Containers/UserDimContainer.cs | 14 +++++++++++--- osu.Game/Rulesets/Mods/ModCinema.cs | 2 -- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs index f867b98fda..fdfde9cc2f 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs @@ -155,9 +155,18 @@ namespace osu.Game.Tests.Visual.Background public void DisableUserDisplaySettingsTest() { performFullSetup(); - AddStep("Start ignoring user settings", () => player.DimmableStoryboard.IgnoreUserSettings.Value = true); + createFakeStoryboard(); + AddStep("Enable Storyboard", () => + { + player.ReplacesBackground.Value = true; + player.StoryboardEnabled.Value = true; + }); + AddStep("Enable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = true); + AddStep("Set dim level to 1", () => songSelect.DimLevel.Value = 1f); waitForDim(); - AddAssert("Check the background is undimmed", () => player.IsBackgroundUndimmed()); + AddAssert("Ignore User Settings", () => player.DimmableStoryboard.IgnoreUserSettings.Value = true); + waitForDim(); + AddAssert("User dim settings ignored", () => !player.DimmableStoryboard.EnableUserDim.Value && player.DimmableStoryboard.DimLevel == 0); } /// @@ -365,8 +374,6 @@ namespace osu.Game.Tests.Visual.Background public new DimmableStoryboard DimmableStoryboard => base.DimmableStoryboard; - public bool IsBackgroundUndimmed() => ((FadeAccessibleBackground)Background).CurrentColour == OsuColour.Gray(1); - // Whether or not the player should be allowed to load. public bool BlockLoad; diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs index bddbbca0ea..ae4e5557be 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -37,7 +37,7 @@ namespace osu.Game.Graphics.Containers /// public bool ContentDisplayed { get; private set; } - public double DimLevel => EnableUserDim.Value && !IgnoreUserSettings.Value ? UserDimLevel.Value : 0; + public double DimLevel => EnableUserDim.Value ? UserDimLevel.Value : 0; protected Bindable UserDimLevel { get; private set; } @@ -69,7 +69,7 @@ namespace osu.Game.Graphics.Containers ShowStoryboard.ValueChanged += _ => UpdateVisuals(); ShowVideo.ValueChanged += _ => UpdateVisuals(); StoryboardReplacesBackground.ValueChanged += _ => UpdateVisuals(); - IgnoreUserSettings.ValueChanged += _ => UpdateVisuals(); + IgnoreUserSettings.ValueChanged += _ => updateSettings(); } protected override void LoadComplete() @@ -91,7 +91,15 @@ namespace osu.Game.Graphics.Containers ContentDisplayed = ShowDimContent; dimContent.FadeTo(ContentDisplayed ? 1 : 0, BACKGROUND_FADE_DURATION, Easing.OutQuint); - dimContent.FadeColour(IgnoreUserSettings.Value ? Color4.White : OsuColour.Gray(1 - (float)DimLevel), BACKGROUND_FADE_DURATION, Easing.OutQuint); + dimContent.FadeColour(OsuColour.Gray(1 - (float)DimLevel), BACKGROUND_FADE_DURATION, Easing.OutQuint); + } + + /// + /// Invoked when the IgnoreUserSettings bindable is changed + /// + private void updateSettings() + { + EnableUserDim.Value = !IgnoreUserSettings.Value; } } } diff --git a/osu.Game/Rulesets/Mods/ModCinema.cs b/osu.Game/Rulesets/Mods/ModCinema.cs index 77bf80b149..5faa2f4f3e 100644 --- a/osu.Game/Rulesets/Mods/ModCinema.cs +++ b/osu.Game/Rulesets/Mods/ModCinema.cs @@ -37,8 +37,6 @@ namespace osu.Game.Rulesets.Mods public void ApplyToPlayer(Player player) { player.Background.EnableUserDim.Value = false; - player.DimmableVideo.EnableUserDim.Value = false; - player.DimmableStoryboard.EnableUserDim.Value = false; player.DimmableVideo.IgnoreUserSettings.Value = true; player.DimmableStoryboard.IgnoreUserSettings.Value = true; From 2ca722423b76898ca121bf3dee1444f552f03d4a Mon Sep 17 00:00:00 2001 From: Albie Date: Wed, 11 Dec 2019 18:58:14 +0000 Subject: [PATCH 023/103] remove uneccesary using statement --- osu.Game/Graphics/Containers/UserDimContainer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs index ae4e5557be..f83b85e023 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -6,7 +6,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Configuration; -using osuTK.Graphics; namespace osu.Game.Graphics.Containers { From 663405d17d4ffeb6e74063db2fddd33f6715bc72 Mon Sep 17 00:00:00 2001 From: Albie Date: Wed, 11 Dec 2019 19:55:45 +0000 Subject: [PATCH 024/103] reduce test length and fix the poorly worded description --- .../Background/TestSceneUserDimContainer.cs | 29 +++++++------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs index fdfde9cc2f..52f8fe0a2e 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs @@ -119,11 +119,7 @@ namespace osu.Game.Tests.Visual.Background { performFullSetup(); createFakeStoryboard(); - AddStep("Enable Storyboard", () => - { - player.ReplacesBackground.Value = true; - player.StoryboardEnabled.Value = true; - }); + enableStoryboard(); waitForDim(); AddAssert("Background is invisible, storyboard is visible", () => songSelect.IsBackgroundInvisible() && player.IsStoryboardVisible); AddStep("Disable Storyboard", () => @@ -149,22 +145,17 @@ namespace osu.Game.Tests.Visual.Background } /// - /// Ensure is able to disable user-defined display settings. + /// Ensure can disable user-defined display settings. /// [Test] public void DisableUserDisplaySettingsTest() { performFullSetup(); createFakeStoryboard(); - AddStep("Enable Storyboard", () => - { - player.ReplacesBackground.Value = true; - player.StoryboardEnabled.Value = true; - }); + enableStoryboard(); AddStep("Enable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = true); - AddStep("Set dim level to 1", () => songSelect.DimLevel.Value = 1f); waitForDim(); - AddAssert("Ignore User Settings", () => player.DimmableStoryboard.IgnoreUserSettings.Value = true); + AddAssert("Ignore user settings", () => player.DimmableStoryboard.IgnoreUserSettings.Value = true); waitForDim(); AddAssert("User dim settings ignored", () => !player.DimmableStoryboard.EnableUserDim.Value && player.DimmableStoryboard.DimLevel == 0); } @@ -194,11 +185,7 @@ namespace osu.Game.Tests.Visual.Background { performFullSetup(); createFakeStoryboard(); - AddStep("Enable Storyboard", () => - { - player.ReplacesBackground.Value = true; - player.StoryboardEnabled.Value = true; - }); + enableStoryboard(); AddStep("Enable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = true); AddStep("Set dim level to 1", () => songSelect.DimLevel.Value = 1f); waitForDim(); @@ -285,6 +272,12 @@ namespace osu.Game.Tests.Visual.Background }); }); + private void enableStoryboard() => AddStep("Enable Storyboard", () => + { + player.ReplacesBackground.Value = true; + player.StoryboardEnabled.Value = true; + }); + private void performFullSetup(bool allowPause = false) { setupUserSettings(); From ad2528d4d2e03a1c355c84934dd73104b1afb81e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Dec 2019 15:05:29 +0900 Subject: [PATCH 025/103] Hide key counter along with other hud elements Also tidies up HUD hide logic and protects against incorrect hiding. --- osu.Game/Screens/Play/HUDOverlay.cs | 60 ++++++++++++++++++++--------- 1 file changed, 41 insertions(+), 19 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 0f9edf5606..35157fca58 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; @@ -43,8 +44,15 @@ namespace osu.Game.Screens.Play private readonly DrawableRuleset drawableRuleset; private readonly IReadOnlyList mods; - private Bindable showHud; + /// + /// Whether the elements that can optionally be hidden should be visible. + /// + public Bindable ShowHud { get; } = new BindableBool(); + + private Bindable configShowHud; + private readonly Container visibilityContainer; + private readonly BindableBool replayLoaded = new BindableBool(); private static bool hasShownNotificationOnce; @@ -53,6 +61,8 @@ namespace osu.Game.Screens.Play private readonly Container topScoreContainer; + private IEnumerable hideTargets => new Drawable[] { visibilityContainer, KeyCounter }; + public HUDOverlay(ScoreProcessor scoreProcessor, DrawableRuleset drawableRuleset, IReadOnlyList mods) { this.scoreProcessor = scoreProcessor; @@ -73,8 +83,6 @@ namespace osu.Game.Screens.Play Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, AutoSizeAxes = Axes.Both, - AutoSizeDuration = 200, - AutoSizeEasing = Easing.Out, Children = new Drawable[] { AccuracyCounter = CreateAccuracyCounter(), @@ -95,6 +103,8 @@ namespace osu.Game.Screens.Play Origin = Anchor.BottomRight, Position = -new Vector2(5, TwoLayerButton.SIZE_RETRACTED.Y), AutoSizeAxes = Axes.Both, + AutoSizeDuration = 150, + AutoSizeEasing = Easing.OutQuint, Direction = FillDirection.Vertical, Children = new Drawable[] { @@ -118,8 +128,29 @@ namespace osu.Game.Screens.Play ModDisplay.Current.Value = mods; - showHud = config.GetBindable(OsuSetting.ShowInterface); - showHud.BindValueChanged(visible => visibilityContainer.FadeTo(visible.NewValue ? 1 : 0, duration, easing), true); + configShowHud = config.GetBindable(OsuSetting.ShowInterface); + + if (!configShowHud.Value && !hasShownNotificationOnce) + { + hasShownNotificationOnce = true; + + notificationOverlay?.Post(new SimpleNotification + { + Text = @"The score overlay is currently disabled. You can toggle this by pressing Shift+Tab." + }); + } + + // start all elements hidden + hideTargets.ForEach(d => d.Hide()); + } + + public override void Hide() => throw new InvalidOperationException($"{nameof(HUDOverlay)} should not be hidden as it will remove the ability of a user to quit. Use {nameof(ShowHud)} instead."); + + protected override void LoadComplete() + { + base.LoadComplete(); + + ShowHud.BindValueChanged(visible => hideTargets.ForEach(d => d.FadeTo(visible.NewValue ? 1 : 0, duration, easing))); ShowHealthbar.BindValueChanged(healthBar => { @@ -135,20 +166,11 @@ namespace osu.Game.Screens.Play } }, true); - if (!showHud.Value && !hasShownNotificationOnce) + configShowHud.BindValueChanged(visible => { - hasShownNotificationOnce = true; - - notificationOverlay?.Post(new SimpleNotification - { - Text = @"The score overlay is currently disabled. You can toggle this by pressing Shift+Tab." - }); - } - } - - protected override void LoadComplete() - { - base.LoadComplete(); + if (!ShowHud.Disabled) + ShowHud.Value = visible.NewValue; + }, true); replayLoaded.BindValueChanged(replayLoadedValueChanged, true); } @@ -189,7 +211,7 @@ namespace osu.Game.Screens.Play switch (e.Key) { case Key.Tab: - showHud.Value = !showHud.Value; + configShowHud.Value = !configShowHud.Value; return true; } } From ffb5cdc6aef7056a8430cd76f79299d5ee087d84 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Dec 2019 14:19:04 +0900 Subject: [PATCH 026/103] Hide settings overlay along with other HUD-hidden content --- osu.Game/Screens/Play/HUDOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 35157fca58..64c3cddf2a 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -94,9 +94,9 @@ namespace osu.Game.Screens.Play Progress = CreateProgress(), ModDisplay = CreateModsContainer(), HitErrorDisplay = CreateHitErrorDisplayOverlay(), + PlayerSettingsOverlay = CreatePlayerSettingsOverlay(), } }, - PlayerSettingsOverlay = CreatePlayerSettingsOverlay(), new FillFlowContainer { Anchor = Anchor.BottomRight, From 4c4199269c267c32ee651369e183c0e4d62a477a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Dec 2019 15:25:37 +0900 Subject: [PATCH 027/103] Use protected constructors --- osu.Game/Rulesets/Mods/ModDaycore.cs | 2 +- osu.Game/Rulesets/Mods/ModNightcore.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModDaycore.cs b/osu.Game/Rulesets/Mods/ModDaycore.cs index 9445895fb0..71a666414f 100644 --- a/osu.Game/Rulesets/Mods/ModDaycore.cs +++ b/osu.Game/Rulesets/Mods/ModDaycore.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Mods private readonly BindableNumber tempoAdjust = new BindableDouble(1); private readonly BindableNumber freqAdjust = new BindableDouble(1); - public ModDaycore() + protected ModDaycore() { SpeedChange.BindValueChanged(val => { diff --git a/osu.Game/Rulesets/Mods/ModNightcore.cs b/osu.Game/Rulesets/Mods/ModNightcore.cs index 9b27925693..c14e02e64d 100644 --- a/osu.Game/Rulesets/Mods/ModNightcore.cs +++ b/osu.Game/Rulesets/Mods/ModNightcore.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mods private readonly BindableNumber tempoAdjust = new BindableDouble(1); private readonly BindableNumber freqAdjust = new BindableDouble(1); - public ModNightcore() + protected ModNightcore() { SpeedChange.BindValueChanged(val => { From 3ccfee64f6ff44c82f3b66270f790b951c745da5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Dec 2019 16:09:42 +0900 Subject: [PATCH 028/103] Add HUDOverlay tests --- .../Visual/Gameplay/TestSceneHUDOverlay.cs | 81 +++++++++++++++++++ osu.Game/Screens/Play/HUD/HitErrorDisplay.cs | 7 +- osu.Game/Screens/Play/HUDOverlay.cs | 19 +++-- 3 files changed, 98 insertions(+), 9 deletions(-) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs new file mode 100644 index 0000000000..39c42980ab --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -0,0 +1,81 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Configuration; +using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Play; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneHUDOverlay : ManualInputManagerTestScene + { + private HUDOverlay hudOverlay; + + private Drawable hideTarget => hudOverlay.KeyCounter; // best way of checking hideTargets without exposing. + + [Resolved] + private OsuConfigManager config { get; set; } + + [Test] + public void TestShownByDefault() + { + createNew(); + + AddAssert("showhud is set", () => hudOverlay.ShowHud.Value); + + AddAssert("hidetarget is visible", () => hideTarget.IsPresent); + AddAssert("pause button is visible", () => hudOverlay.HoldToQuit.IsPresent); + } + + [Test] + public void TestFadesInOnLoadComplete() + { + float? initialAlpha = null; + + createNew(h => h.OnLoadComplete += _ => initialAlpha = hideTarget.Alpha); + AddUntilStep("wait for load", () => hudOverlay.IsAlive); + AddAssert("initial alpha was less than 1", () => initialAlpha != null && initialAlpha < 1); + } + + [Test] + public void TestHideExternally() + { + createNew(); + + AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false); + + AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent); + AddAssert("pause button is still visible", () => hudOverlay.HoldToQuit.IsPresent); + } + + [Test] + public void TestExternalHideDoesntAffectConfig() + { + bool originalConfigValue = false; + + createNew(); + + AddStep("get original config value", () => originalConfigValue = config.Get(OsuSetting.ShowInterface)); + + AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false); + AddAssert("config unchanged", () => originalConfigValue == config.Get(OsuSetting.ShowInterface)); + + AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true); + AddAssert("config unchanged", () => originalConfigValue == config.Get(OsuSetting.ShowInterface)); + } + + private void createNew(Action action = null) + { + AddStep("create overlay", () => + { + Child = hudOverlay = new HUDOverlay(null, null, Array.Empty()); + + action?.Invoke(hudOverlay); + }); + } + } +} diff --git a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs index 54556f8648..6196ce4026 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs @@ -31,7 +31,8 @@ namespace osu.Game.Screens.Play.HUD RelativeSizeAxes = Axes.Both; - processor.NewJudgement += onNewJudgement; + if (processor != null) + processor.NewJudgement += onNewJudgement; } [BackgroundDependencyLoader] @@ -96,7 +97,9 @@ namespace osu.Game.Screens.Play.HUD protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - processor.NewJudgement -= onNewJudgement; + + if (processor != null) + processor.NewJudgement -= onNewJudgement; } } } diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 64c3cddf2a..7df780b678 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -118,13 +118,18 @@ namespace osu.Game.Screens.Play [BackgroundDependencyLoader(true)] private void load(OsuConfigManager config, NotificationOverlay notificationOverlay) { - BindProcessor(scoreProcessor); - BindDrawableRuleset(drawableRuleset); + if (scoreProcessor != null) + BindProcessor(scoreProcessor); - Progress.Objects = drawableRuleset.Objects; - Progress.AllowSeeking = drawableRuleset.HasReplayLoaded.Value; - Progress.RequestSeek = time => RequestSeek(time); - Progress.ReferenceClock = drawableRuleset.FrameStableClock; + if (drawableRuleset != null) + { + BindDrawableRuleset(drawableRuleset); + + Progress.Objects = drawableRuleset.Objects; + Progress.AllowSeeking = drawableRuleset.HasReplayLoaded.Value; + Progress.RequestSeek = time => RequestSeek(time); + Progress.ReferenceClock = drawableRuleset.FrameStableClock; + } ModDisplay.Current.Value = mods; @@ -279,7 +284,7 @@ namespace osu.Game.Screens.Play Margin = new MarginPadding { Top = 20, Right = 10 }, }; - protected virtual HitErrorDisplay CreateHitErrorDisplayOverlay() => new HitErrorDisplay(scoreProcessor, drawableRuleset.FirstAvailableHitWindows); + protected virtual HitErrorDisplay CreateHitErrorDisplayOverlay() => new HitErrorDisplay(scoreProcessor, drawableRuleset?.FirstAvailableHitWindows); protected virtual PlayerSettingsOverlay CreatePlayerSettingsOverlay() => new PlayerSettingsOverlay(); From 94f3dbb2f65afef1aaff9f63a7070efa11f0985b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Dec 2019 16:09:50 +0900 Subject: [PATCH 029/103] Adjust transitions slightly --- osu.Game/Screens/Play/HUDOverlay.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 7df780b678..dc32fc7cd5 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -24,8 +24,8 @@ namespace osu.Game.Screens.Play { public class HUDOverlay : Container { - private const int duration = 250; - private const Easing easing = Easing.OutQuint; + private const int fade_duration = 400; + private const Easing fade_easing = Easing.Out; public readonly KeyCounterDisplay KeyCounter; public readonly RollingCounter ComboCounter; @@ -103,8 +103,8 @@ namespace osu.Game.Screens.Play Origin = Anchor.BottomRight, Position = -new Vector2(5, TwoLayerButton.SIZE_RETRACTED.Y), AutoSizeAxes = Axes.Both, - AutoSizeDuration = 150, - AutoSizeEasing = Easing.OutQuint, + AutoSizeDuration = fade_duration, + AutoSizeEasing = fade_easing, Direction = FillDirection.Vertical, Children = new Drawable[] { @@ -155,19 +155,19 @@ namespace osu.Game.Screens.Play { base.LoadComplete(); - ShowHud.BindValueChanged(visible => hideTargets.ForEach(d => d.FadeTo(visible.NewValue ? 1 : 0, duration, easing))); + ShowHud.BindValueChanged(visible => hideTargets.ForEach(d => d.FadeTo(visible.NewValue ? 1 : 0, fade_duration, fade_easing))); ShowHealthbar.BindValueChanged(healthBar => { if (healthBar.NewValue) { - HealthDisplay.FadeIn(duration, easing); - topScoreContainer.MoveToY(30, duration, easing); + HealthDisplay.FadeIn(fade_duration, fade_easing); + topScoreContainer.MoveToY(30, fade_duration, fade_easing); } else { - HealthDisplay.FadeOut(duration, easing); - topScoreContainer.MoveToY(0, duration, easing); + HealthDisplay.FadeOut(fade_duration, fade_easing); + topScoreContainer.MoveToY(0, fade_duration, fade_easing); } }, true); From e4297ffeaded24ea61f8fd188e7fb92215d70674 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Dec 2019 15:09:55 +0900 Subject: [PATCH 030/103] Hide HUD in a better way --- osu.Game/Rulesets/Mods/ModCinema.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModCinema.cs b/osu.Game/Rulesets/Mods/ModCinema.cs index 5faa2f4f3e..5262813b08 100644 --- a/osu.Game/Rulesets/Mods/ModCinema.cs +++ b/osu.Game/Rulesets/Mods/ModCinema.cs @@ -30,8 +30,8 @@ namespace osu.Game.Rulesets.Mods public void ApplyToHUD(HUDOverlay overlay) { - overlay.AlwaysPresent = true; - overlay.Hide(); + overlay.ShowHud.Value = false; + overlay.ShowHud.Disabled = true; } public void ApplyToPlayer(Player player) From 99280db69487c1f0b8542031c8d6037fe875084e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Dec 2019 15:16:34 +0900 Subject: [PATCH 031/103] Add note about AlwaysPresent requirement --- osu.Game/Rulesets/Mods/ModCinema.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Rulesets/Mods/ModCinema.cs b/osu.Game/Rulesets/Mods/ModCinema.cs index 5262813b08..1e4cf66711 100644 --- a/osu.Game/Rulesets/Mods/ModCinema.cs +++ b/osu.Game/Rulesets/Mods/ModCinema.cs @@ -16,6 +16,7 @@ namespace osu.Game.Rulesets.Mods { drawableRuleset.SetReplayScore(CreateReplayScore(drawableRuleset.Beatmap)); + // AlwaysPresent required for hitsounds drawableRuleset.Playfield.AlwaysPresent = true; drawableRuleset.Playfield.Hide(); } From d15f49f60f33c5beba335b75f0e0b8d40fc8ee10 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Dec 2019 15:14:59 +0900 Subject: [PATCH 032/103] Also hide the break overlay --- osu.Game/Rulesets/Mods/ModCinema.cs | 2 ++ osu.Game/Screens/Play/Player.cs | 10 +++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModCinema.cs b/osu.Game/Rulesets/Mods/ModCinema.cs index 1e4cf66711..3487d49e08 100644 --- a/osu.Game/Rulesets/Mods/ModCinema.cs +++ b/osu.Game/Rulesets/Mods/ModCinema.cs @@ -41,6 +41,8 @@ namespace osu.Game.Rulesets.Mods player.DimmableVideo.IgnoreUserSettings.Value = true; player.DimmableStoryboard.IgnoreUserSettings.Value = true; + + player.BreakOverlay.Hide(); } } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 7abd60b3c1..1d1252063f 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -69,7 +69,7 @@ namespace osu.Game.Screens.Play private SampleChannel sampleRestart; - private BreakOverlay breakOverlay; + public BreakOverlay BreakOverlay; protected ScoreProcessor ScoreProcessor { get; private set; } protected DrawableRuleset DrawableRuleset { get; private set; } @@ -149,7 +149,7 @@ namespace osu.Game.Screens.Play foreach (var mod in Mods.Value.OfType()) mod.ApplyToScoreProcessor(ScoreProcessor); - breakOverlay.IsBreakTime.ValueChanged += _ => updatePauseOnFocusLostState(); + BreakOverlay.IsBreakTime.ValueChanged += _ => updatePauseOnFocusLostState(); } private void addUnderlayComponents(Container target) @@ -183,7 +183,7 @@ namespace osu.Game.Screens.Play { target.AddRange(new[] { - breakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, DrawableRuleset.GameplayStartTime, ScoreProcessor) + BreakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, DrawableRuleset.GameplayStartTime, ScoreProcessor) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -248,7 +248,7 @@ namespace osu.Game.Screens.Play private void updatePauseOnFocusLostState() => HUDOverlay.HoldToQuit.PauseOnFocusLost = PauseOnFocusLost && !DrawableRuleset.HasReplayLoaded.Value - && !breakOverlay.IsBreakTime.Value; + && !BreakOverlay.IsBreakTime.Value; private WorkingBeatmap loadBeatmap() { @@ -477,7 +477,7 @@ namespace osu.Game.Screens.Play PauseOverlay.Hide(); // breaks and time-based conditions may allow instant resume. - if (breakOverlay.IsBreakTime.Value) + if (BreakOverlay.IsBreakTime.Value) completeResume(); else DrawableRuleset.RequestResume(completeResume); From a0792f82e8bb8031cd97c22cdadd6109a0445e04 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Dec 2019 17:05:47 +0900 Subject: [PATCH 033/103] Re-jig mod select logic to reduce event fires --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index e8ea43e3f2..a6f9642a56 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -360,8 +360,8 @@ namespace osu.Game.Overlays.Mods { base.LoadComplete(); + SelectedMods.BindValueChanged(selectedModsChanged); Ruleset.BindValueChanged(rulesetChanged, true); - SelectedMods.BindValueChanged(selectedModsChanged, true); } protected override void PopOut() From c4bc57484fdee19279ac616d690891494d67e0a4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Dec 2019 17:12:01 +0900 Subject: [PATCH 034/103] Fix test logic and add regression test --- .../UserInterface/TestSceneModSettings.cs | 84 ++++++++++++++----- 1 file changed, 62 insertions(+), 22 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs index fc44c5f595..8117a4ad78 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs @@ -2,15 +2,20 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; -using osu.Framework.Allocation; +using NUnit.Framework; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Mods; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; namespace osu.Game.Tests.Visual.UserInterface { @@ -18,28 +23,51 @@ namespace osu.Game.Tests.Visual.UserInterface { private TestModSelectOverlay modSelect; - [BackgroundDependencyLoader] - private void load() + Mod testCustomisableMod = new TestModCustomisable1(); + + [Test] + public void TestButtonShowsOnCustomisableMod() { - Add(modSelect = new TestModSelectOverlay - { - RelativeSizeAxes = Axes.X, - Origin = Anchor.BottomCentre, - Anchor = Anchor.BottomCentre, - }); + createModSelect(); - var testMod = new TestModCustomisable1(); - - AddStep("open", modSelect.Show); + AddStep("open", () => modSelect.Show()); AddAssert("button disabled", () => !modSelect.CustomiseButton.Enabled.Value); AddUntilStep("wait for button load", () => modSelect.ButtonsLoaded); - AddStep("select mod", () => modSelect.SelectMod(testMod)); + AddStep("select mod", () => modSelect.SelectMod(testCustomisableMod)); AddAssert("button enabled", () => modSelect.CustomiseButton.Enabled.Value); AddStep("open Customisation", () => modSelect.CustomiseButton.Click()); - AddStep("deselect mod", () => modSelect.SelectMod(testMod)); + AddStep("deselect mod", () => modSelect.SelectMod(testCustomisableMod)); AddAssert("controls hidden", () => modSelect.ModSettingsContainer.Alpha == 0); } + [Test] + public void TestButtonShowsOnModAlreadyAdded() + { + AddStep("set active mods", () => Mods.Value = new List { testCustomisableMod }); + + createModSelect(); + + AddAssert("mods still active", () => Mods.Value.Count == 1); + + AddStep("open", () => modSelect.Show()); + AddAssert("button enabled", () => modSelect.CustomiseButton.Enabled.Value); + } + + private void createModSelect() + { + AddStep("create mod select", () => + { + Ruleset.Value = new TestRulesetInfo(); + + Child = modSelect = new TestModSelectOverlay + { + RelativeSizeAxes = Axes.X, + Origin = Anchor.BottomCentre, + Anchor = Anchor.BottomCentre, + }; + }); + } + private class TestModSelectOverlay : ModSelectOverlay { public new Container ModSettingsContainer => base.ModSettingsContainer; @@ -50,24 +78,36 @@ namespace osu.Game.Tests.Visual.UserInterface public void SelectMod(Mod mod) => ModSectionsContainer.Children.Single(s => s.ModType == mod.Type) .ButtonsContainer.OfType().Single(b => b.Mods.Any(m => m.GetType() == mod.GetType())).SelectNext(1); + } - protected override void LoadComplete() + public class TestRulesetInfo : RulesetInfo + { + public override Ruleset CreateInstance() => new TestCustomisableModRuleset(); + + public class TestCustomisableModRuleset : Ruleset { - base.LoadComplete(); - - foreach (var section in ModSectionsContainer) + public override IEnumerable GetModsFor(ModType type) { - if (section.ModType == ModType.Conversion) + if (type == ModType.Conversion) { - section.Mods = new Mod[] + return new Mod[] { new TestModCustomisable1(), new TestModCustomisable2() }; } - else - section.Mods = Array.Empty(); + + return Array.Empty(); } + + public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList mods) => throw new NotImplementedException(); + + public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => throw new NotImplementedException(); + + public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => throw new NotImplementedException(); + + public override string Description { get; } + public override string ShortName { get; } } } From 623ab1ef3be8f3ef21b71797622894690b21d735 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Dec 2019 17:28:31 +0900 Subject: [PATCH 035/103] Update time ramp preview on setting change --- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index e10afa7d7c..5276c196f7 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -35,6 +35,12 @@ namespace osu.Game.Rulesets.Mods private Track track; + public ModTimeRamp() + { + // for preview purpose at song select. eventually we'll want to be able to update every frame. + FinalRate.BindValueChanged(val => applyAdjustment(1), true); + } + public void ApplyToTrack(Track track) { this.track = track; From c6cbf0f28a968ce42c5ba3f7731f98da822ac7e6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 12 Dec 2019 16:27:11 +0300 Subject: [PATCH 036/103] Add 1.0 skin to osu! ruleset test --- .../Resources/old-skin/approachcircle.png | Bin 0 -> 4540 bytes .../Resources/old-skin/hit0.png | Bin 0 -> 12904 bytes .../Resources/old-skin/hit100.png | Bin 0 -> 30853 bytes .../Resources/old-skin/hit300.png | Bin 0 -> 33649 bytes .../Resources/old-skin/hit50.png | Bin 0 -> 27832 bytes .../Resources/old-skin/hitcircle.png | Bin 0 -> 3572 bytes .../Resources/old-skin/hitcircleoverlay.png | Bin 0 -> 7113 bytes .../Resources/old-skin/skin.ini | 2 ++ .../SkinnableTestScene.cs | 5 ++++- 9 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/approachcircle.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit0.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit100.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit300.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit50.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hitcircle.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hitcircleoverlay.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/skin.ini diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/approachcircle.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/approachcircle.png new file mode 100644 index 0000000000000000000000000000000000000000..ff8b02ce800824f510e8655a1a9d977418bbdeb3 GIT binary patch literal 4540 zcmV;t5ku~YP);J

@NHN+%xmd&%Mk3yL--e&pG%0_n}mDQ`*|v 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�cZa_2kS}f04>ihEu+(-}$68mAH8q>c4Yg}Zw1U5cyjPrzcc;21 zO}$F%L)_@T&0)*iXf-CvhjIO&mqFgsMtO0u9|8C^bCA~uMy{agX#U1Lf3yS=T8V3V zC`|-zcm6mMrruab(wUEJ^lq~9wIU%F(T@Dr`RSvfIRR#VjIn%mnC8%Gu%2`$9~bp< z+maNMH~22(3vM`d=S(0OZ@g$m<3Y_Ge4+FUjoH2rY^OlFHiUDH9u2-Jr!WDfO;0sQ zSH5~hJhf0|pdF`2)~VAW?&z%MnQ=!f&<|VfzMBwInyt$Rx%>X<=SlD4!;UNT>@PlNtt*&nR zy0BzmIP`)3j5bWkjyClEQIg)5FK C#z4*13%nv%sar3_~4ci)7wz%BiUeMi~U~D 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 Dvd!~3 literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit300.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit300.png new file mode 100644 index 0000000000000000000000000000000000000000..24945f7d925ee3cd4dbb741907aa1a4e8e90a1c5 GIT binary patch literal 33649 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 PUkRuy>nK&jZ6p5&sNh6p literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hitcircle.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hitcircle.png new file mode 100644 index 0000000000000000000000000000000000000000..a5a3545abf2510e7eec395bf47b73d9082e1fd8d GIT binary patch literal 3572 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 u5 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 literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/skin.ini b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/skin.ini new file mode 100644 index 0000000000..5369de24e9 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/skin.ini @@ -0,0 +1,2 @@ +[General] +Version: 1.0 \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs b/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs index 38aac50df6..2fad3bd04f 100644 --- a/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs +++ b/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs @@ -19,9 +19,10 @@ namespace osu.Game.Rulesets.Osu.Tests private Skin metricsSkin; private Skin defaultSkin; private Skin specialSkin; + private Skin oldSkin; protected SkinnableTestScene() - : base(2, 2) + : base(2, 3) { } @@ -33,6 +34,7 @@ namespace osu.Game.Rulesets.Osu.Tests metricsSkin = new TestLegacySkin(new SkinInfo(), new NamespacedResourceStore(dllStore, "Resources/metrics_skin"), audio, true); defaultSkin = skinManager.GetSkin(DefaultLegacySkin.Info); specialSkin = new TestLegacySkin(new SkinInfo(), new NamespacedResourceStore(dllStore, "Resources/special_skin"), audio, true); + oldSkin = new TestLegacySkin(new SkinInfo(), new NamespacedResourceStore(dllStore, "Resources/old_skin"), audio, true); } public void SetContents(Func creationFunction) @@ -41,6 +43,7 @@ namespace osu.Game.Rulesets.Osu.Tests Cell(1).Child = createProvider(metricsSkin, creationFunction); Cell(2).Child = createProvider(defaultSkin, creationFunction); Cell(3).Child = createProvider(specialSkin, creationFunction); + Cell(4).Child = createProvider(oldSkin, creationFunction); } private Drawable createProvider(Skin skin, Func creationFunction) From 952bc96bbf3223b2415a486c3b8b922e57328ee4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 13 Dec 2019 20:05:38 +0900 Subject: [PATCH 037/103] Use GameBase data sources for Beatmap/Mods/Ruleset Sourced in via OsuScreenDependencies for management --- osu.Game/Tests/Visual/OsuTestScene.cs | 40 ++++++++++----------------- 1 file changed, 15 insertions(+), 25 deletions(-) diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 4ca0ec0f7e..4a561f17a3 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -12,7 +12,6 @@ using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Textures; using osu.Framework.Platform; using osu.Framework.Testing; using osu.Framework.Timing; @@ -21,6 +20,7 @@ using osu.Game.Database; using osu.Game.Online.API; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; +using osu.Game.Screens; using osu.Game.Storyboards; using osu.Game.Tests.Beatmaps; @@ -28,21 +28,13 @@ namespace osu.Game.Tests.Visual { public abstract class OsuTestScene : TestScene { - [Cached(typeof(Bindable))] - [Cached(typeof(IBindable))] - private NonNullableBindable beatmap; + protected Bindable Beatmap { get; private set; } - protected Bindable Beatmap => beatmap; + protected Bindable Ruleset; - [Cached] - [Cached(typeof(IBindable))] - protected readonly Bindable Ruleset = new Bindable(); + protected Bindable> Mods; - [Cached] - [Cached(Type = typeof(IBindable>))] - protected readonly Bindable> Mods = new Bindable>(Array.Empty()); - - protected new DependencyContainer Dependencies { get; private set; } + protected new OsuScreenDependencies Dependencies { get; private set; } private readonly Lazy localStorage; protected Storage LocalStorage => localStorage.Value; @@ -72,18 +64,16 @@ namespace osu.Game.Tests.Visual protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { - // This is the earliest we can get OsuGameBase, which is used by the dummy working beatmap to find textures - var working = new DummyWorkingBeatmap(parent.Get(), parent.Get()); + Dependencies = new OsuScreenDependencies(false, base.CreateChildDependencies(parent)); - beatmap = new NonNullableBindable(working) { Default = working }; - beatmap.BindValueChanged(b => ScheduleAfterChildren(() => - { - // compare to last beatmap as sometimes the two may share a track representation (optimisation, see WorkingBeatmap.TransferTo) - if (b.OldValue?.TrackLoaded == true && b.OldValue?.Track != b.NewValue?.Track) - b.OldValue.RecycleTrack(); - })); + Beatmap = Dependencies.Beatmap; + Beatmap.SetDefault(); - Dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + Ruleset = Dependencies.Ruleset; + Ruleset.SetDefault(); + + Mods = Dependencies.Mods; + Mods.SetDefault(); if (!UseOnlineAPI) { @@ -135,8 +125,8 @@ namespace osu.Game.Tests.Visual { base.Dispose(isDisposing); - if (beatmap?.Value.TrackLoaded == true) - beatmap.Value.Track.Stop(); + if (Beatmap?.Value.TrackLoaded == true) + Beatmap.Value.Track.Stop(); if (contextFactory.IsValueCreated) contextFactory.Value.ResetDatabase(); From 395b058ff872a28cf74554088ec04f26eff319d8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 13 Dec 2019 20:05:54 +0900 Subject: [PATCH 038/103] Fix OsuScreenDependencies not caching non-leased versions --- osu.Game/Screens/OsuScreenDependencies.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game/Screens/OsuScreenDependencies.cs b/osu.Game/Screens/OsuScreenDependencies.cs index 115f4b7e1a..8d54829d49 100644 --- a/osu.Game/Screens/OsuScreenDependencies.cs +++ b/osu.Game/Screens/OsuScreenDependencies.cs @@ -26,16 +26,26 @@ namespace osu.Game.Screens Beatmap = parent.Get>()?.GetBoundCopy(); if (Beatmap == null) + { Cache(Beatmap = parent.Get>().BeginLease(false)); + CacheAs(Beatmap); + } Ruleset = parent.Get>()?.GetBoundCopy(); if (Ruleset == null) + { Cache(Ruleset = parent.Get>().BeginLease(true)); + CacheAs(Ruleset); + } Mods = parent.Get>>()?.GetBoundCopy(); + if (Mods == null) + { Cache(Mods = parent.Get>>().BeginLease(true)); + CacheAs(Mods); + } } else { From 3dc2b59d2a066e4827f52aff6c7951cd72cfddd4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 13 Dec 2019 20:06:12 +0900 Subject: [PATCH 039/103] Move variable above common bindables --- osu.Game/Screens/OsuScreen.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index 94165fe4b7..6394fb8d23 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -87,12 +87,12 @@ namespace osu.Game.Screens public virtual float BackgroundParallaxAmount => 1; + public virtual bool AllowRateAdjustments => true; + public Bindable Beatmap { get; private set; } public Bindable Ruleset { get; private set; } - public virtual bool AllowRateAdjustments => true; - public Bindable> Mods { get; private set; } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) From 7033974733537e4d32984f4efeae2c578534fc04 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 13 Dec 2019 20:06:56 +0900 Subject: [PATCH 040/103] Fix test regressions (incorrect from the start) --- .../TestSceneDrawableHitObjectsHidden.cs | 6 ++++-- osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleHidden.cs | 5 +++-- osu.Game.Rulesets.Osu.Tests/TestSceneSliderHidden.cs | 5 +++-- osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerHidden.cs | 5 +++-- .../Visual/UserInterface/TestSceneModSelectOverlay.cs | 2 +- 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjectsHidden.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjectsHidden.cs index f6d26addaa..08107e01eb 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjectsHidden.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjectsHidden.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using NUnit.Framework; using osu.Game.Rulesets.Catch.Mods; namespace osu.Game.Rulesets.Catch.Tests @@ -12,9 +13,10 @@ namespace osu.Game.Rulesets.Catch.Tests { public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(CatchModHidden) }).ToList(); - public TestSceneDrawableHitObjectsHidden() + [SetUp] + public void SetUp() => Schedule(() => { Mods.Value = new[] { new CatchModHidden() }; - } + }); } } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleHidden.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleHidden.cs index 55c6b22146..eaba82a469 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleHidden.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleHidden.cs @@ -14,9 +14,10 @@ namespace osu.Game.Rulesets.Osu.Tests { public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModHidden) }).ToList(); - public TestSceneHitCircleHidden() + [SetUp] + public void SetUp() => Schedule(() => { Mods.Value = new[] { new OsuModHidden() }; - } + }); } } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderHidden.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderHidden.cs index 2a9c1d167b..a1795fb877 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderHidden.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderHidden.cs @@ -14,9 +14,10 @@ namespace osu.Game.Rulesets.Osu.Tests { public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModHidden) }).ToList(); - public TestSceneSliderHidden() + [SetUp] + public void SetUp() => Schedule(() => { Mods.Value = new[] { new OsuModHidden() }; - } + }); } } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerHidden.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerHidden.cs index a0ab1908d6..2976d4fdce 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerHidden.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerHidden.cs @@ -14,9 +14,10 @@ namespace osu.Game.Rulesets.Osu.Tests { public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModHidden) }).ToList(); - public TestSceneSpinnerHidden() + [SetUp] + public void SetUp() => Schedule(() => { Mods.Value = new[] { new OsuModHidden() }; - } + }); } } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index be50200e1c..8cf2d29906 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -124,7 +124,7 @@ namespace osu.Game.Tests.Visual.UserInterface var easierMods = instance.GetModsFor(ModType.DifficultyReduction); var noFailMod = easierMods.FirstOrDefault(m => m is OsuModNoFail); - AddStep("set mods externally", () => { modDisplay.Current.Value = new[] { noFailMod }; }); + AddStep("set mods externally", () => { Mods.Value = new[] { noFailMod }; }); changeRuleset(rulesetOsu); From a1f8ab1735b755bd98b27697dbf3f99828716f28 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 13 Dec 2019 20:13:53 +0900 Subject: [PATCH 041/103] Fix unrequired type keyword --- osu.Game/OsuGameBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index f310da3883..207fb91aab 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -80,7 +80,7 @@ namespace osu.Game // todo: move this to SongSelect once Screen has the ability to unsuspend. [Cached] - [Cached(Type = typeof(IBindable>))] + [Cached(typeof(IBindable>))] protected readonly Bindable> Mods = new Bindable>(Array.Empty()); protected Bindable Beatmap { get; private set; } // cached via load() method From 0bbaf9b7fbe760d4b57ca66bf82c114e952cf824 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 13 Dec 2019 20:21:35 +0900 Subject: [PATCH 042/103] Fix mod select overlay tests not running individually --- .../TestSceneModSelectOverlay.cs | 101 +++++++++--------- 1 file changed, 52 insertions(+), 49 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 8cf2d29906..5f77116c67 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -8,13 +8,16 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Testing; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Mods; using osu.Game.Overlays.Mods.Sections; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Mania.Mods; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.UI; using osu.Game.Screens.Play.HUD; @@ -48,42 +51,48 @@ namespace osu.Game.Tests.Visual.UserInterface private void load(RulesetStore rulesets) { this.rulesets = rulesets; + } - Add(modSelect = new TestModSelectOverlay + [SetUp] + public void SetUp() => Schedule(() => + { + Children = new Drawable[] { - RelativeSizeAxes = Axes.X, - Origin = Anchor.BottomCentre, - Anchor = Anchor.BottomCentre, - }); + modSelect = new TestModSelectOverlay + { + RelativeSizeAxes = Axes.X, + Origin = Anchor.BottomCentre, + Anchor = Anchor.BottomCentre, + }, - Add(modDisplay = new ModDisplay - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - AutoSizeAxes = Axes.Both, - Position = new Vector2(0, 25), - }); + modDisplay = new ModDisplay + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.Both, + Position = new Vector2(0, 25), + Current = { BindTarget = modSelect.SelectedMods } + } + }; + }); - modDisplay.Current.UnbindBindings(); - modDisplay.Current.BindTo(modSelect.SelectedMods); - - AddStep("Show", modSelect.Show); - AddStep("Toggle", modSelect.ToggleVisibility); - AddStep("Toggle", modSelect.ToggleVisibility); + [SetUpSteps] + public void SetUpSteps() + { + AddStep("show", () => modSelect.Show()); } [Test] public void TestOsuMods() { - var ruleset = rulesets.AvailableRulesets.First(r => r.ID == 0); - changeRuleset(ruleset); + changeRuleset(0); - var instance = ruleset.CreateInstance(); + var osu = new OsuRuleset(); - var easierMods = instance.GetModsFor(ModType.DifficultyReduction); - var harderMods = instance.GetModsFor(ModType.DifficultyIncrease); + var easierMods = osu.GetModsFor(ModType.DifficultyReduction); + var harderMods = osu.GetModsFor(ModType.DifficultyIncrease); - var noFailMod = easierMods.FirstOrDefault(m => m is OsuModNoFail); + var noFailMod = osu.GetModsFor(ModType.DifficultyReduction).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)); @@ -97,8 +106,8 @@ namespace osu.Game.Tests.Visual.UserInterface testMultiMod(doubleTimeMod); testIncompatibleMods(easy, hardRock); testDeselectAll(easierMods.Where(m => !(m is MultiMod))); - testMultiplierTextColour(noFailMod, modSelect.LowMultiplierColour); - testMultiplierTextColour(hiddenMod, modSelect.HighMultiplierColour); + testMultiplierTextColour(noFailMod, () => modSelect.LowMultiplierColour); + testMultiplierTextColour(hiddenMod, () => modSelect.HighMultiplierColour); testUnimplementedMod(spunOutMod); } @@ -106,37 +115,31 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestManiaMods() { - var ruleset = rulesets.AvailableRulesets.First(r => r.ID == 3); - changeRuleset(ruleset); + changeRuleset(3); - testRankedText(ruleset.CreateInstance().GetModsFor(ModType.Conversion).First(m => m is ManiaModRandom)); + testRankedText(new ManiaRuleset().GetModsFor(ModType.Conversion).First(m => m is ManiaModRandom)); } [Test] public void TestRulesetChanges() { - var rulesetOsu = rulesets.AvailableRulesets.First(r => r.ID == 0); - var rulesetMania = rulesets.AvailableRulesets.First(r => r.ID == 3); + changeRuleset(0); - changeRuleset(null); - - var instance = rulesetOsu.CreateInstance(); - var easierMods = instance.GetModsFor(ModType.DifficultyReduction); - var noFailMod = easierMods.FirstOrDefault(m => m is OsuModNoFail); + var noFailMod = new OsuRuleset().GetModsFor(ModType.DifficultyReduction).FirstOrDefault(m => m is OsuModNoFail); AddStep("set mods externally", () => { Mods.Value = new[] { noFailMod }; }); - changeRuleset(rulesetOsu); + changeRuleset(0); AddAssert("ensure mods still selected", () => modDisplay.Current.Value.Single(m => m is OsuModNoFail) != null); - changeRuleset(rulesetMania); + changeRuleset(3); - AddAssert("ensure mods not selected", () => !modDisplay.Current.Value.Any(m => m is OsuModNoFail)); + AddAssert("ensure mods not selected", () => modDisplay.Current.Value.Count == 0); - changeRuleset(rulesetOsu); + changeRuleset(0); - AddAssert("ensure mods not selected", () => !modDisplay.Current.Value.Any()); + AddAssert("ensure mods not selected", () => modDisplay.Current.Value.Count == 0); } private void testSingleMod(Mod mod) @@ -198,19 +201,19 @@ namespace osu.Game.Tests.Visual.UserInterface selectNext(mod); AddAssert("check for any selection", () => modSelect.SelectedMods.Value.Any()); - AddStep("deselect all", modSelect.DeselectAllButton.Action.Invoke); + AddStep("deselect all", () => modSelect.DeselectAllButton.Action.Invoke()); AddAssert("check for no selection", () => !modSelect.SelectedMods.Value.Any()); } - private void testMultiplierTextColour(Mod mod, Color4 colour) + private void testMultiplierTextColour(Mod mod, Func getCorrectColour) { - checkLabelColor(Color4.White); + checkLabelColor(() => Color4.White); selectNext(mod); AddWaitStep("wait for changing colour", 1); - checkLabelColor(colour); + checkLabelColor(getCorrectColour); selectPrevious(mod); AddWaitStep("wait for changing colour", 1); - checkLabelColor(Color4.White); + checkLabelColor(() => Color4.White); } private void testRankedText(Mod mod) @@ -235,9 +238,9 @@ namespace osu.Game.Tests.Visual.UserInterface }); } - private void changeRuleset(RulesetInfo ruleset) + private void changeRuleset(int? id) { - AddStep($"change ruleset to {ruleset}", () => { Ruleset.Value = ruleset; }); + AddStep($"change ruleset to {(id.ToString() ?? "none")}", () => { Ruleset.Value = rulesets.AvailableRulesets.FirstOrDefault(r => r.ID == id); }); waitForLoad(); } @@ -253,7 +256,7 @@ namespace osu.Game.Tests.Visual.UserInterface }); } - private void checkLabelColor(Color4 color) => AddAssert("check label has expected colour", () => modSelect.MultiplierLabel.Colour.AverageColour == color); + private void checkLabelColor(Func getColour) => AddAssert("check label has expected colour", () => modSelect.MultiplierLabel.Colour.AverageColour == getColour()); private class TestModSelectOverlay : ModSelectOverlay { From 34f67b9cadf7a01e8bae1a31dcc8e0b2435ca40f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 13 Dec 2019 20:31:20 +0900 Subject: [PATCH 043/103] 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 252570a150..8b266b08ba 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -54,6 +54,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index a07348b57c..9ec833c9ac 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -23,7 +23,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 544bba3963..1829cbe32a 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -74,7 +74,7 @@ - + @@ -82,7 +82,7 @@ - + From f349e7ff78f9f3a7dec1851241c5478209dc8ca2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 13 Dec 2019 20:35:34 +0900 Subject: [PATCH 044/103] Fix non-null ?? usage --- .../Visual/UserInterface/TestSceneModSelectOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 5f77116c67..2738b5b8fc 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -240,7 +240,7 @@ namespace osu.Game.Tests.Visual.UserInterface private void changeRuleset(int? id) { - AddStep($"change ruleset to {(id.ToString() ?? "none")}", () => { Ruleset.Value = rulesets.AvailableRulesets.FirstOrDefault(r => r.ID == id); }); + AddStep($"change ruleset to {(id?.ToString() ?? "none")}", () => { Ruleset.Value = rulesets.AvailableRulesets.FirstOrDefault(r => r.ID == id); }); waitForLoad(); } From 440a8470e146276a20899dcb8b8513a02b1fa622 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Dec 2019 18:53:25 +0900 Subject: [PATCH 045/103] Move available mods to global context This also tidies up ModSelectOverlay and setting creation flow in general. --- osu.Game/OsuGameBase.cs | 17 ++++++++ osu.Game/Overlays/Mods/ModControlSection.cs | 10 ++--- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 43 ++++++++------------- 3 files changed, 37 insertions(+), 33 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 207fb91aab..78a035c5d6 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -83,6 +83,11 @@ namespace osu.Game [Cached(typeof(IBindable>))] protected readonly Bindable> Mods = new Bindable>(Array.Empty()); + ///

+ /// Mods available for the current . + /// + public readonly Bindable>> AvailableMods = new Bindable>>(); + protected Bindable Beatmap { get; private set; } // cached via load() method private Bindable fpsDisplayVisible; @@ -233,6 +238,18 @@ namespace osu.Game PreviewTrackManager previewTrackManager; dependencies.Cache(previewTrackManager = new PreviewTrackManager()); Add(previewTrackManager); + + Ruleset.BindValueChanged(onRulesetChanged); + } + + private void onRulesetChanged(ValueChangedEvent r) + { + var dict = new Dictionary>(); + + foreach (ModType type in Enum.GetValues(typeof(ModType))) dict[type] = r.NewValue?.CreateInstance().GetModsFor(type).ToList(); + + SelectedMods.Value = Array.Empty(); + AvailableMods.Value = dict; } protected virtual Container CreateScalingContainer() => new DrawSizePreservingFillContainer(); diff --git a/osu.Game/Overlays/Mods/ModControlSection.cs b/osu.Game/Overlays/Mods/ModControlSection.cs index f4b588ddb3..10b3bc7c2b 100644 --- a/osu.Game/Overlays/Mods/ModControlSection.cs +++ b/osu.Game/Overlays/Mods/ModControlSection.cs @@ -1,10 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Mods; @@ -12,14 +12,13 @@ using osuTK; namespace osu.Game.Overlays.Mods { - public class ModControlSection : Container + public class ModControlSection : CompositeDrawable { protected FillFlowContainer FlowContent; - protected override Container Content => FlowContent; public readonly Mod Mod; - public ModControlSection(Mod mod) + public ModControlSection(Mod mod, IEnumerable modControls) { Mod = mod; @@ -33,9 +32,8 @@ namespace osu.Game.Overlays.Mods Direction = FillDirection.Vertical, AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, + ChildrenEnumerable = modControls }; - - AddRange(Mod.CreateSettingsControls()); } [BackgroundDependencyLoader] diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index e8ea43e3f2..247f4a2947 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -20,7 +20,6 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Mods.Sections; -using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens; using osuTK; @@ -50,7 +49,7 @@ namespace osu.Game.Overlays.Mods protected readonly Bindable> SelectedMods = new Bindable>(Array.Empty()); - protected readonly IBindable Ruleset = new Bindable(); + private Bindable>> availableMods; protected Color4 LowMultiplierColour; protected Color4 HighMultiplierColour; @@ -322,14 +321,14 @@ namespace osu.Game.Overlays.Mods } [BackgroundDependencyLoader(true)] - private void load(OsuColour colours, IBindable ruleset, AudioManager audio, Bindable> mods) + private void load(OsuColour colours, AudioManager audio, Bindable> selectedMods, OsuGameBase osu) { LowMultiplierColour = colours.Red; HighMultiplierColour = colours.Green; UnrankedLabel.Colour = colours.Blue; - Ruleset.BindTo(ruleset); - if (mods != null) SelectedMods.BindTo(mods); + availableMods = osu.AvailableMods.GetBoundCopy(); + SelectedMods.BindTo(selectedMods); sampleOn = audio.Samples.Get(@"UI/check-on"); sampleOff = audio.Samples.Get(@"UI/check-off"); @@ -360,7 +359,7 @@ namespace osu.Game.Overlays.Mods { base.LoadComplete(); - Ruleset.BindValueChanged(rulesetChanged, true); + availableMods.BindValueChanged(availableModsChanged, true); SelectedMods.BindValueChanged(selectedModsChanged, true); } @@ -410,22 +409,12 @@ namespace osu.Game.Overlays.Mods return base.OnKeyDown(e); } - private void rulesetChanged(ValueChangedEvent e) + private void availableModsChanged(ValueChangedEvent>> mods) { - if (e.NewValue == null) return; - - var instance = e.NewValue.CreateInstance(); + if (mods.NewValue == null) return; foreach (var section in ModSectionsContainer.Children) - section.Mods = instance.GetModsFor(section.ModType); - - // attempt to re-select any already selected mods. - // this may be the first time we are receiving the ruleset, in which case they will still match. - selectedModsChanged(new ValueChangedEvent>(SelectedMods.Value, SelectedMods.Value)); - - // write the mods back to the SelectedMods bindable in the case a change was not applicable. - // this generally isn't required as the previous line will perform deselection; just here for safety. - refreshSelectedMods(); + section.Mods = mods.NewValue[section.ModType]; } private void selectedModsChanged(ValueChangedEvent> mods) @@ -462,17 +451,17 @@ namespace osu.Game.Overlays.Mods private void updateModSettings(ValueChangedEvent> selectedMods) { - foreach (var added in selectedMods.NewValue.Except(selectedMods.OldValue)) + ModSettingsContent.Clear(); + + foreach (var mod in selectedMods.NewValue) { - var controls = added.CreateSettingsControls().ToList(); - if (controls.Count > 0) - ModSettingsContent.Add(new ModControlSection(added) { Children = controls }); + var settings = mod.CreateSettingsControls().ToList(); + if (settings.Count > 0) + ModSettingsContent.Add(new ModControlSection(mod, settings)); } - foreach (var removed in selectedMods.OldValue.Except(selectedMods.NewValue)) - ModSettingsContent.RemoveAll(section => section.Mod == removed); + bool hasSettings = ModSettingsContent.Count > 0; - bool hasSettings = ModSettingsContent.Children.Count > 0; CustomiseButton.Enabled.Value = hasSettings; if (!hasSettings) @@ -502,7 +491,7 @@ namespace osu.Game.Overlays.Mods { base.Dispose(isDisposing); - Ruleset.UnbindAll(); + availableMods.UnbindAll(); SelectedMods.UnbindAll(); } From 7fdaf338f3af99870215902b66ff759228d53038 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Dec 2019 17:12:01 +0900 Subject: [PATCH 046/103] Fix test logic and add regression test --- .../UserInterface/TestSceneModSettings.cs | 84 ++++++++++++++----- 1 file changed, 62 insertions(+), 22 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs index fc44c5f595..8117a4ad78 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs @@ -2,15 +2,20 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; -using osu.Framework.Allocation; +using NUnit.Framework; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Mods; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; namespace osu.Game.Tests.Visual.UserInterface { @@ -18,28 +23,51 @@ namespace osu.Game.Tests.Visual.UserInterface { private TestModSelectOverlay modSelect; - [BackgroundDependencyLoader] - private void load() + Mod testCustomisableMod = new TestModCustomisable1(); + + [Test] + public void TestButtonShowsOnCustomisableMod() { - Add(modSelect = new TestModSelectOverlay - { - RelativeSizeAxes = Axes.X, - Origin = Anchor.BottomCentre, - Anchor = Anchor.BottomCentre, - }); + createModSelect(); - var testMod = new TestModCustomisable1(); - - AddStep("open", modSelect.Show); + AddStep("open", () => modSelect.Show()); AddAssert("button disabled", () => !modSelect.CustomiseButton.Enabled.Value); AddUntilStep("wait for button load", () => modSelect.ButtonsLoaded); - AddStep("select mod", () => modSelect.SelectMod(testMod)); + AddStep("select mod", () => modSelect.SelectMod(testCustomisableMod)); AddAssert("button enabled", () => modSelect.CustomiseButton.Enabled.Value); AddStep("open Customisation", () => modSelect.CustomiseButton.Click()); - AddStep("deselect mod", () => modSelect.SelectMod(testMod)); + AddStep("deselect mod", () => modSelect.SelectMod(testCustomisableMod)); AddAssert("controls hidden", () => modSelect.ModSettingsContainer.Alpha == 0); } + [Test] + public void TestButtonShowsOnModAlreadyAdded() + { + AddStep("set active mods", () => Mods.Value = new List { testCustomisableMod }); + + createModSelect(); + + AddAssert("mods still active", () => Mods.Value.Count == 1); + + AddStep("open", () => modSelect.Show()); + AddAssert("button enabled", () => modSelect.CustomiseButton.Enabled.Value); + } + + private void createModSelect() + { + AddStep("create mod select", () => + { + Ruleset.Value = new TestRulesetInfo(); + + Child = modSelect = new TestModSelectOverlay + { + RelativeSizeAxes = Axes.X, + Origin = Anchor.BottomCentre, + Anchor = Anchor.BottomCentre, + }; + }); + } + private class TestModSelectOverlay : ModSelectOverlay { public new Container ModSettingsContainer => base.ModSettingsContainer; @@ -50,24 +78,36 @@ namespace osu.Game.Tests.Visual.UserInterface public void SelectMod(Mod mod) => ModSectionsContainer.Children.Single(s => s.ModType == mod.Type) .ButtonsContainer.OfType().Single(b => b.Mods.Any(m => m.GetType() == mod.GetType())).SelectNext(1); + } - protected override void LoadComplete() + public class TestRulesetInfo : RulesetInfo + { + public override Ruleset CreateInstance() => new TestCustomisableModRuleset(); + + public class TestCustomisableModRuleset : Ruleset { - base.LoadComplete(); - - foreach (var section in ModSectionsContainer) + public override IEnumerable GetModsFor(ModType type) { - if (section.ModType == ModType.Conversion) + if (type == ModType.Conversion) { - section.Mods = new Mod[] + return new Mod[] { new TestModCustomisable1(), new TestModCustomisable2() }; } - else - section.Mods = Array.Empty(); + + return Array.Empty(); } + + public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList mods) => throw new NotImplementedException(); + + public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => throw new NotImplementedException(); + + public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => throw new NotImplementedException(); + + public override string Description { get; } + public override string ShortName { get; } } } From 76aa4f9fb2ecd55d0e209a197d73ad13f5370bbe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Dec 2019 17:36:37 +0900 Subject: [PATCH 047/103] Fix code style issues --- osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs index 8117a4ad78..d17408ff95 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs @@ -23,7 +23,7 @@ namespace osu.Game.Tests.Visual.UserInterface { private TestModSelectOverlay modSelect; - Mod testCustomisableMod = new TestModCustomisable1(); + private readonly Mod testCustomisableMod = new TestModCustomisable1(); [Test] public void TestButtonShowsOnCustomisableMod() @@ -106,8 +106,8 @@ namespace osu.Game.Tests.Visual.UserInterface public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => throw new NotImplementedException(); - public override string Description { get; } - public override string ShortName { get; } + public override string Description { get; } = "test"; + public override string ShortName { get; } = "tst"; } } From 8052aeb2383da8302aa677782acb4f2457edd200 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 13 Dec 2019 00:23:32 +0900 Subject: [PATCH 048/103] Fix potential nullref in disposal logic --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 247f4a2947..7f07ce620c 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -491,8 +491,8 @@ namespace osu.Game.Overlays.Mods { base.Dispose(isDisposing); - availableMods.UnbindAll(); - SelectedMods.UnbindAll(); + availableMods?.UnbindAll(); + SelectedMods?.UnbindAll(); } #endregion From bc311465609b5ee76dff71e4850feaa409863da6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 13 Dec 2019 21:45:38 +0900 Subject: [PATCH 049/103] Mods -> SelectedMods --- .../TestSceneAutoJuiceStream.cs | 2 +- .../TestSceneDrawableHitObjects.cs | 2 +- .../TestSceneDrawableHitObjectsHidden.cs | 2 +- osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs | 2 +- .../TestSceneHitCircleHidden.cs | 2 +- .../TestSceneOsuFlashlight.cs | 2 +- osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs | 2 +- osu.Game.Rulesets.Osu.Tests/TestSceneSliderHidden.cs | 2 +- osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs | 2 +- .../TestSceneSpinnerHidden.cs | 2 +- .../TestSceneTaikoSuddenDeath.cs | 2 +- .../Visual/Background/TestSceneUserDimBackgrounds.cs | 2 +- osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs | 2 +- .../Visual/Gameplay/TestSceneFailAnimation.cs | 2 +- .../Visual/Gameplay/TestSceneFailJudgement.cs | 2 +- .../Visual/Gameplay/TestSceneGameplayRewinding.cs | 2 +- .../Visual/Gameplay/TestScenePlayerLoader.cs | 6 +++--- .../Visual/SongSelect/TestScenePlaySongSelect.cs | 12 ++++++------ .../UserInterface/TestSceneModSelectOverlay.cs | 2 +- .../Visual/UserInterface/TestSceneModSettings.cs | 6 +++--- osu.Game/OsuGameBase.cs | 2 +- osu.Game/Tests/Visual/AllPlayersTestScene.cs | 2 +- osu.Game/Tests/Visual/OsuTestScene.cs | 6 +++--- osu.Game/Tests/Visual/PlayerTestScene.cs | 4 ++-- 24 files changed, 36 insertions(+), 36 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs index ab3c040b4e..74a9c05bf9 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Catch.Tests protected override Player CreatePlayer(Ruleset ruleset) { - Mods.Value = Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray(); + SelectedMods.Value = SelectedMods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray(); return base.CreatePlayer(ruleset); } } diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs index 02a017ce45..1eb913e900 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs @@ -151,7 +151,7 @@ namespace osu.Game.Rulesets.Catch.Tests private void addToPlayfield(DrawableCatchHitObject drawable) { - foreach (var mod in Mods.Value.OfType()) + foreach (var mod in SelectedMods.Value.OfType()) mod.ApplyToDrawableHitObjects(new[] { drawable }); drawableRuleset.Playfield.Add(drawable); diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjectsHidden.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjectsHidden.cs index 08107e01eb..8c3dfef39c 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjectsHidden.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjectsHidden.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Catch.Tests [SetUp] public void SetUp() => Schedule(() => { - Mods.Value = new[] { new CatchModHidden() }; + SelectedMods.Value = new[] { new CatchModHidden() }; }); } } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs index 64f353c4d9..098e277fff 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs @@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Tests var drawable = CreateDrawableHitCircle(circle, auto); - foreach (var mod in Mods.Value.OfType()) + foreach (var mod in SelectedMods.Value.OfType()) mod.ApplyToDrawableHitObjects(new[] { drawable }); return drawable; diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleHidden.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleHidden.cs index eaba82a469..21ebce8c23 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleHidden.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleHidden.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Tests [SetUp] public void SetUp() => Schedule(() => { - Mods.Value = new[] { new OsuModHidden() }; + SelectedMods.Value = new[] { new OsuModHidden() }; }); } } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuFlashlight.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuFlashlight.cs index 64e7632b1b..412effe176 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuFlashlight.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuFlashlight.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Osu.Tests { protected override Player CreatePlayer(Ruleset ruleset) { - Mods.Value = new Mod[] { new OsuModAutoplay(), new OsuModFlashlight(), }; + SelectedMods.Value = new Mod[] { new OsuModAutoplay(), new OsuModFlashlight(), }; return base.CreatePlayer(ruleset); } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs index a9d5c03517..e8386363be 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs @@ -362,7 +362,7 @@ namespace osu.Game.Rulesets.Osu.Tests var drawable = CreateDrawableSlider(slider); - foreach (var mod in Mods.Value.OfType()) + foreach (var mod in SelectedMods.Value.OfType()) mod.ApplyToDrawableHitObjects(new[] { drawable }); drawable.OnNewResult += onNewResult; diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderHidden.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderHidden.cs index a1795fb877..d0ee1bddb5 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderHidden.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderHidden.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Tests [SetUp] public void SetUp() => Schedule(() => { - Mods.Value = new[] { new OsuModHidden() }; + SelectedMods.Value = new[] { new OsuModHidden() }; }); } } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs index 3ed3f3e981..f53b64c729 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Tests Depth = depthIndex++ }; - foreach (var mod in Mods.Value.OfType()) + foreach (var mod in SelectedMods.Value.OfType()) mod.ApplyToDrawableHitObjects(new[] { drawable }); Add(drawable); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerHidden.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerHidden.cs index 2976d4fdce..dd863deed2 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerHidden.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerHidden.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Tests [SetUp] public void SetUp() => Schedule(() => { - Mods.Value = new[] { new OsuModHidden() }; + SelectedMods.Value = new[] { new OsuModHidden() }; }); } } diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs index d0db193738..140433a523 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Taiko.Tests protected override Player CreatePlayer(Ruleset ruleset) { - Mods.Value = Mods.Value.Concat(new[] { new TaikoModSuddenDeath() }).ToArray(); + SelectedMods.Value = SelectedMods.Value.Concat(new[] { new TaikoModSuddenDeath() }).ToArray(); return new ScoreAccessiblePlayer(); } diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index 2d2726bbd3..589ec7e8aa 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -280,7 +280,7 @@ namespace osu.Game.Tests.Visual.Background AddUntilStep("Song select has selection", () => songSelect.Carousel.SelectedBeatmap != null); AddStep("Set default user settings", () => { - Mods.Value = Mods.Value.Concat(new[] { new OsuModNoFail() }).ToArray(); + SelectedMods.Value = SelectedMods.Value.Concat(new[] { new OsuModNoFail() }).ToArray(); songSelect.DimLevel.Value = 0.7f; songSelect.BlurLevel.Value = 0.4f; }); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs index 5ee109e3dd..069b965d9b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs @@ -18,7 +18,7 @@ namespace osu.Game.Tests.Visual.Gameplay protected override Player CreatePlayer(Ruleset ruleset) { - Mods.Value = Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray(); + SelectedMods.Value = SelectedMods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray(); return new ScoreAccessiblePlayer(); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs index f4e8a68819..992c47f856 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs @@ -14,7 +14,7 @@ namespace osu.Game.Tests.Visual.Gameplay { protected override Player CreatePlayer(Ruleset ruleset) { - Mods.Value = Array.Empty(); + SelectedMods.Value = Array.Empty(); return new FailPlayer(); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs index cca6301b02..1580aac8c5 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs @@ -14,7 +14,7 @@ namespace osu.Game.Tests.Visual.Gameplay { protected override Player CreatePlayer(Ruleset ruleset) { - Mods.Value = Array.Empty(); + SelectedMods.Value = Array.Empty(); return new FailPlayer(); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs index b2b58a63fb..5336c720a1 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs @@ -67,7 +67,7 @@ namespace osu.Game.Tests.Visual.Gameplay protected override Player CreatePlayer(Ruleset ruleset) { - Mods.Value = Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray(); + SelectedMods.Value = SelectedMods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray(); return new RulesetExposingPlayer(); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index f02361e685..f68f4b8b83 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.Gameplay beforeLoadAction?.Invoke(); Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); - foreach (var mod in Mods.Value.OfType()) + foreach (var mod in SelectedMods.Value.OfType()) mod.ApplyToTrack(Beatmap.Value.Track); InputManager.Child = container = new TestPlayerLoaderContainer( @@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestEarlyExit() { - AddStep("load dummy beatmap", () => ResetPlayer(false, () => Mods.Value = new[] { new OsuModNightcore() })); + AddStep("load dummy beatmap", () => ResetPlayer(false, () => SelectedMods.Value = new[] { new OsuModNightcore() })); AddUntilStep("wait for current", () => loader.IsCurrentScreen()); AddAssert("mod rate applied", () => Beatmap.Value.Track.Rate != 1); AddStep("exit loader", () => loader.Exit()); @@ -123,7 +123,7 @@ namespace osu.Game.Tests.Visual.Gameplay TestMod playerMod1 = null; TestMod playerMod2 = null; - AddStep("load player", () => { ResetPlayer(true, () => Mods.Value = new[] { gameMod = new TestMod() }); }); + AddStep("load player", () => { ResetPlayer(true, () => SelectedMods.Value = new[] { gameMod = new TestMod() }); }); AddUntilStep("wait for loader to become current", () => loader.IsCurrentScreen()); AddStep("mouse in centre", () => InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre)); diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 5dd02c1ddd..00fa95bedc 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -256,17 +256,17 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("change ruleset", () => { - Mods.ValueChanged += onModChange; + SelectedMods.ValueChanged += onModChange; songSelect.Ruleset.ValueChanged += onRulesetChange; Ruleset.Value = new TaikoRuleset().RulesetInfo; - Mods.ValueChanged -= onModChange; + SelectedMods.ValueChanged -= onModChange; songSelect.Ruleset.ValueChanged -= onRulesetChange; }); AddAssert("mods changed before ruleset", () => modChangeIndex < rulesetChangeIndex); - AddAssert("empty mods", () => !Mods.Value.Any()); + AddAssert("empty mods", () => !SelectedMods.Value.Any()); void onModChange(ValueChangedEvent> e) => modChangeIndex = actionIndex++; void onRulesetChange(ValueChangedEvent e) => rulesetChangeIndex = actionIndex++; @@ -275,7 +275,7 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestModsRetainedBetweenSongSelect() { - AddAssert("empty mods", () => !Mods.Value.Any()); + AddAssert("empty mods", () => !SelectedMods.Value.Any()); createSongSelect(); @@ -285,7 +285,7 @@ namespace osu.Game.Tests.Visual.SongSelect createSongSelect(); - AddAssert("mods retained", () => Mods.Value.Any()); + AddAssert("mods retained", () => SelectedMods.Value.Any()); } [Test] @@ -332,7 +332,7 @@ namespace osu.Game.Tests.Visual.SongSelect private void checkMusicPlaying(bool playing) => AddUntilStep($"music {(playing ? "" : "not ")}playing", () => music.IsPlaying == playing); - private void changeMods(params Mod[] mods) => AddStep($"change mods to {string.Join(", ", mods.Select(m => m.Acronym))}", () => Mods.Value = mods); + private void changeMods(params Mod[] mods) => AddStep($"change mods to {string.Join(", ", mods.Select(m => m.Acronym))}", () => SelectedMods.Value = mods); private void changeRuleset(int id) => AddStep($"change ruleset to {id}", () => Ruleset.Value = rulesets.AvailableRulesets.First(r => r.ID == id)); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 2738b5b8fc..12ee4ceb2e 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -127,7 +127,7 @@ namespace osu.Game.Tests.Visual.UserInterface var noFailMod = new OsuRuleset().GetModsFor(ModType.DifficultyReduction).FirstOrDefault(m => m is OsuModNoFail); - AddStep("set mods externally", () => { Mods.Value = new[] { noFailMod }; }); + AddStep("set mods externally", () => { SelectedMods.Value = new[] { noFailMod }; }); changeRuleset(0); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs index d17408ff95..edc749cbaa 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs @@ -43,11 +43,11 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestButtonShowsOnModAlreadyAdded() { - AddStep("set active mods", () => Mods.Value = new List { testCustomisableMod }); + AddStep("set active mods", () => SelectedMods.Value = new List { testCustomisableMod }); createModSelect(); - AddAssert("mods still active", () => Mods.Value.Count == 1); + AddAssert("mods still active", () => SelectedMods.Value.Count == 1); AddStep("open", () => modSelect.Show()); AddAssert("button enabled", () => modSelect.CustomiseButton.Enabled.Value); @@ -100,7 +100,7 @@ namespace osu.Game.Tests.Visual.UserInterface return Array.Empty(); } - public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList mods) => throw new NotImplementedException(); + public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => throw new NotImplementedException(); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => throw new NotImplementedException(); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 78a035c5d6..834a64e597 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -81,7 +81,7 @@ namespace osu.Game // todo: move this to SongSelect once Screen has the ability to unsuspend. [Cached] [Cached(typeof(IBindable>))] - protected readonly Bindable> Mods = new Bindable>(Array.Empty()); + protected readonly Bindable> SelectedMods = new Bindable>(Array.Empty()); /// /// Mods available for the current . diff --git a/osu.Game/Tests/Visual/AllPlayersTestScene.cs b/osu.Game/Tests/Visual/AllPlayersTestScene.cs index b7d1979b0d..dd65c8c382 100644 --- a/osu.Game/Tests/Visual/AllPlayersTestScene.cs +++ b/osu.Game/Tests/Visual/AllPlayersTestScene.cs @@ -55,7 +55,7 @@ namespace osu.Game.Tests.Visual var working = CreateWorkingBeatmap(rulesetInfo); Beatmap.Value = working; - Mods.Value = new[] { ruleset.GetAllMods().First(m => m is ModNoFail) }; + SelectedMods.Value = new[] { ruleset.GetAllMods().First(m => m is ModNoFail) }; Player?.Exit(); Player = null; diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 4a561f17a3..18dbd212cc 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual protected Bindable Ruleset; - protected Bindable> Mods; + protected Bindable> SelectedMods; protected new OsuScreenDependencies Dependencies { get; private set; } @@ -72,8 +72,8 @@ namespace osu.Game.Tests.Visual Ruleset = Dependencies.Ruleset; Ruleset.SetDefault(); - Mods = Dependencies.Mods; - Mods.SetDefault(); + SelectedMods = Dependencies.Mods; + SelectedMods.SetDefault(); if (!UseOnlineAPI) { diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs index 2c5a51ca02..3ed65bee61 100644 --- a/osu.Game/Tests/Visual/PlayerTestScene.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -53,14 +53,14 @@ namespace osu.Game.Tests.Visual { var noFailMod = ruleset.GetAllMods().FirstOrDefault(m => m is ModNoFail); if (noFailMod != null) - Mods.Value = new[] { noFailMod }; + SelectedMods.Value = new[] { noFailMod }; } if (Autoplay) { var mod = ruleset.GetAutoplayMod(); if (mod != null) - Mods.Value = Mods.Value.Concat(mod.Yield()).ToArray(); + SelectedMods.Value = SelectedMods.Value.Concat(mod.Yield()).ToArray(); } Player = CreatePlayer(ruleset); From af1566285698e8341a998d8d2f8bf7dea65697dd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Dec 2019 17:45:11 +0900 Subject: [PATCH 050/103] Fix WindUp applying too much change --- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 4 +++- osu.Game/Rulesets/Mods/ModWindDown.cs | 8 ++++---- osu.Game/Rulesets/Mods/ModWindUp.cs | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index 5276c196f7..4ce364ef1a 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -45,6 +45,8 @@ namespace osu.Game.Rulesets.Mods { this.track = track; track.AddAdjustment(AdjustableProperty.Frequency, SpeedChange); + + FinalRate.TriggerChange(); } public virtual void ApplyToBeatmap(IBeatmap beatmap) @@ -67,6 +69,6 @@ namespace osu.Game.Rulesets.Mods /// /// The amount of adjustment to apply (from 0..1). private void applyAdjustment(double amount) => - SpeedChange.Value = 1 + (Math.Sign(FinalRate.Value) * Math.Clamp(amount, 0, 1) * Math.Abs(FinalRate.Value)); + SpeedChange.Value = 1 + (FinalRate.Value - 1) * Math.Clamp(amount, 0, 1); } } diff --git a/osu.Game/Rulesets/Mods/ModWindDown.cs b/osu.Game/Rulesets/Mods/ModWindDown.cs index 680c1a10fb..5416f1ac22 100644 --- a/osu.Game/Rulesets/Mods/ModWindDown.cs +++ b/osu.Game/Rulesets/Mods/ModWindDown.cs @@ -20,10 +20,10 @@ namespace osu.Game.Rulesets.Mods [SettingSource("Final rate", "The speed increase to ramp towards")] public override BindableNumber FinalRate { get; } = new BindableDouble { - MinValue = -0.5, - MaxValue = -0.01, - Default = -0.25, - Value = -0.25, + MinValue = 0.5, + MaxValue = 0.99, + Default = 0.75, + Value = 0.75, Precision = 0.01, }; diff --git a/osu.Game/Rulesets/Mods/ModWindUp.cs b/osu.Game/Rulesets/Mods/ModWindUp.cs index ca9ce0ea3e..3cf584f3dd 100644 --- a/osu.Game/Rulesets/Mods/ModWindUp.cs +++ b/osu.Game/Rulesets/Mods/ModWindUp.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Mods [SettingSource("Final rate", "The speed increase to ramp towards")] public override BindableNumber FinalRate { get; } = new BindableDouble { - MinValue = 0.01, + MinValue = 1.01, MaxValue = 2, Default = 1.5, Value = 1.5, From 72ea871bffd35b6cab8ac8b83a6015e628e22d80 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 12 Dec 2019 23:33:18 +0900 Subject: [PATCH 051/103] Make constructor protected --- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index 4ce364ef1a..133f9ceb39 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Mods private Track track; - public ModTimeRamp() + protected ModTimeRamp() { // for preview purpose at song select. eventually we'll want to be able to update every frame. FinalRate.BindValueChanged(val => applyAdjustment(1), true); From ef94df917ceb55dd7cdca2ce116495998d36ea7a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 13 Dec 2019 21:56:03 +0900 Subject: [PATCH 052/103] Add whitespace --- osu.Game/OsuGameBase.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 834a64e597..d9b569bf0e 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -246,7 +246,8 @@ namespace osu.Game { var dict = new Dictionary>(); - foreach (ModType type in Enum.GetValues(typeof(ModType))) dict[type] = r.NewValue?.CreateInstance().GetModsFor(type).ToList(); + foreach (ModType type in Enum.GetValues(typeof(ModType))) + dict[type] = r.NewValue?.CreateInstance().GetModsFor(type).ToList(); SelectedMods.Value = Array.Empty(); AvailableMods.Value = dict; From b94d5bf82eba5c96f0303e408caf170885a86c3b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 14 Dec 2019 00:42:31 +0900 Subject: [PATCH 053/103] Fix error when entering multiplayer game in different ruleset --- osu.Game/OsuGameBase.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index d9b569bf0e..21cc4eaccc 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -249,7 +249,8 @@ namespace osu.Game foreach (ModType type in Enum.GetValues(typeof(ModType))) dict[type] = r.NewValue?.CreateInstance().GetModsFor(type).ToList(); - SelectedMods.Value = Array.Empty(); + if (!SelectedMods.Disabled) + SelectedMods.Value = Array.Empty(); AvailableMods.Value = dict; } From ddb2cfc46de5ee7881aaa66c9e752109e935e021 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sat, 14 Dec 2019 18:02:56 +0800 Subject: [PATCH 054/103] Use GetEndTime in Taiko and Mania --- osu.Game.Rulesets.Mania/Difficulty/Skills/Individual.cs | 4 ++-- osu.Game.Rulesets.Mania/Difficulty/Skills/Overall.cs | 4 ++-- osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs | 3 +-- osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs | 5 +---- osu.Game/Rulesets/Objects/HitObject.cs | 2 +- 5 files changed, 7 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/Skills/Individual.cs b/osu.Game.Rulesets.Mania/Difficulty/Skills/Individual.cs index 059cd39641..4f7ab87fad 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/Skills/Individual.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/Skills/Individual.cs @@ -5,7 +5,7 @@ using System.Linq; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mania.Difficulty.Preprocessing; -using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Mania.Difficulty.Skills { @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills protected override double StrainValueOf(DifficultyHitObject current) { var maniaCurrent = (ManiaDifficultyHitObject)current; - var endTime = (maniaCurrent.BaseObject as HoldNote)?.EndTime ?? maniaCurrent.BaseObject.StartTime; + var endTime = maniaCurrent.BaseObject.GetEndTime(); try { diff --git a/osu.Game.Rulesets.Mania/Difficulty/Skills/Overall.cs b/osu.Game.Rulesets.Mania/Difficulty/Skills/Overall.cs index ed25173d38..bbbb93fd8b 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/Skills/Overall.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/Skills/Overall.cs @@ -4,7 +4,7 @@ using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mania.Difficulty.Preprocessing; -using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Mania.Difficulty.Skills { @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills protected override double StrainValueOf(DifficultyHitObject current) { var maniaCurrent = (ManiaDifficultyHitObject)current; - var endTime = (maniaCurrent.BaseObject as HoldNote)?.EndTime ?? maniaCurrent.BaseObject.StartTime; + var endTime = maniaCurrent.BaseObject.GetEndTime(); double holdFactor = 1.0; // Factor in case something else is held double holdAddition = 0; // Addition to the current note in case it's a hold and has to be released awkwardly diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs index 6f4fbd0651..c41727557b 100644 --- a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs @@ -3,7 +3,6 @@ using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Judgements; using osu.Game.Rulesets.Taiko.Scoring; @@ -38,7 +37,7 @@ namespace osu.Game.Rulesets.Taiko.Objects base.CreateNestedHitObjects(); if (IsStrong) - AddNested(new StrongHitObject { StartTime = (this as IHasEndTime)?.EndTime ?? StartTime }); + AddNested(new StrongHitObject { StartTime = this.GetEndTime() }); } public override Judgement CreateJudgement() => new TaikoJudgement(); diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs index e61953aeb8..4b234b56d4 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; using osu.Game.Replays; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Taiko.Beatmaps; @@ -39,9 +38,7 @@ namespace osu.Game.Rulesets.Taiko.Replays for (int i = 0; i < Beatmap.HitObjects.Count; i++) { TaikoHitObject h = Beatmap.HitObjects[i]; - - IHasEndTime endTimeData = h as IHasEndTime; - double endTime = endTimeData?.EndTime ?? h.StartTime; + double endTime = h.GetEndTime(); switch (h) { diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 1179efaa6e..bd96441ebb 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -104,7 +104,7 @@ namespace osu.Game.Rulesets.Objects ApplyDefaultsToSelf(controlPointInfo, difficulty); // This is done here since ApplyDefaultsToSelf may be used to determine the end time - SampleControlPoint = controlPointInfo.SamplePointAt(((this as IHasEndTime)?.EndTime ?? StartTime) + control_point_leniency); + SampleControlPoint = controlPointInfo.SamplePointAt(this.GetEndTime() + control_point_leniency); nestedHitObjects.Clear(); From 489d9dc7b5d1f56ca7429774fe44970106a0aed8 Mon Sep 17 00:00:00 2001 From: mcendu Date: Sat, 14 Dec 2019 18:33:56 +0800 Subject: [PATCH 055/103] Switch Expandtarget and "cursormiddle" --- osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs index 02152fa51e..e96bd29ad5 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs @@ -28,18 +28,18 @@ namespace osu.Game.Rulesets.Osu.Skinning InternalChildren = new[] { + ExpandTarget = new NonPlayfieldSprite + { + Texture = skin.GetTexture("cursor"), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, new NonPlayfieldSprite { Texture = skin.GetTexture("cursormiddle"), Anchor = Anchor.Centre, Origin = Anchor.Centre, }, - ExpandTarget = new NonPlayfieldSprite - { - Texture = skin.GetTexture("cursor"), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - } }; } From 41d4609c9238e26b8f4fb73a68bb381eff1cf04e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 15 Dec 2019 02:36:49 +0900 Subject: [PATCH 056/103] Fix crash on trying to retrieve mods from unavailable ruleset --- osu.Game/OsuGameBase.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 21cc4eaccc..22b8d9d012 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -246,8 +246,11 @@ namespace osu.Game { var dict = new Dictionary>(); - foreach (ModType type in Enum.GetValues(typeof(ModType))) - dict[type] = r.NewValue?.CreateInstance().GetModsFor(type).ToList(); + if (r.NewValue?.Available == true) + { + foreach (ModType type in Enum.GetValues(typeof(ModType))) + dict[type] = r.NewValue.CreateInstance().GetModsFor(type).ToList(); + } if (!SelectedMods.Disabled) SelectedMods.Value = Array.Empty(); From 244eb56455d5bbb7deb1afecdbbd6013f913294e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 15 Dec 2019 03:01:37 +0900 Subject: [PATCH 057/103] Fix test ruleset availability --- osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs index edc749cbaa..8dcb7dcbf8 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs @@ -84,6 +84,11 @@ namespace osu.Game.Tests.Visual.UserInterface { public override Ruleset CreateInstance() => new TestCustomisableModRuleset(); + public TestRulesetInfo() + { + Available = true; + } + public class TestCustomisableModRuleset : Ruleset { public override IEnumerable GetModsFor(ModType type) From 91bb851a7dbc1db8f470be81a7bd4506d9f45a17 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 15 Dec 2019 03:32:50 +0900 Subject: [PATCH 058/103] 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 8b266b08ba..239e1c2d31 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -54,6 +54,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 9ec833c9ac..56d65830f9 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -23,7 +23,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 1829cbe32a..a90e90db19 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -74,7 +74,7 @@ - + @@ -82,7 +82,7 @@ - + From a04f4b76bb93f9a94d06724cf803ac6a0b835a43 Mon Sep 17 00:00:00 2001 From: Joehu Date: Fri, 13 Dec 2019 22:27:14 -0800 Subject: [PATCH 059/103] Allow changing volume using alt when hovering scroll containers --- osu.Game/Graphics/Containers/OsuScrollContainer.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Graphics/Containers/OsuScrollContainer.cs b/osu.Game/Graphics/Containers/OsuScrollContainer.cs index 2721ce55dc..df4c3a3324 100644 --- a/osu.Game/Graphics/Containers/OsuScrollContainer.cs +++ b/osu.Game/Graphics/Containers/OsuScrollContainer.cs @@ -83,6 +83,13 @@ namespace osu.Game.Graphics.Containers return base.OnDragEnd(e); } + protected override bool OnScroll(ScrollEvent e) + { + if (e.AltPressed) return false; + + return base.OnScroll(e); + } + protected override ScrollbarContainer CreateScrollbar(Direction direction) => new OsuScrollbar(direction); protected class OsuScrollbar : ScrollbarContainer From 5af363c92043d81c86d5254e07ac986c06a4d5ad Mon Sep 17 00:00:00 2001 From: Joehu Date: Sat, 14 Dec 2019 12:58:13 -0800 Subject: [PATCH 060/103] Use default placeholder text on chat channel search box --- osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs b/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs index 505d2d6f89..25a9a51638 100644 --- a/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs +++ b/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs @@ -115,11 +115,7 @@ namespace osu.Game.Overlays.Chat.Selection Font = OsuFont.GetFont(size: 20), Shadow = false, }, - search = new HeaderSearchTextBox - { - RelativeSizeAxes = Axes.X, - PlaceholderText = @"Search", - }, + search = new HeaderSearchTextBox { RelativeSizeAxes = Axes.X }, }, }, }, From 19a3c959923328c8b91e4653ee003b101f60c770 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sat, 14 Dec 2019 21:27:26 +0800 Subject: [PATCH 061/103] Update InspectCode to 2019.3 --- build/InspectCode.cake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/InspectCode.cake b/build/InspectCode.cake index bd3fdf5f93..06c56dce87 100644 --- a/build/InspectCode.cake +++ b/build/InspectCode.cake @@ -1,5 +1,5 @@ #addin "nuget:?package=CodeFileSanity&version=0.0.33" -#addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2019.2.1" +#addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2019.3.0" #tool "nuget:?package=NVika.MSBuild&version=1.0.1" var nVikaToolPath = GetFiles("./tools/NVika.MSBuild.*/tools/NVika.exe").First(); From ab70abe8bd2b577c049cad84283f0b74e1dcbc60 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sat, 14 Dec 2019 21:28:13 +0800 Subject: [PATCH 062/103] Turn off unexpected new warnings. --- osu.Game/Screens/Select/BeatmapCarousel.cs | 4 ++++ osu.sln.DotSettings | 1 + 2 files changed, 5 insertions(+) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index ec524043ee..4acc619753 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -587,13 +587,16 @@ namespace osu.Game.Screens.Select switch (d) { case DrawableCarouselBeatmapSet set: + { lastSet = set; set.MoveToX(set.Item.State.Value == CarouselItemState.Selected ? -100 : 0, 500, Easing.OutExpo); set.MoveToY(currentY, 750, Easing.OutExpo); break; + } case DrawableCarouselBeatmap beatmap: + { if (beatmap.Item.State.Value == CarouselItemState.Selected) scrollTarget = currentY + beatmap.DrawHeight / 2 - DrawHeight / 2; @@ -619,6 +622,7 @@ namespace osu.Game.Screens.Select } break; + } } } diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 9b400de390..105d22fe3e 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -215,6 +215,7 @@ HINT HINT HINT + HINT DO_NOT_SHOW WARNING WARNING From 8b570233495c2c654b9493bb7d6415580c4c8174 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sat, 14 Dec 2019 20:22:39 +0800 Subject: [PATCH 063/103] Require 3.1.100 SDK in global.json --- global.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/global.json b/global.json index 43bb34912a..6858d4044d 100644 --- a/global.json +++ b/global.json @@ -1,4 +1,9 @@ { + "sdk": { + "allowPrerelease": false, + "rollForward": "minor", + "version": "3.1.100" + }, "msbuild-sdks": { "Microsoft.Build.Traversal": "2.0.24" } From cea3a66d4a0f9e235aa7d5e3966281507229cfc1 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sat, 14 Dec 2019 20:26:28 +0800 Subject: [PATCH 064/103] Use static local method fixed for roslyn 3.4 --- .../Visual/Online/TestSceneBeatmapSetOverlayDetails.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlayDetails.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlayDetails.cs index 2a45e68c0a..96c0c59695 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlayDetails.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlayDetails.cs @@ -44,7 +44,7 @@ namespace osu.Game.Tests.Visual.Online AddStep("set second set", () => details.BeatmapSet = secondSet); AddAssert("ratings set", () => details.Ratings.Metrics == secondSet.Metrics); - BeatmapSetInfo createSet() => new BeatmapSetInfo + static BeatmapSetInfo createSet() => new BeatmapSetInfo { Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).Select(_ => RNG.Next(10)).ToArray() }, Beatmaps = new List From c457571da6d69f41ac7722bda6532fa99722c611 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sat, 14 Dec 2019 20:54:22 +0800 Subject: [PATCH 065/103] Use index and range expressions --- .editorconfig | 4 ++-- osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs | 2 +- osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs | 6 +++--- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 2 +- osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs | 8 ++++---- .../Visual/UserInterface/TestSceneBeatSyncedContainer.cs | 4 ++-- osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs | 4 ++-- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 2 +- osu.Game/Graphics/Containers/LinkFlowContainer.cs | 2 +- .../Rulesets/Objects/Legacy/ConvertHitObjectParser.cs | 4 ++-- osu.Game/Rulesets/Objects/SliderPath.cs | 6 +++--- osu.sln.DotSettings | 2 +- 12 files changed, 23 insertions(+), 23 deletions(-) diff --git a/.editorconfig b/.editorconfig index b5333ad8e7..8cdb92d11c 100644 --- a/.editorconfig +++ b/.editorconfig @@ -176,8 +176,8 @@ 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 = false:none -csharp_style_prefer_range_operator = false:none +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 diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 9069c09ae4..6e4491de94 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps prevNoteTimes.RemoveAt(0); prevNoteTimes.Add(newNoteTime); - density = (prevNoteTimes[prevNoteTimes.Count - 1] - prevNoteTimes[0]) / prevNoteTimes.Count; + density = (prevNoteTimes[^1] - prevNoteTimes[0]) / prevNoteTimes.Count; } private double lastTime; diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs index 5f75cbabec..b6fc9821a4 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs @@ -286,11 +286,11 @@ namespace osu.Game.Rulesets.Osu.Tests private bool assertGreatJudge() => judgementResults.Last().Type == HitResult.Great; - private bool assertHeadMissTailTracked() => judgementResults[judgementResults.Count - 2].Type == HitResult.Great && judgementResults.First().Type == HitResult.Miss; + private bool assertHeadMissTailTracked() => judgementResults[^2].Type == HitResult.Great && judgementResults.First().Type == HitResult.Miss; - private bool assertMidSliderJudgements() => judgementResults[judgementResults.Count - 2].Type == HitResult.Great; + private bool assertMidSliderJudgements() => judgementResults[^2].Type == HitResult.Great; - private bool assertMidSliderJudgementFail() => judgementResults[judgementResults.Count - 2].Type == HitResult.Miss; + private bool assertMidSliderJudgementFail() => judgementResults[^2].Type == HitResult.Miss; private ScoreAccessibleReplayPlayer currentPlayer; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 9b820261ab..2497e428fc 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders protected override bool OnDoubleClick(DoubleClickEvent e) { // Todo: This should all not occur on double click, but rather if the previous control point is hovered. - segmentStart = HitObject.Path.ControlPoints[HitObject.Path.ControlPoints.Count - 1]; + segmentStart = HitObject.Path.ControlPoints[^1]; segmentStart.Type.Value = PathType.Linear; currentSegmentLength = 1; diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index bd59e8a03f..2686ba4fd2 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -156,9 +156,9 @@ namespace osu.Game.Rulesets.Osu.Replays // TODO: Shouldn't the spinner always spin in the same direction? if (h is Spinner) { - calcSpinnerStartPosAndDirection(((OsuReplayFrame)Frames[Frames.Count - 1]).Position, out startPosition, out spinnerDirection); + calcSpinnerStartPosAndDirection(((OsuReplayFrame)Frames[^1]).Position, out startPosition, out spinnerDirection); - Vector2 spinCentreOffset = SPINNER_CENTRE - ((OsuReplayFrame)Frames[Frames.Count - 1]).Position; + Vector2 spinCentreOffset = SPINNER_CENTRE - ((OsuReplayFrame)Frames[^1]).Position; if (spinCentreOffset.Length > SPIN_RADIUS) { @@ -230,7 +230,7 @@ namespace osu.Game.Rulesets.Osu.Replays private void moveToHitObject(OsuHitObject h, Vector2 targetPos, Easing easing) { - OsuReplayFrame lastFrame = (OsuReplayFrame)Frames[Frames.Count - 1]; + OsuReplayFrame lastFrame = (OsuReplayFrame)Frames[^1]; // Wait until Auto could "see and react" to the next note. double waitTime = h.StartTime - Math.Max(0.0, h.TimePreempt - reactionTime); @@ -363,7 +363,7 @@ namespace osu.Game.Rulesets.Osu.Replays } // We only want to let go of our button if we are at the end of the current replay. Otherwise something is still going on after us so we need to keep the button pressed! - if (Frames[Frames.Count - 1].Time <= endFrame.Time) + if (Frames[^1].Time <= endFrame.Time) AddFrameToReplay(endFrame); } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs index ed44d82bce..b0b673d6a4 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs @@ -157,7 +157,7 @@ namespace osu.Game.Tests.Visual.UserInterface private TimingControlPoint getNextTimingPoint(TimingControlPoint current) { - if (timingPoints[timingPoints.Count - 1] == current) + if (timingPoints[^1] == current) return current; int index = timingPoints.IndexOf(current); // -1 means that this is a "default beat" @@ -169,7 +169,7 @@ namespace osu.Game.Tests.Visual.UserInterface { if (timingPoints.Count == 0) return 0; - if (timingPoints[timingPoints.Count - 1] == current) + if (timingPoints[^1] == current) return (int)Math.Ceiling((Beatmap.Value.Track.Length - current.Time) / current.BeatLength); return (int)Math.Ceiling((getNextTimingPoint(current).Time - current.Time) / current.BeatLength); diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index ce2783004c..03496952e7 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -195,8 +195,8 @@ namespace osu.Game.Beatmaps.ControlPoints if (time < list[0].Time) return null; - if (time >= list[list.Count - 1].Time) - return list[list.Count - 1]; + if (time >= list[^1].Time) + return list[^1]; int l = 0; int r = list.Count - 2; diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 2b914669cb..e401e3fb97 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -34,7 +34,7 @@ namespace osu.Game.Beatmaps.Formats if (line.StartsWith(@"[", StringComparison.Ordinal) && line.EndsWith(@"]", StringComparison.Ordinal)) { - if (!Enum.TryParse(line.Substring(1, line.Length - 2), out section)) + if (!Enum.TryParse(line[1..^1], out section)) { Logger.Log($"Unknown section \"{line}\" in \"{output}\""); section = Section.None; diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index 2bbac92f7f..9735f6373d 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -37,7 +37,7 @@ namespace osu.Game.Graphics.Containers foreach (var link in links) { - AddText(text.Substring(previousLinkEnd, link.Index - previousLinkEnd)); + AddText(text[previousLinkEnd..link.Index]); AddLink(text.Substring(link.Index, link.Length), link.Action, link.Argument ?? link.Url); previousLinkEnd = link.Index + link.Length; } diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index b5b1e26486..7fddb442d1 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -184,7 +184,7 @@ namespace osu.Game.Rulesets.Objects.Legacy result = CreateSlider(pos, combo, comboOffset, convertControlPoints(points, pathType), length, repeatCount, nodeSamples); // The samples are played when the slider ends, which is the last node - result.Samples = nodeSamples[nodeSamples.Count - 1]; + result.Samples = nodeSamples[^1]; } else if (type.HasFlag(ConvertHitObjectType.Spinner)) { @@ -279,7 +279,7 @@ namespace osu.Game.Rulesets.Objects.Legacy { if (vertices[i] == vertices[i - 1]) { - points[points.Count - 1].Type.Value = type; + points[^1].Type.Value = type; continue; } diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index 86deba3b93..293138097f 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -92,7 +92,7 @@ namespace osu.Game.Rulesets.Objects get { ensureValid(); - return cumulativeLength.Count == 0 ? 0 : cumulativeLength[cumulativeLength.Count - 1]; + return cumulativeLength.Count == 0 ? 0 : cumulativeLength[^1]; } } @@ -251,7 +251,7 @@ namespace osu.Game.Rulesets.Objects if (calculatedLength > expectedDistance) { // The path will be shortened further, in which case we should trim any more unnecessary lengths and their associated path segments - while (cumulativeLength.Count > 0 && cumulativeLength[cumulativeLength.Count - 1] >= expectedDistance) + while (cumulativeLength.Count > 0 && cumulativeLength[^1] >= expectedDistance) { cumulativeLength.RemoveAt(cumulativeLength.Count - 1); calculatedPath.RemoveAt(pathEndIndex--); @@ -269,7 +269,7 @@ namespace osu.Game.Rulesets.Objects // The direction of the segment to shorten or lengthen Vector2 dir = (calculatedPath[pathEndIndex] - calculatedPath[pathEndIndex - 1]).Normalized(); - calculatedPath[pathEndIndex] = calculatedPath[pathEndIndex - 1] + dir * (float)(expectedDistance - cumulativeLength[cumulativeLength.Count - 1]); + calculatedPath[pathEndIndex] = calculatedPath[pathEndIndex - 1] + dir * (float)(expectedDistance - cumulativeLength[^1]); cumulativeLength.Add(expectedDistance); } } diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 105d22fe3e..12571be31d 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -220,7 +220,7 @@ WARNING WARNING WARNING - DO_NOT_SHOW + WARNING WARNING True From 9062fe1935b114fd214b5d84f9319fbdb7cd2053 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 Dec 2019 13:32:53 +0900 Subject: [PATCH 066/103] Fix crashes on custom skins due to extension-less file lookups --- osu.Game/Skinning/LegacySkinResourceStore.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Skinning/LegacySkinResourceStore.cs b/osu.Game/Skinning/LegacySkinResourceStore.cs index 72f3b9ed78..7c799d9c89 100644 --- a/osu.Game/Skinning/LegacySkinResourceStore.cs +++ b/osu.Game/Skinning/LegacySkinResourceStore.cs @@ -22,10 +22,8 @@ namespace osu.Game.Skinning if (source.Files == null) return null; - bool hasExtension = filename.Contains('.'); - var file = source.Files.Find(f => - string.Equals(hasExtension ? f.Filename : Path.ChangeExtension(f.Filename, null), filename, StringComparison.InvariantCultureIgnoreCase)); + string.Equals(f.Filename, filename, StringComparison.InvariantCultureIgnoreCase)); return file?.FileInfo.StoragePath; } From befb78f83b890f847b16cbcd2c52310bc7d15975 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 Dec 2019 14:01:08 +0900 Subject: [PATCH 067/103] Simplify LegacySkinResourceStore by deriving from ResourceStore --- osu.Game/Skinning/LegacySkinResourceStore.cs | 60 ++++---------------- 1 file changed, 12 insertions(+), 48 deletions(-) diff --git a/osu.Game/Skinning/LegacySkinResourceStore.cs b/osu.Game/Skinning/LegacySkinResourceStore.cs index 7c799d9c89..79a4e2e932 100644 --- a/osu.Game/Skinning/LegacySkinResourceStore.cs +++ b/osu.Game/Skinning/LegacySkinResourceStore.cs @@ -3,75 +3,39 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Threading.Tasks; using osu.Framework.IO.Stores; using osu.Game.Database; namespace osu.Game.Skinning { - public class LegacySkinResourceStore : IResourceStore + public class LegacySkinResourceStore : ResourceStore where T : INamedFileInfo { private readonly IHasFiles source; - private readonly IResourceStore underlyingStore; - - private string getPathForFile(string filename) - { - if (source.Files == null) - return null; - - var file = source.Files.Find(f => - string.Equals(f.Filename, filename, StringComparison.InvariantCultureIgnoreCase)); - return file?.FileInfo.StoragePath; - } public LegacySkinResourceStore(IHasFiles source, IResourceStore underlyingStore) + : base(underlyingStore) { this.source = source; - this.underlyingStore = underlyingStore; } - public Stream GetStream(string name) + protected override IEnumerable GetFilenames(string name) { - string path = getPathForFile(name); - return path == null ? null : underlyingStore.GetStream(path); - } + if (source.Files == null) + yield break; - public IEnumerable GetAvailableResources() => source.Files.Select(f => f.Filename); - - byte[] IResourceStore.Get(string name) => GetAsync(name).Result; - - public Task GetAsync(string name) - { - string path = getPathForFile(name); - return path == null ? Task.FromResult(null) : underlyingStore.GetAsync(path); - } - - #region IDisposable Support - - private bool isDisposed; - - protected virtual void Dispose(bool disposing) - { - if (!isDisposed) + foreach (var filename in base.GetFilenames(name)) { - isDisposed = true; + var path = getPathForFile(filename); + if (path != null) + yield return path; } } - ~LegacySkinResourceStore() - { - Dispose(false); - } + private string getPathForFile(string filename) => + source.Files.Find(f => string.Equals(f.Filename, filename, StringComparison.InvariantCultureIgnoreCase))?.FileInfo.StoragePath; - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - #endregion + public override IEnumerable GetAvailableResources() => source.Files.Select(f => f.Filename); } } From db3dc4f3755e7254c4c039ea48f23c7a2edb1bb7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 16 Dec 2019 15:15:26 +0900 Subject: [PATCH 068/103] Optimise cursortrail with custom vertex logic --- .../UI/Cursor/CursorTrail.cs | 70 +++++++++++-------- 1 file changed, 39 insertions(+), 31 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index 80291c002e..4d6db83d7a 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -174,7 +174,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private void addPart(Vector2 screenSpacePosition) { parts[currentIndex].Position = screenSpacePosition; - parts[currentIndex].Time = time; + parts[currentIndex].Time = time + 1; ++parts[currentIndex].InvalidationID; currentIndex = (currentIndex + 1) % max_sprites; @@ -201,7 +201,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private readonly TrailPart[] parts = new TrailPart[max_sprites]; private Vector2 size; - private readonly TrailBatch vertexBatch = new TrailBatch(max_sprites, 1); + private readonly QuadBatch vertexBatch = new QuadBatch(max_sprites, 1); public TrailDrawNode(CursorTrail source) : base(source) @@ -227,23 +227,50 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor shader.Bind(); shader.GetUniform("g_FadeClock").UpdateValue(ref time); - for (int i = 0; i < parts.Length; ++i) + RectangleF textureRect = texture.GetTextureRect(); + + foreach (var part in parts) { - if (parts[i].InvalidationID == -1) + if (part.InvalidationID == -1) continue; - vertexBatch.DrawTime = parts[i].Time; + if (time - part.Time >= 1) + continue; - Vector2 pos = parts[i].Position; + vertexBatch.Add(new TexturedTrailVertex + { + Position = new Vector2(part.Position.X - size.X / 2, part.Position.Y + size.Y / 2), + TexturePosition = textureRect.BottomLeft, + Colour = DrawColourInfo.Colour.BottomLeft.Linear, + Time = part.Time + }); - DrawQuad( - texture, - new Quad(pos.X - size.X / 2, pos.Y - size.Y / 2, size.X, size.Y), - DrawColourInfo.Colour, - null, - vertexBatch.AddAction); + vertexBatch.Add(new TexturedTrailVertex + { + Position = new Vector2(part.Position.X + size.X / 2, part.Position.Y + size.Y / 2), + TexturePosition = textureRect.BottomRight, + Colour = DrawColourInfo.Colour.BottomRight.Linear, + Time = part.Time + }); + + vertexBatch.Add(new TexturedTrailVertex + { + Position = new Vector2(part.Position.X + size.X / 2, part.Position.Y - size.Y / 2), + TexturePosition = textureRect.TopRight, + Colour = DrawColourInfo.Colour.TopRight.Linear, + Time = part.Time + }); + + vertexBatch.Add(new TexturedTrailVertex + { + Position = new Vector2(part.Position.X - size.X / 2, part.Position.Y - size.Y / 2), + TexturePosition = textureRect.TopLeft, + Colour = DrawColourInfo.Colour.TopLeft.Linear, + Time = part.Time + }); } + vertexBatch.Draw(); shader.Unbind(); } @@ -253,25 +280,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor vertexBatch.Dispose(); } - - // Todo: This shouldn't exist, but is currently used to reduce allocations by caching variable-capturing closures. - private class TrailBatch : QuadBatch - { - public new readonly Action AddAction; - public float DrawTime; - - public TrailBatch(int size, int maxBuffers) - : base(size, maxBuffers) - { - AddAction = v => Add(new TexturedTrailVertex - { - Position = v.Position, - TexturePosition = v.TexturePosition, - Time = DrawTime + 1, - Colour = v.Colour, - }); - } - } } [StructLayout(LayoutKind.Sequential)] From a554ca728bf0b8e4f7db9034e08a908c7c549dc5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 16 Dec 2019 15:27:54 +0900 Subject: [PATCH 069/103] Don't reuse the same control point references --- osu.Game.Rulesets.Catch/Objects/JuiceStream.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Slider.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index d5d99640af..a4ed966abb 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -128,7 +128,7 @@ namespace osu.Game.Rulesets.Catch.Objects if (value != null) { - path.ControlPoints.AddRange(value.ControlPoints); + path.ControlPoints.AddRange(value.ControlPoints.Select(c => new PathControlPoint(c.Position.Value, c.Type.Value))); path.ExpectedDistance.Value = value.ExpectedDistance.Value; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 34e5a7f3cd..fe65ab78d1 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.Objects if (value != null) { - path.ControlPoints.AddRange(value.ControlPoints); + path.ControlPoints.AddRange(value.ControlPoints.Select(c => new PathControlPoint(c.Position.Value, c.Type.Value))); path.ExpectedDistance.Value = value.ExpectedDistance.Value; } } From fef1877095ff240dbb65bb6d020cc376d965e482 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 16 Dec 2019 07:36:30 +0000 Subject: [PATCH 070/103] Bump ppy.osu.Game.Resources from 2019.1010.0 to 2019.1215.0 Bumps [ppy.osu.Game.Resources](https://github.com/ppy/osu-resources) from 2019.1010.0 to 2019.1215.0. - [Release notes](https://github.com/ppy/osu-resources/releases) - [Commits](https://github.com/ppy/osu-resources/compare/2019.1010.0...2019.1215.0) Signed-off-by: dependabot-preview[bot] --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 239e1c2d31..abb3cc8244 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -53,7 +53,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 56d65830f9..e5f34b1c7e 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -22,7 +22,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index a90e90db19..c84e617285 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -73,7 +73,7 @@ - + From add04e98e18d734e84fd88a1aad35fe71a5c90ba Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 16 Dec 2019 18:10:44 +0900 Subject: [PATCH 071/103] Fix cursortrail texture not being bound --- osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index 4d6db83d7a..4e86662ec6 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -227,6 +227,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor shader.Bind(); shader.GetUniform("g_FadeClock").UpdateValue(ref time); + texture.TextureGL.Bind(); + RectangleF textureRect = texture.GetTextureRect(); foreach (var part in parts) From 83f77d9c3510cd4c68f1574c2a90993d04932779 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 16 Dec 2019 18:41:02 +0900 Subject: [PATCH 072/103] Make the layout faster --- osu.Game/Screens/Play/HUDOverlay.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index dc32fc7cd5..840c1d37e3 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -24,7 +24,7 @@ namespace osu.Game.Screens.Play { public class HUDOverlay : Container { - private const int fade_duration = 400; + private const float fade_duration = 400; private const Easing fade_easing = Easing.Out; public readonly KeyCounterDisplay KeyCounter; @@ -103,8 +103,8 @@ namespace osu.Game.Screens.Play Origin = Anchor.BottomRight, Position = -new Vector2(5, TwoLayerButton.SIZE_RETRACTED.Y), AutoSizeAxes = Axes.Both, - AutoSizeDuration = fade_duration, - AutoSizeEasing = fade_easing, + LayoutDuration = fade_duration / 2, + LayoutEasing = fade_easing, Direction = FillDirection.Vertical, Children = new Drawable[] { From a276643a4bcc8a2a6d376ec43fee0ce816cddea2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 16 Dec 2019 18:41:14 +0900 Subject: [PATCH 073/103] Reorder health display and score elements --- osu.Game/Screens/Play/HUDOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 840c1d37e3..e2f362780d 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -78,6 +78,7 @@ namespace osu.Game.Screens.Play RelativeSizeAxes = Axes.Both, Children = new Drawable[] { + HealthDisplay = CreateHealthDisplay(), topScoreContainer = new Container { Anchor = Anchor.TopCentre, @@ -90,7 +91,6 @@ namespace osu.Game.Screens.Play ComboCounter = CreateComboCounter(), }, }, - HealthDisplay = CreateHealthDisplay(), Progress = CreateProgress(), ModDisplay = CreateModsContainer(), HitErrorDisplay = CreateHitErrorDisplayOverlay(), From a85653ebec2607ca2675a1b106ebcd814c75fa66 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Dec 2019 12:24:59 +0900 Subject: [PATCH 074/103] Add comment --- osu.Game/Graphics/Containers/OsuScrollContainer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Graphics/Containers/OsuScrollContainer.cs b/osu.Game/Graphics/Containers/OsuScrollContainer.cs index df4c3a3324..ab72276ad0 100644 --- a/osu.Game/Graphics/Containers/OsuScrollContainer.cs +++ b/osu.Game/Graphics/Containers/OsuScrollContainer.cs @@ -85,6 +85,8 @@ namespace osu.Game.Graphics.Containers protected override bool OnScroll(ScrollEvent e) { + // allow for controlling volume when alt is held. + // mostly for compatibility with osu-stable. if (e.AltPressed) return false; return base.OnScroll(e); From f8ffa676931ebdc01946520c5cf5072ac7caf2f2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Dec 2019 13:21:23 +0900 Subject: [PATCH 075/103] Add test and isolate ignore bindable from EnableUserDim --- .../Visual/Background/TestSceneUserDimContainer.cs | 14 ++++++++++++++ osu.Game/Graphics/Containers/UserDimContainer.cs | 12 ++---------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs index 472c43096f..d5d4c7e5ec 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs @@ -88,6 +88,20 @@ namespace osu.Game.Tests.Visual.Background AddUntilStep("not lightened", () => userDimContainer.DimEqual(test_user_dim)); } + [Test] + public void TestIgnoreUserSettings() + { + AddStep("set dim level 0.6", () => userDimContainer.UserDimLevel.Value = test_user_dim); + AddUntilStep("dim reached", () => userDimContainer.DimEqual(test_user_dim)); + + AddStep($"ignore settings", () => userDimContainer.IgnoreUserSettings.Value = true); + AddUntilStep("no dim", () => userDimContainer.DimEqual(0)); + AddStep("set break", () => isBreakTime.Value = true); + AddAssert("no dim", () => userDimContainer.DimEqual(0)); + AddStep("clear break", () => isBreakTime.Value = false); + AddAssert("no dim", () => userDimContainer.DimEqual(0)); + } + private class TestUserDimContainer : UserDimContainer { public bool DimEqual(float expectedDimLevel) => Content.Colour == OsuColour.Gray(1f - expectedDimLevel); diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs index e44e7a0d57..65c104b92f 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -59,7 +59,7 @@ namespace osu.Game.Graphics.Containers private float breakLightening => LightenDuringBreaks.Value && IsBreakTime.Value ? BREAK_LIGHTEN_AMOUNT : 0; - protected float DimLevel => Math.Max(EnableUserDim.Value ? (float)UserDimLevel.Value - breakLightening : 0, 0); + protected float DimLevel => Math.Max(EnableUserDim.Value && !IgnoreUserSettings.Value ? (float)UserDimLevel.Value - breakLightening : 0, 0); protected override Container Content => dimContent; @@ -88,7 +88,7 @@ namespace osu.Game.Graphics.Containers ShowStoryboard.ValueChanged += _ => UpdateVisuals(); ShowVideo.ValueChanged += _ => UpdateVisuals(); StoryboardReplacesBackground.ValueChanged += _ => UpdateVisuals(); - IgnoreUserSettings.ValueChanged += _ => updateSettings(); + IgnoreUserSettings.ValueChanged += _ => UpdateVisuals(); } protected override void LoadComplete() @@ -112,13 +112,5 @@ namespace osu.Game.Graphics.Containers dimContent.FadeTo(ContentDisplayed ? 1 : 0, BACKGROUND_FADE_DURATION, Easing.OutQuint); dimContent.FadeColour(OsuColour.Gray(1f - DimLevel), BACKGROUND_FADE_DURATION, Easing.OutQuint); } - - /// - /// Invoked when the IgnoreUserSettings bindable is changed - /// - private void updateSettings() - { - EnableUserDim.Value = !IgnoreUserSettings.Value; - } } } From 46dc2251e88703029e0234c18794850a5c1475e9 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 28 Nov 2019 21:39:33 +0800 Subject: [PATCH 076/103] Add fxcop with every violated rule off. --- CodeAnalysis/osu.ruleset | 63 ++++++++++++++++++++++++++++++++++++++++ Directory.Build.props | 4 +++ osu.sln | 1 + 3 files changed, 68 insertions(+) create mode 100644 CodeAnalysis/osu.ruleset diff --git a/CodeAnalysis/osu.ruleset b/CodeAnalysis/osu.ruleset new file mode 100644 index 0000000000..9bcca40983 --- /dev/null +++ b/CodeAnalysis/osu.ruleset @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Directory.Build.props b/Directory.Build.props index c0d740bac1..27a0bd0d48 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -18,7 +18,11 @@ + + + $(MSBuildThisFileDirectory)CodeAnalysis\osu.ruleset + true $(NoWarn);CS1591 diff --git a/osu.sln b/osu.sln index 1f4faae6b9..79823848f0 100644 --- a/osu.sln +++ b/osu.sln @@ -60,6 +60,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution global.json = global.json osu.Android.props = osu.Android.props osu.iOS.props = osu.iOS.props + CodeAnalysis\osu.ruleset = CodeAnalysis\osu.ruleset osu.sln.DotSettings = osu.sln.DotSettings osu.TestProject.props = osu.TestProject.props EndProjectSection From 9875fcea9971470479006fe81bf15103142fdb79 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Dec 2019 13:54:25 +0900 Subject: [PATCH 077/103] Add numbers to old skin for better identification --- .../Resources/old-skin/default-0.png | Bin 0 -> 2003 bytes .../Resources/old-skin/default-1.png | Bin 0 -> 1191 bytes .../Resources/old-skin/default-2.png | Bin 0 -> 1756 bytes .../Resources/old-skin/default-3.png | Bin 0 -> 1822 bytes .../Resources/old-skin/default-4.png | Bin 0 -> 1814 bytes .../Resources/old-skin/default-5.png | Bin 0 -> 1848 bytes .../Resources/old-skin/default-6.png | Bin 0 -> 2014 bytes .../Resources/old-skin/default-7.png | Bin 0 -> 1452 bytes .../Resources/old-skin/default-8.png | Bin 0 -> 1953 bytes .../Resources/old-skin/default-9.png | Bin 0 -> 1814 bytes 10 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-0.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-1.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-2.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-3.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-4.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-5.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-6.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-7.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-8.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-9.png diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-0.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-0.png new file mode 100644 index 0000000000000000000000000000000000000000..2af0569bcb30f4e87135669268698a79ac486f1f GIT binary patch literal 2003 zcmV;^2Q2uBP)0&s!{Eg(K)vw7OGA_klVuXw5~tP0kR@Dz8%i8MTl@(y z-HHy1)1^gl{JB-y+HK0-x1Q6y9t-`>_Z71(JIO;iA0MaZIq(1befqh%Io-c~gz5eR zLm$Gm41gB_aex&76Cer@2?%HRpx7S*9suqGCK+)4NX?g`CPir6_!2+@zzkReSO<_@ z^LM}y;5NVxn2{xn2}vVXR=YR*Dp~j2@G20fY&*JmX?;P$jC^e%tp`?UA%a4 zu&1YI6!hJjnwo0bzI}UET3VV}Mj~dj*+MQgfi!+r62RUDG$O!&o84~r!#T;(-`_t1 zr}!~nhvLwoLto3R)z;S5PyYV{z;cY#9Qpu~Nd?qsv4QUH?i(a@TN=^W5s+ zINU!W^Bs7wM5WSLKIbQq$h?xz7Su@L?Afz4dAr6`C(!syOAst9EQ}-NzzRxYHNR=s zu3gdz9R@X$wA};TU93o+l9ECXIGU4)pvhf!cD7k|;j34#(ya4GsxqM->FMe5LV4gL zVPg8_OE1)@(z$czD2ZQ!RAic(6H!r7MxjXaEf~YS645tVd*DWlMgMq^+n4fRFqaBN zw+4T#B&sA$0s|IFmu&!-QWAoGYhn^jPDVP-76R5i1;za`zg0#@8#V6#F((n_Uwzp! z=|!zp>m;o(75s~)Lz|tQ^^&QVlUPa%5!ppYMn;@M4{82_Uat>l%{8u0(%&~UG(-cW zi<2;Dk!NRT=LCrlP6euz!W#{;MOJ{}4oNST81*z4??Raas~UBiH*Z!|=0c@7N~ZTU zlfV`WRaXT@?cX?%NY*HM2PN^RS`t%JQ`81LLF~c6YZV+DHf%7fkUDkec52vedXomh zJW`-adE6e;ka%ICUKk*YCz)zgskynCR*g47l^%+c7!wjX z#l<_Bu4KVXl)`fOc$3fT>+5sW)GNo19pjlYI-)yb?&RjhJ6b*#`!ZjEM#_GDjmH4N zBdzDEg!iiHq8~kaw2OEp?4V4EB+$Y~_=QuaPDzp2wrv}i1{PPZa%sRPM&k^;h2Lwj zSfo2B!^6W4^4tR5xD{0CQJ?~4lo^Ze#EBCQ{70oA`X}+PQNlZN#-x8x~2pS*Ol7_U_%=En^qhTp*W9bx$4#6z~h!0;;X8EmEUKq=VAO z4EmXP6fPuFsMD)#Hk&IUA>j|u;WbLWtfZu5?bz7ZIC!_G5{GOg3pBG6=N{Y_U&tI!1Q-ydi0I`H>!)LlD z45>cBG@7uSp104&w z{3XU<8rTAj@Oe6P7}95XE9d0NleZwRn%uN$)5`{fAtKZS&YU@Osj{-Nqqnzrh9r|; z00x5*STNrSBoQ>!hK7b5aI3jmt1@(-*mT1twD2$ir-<9c^HomT($(d0h7A8xHu!Y zZ17;uHPe5A_(nb#ulv=dk=v5~f3R1R!9V>)r0sg5V) zH9!!_AOU<)Wi?B)Q+RVX$fobIVg`XPp}3U3NfT-k;)r0a7;M5VXA%n|UdF>(IS6kk lHvJ5V|K9=phm5}k7yxVV^TAT2(qBnTK@ z0-l32%{aNe_uRQn>?9|hf!n#ip2wW8x3=Ts3m|d+4Qb+fVseKf#rdn zA?|+&whi_j>@!#lY)`y^MH#JOM?c(q&ai4^Z*+HeKd=%Lm&=tO3A<6cJILgb*v)S|t*jXcwTurt&j1E-0hBxf&;7YnT zaKvIUo~)Z-yL#n|N9;wGU>Q!QGizaCA(Ck0yu7^Z+}zwOiin+^oxP!S<4u>OyDx{~UhYP_b_|;Gd6SKuUGbbgpp9`t4 zuP>&Gn4O&!z42De57RsXBCzh=d|g#lMHVtPHYN&LW+|WyMwhvehK7cGGhylN?d3wy z(_gF-T0Ak^+uOZn!Xh-gFxXFJArKb)^hJCPozzWzFLZ6OLOwxQ4y||u5%CTW3-#G%EpOKiF?SBr{}0zPSNZ7njxA)!!clPzCqVIo-wgyl56+~n(;ni_Jo1OkD1Vfhil za$v)$cVI>ZjFcm#>+F2@4IJ*tqtzP@=%Sg1mf)-rL@(a}LpQi(m5Sff)D!f5RVU)R;u zk%dU?u>@h+wc?TUV7K|g=kt+;=&^^25GQMNC+uM+ECPG1COS4pH=SMgkj!@Zy;t^zzGL^7M?C~|t z6(P*kfX1LgbBAOApi1 z{yuZhQ9(k1J@9cko>SVso=31tK4U#XWniAXQxWlxN0?XA z85)+b3Nrd#2H(8Hm=weF!_2XsP{{urDf3fW$_V)*zyQwC!s?l~#8?0T002ovPDHLk FV1oRZC2Ifx literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-2.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-2.png new file mode 100644 index 0000000000000000000000000000000000000000..dbf7bc73bc620b8782be5518443a36c91d1216cb GIT binary patch literal 1756 zcmV<21|#{2P)p$w|{(9A*RO z%p)u&yhey6EFmmnL=hHhoQDXF6GjOWgfYS};Su3)o_`i<;vgGCSVdS(SWj3U!T>%b z+#z@f_XrR8=7J&~#YA@yR?dK)=2trhXbg7}s#TVi5kV`Gf|W4K6PU!MmH&*(K^ z1P2Zr5N41hELn{>GGIKuq^71W2{vM6m$taLxGxF|3vJM?k?Rx}7t2q5wLguAeAL(1 zd#qNg4NpPuxe7O14mqn4ALo>7ayp%lgY{;GQ%tnBwmOYm3$o>}uS3pi#Q)`b!GuH* zj6OR$I(}8oojrT@wvlT|vPE&XXc}?OPItxe$&)9q8eN!WWo6gZBohMVS+q2cOy!fw z@#DvP+S=NB^j^8Ds_L?ox~~v!hiJJVuurv!|Aove4l*(_?0U#oR8-h;!+eMW>3Xn6 z(A?ZC90_TERP|ykmtMlRGWo-Y51;p^d3g4&UAr13(ik?vbCLCOa&i)k$WE$T{y?p6 z(fTowA0yl8;zM7tRasf-2Jp|bv$I#m#Kicz9gWfyi~K8Y5T<+>=jkQxa=GMn;U#X* zE&fiB$fHBaVk<)R=9P*cwed5djUQ_i*BQP+Ph_KB;!RCWZt3uM-|7r>5|ekK_9v_( zY=AqdoD1*oBftXtQN_`D5gct4Z6>5{y#*U&LWQ0n{+Oi_m^hP%Muz|*gJ@nNyo#*O zt9(Q>yrS6WVhug&PH`Qw$M^L{n|$9P>HC^+JB!{65wWKn-3I}F8i%zg4o;mqWi=A* zW5(j~V$B3@mV+SuA<7HU_T9R5>oZh} zdamPeINa&!>F1@S^Eq)H)NCX|O`7VJJiVlmEB5T!W0$7BferpI=ZA=}nHMYMkuH-q zqU-(p_n(&}f^y*oudUD#XZKi!+(JkWmNd1BHYrCSdtV{aqtmLaAW27XD*uGAG{mHl zXue~cRoxRbO8ONvuY@8&8ty^q|5LrB(b6eMBiWsSi%e>`hsB^Y;&?A{tGvra2`0;k zdtf0-nuc?HjZb;!Dk$Pa8jbW?&W6k6VDJ3N( z88eMCBHY>8={|Dg$W@K~Z!`#M^G`Ta-r+?$?w2@|ewXlp{3FQX=+UFrnQ9ptzHs4! zi*D4Zh_I0X8FeekE2XkZCas4s$Wr^}K7IQ1?Eu}8-tjRfqkd(5ZQ`ge;2%{2y}iBY zV!}4)iTxzF5K;tB0XcKx)!#O;-Sr|~m*^RFb#*T3aKvBA2T?OiWPLbEqpX$W=2Dil zQ^YyJtO=$Q59P4gY|3_RTr1+(P)rdr3CF7w#r~qaS$fxBb%?jbzE>2vxw&E0Hrz?_ z{4U-OoKBo&vQA87W@aXalXzoeqbw7*Rz^j{k#yb`>3p`{Woiv+^Jik3cPofTibjA+ z%&?M1ZLZk-d8k>dFzI}g^bYYQ8DEl2Zu*Nj%pEWL|9Oc>+x^L8s>q@RYT=G%13S#Z yFpz!1`;+&0({xl z!WaUPdgUhj#U&~~aA31EC}4od2qXp);Rk>C$q(V@0L@|`3WhWb5t+!%0Xk{M#jwO7 zFy87s^+8jVj(AW5lU`k2HI|i?>6ues16J%NAXsfYe_EJVR8;t6wX3VE zSrhpVa0^y@&r#+K0;sL=Co@y+3lnK+X)(|g6VEjuz3VLHV>CI12US&7Q6V8Ahka}! zeHcmD@7Rmu)|4I@85uk~J8N*4gi@&t=6&$O!on_*i7c2!nNn^mbApYdV-}Nxx;?_f z!vm(Krrzfv-rn9mnxCI9tTsjy9dP{X5sr3<1W z930H%4Sf)^H{*i zBS{23h>YEV&d$zkj2iBePfAL3r{lM!Xu;d&A9CaVthdcD?N@}gDm z0j^g(N?h~+p(Fv+QpuyO2L%OPpy_qMm(EHzw+0F`z+JRQz&J@&lSuWsJkUqbN^g;mSxM zT!Pmws0%MwZN(QZF1vWPUU$a#S@Iu^!cEWv=#M*<86LjhJejQzO*3&iVy0Oqs=}c^78V|VEb9L^3T!n)9mi?uJ-iwG}{)zAn>o) zRH7&YS{gtoba-xnJ#*CK%&C_sDjlchk}Nkjms_j%_xH~;(|h9TJb<>r(jQz|St)T> zBrz%a0kswq$#J<~3W$==I$n?MulP}{wpWMGgDweGY!)P?#mqK!LbJHXy&Ex1u8VV5|6Z3J^r>Hsg2ytxRT5j0W(_@fAd~9rN zQD_QHOiX0+kjBNaC8iu}0 z)H~d>*;k=$m=R7u93Pg@Ru1KL!s6m$9lpP0NF$dHN2INV1oi*UQajCJo0XN7Y2JE0 zh%4OK*!UCkszew+5Z262DjaJCM6bTjQ<}EeDTJ}Nq|}UJvgiXq_NEQNbqnx4;10eD zbg9~6h$~2Fz03AZPy#@@j*Ezh2z8d6Fe)r8ELfZioYMfu#?jHyKN*J;k2$K8egz&I zTxBA=BUD77D9Ql5?4{6dqA>RhzySB(bgRC#^C|^bHkMMh$^%4ilPQtOJaYUw8a|12={s5$!(MW|36v#JpL1403@th-uUEs@Bjb+ M07*qoM6N<$f~Rj>w*UYD literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-4.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-4.png new file mode 100644 index 0000000000000000000000000000000000000000..4564f6d8bff88ce7a1e983aa9192b8c882789f5d GIT binary patch literal 1814 zcmV+x2kH2UP)MX4xD zf25+&_`^?;7>(E#Nd$>eNnaomF(fo5Ch!6eeIN!x0uMwXfe?AmHFJrMt*yQ6a5&_FpC`1^Z%w=C zJCY8WYw)Tc313)1#2v`)6Jb+6$LNigvs5SK0`Y68Roagv#t zd04gI?(XgpsX0WL_w_Uuj!^g^YjkvUm{A7?@uH%lNN(QG(FqU9pAJXFeQ9ZFQL245 zH#f^fMrnrsQX#Tpr%s)UGP@en)6?beh7t+$b>j)X=yP{WO#V^nzYkT ziD-p&VPZL<2?uf!UK@vnZc~*cFyuGx+{z;nBC(An#oOW<1R!anXgv}n{$vuE zg|7&3&&S1m85tRoCf&**?`=v%m;{7fnh2L5k)NNR||qVjAY%u8;Hj*e1)OT4L0@dLYwS=z2sXh`JJ z%nFIRy1F#Irw9_*^);6di-j#4_6{SG#Dy$+QPaLj_>#sv?CXNWD}<+o7IH^|T&d3m z30*Ui&cZ4yD?M$zNSj}U+pZQLhy%XR^MwTqMMWqq>!TaKBZ*Om^nL@*6nY(h( zesPn{W{cm-Bw%fAZ3A+Gg>sOHTKmM+q@<)HmMw0Y1ZzYt2dx$vjA$Tln2QAKu5!?V zeB)LoVz=96w;C2o*jGwvSFF0a`rEBcq`bUb-nYh9_dt$XSRk>S;h zK?z%})-&Avj&PR7@05u^BtAxXhwD!3W*r?JNqV`9E2Ti0XQyJN6UP!^m&@uJd}_D{>E`RYWN-cbhDXLyEsd@;Ht{L zsYE0)D6<~oHKX!BSCcIMuCIl}x)SlW{ZC}8+1~;T0CqcF2-dk}y8r+H07*qoM6N<$ Ef`bE6AOHXW literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-5.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-5.png new file mode 100644 index 0000000000000000000000000000000000000000..dcef35eb594a4c047cf37dcfb7acb660cb155de6 GIT binary patch literal 1848 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 literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-7.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-7.png new file mode 100644 index 0000000000000000000000000000000000000000..b9079ad5d5f863ccbbbeac6ab2677ac0d2645698 GIT binary patch literal 1452 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 n?OHhGXep*ZTPPG6 z96*o~Ka@eKiOiH35FtuXA%TyY06%;%fA9$jL;O%6p$U)@HP(nCkWvBxO@t7JLo&jBQyWN|r{ch6hwv-du0@2lV4-PIkJVTV8TdcDlE zHUN9TS%53R8Q=i0C7*c=FajO`wgC6o^^r7LL4u9|Pr%CzJ_65g1Lgn=fF-~R;343N zmWTD|1{y4@XBK=F@FpXB5Cch%17-mmY$vRfwfX_Q9=G--K&;Y=US3}IadB~OF)=Y- zo}Ql09v&V}%I8;BR<@RxmkkRG3tNMOgX^80oogyzgKlyIa0~d{mm&*9&%otvKoq@S zP*C8Po}L~Q931Q?YD%NgxHCLFJlECLHCIzpGpG3TAYcG}dsl^+MY4?OWk4vumz|yM zU0q$B;Ns%qbPztUxw*O3*w{E&R#rAiZr=iQ0IzjTWR*m5yIWgZqaq_CgAOAlO;1lR z#>U3BFD)(Yuv@Z(Ep`kRmx*cW?MJs1zWwjHZ{u$5*{2tO& zM2Vh)OW1yhqE9w7Gz@rqdw&7z)rN(IT^$=6o7?L-bS-%9GJYq!^$y@giRG3}K^4QRZOk=;(+P-TJ(|ymkdq z^A=`iX7+1Y12YT;GMoAXUQlxbFF*+X5*ixnl$e+pEJAc(U|_Pjxp|RI(GvB5&j6-K z$Kb(0(y~@@ad9}A27w{%&17L>%T6cLsH&>EBpM(^MMZ;T`W@g;7R}TA=Ire3nwGT! z0s_3jSbSG;x6e__q6m3|TU9hgp;bplMz*-y0d51)$3zd9hQVOi(YhuaI=*D;LCIcV z0!5dSlHx6D)!N$HaWWkRqDC^8(x4Dzl*a{1)*MLj@$r75`H=+GG*eS_oRlGpu0WOSCPWCiWQbOqp6&0EOr8s14>8`oDx;l%j zO;Q-T!D&wRgzPcd*Vjk4-JoJse;&B|5J1T9q|E^z@&731&jX&54P3P{MeR%WRnrj{ z@Rbc*mGQ(zxqVts89rcpsgIu}zhlq_b3r5{)YsS7cgTsjZKDhfEJH*V3=9lBPo}Ri zjiF;DkjVvdb91GvN>bt~>I_cStja+W3RxCUI}9E+eI0NS2%lqxrD>$5rV5L8%->|X z0V(kjC2O*7eSQ5LN`XVQqqerTF;VMQ zR8&yw`CCv>nnzDh4;AdIX0l9l{LcSUQc^Om*oHkvX=!PKg0Qqz`3@69uId<39jg}X zS?yJW0Eu>OS7J;}O)WsF{sUxy&o?$U3^Hqsj*iZ99)M(QNtrluvWzeW#;5#)V5u9s zM=)SVz9D1-IX`6$hHWC;wkxd3X4=ZSy1HZs2M7EAqf-bmvj)XxE2+W00*()!scu;p zXlv>&W5XJV=xFw>(WLZkX&P@t!xQP~-xb+I6BY-;^L{YrMsnkt+wp>M_8{EN~O1jQh5XIM=AM?&4=F#5X-efHT2G4jbi!w4Y zhLp`4z_8B3J1$wvIC4EiEm{d$oQzNi2!TmYZrnEU5qs)^eyUS^oOd6!H1_ z`BByqz7>)(Fa2M9!(H+oGBg=jEL*B$aYLa_Z3P|`o z)32V)HPqho$eCgySO5S307*qoM6N<$ Ef@aS_IsgCw literal 0 HcmV?d00001 From ecfc6dfa3d41f27190381abe28f9dd28e86e46bd Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 28 Nov 2019 21:41:29 +0800 Subject: [PATCH 078/103] CA1825: use Array.Empty. --- CodeAnalysis/osu.ruleset | 3 --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 3 ++- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 4 ++-- osu.Game.Rulesets.Osu/OsuRuleset.cs | 3 ++- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 3 ++- osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs | 3 ++- .../Visual/Multiplayer/TestSceneMatchParticipants.cs | 2 +- osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs | 2 +- .../Visual/Online/TestSceneUserProfilePreviousUsernames.cs | 2 +- osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs | 2 +- .../Screens/Ladder/Components/DrawableMatchTeam.cs | 2 +- osu.Game/Beatmaps/BeatmapInfo.cs | 4 ++-- osu.Game/Beatmaps/DummyWorkingBeatmap.cs | 2 +- osu.Game/Overlays/Mods/ModSection.cs | 2 +- osu.Game/Overlays/Music/PlaylistList.cs | 2 +- osu.Game/Overlays/UserProfileOverlay.cs | 6 ++---- osu.Game/Rulesets/Mods/Mod.cs | 2 +- osu.Game/Rulesets/Ruleset.cs | 4 ++-- osu.Game/Screens/Play/SquareGraph.cs | 2 +- osu.Game/Screens/Select/Details/FailRetryGraph.cs | 4 ++-- 20 files changed, 28 insertions(+), 29 deletions(-) diff --git a/CodeAnalysis/osu.ruleset b/CodeAnalysis/osu.ruleset index 9bcca40983..61e6520beb 100644 --- a/CodeAnalysis/osu.ruleset +++ b/CodeAnalysis/osu.ruleset @@ -57,7 +57,4 @@ - - - \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 506fa23fa9..bf5b00528b 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -18,6 +18,7 @@ using osu.Game.Rulesets.Catch.Beatmaps; using osu.Game.Rulesets.Catch.Difficulty; using osu.Game.Rulesets.Difficulty; using osu.Game.Scoring; +using System; namespace osu.Game.Rulesets.Catch { @@ -112,7 +113,7 @@ namespace osu.Game.Rulesets.Catch }; default: - return new Mod[] { }; + return Array.Empty(); } } diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index a96c79b40b..3e3dc5cf66 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -158,7 +158,7 @@ namespace osu.Game.Rulesets.Mania }; default: - return new Mod[] { }; + return Array.Empty(); } } @@ -268,7 +268,7 @@ namespace osu.Game.Rulesets.Mania return stage1Bindings.Concat(stage2Bindings); } - return new KeyBinding[0]; + return Array.Empty(); } public override string GetVariantName(int variant) diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 2f43909332..b182e5a658 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -26,6 +26,7 @@ using osu.Game.Rulesets.Osu.Difficulty; using osu.Game.Rulesets.Osu.Skinning; using osu.Game.Scoring; using osu.Game.Skinning; +using System; namespace osu.Game.Rulesets.Osu { @@ -149,7 +150,7 @@ namespace osu.Game.Rulesets.Osu }; default: - return new Mod[] { }; + return Array.Empty(); } } diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index ab9c95159c..0b4cb9801e 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -18,6 +18,7 @@ using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Taiko.Beatmaps; using osu.Game.Rulesets.Taiko.Difficulty; using osu.Game.Scoring; +using System; namespace osu.Game.Rulesets.Taiko { @@ -111,7 +112,7 @@ namespace osu.Game.Rulesets.Taiko }; default: - return new Mod[] { }; + return Array.Empty(); } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs index 875e7b9758..4c5c18f38a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; @@ -31,7 +32,7 @@ namespace osu.Game.Tests.Visual.Gameplay requestCount = 0; increment = skip_time; - Child = gameplayClockContainer = new GameplayClockContainer(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), new Mod[] { }, 0) + Child = gameplayClockContainer = new GameplayClockContainer(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), Array.Empty(), 0) { RelativeSizeAxes = Axes.Both, Children = new Drawable[] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchParticipants.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchParticipants.cs index 50df4022dc..1ac914e27d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchParticipants.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchParticipants.cs @@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.Multiplayer }); AddStep(@"set max", () => Room.MaxParticipants.Value = 10); - AddStep(@"clear users", () => Room.Participants.Value = new User[] { }); + AddStep(@"clear users", () => Room.Participants.Value = System.Array.Empty()); AddStep(@"set max to null", () => Room.MaxParticipants.Value = null); } } diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs index 98da63508b..15f9c9a013 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs @@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual.Online }, Title = "osu!volunteer", Colour = "ff0000", - Achievements = new User.UserAchievement[0], + Achievements = Array.Empty(), }; public TestSceneUserProfileOverlay() diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfilePreviousUsernames.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfilePreviousUsernames.cs index d09a50b12c..048a1950fd 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfilePreviousUsernames.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfilePreviousUsernames.cs @@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Online new User { PreviousUsernames = new[] { "longusername", "longerusername" } }, new User { PreviousUsernames = new[] { "test", "angelsim", "verylongusername" } }, new User { PreviousUsernames = new[] { "ihavenoidea", "howcani", "makethistext", "anylonger" } }, - new User { PreviousUsernames = new string[0] }, + new User { PreviousUsernames = Array.Empty() }, null }; diff --git a/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs index ba63013886..f3eecf8afe 100644 --- a/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs @@ -49,7 +49,7 @@ namespace osu.Game.Tournament.Screens.Editors get { if (editorInfo == null) - return new MenuItem[0]; + return Array.Empty(); return new MenuItem[] { diff --git a/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs b/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs index ded21730f3..031d6bf3d2 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs @@ -192,7 +192,7 @@ namespace osu.Game.Tournament.Screens.Ladder.Components get { if (editorInfo == null) - return new MenuItem[0]; + return Array.Empty(); return new MenuItem[] { diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 6e82c465dc..bcc9ab885e 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -98,7 +98,7 @@ namespace osu.Game.Beatmaps { if (string.IsNullOrEmpty(value)) { - Bookmarks = new int[0]; + Bookmarks = Array.Empty(); return; } @@ -111,7 +111,7 @@ namespace osu.Game.Beatmaps } [NotMapped] - public int[] Bookmarks { get; set; } = new int[0]; + public int[] Bookmarks { get; set; } = Array.Empty(); public double DistanceSpacing { get; set; } public int BeatDivisor { get; set; } diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs index 59a27e3fde..9ea254b23f 100644 --- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs @@ -55,7 +55,7 @@ namespace osu.Game.Beatmaps private class DummyRuleset : Ruleset { - public override IEnumerable GetModsFor(ModType type) => new Mod[] { }; + public override IEnumerable GetModsFor(ModType type) => Array.Empty(); public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) { diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index c55d1d8f70..7235a18a23 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -81,7 +81,7 @@ namespace osu.Game.Overlays.Mods } } - private ModButton[] buttons = { }; + private ModButton[] buttons = Array.Empty(); protected override bool OnKeyDown(KeyDownEvent e) { diff --git a/osu.Game/Overlays/Music/PlaylistList.cs b/osu.Game/Overlays/Music/PlaylistList.cs index 83528298b1..3cd04ac809 100644 --- a/osu.Game/Overlays/Music/PlaylistList.cs +++ b/osu.Game/Overlays/Music/PlaylistList.cs @@ -239,7 +239,7 @@ namespace osu.Game.Overlays.Music private class ItemSearchContainer : FillFlowContainer, IHasFilterableChildren { - public IEnumerable FilterTerms => new string[] { }; + public IEnumerable FilterTerms => Array.Empty(); public bool MatchingFilter { diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index 468eb22b01..b5e7b8bedb 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -55,10 +56,7 @@ namespace osu.Game.Overlays new BeatmapsSection(), new KudosuSection() } - : new ProfileSection[] - { - //new AboutSection(), - }; + : Array.Empty(); tabs = new ProfileTabControl { diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 1c280c820d..b780ec9e76 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Mods /// The mods this mod cannot be enabled with. /// [JsonIgnore] - public virtual Type[] IncompatibleMods => new Type[] { }; + public virtual Type[] IncompatibleMods => Array.Empty(); ///

/// Creates a copy of this initialised to a default state. diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 45aa904b98..2550f69286 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets /// /// The legacy enum which will be converted /// An enumerable of constructed s - public virtual IEnumerable ConvertLegacyMods(LegacyMods mods) => new Mod[] { }; + public virtual IEnumerable ConvertLegacyMods(LegacyMods mods) => Array.Empty(); public ModAutoplay GetAutoplayMod() => GetAllMods().OfType().First(); @@ -116,7 +116,7 @@ namespace osu.Game.Rulesets /// /// A variant. /// A list of valid s. - public virtual IEnumerable GetDefaultKeyBindings(int variant = 0) => new KeyBinding[] { }; + public virtual IEnumerable GetDefaultKeyBindings(int variant = 0) => Array.Empty(); /// /// Gets the name for a key binding variant. This is used for display in the settings overlay. diff --git a/osu.Game/Screens/Play/SquareGraph.cs b/osu.Game/Screens/Play/SquareGraph.cs index 715ba3c065..a667466965 100644 --- a/osu.Game/Screens/Play/SquareGraph.cs +++ b/osu.Game/Screens/Play/SquareGraph.cs @@ -38,7 +38,7 @@ namespace osu.Game.Screens.Play } } - private float[] calculatedValues = { }; // values but adjusted to fit the amount of columns + private float[] calculatedValues = Array.Empty(); // values but adjusted to fit the amount of columns private int[] values; diff --git a/osu.Game/Screens/Select/Details/FailRetryGraph.cs b/osu.Game/Screens/Select/Details/FailRetryGraph.cs index 121f8efe5a..134fd0598a 100644 --- a/osu.Game/Screens/Select/Details/FailRetryGraph.cs +++ b/osu.Game/Screens/Select/Details/FailRetryGraph.cs @@ -27,8 +27,8 @@ namespace osu.Game.Screens.Select.Details metrics = value; - var retries = Metrics?.Retries ?? new int[0]; - var fails = Metrics?.Fails ?? new int[0]; + var retries = Metrics?.Retries ?? Array.Empty(); + var fails = Metrics?.Fails ?? Array.Empty(); float maxValue = fails.Any() ? fails.Zip(retries, (fail, retry) => fail + retry).Max() : 0; failGraph.MaxValue = maxValue; From d7b3578cc61f6cd99d940443ef0d14e8faeccbb2 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 28 Nov 2019 21:52:05 +0800 Subject: [PATCH 079/103] CA2201: throw correct exception type. --- CodeAnalysis/osu.ruleset | 3 +++ osu.Game.Tests/Visual/TestSceneOsuGame.cs | 4 ++-- osu.Game/Online/API/APIRequest.cs | 10 +++++++++- .../Rulesets/Difficulty/Utils/LimitedCapacityStack.cs | 2 +- 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/CodeAnalysis/osu.ruleset b/CodeAnalysis/osu.ruleset index 61e6520beb..b82799fea4 100644 --- a/CodeAnalysis/osu.ruleset +++ b/CodeAnalysis/osu.ruleset @@ -57,4 +57,7 @@ + + + \ No newline at end of file diff --git a/osu.Game.Tests/Visual/TestSceneOsuGame.cs b/osu.Game.Tests/Visual/TestSceneOsuGame.cs index e495b2a95a..492494ada3 100644 --- a/osu.Game.Tests/Visual/TestSceneOsuGame.cs +++ b/osu.Game.Tests/Visual/TestSceneOsuGame.cs @@ -111,7 +111,7 @@ namespace osu.Game.Tests.Visual foreach (var type in requiredGameDependencies) { if (game.Dependencies.Get(type) == null) - throw new Exception($"{type} has not been cached"); + throw new InvalidOperationException($"{type} has not been cached"); } return true; @@ -121,7 +121,7 @@ namespace osu.Game.Tests.Visual foreach (var type in requiredGameBaseDependencies) { if (gameBase.Dependencies.Get(type) == null) - throw new Exception($"{type} has not been cached"); + throw new InvalidOperationException($"{type} has not been cached"); } return true; diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs index b424e0f086..fcbd4d314a 100644 --- a/osu.Game/Online/API/APIRequest.cs +++ b/osu.Game/Online/API/APIRequest.cs @@ -122,7 +122,7 @@ namespace osu.Game.Online.API // attempt to decode a displayable error string. var error = JsonConvert.DeserializeObject(responseString); if (error != null) - e = new Exception(error.ErrorMessage, e); + e = new APIException(error.ErrorMessage, e); } catch { @@ -154,6 +154,14 @@ namespace osu.Game.Online.API } } + public class APIException : InvalidOperationException + { + public APIException(string messsage, Exception innerException) + : base(messsage, innerException) + { + } + } + public delegate void APIFailureHandler(Exception e); public delegate void APISuccessHandler(); diff --git a/osu.Game/Rulesets/Difficulty/Utils/LimitedCapacityStack.cs b/osu.Game/Rulesets/Difficulty/Utils/LimitedCapacityStack.cs index d47caf409b..3cab04d904 100644 --- a/osu.Game/Rulesets/Difficulty/Utils/LimitedCapacityStack.cs +++ b/osu.Game/Rulesets/Difficulty/Utils/LimitedCapacityStack.cs @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Difficulty.Utils get { if (i < 0 || i > Count - 1) - throw new IndexOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(i)); i += marker; if (i > capacity - 1) From 3c39fde7ff938f0397639cec8f7e871fdf8c4b9f Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 28 Nov 2019 21:59:49 +0800 Subject: [PATCH 080/103] CA1065: throw NotSupportedException in properties. --- CodeAnalysis/osu.ruleset | 1 - osu.Game/Rulesets/UI/DrawableRuleset.cs | 28 ++++++++++++------------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/CodeAnalysis/osu.ruleset b/CodeAnalysis/osu.ruleset index b82799fea4..2f072ffa45 100644 --- a/CodeAnalysis/osu.ruleset +++ b/CodeAnalysis/osu.ruleset @@ -39,7 +39,6 @@ - diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 5033fd0686..0bb99517ef 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -515,34 +515,34 @@ namespace osu.Game.Rulesets.UI public Stream GetStream(string name) => primary.GetStream(name) ?? secondary.GetStream(name); - public IEnumerable GetAvailableResources() => throw new NotImplementedException(); + public IEnumerable GetAvailableResources() => throw new NotSupportedException(); - public void AddAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => throw new NotImplementedException(); + public void AddAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => throw new NotSupportedException(); - public void RemoveAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => throw new NotImplementedException(); + public void RemoveAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => throw new NotSupportedException(); - public BindableNumber Volume => throw new NotImplementedException(); + public BindableNumber Volume => throw new NotSupportedException(); - public BindableNumber Balance => throw new NotImplementedException(); + public BindableNumber Balance => throw new NotSupportedException(); - public BindableNumber Frequency => throw new NotImplementedException(); + public BindableNumber Frequency => throw new NotSupportedException(); - public BindableNumber Tempo => throw new NotImplementedException(); + public BindableNumber Tempo => throw new NotSupportedException(); - public IBindable GetAggregate(AdjustableProperty type) => throw new NotImplementedException(); + public IBindable GetAggregate(AdjustableProperty type) => throw new NotSupportedException(); - public IBindable AggregateVolume => throw new NotImplementedException(); + public IBindable AggregateVolume => throw new NotSupportedException(); - public IBindable AggregateBalance => throw new NotImplementedException(); + public IBindable AggregateBalance => throw new NotSupportedException(); - public IBindable AggregateFrequency => throw new NotImplementedException(); + public IBindable AggregateFrequency => throw new NotSupportedException(); - public IBindable AggregateTempo => throw new NotImplementedException(); + public IBindable AggregateTempo => throw new NotSupportedException(); public int PlaybackConcurrency { - get => throw new NotImplementedException(); - set => throw new NotImplementedException(); + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); } public void Dispose() From 09257b0c6dd17130b5d7c6e9f31726328e9bb368 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 28 Nov 2019 22:07:46 +0800 Subject: [PATCH 081/103] CA1820: use IsNullOrEmpty. --- CodeAnalysis/osu.ruleset | 1 - osu.Game/Overlays/DirectOverlay.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/CodeAnalysis/osu.ruleset b/CodeAnalysis/osu.ruleset index 2f072ffa45..5329e84024 100644 --- a/CodeAnalysis/osu.ruleset +++ b/CodeAnalysis/osu.ruleset @@ -48,7 +48,6 @@ - diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs index aedbd1b08b..9daf55c796 100644 --- a/osu.Game/Overlays/DirectOverlay.cs +++ b/osu.Game/Overlays/DirectOverlay.cs @@ -116,7 +116,7 @@ namespace osu.Game.Overlays Filter.Search.Current.ValueChanged += text => { - if (text.NewValue != string.Empty) + if (!string.IsNullOrEmpty(text.NewValue)) { Header.Tabs.Current.Value = DirectTab.Search; From d5994ed4845b196b249f0c51db70d999ccbe359d Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 28 Nov 2019 22:21:21 +0800 Subject: [PATCH 082/103] CA2208: create exceptions correctly. --- CodeAnalysis/osu.ruleset | 1 - osu.Game/Graphics/ScreenshotManager.cs | 2 +- osu.Game/Rulesets/Difficulty/Utils/LimitedCapacityStack.cs | 2 +- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 4 ++-- osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs | 2 +- osu.Game/Rulesets/Scoring/HitWindows.cs | 2 +- osu.Game/Screens/Play/ReplayPlayerLoader.cs | 2 +- osu.Game/Users/UserPanel.cs | 2 +- 8 files changed, 8 insertions(+), 9 deletions(-) diff --git a/CodeAnalysis/osu.ruleset b/CodeAnalysis/osu.ruleset index 5329e84024..2a0c6fb928 100644 --- a/CodeAnalysis/osu.ruleset +++ b/CodeAnalysis/osu.ruleset @@ -51,7 +51,6 @@ - diff --git a/osu.Game/Graphics/ScreenshotManager.cs b/osu.Game/Graphics/ScreenshotManager.cs index 02d928ec66..b9151b7393 100644 --- a/osu.Game/Graphics/ScreenshotManager.cs +++ b/osu.Game/Graphics/ScreenshotManager.cs @@ -119,7 +119,7 @@ namespace osu.Game.Graphics break; default: - throw new ArgumentOutOfRangeException(nameof(screenshotFormat)); + throw new InvalidOperationException($"Unknown enum member {nameof(ScreenshotFormat)} {screenshotFormat.Value}."); } notificationOverlay.Post(new SimpleNotification diff --git a/osu.Game/Rulesets/Difficulty/Utils/LimitedCapacityStack.cs b/osu.Game/Rulesets/Difficulty/Utils/LimitedCapacityStack.cs index 3cab04d904..1fc5abce90 100644 --- a/osu.Game/Rulesets/Difficulty/Utils/LimitedCapacityStack.cs +++ b/osu.Game/Rulesets/Difficulty/Utils/LimitedCapacityStack.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Difficulty.Utils public LimitedCapacityStack(int capacity) { if (capacity < 0) - throw new ArgumentOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(capacity)); this.capacity = capacity; array = new T[capacity]; diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index ed48ddbc2f..386805d7e5 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -150,8 +150,8 @@ namespace osu.Game.Rulesets.Objects.Drawables if (HitObject.SampleControlPoint == null) { - throw new ArgumentNullException(nameof(HitObject.SampleControlPoint), $"{nameof(HitObject)}s must always have an attached {nameof(HitObject.SampleControlPoint)}." - + $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}."); + throw new InvalidOperationException($"{nameof(HitObject)}s must always have an attached {nameof(HitObject.SampleControlPoint)}." + + $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}."); } samples = samples.Select(s => HitObject.SampleControlPoint.ApplyTo(s)).ToArray(); diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 7fddb442d1..bdd019719b 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -118,7 +118,7 @@ namespace osu.Game.Rulesets.Objects.Legacy int repeatCount = Parsing.ParseInt(split[6]); if (repeatCount > 9000) - throw new ArgumentOutOfRangeException(nameof(repeatCount), @"Repeat count is way too high"); + throw new FormatException(@"Repeat count is way too high"); // osu-stable treated the first span of the slider as a repeat, but no repeats are happening repeatCount = Math.Max(0, repeatCount - 1); diff --git a/osu.Game/Rulesets/Scoring/HitWindows.cs b/osu.Game/Rulesets/Scoring/HitWindows.cs index 39d67f1071..018b50bd3d 100644 --- a/osu.Game/Rulesets/Scoring/HitWindows.cs +++ b/osu.Game/Rulesets/Scoring/HitWindows.cs @@ -165,7 +165,7 @@ namespace osu.Game.Rulesets.Scoring return miss; default: - throw new ArgumentException(nameof(result)); + throw new ArgumentException("Unknown enum member", nameof(result)); } } diff --git a/osu.Game/Screens/Play/ReplayPlayerLoader.cs b/osu.Game/Screens/Play/ReplayPlayerLoader.cs index 86179ef067..c8ca604902 100644 --- a/osu.Game/Screens/Play/ReplayPlayerLoader.cs +++ b/osu.Game/Screens/Play/ReplayPlayerLoader.cs @@ -15,7 +15,7 @@ namespace osu.Game.Screens.Play : base(() => new ReplayPlayer(score)) { if (score.Replay == null) - throw new ArgumentNullException(nameof(score.Replay), $"{nameof(score)} must have a non-null {nameof(score.Replay)}."); + throw new ArgumentException($"{nameof(score)} must have a non-null {nameof(score.Replay)}.", nameof(score)); scoreInfo = score.ScoreInfo; } diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index c63c12773e..6ddbc13a06 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -63,7 +63,7 @@ namespace osu.Game.Users private void load(UserProfileOverlay profile) { if (colours == null) - throw new ArgumentNullException(nameof(colours)); + throw new InvalidOperationException($"{nameof(colours)} not initialized!"); FillFlowContainer infoContainer; From e46f6627e4f0a932a5d52c255810d6277a7263c7 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 28 Nov 2019 22:26:10 +0800 Subject: [PATCH 083/103] CA1052: make type static. --- CodeAnalysis/osu.ruleset | 1 - osu.Game/IO/Legacy/SerializationReader.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/CodeAnalysis/osu.ruleset b/CodeAnalysis/osu.ruleset index 2a0c6fb928..0756adb05c 100644 --- a/CodeAnalysis/osu.ruleset +++ b/CodeAnalysis/osu.ruleset @@ -9,7 +9,6 @@ - diff --git a/osu.Game/IO/Legacy/SerializationReader.cs b/osu.Game/IO/Legacy/SerializationReader.cs index 82b2c4be32..aeb3ce754d 100644 --- a/osu.Game/IO/Legacy/SerializationReader.cs +++ b/osu.Game/IO/Legacy/SerializationReader.cs @@ -192,7 +192,7 @@ namespace osu.Game.IO.Legacy } } - public class DynamicDeserializer + public static class DynamicDeserializer { private static VersionConfigToNamespaceAssemblyObjectBinder versionBinder; private static BinaryFormatter formatter; From caf3f774baac67213e470d66f5e21e8e8f7616c5 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 28 Nov 2019 22:39:09 +0800 Subject: [PATCH 084/103] CA1309: compare strings correctly. --- CodeAnalysis/osu.ruleset | 1 + osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs | 2 +- osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs | 2 +- osu.Game/Scoring/ScoreManager.cs | 2 +- osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs | 6 +++--- osu.Game/Screens/Select/FilterCriteria.cs | 2 +- osu.Game/Skinning/LegacySkinResourceStore.cs | 2 +- .../Storyboards/Drawables/DrawableStoryboardAnimation.cs | 2 +- osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs | 2 +- 9 files changed, 11 insertions(+), 10 deletions(-) diff --git a/CodeAnalysis/osu.ruleset b/CodeAnalysis/osu.ruleset index 0756adb05c..1aa8f66c93 100644 --- a/CodeAnalysis/osu.ruleset +++ b/CodeAnalysis/osu.ruleset @@ -54,6 +54,7 @@ + \ No newline at end of file diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs index 4924842e81..f9d71a2a6e 100644 --- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs @@ -42,7 +42,7 @@ namespace osu.Game.Beatmaps } } - private string getPathForFile(string filename) => BeatmapSetInfo.Files.FirstOrDefault(f => string.Equals(f.Filename, filename, StringComparison.InvariantCultureIgnoreCase))?.FileInfo.StoragePath; + private string getPathForFile(string filename) => BeatmapSetInfo.Files.FirstOrDefault(f => string.Equals(f.Filename, filename, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; private TextureStore textureStore; diff --git a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs index 42865c686c..393bcfdb3c 100644 --- a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs @@ -70,6 +70,6 @@ namespace osu.Game.Beatmaps.ControlPoints public override bool EquivalentTo(ControlPoint other) => other is SampleControlPoint otherTyped && - string.Equals(SampleBank, otherTyped.SampleBank) && SampleVolume == otherTyped.SampleVolume; + SampleBank == otherTyped.SampleBank && SampleVolume == otherTyped.SampleVolume; } } diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 3279af05b6..332b3e3f05 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -57,7 +57,7 @@ namespace osu.Game.Scoring } protected override IEnumerable GetStableImportPaths(Storage stableStorage) - => stableStorage.GetFiles(ImportFromStablePath).Where(p => HandledExtensions.Any(ext => Path.GetExtension(p)?.Equals(ext, StringComparison.InvariantCultureIgnoreCase) ?? false)); + => stableStorage.GetFiles(ImportFromStablePath).Where(p => HandledExtensions.Any(ext => Path.GetExtension(p)?.Equals(ext, StringComparison.OrdinalIgnoreCase) ?? false)); public Score GetScore(ScoreInfo score) => new LegacyDatabasedScore(score, rulesets, beatmaps(), Files.Store); diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index 35816fe620..301d0d4dae 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -37,13 +37,13 @@ namespace osu.Game.Screens.Select.Carousel { default: case SortMode.Artist: - return string.Compare(BeatmapSet.Metadata.Artist, otherSet.BeatmapSet.Metadata.Artist, StringComparison.InvariantCultureIgnoreCase); + return string.Compare(BeatmapSet.Metadata.Artist, otherSet.BeatmapSet.Metadata.Artist, StringComparison.OrdinalIgnoreCase); case SortMode.Title: - return string.Compare(BeatmapSet.Metadata.Title, otherSet.BeatmapSet.Metadata.Title, StringComparison.InvariantCultureIgnoreCase); + return string.Compare(BeatmapSet.Metadata.Title, otherSet.BeatmapSet.Metadata.Title, StringComparison.OrdinalIgnoreCase); case SortMode.Author: - return string.Compare(BeatmapSet.Metadata.Author.Username, otherSet.BeatmapSet.Metadata.Author.Username, StringComparison.InvariantCultureIgnoreCase); + return string.Compare(BeatmapSet.Metadata.Author.Username, otherSet.BeatmapSet.Metadata.Author.Username, StringComparison.OrdinalIgnoreCase); case SortMode.DateAdded: return otherSet.BeatmapSet.DateAdded.CompareTo(BeatmapSet.DateAdded); diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index e3ad76ac35..c4d9996377 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -105,7 +105,7 @@ namespace osu.Game.Screens.Select public string SearchTerm; - public bool Equals(OptionalTextFilter other) => SearchTerm?.Equals(other.SearchTerm) ?? true; + public bool Equals(OptionalTextFilter other) => SearchTerm == other.SearchTerm; } } } diff --git a/osu.Game/Skinning/LegacySkinResourceStore.cs b/osu.Game/Skinning/LegacySkinResourceStore.cs index 79a4e2e932..249d48b34b 100644 --- a/osu.Game/Skinning/LegacySkinResourceStore.cs +++ b/osu.Game/Skinning/LegacySkinResourceStore.cs @@ -34,7 +34,7 @@ namespace osu.Game.Skinning } private string getPathForFile(string filename) => - source.Files.Find(f => string.Equals(f.Filename, filename, StringComparison.InvariantCultureIgnoreCase))?.FileInfo.StoragePath; + source.Files.Find(f => string.Equals(f.Filename, filename, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; public override IEnumerable GetAvailableResources() => source.Files.Select(f => f.Filename); } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs index de3077c025..4f8e39fa1b 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs @@ -71,7 +71,7 @@ namespace osu.Game.Storyboards.Drawables { var framePath = Animation.Path.Replace(".", frame + "."); - var path = beatmap.Value.BeatmapSetInfo.Files.Find(f => f.Filename.Equals(framePath, StringComparison.InvariantCultureIgnoreCase))?.FileInfo.StoragePath; + var path = beatmap.Value.BeatmapSetInfo.Files.Find(f => f.Filename.Equals(framePath, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; if (path == null) continue; diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs index 3a117d1713..ff48dab7e5 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs @@ -66,7 +66,7 @@ namespace osu.Game.Storyboards.Drawables [BackgroundDependencyLoader] private void load(IBindable beatmap, TextureStore textureStore) { - var path = beatmap.Value.BeatmapSetInfo?.Files?.Find(f => f.Filename.Equals(Sprite.Path, StringComparison.InvariantCultureIgnoreCase))?.FileInfo.StoragePath; + var path = beatmap.Value.BeatmapSetInfo?.Files?.Find(f => f.Filename.Equals(Sprite.Path, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; if (path == null) return; From 61a6106e5270f692b19fe7c265b990e64254ff73 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 3 Dec 2019 19:20:49 +0800 Subject: [PATCH 085/103] CA2200: don't explictly throw caught exception. --- CodeAnalysis/osu.ruleset | 1 - osu.Game/Online/API/APIAccess.cs | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CodeAnalysis/osu.ruleset b/CodeAnalysis/osu.ruleset index 1aa8f66c93..0ec2f24797 100644 --- a/CodeAnalysis/osu.ruleset +++ b/CodeAnalysis/osu.ruleset @@ -38,7 +38,6 @@ - diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 1c45d26afd..8bfc28e774 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -10,6 +10,7 @@ using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json.Linq; using osu.Framework.Bindables; +using osu.Framework.Extensions.ExceptionExtensions; using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Game.Configuration; @@ -249,7 +250,7 @@ namespace osu.Game.Online.API catch { // if we couldn't deserialize the error message let's throw the original exception outwards. - throw e; + e.Rethrow(); } } From 40b43b85f17499d0e772b03ebcaf9b02434f343c Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 10 Dec 2019 21:04:26 +0800 Subject: [PATCH 086/103] CA1715: use prefix for generic parameters. --- CodeAnalysis/osu.ruleset | 1 - .../Objects/Drawables/DrawableTaikoHitObject.cs | 8 ++++---- ...MutableDatabaseBackedStoreWithFileIncludes.cs | 6 +++--- .../UserInterfaceV2/LabelledComponent.cs | 6 +++--- osu.Game/IO/Legacy/SerializationReader.cs | 6 +++--- osu.Game/IO/Legacy/SerializationWriter.cs | 4 ++-- osu.Game/Online/Leaderboards/Leaderboard.cs | 10 +++++----- osu.Game/Overlays/OverlayHeaderTabControl.cs | 2 +- osu.Game/Overlays/OverlayTabControl.cs | 8 ++++---- osu.Game/Overlays/Settings/SettingsSlider.cs | 16 ++++++++-------- osu.Game/Overlays/UserProfileOverlay.cs | 2 +- 11 files changed, 34 insertions(+), 35 deletions(-) diff --git a/CodeAnalysis/osu.ruleset b/CodeAnalysis/osu.ruleset index 0ec2f24797..d497365f87 100644 --- a/CodeAnalysis/osu.ruleset +++ b/CodeAnalysis/osu.ruleset @@ -17,7 +17,6 @@ - diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index 0db6498c12..2da5a9c403 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -105,19 +105,19 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables } } - public abstract class DrawableTaikoHitObject : DrawableTaikoHitObject - where TaikoHitType : TaikoHitObject + public abstract class DrawableTaikoHitObject : DrawableTaikoHitObject + where TTaikoHit : TaikoHitObject { public override Vector2 OriginPosition => new Vector2(DrawHeight / 2); - public new TaikoHitType HitObject; + public new TTaikoHit HitObject; protected readonly Vector2 BaseSize; protected readonly TaikoPiece MainPiece; private readonly Container strongHitContainer; - protected DrawableTaikoHitObject(TaikoHitType hitObject) + protected DrawableTaikoHitObject(TTaikoHit hitObject) : base(hitObject) { HitObject = hitObject; diff --git a/osu.Game/Database/MutableDatabaseBackedStoreWithFileIncludes.cs b/osu.Game/Database/MutableDatabaseBackedStoreWithFileIncludes.cs index 5d6ff6b09b..102081cd65 100644 --- a/osu.Game/Database/MutableDatabaseBackedStoreWithFileIncludes.cs +++ b/osu.Game/Database/MutableDatabaseBackedStoreWithFileIncludes.cs @@ -7,9 +7,9 @@ using osu.Framework.Platform; namespace osu.Game.Database { - public abstract class MutableDatabaseBackedStoreWithFileIncludes : MutableDatabaseBackedStore - where T : class, IHasPrimaryKey, ISoftDelete, IHasFiles - where U : INamedFileInfo + public abstract class MutableDatabaseBackedStoreWithFileIncludes : MutableDatabaseBackedStore + where T : class, IHasPrimaryKey, ISoftDelete, IHasFiles + where TFileInfo : INamedFileInfo { protected MutableDatabaseBackedStoreWithFileIncludes(IDatabaseContextFactory contextFactory, Storage storage = null) : base(contextFactory, storage) diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledComponent.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledComponent.cs index 1819b36667..dd6a902989 100644 --- a/osu.Game/Graphics/UserInterfaceV2/LabelledComponent.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledComponent.cs @@ -7,15 +7,15 @@ using osu.Framework.Graphics.UserInterface; namespace osu.Game.Graphics.UserInterfaceV2 { - public abstract class LabelledComponent : LabelledDrawable, IHasCurrentValue - where T : Drawable, IHasCurrentValue + public abstract class LabelledComponent : LabelledDrawable, IHasCurrentValue + where TDrawable : Drawable, IHasCurrentValue { protected LabelledComponent(bool padded) : base(padded) { } - public Bindable Current + public Bindable Current { get => Component.Current; set => Component.Current = value; diff --git a/osu.Game/IO/Legacy/SerializationReader.cs b/osu.Game/IO/Legacy/SerializationReader.cs index aeb3ce754d..17cbd19838 100644 --- a/osu.Game/IO/Legacy/SerializationReader.cs +++ b/osu.Game/IO/Legacy/SerializationReader.cs @@ -116,13 +116,13 @@ namespace osu.Game.IO.Legacy } /// Reads a generic Dictionary from the buffer. - public IDictionary ReadDictionary() + public IDictionary ReadDictionary() { int count = ReadInt32(); if (count < 0) return null; - IDictionary d = new Dictionary(); - for (int i = 0; i < count; i++) d[(T)ReadObject()] = (U)ReadObject(); + IDictionary d = new Dictionary(); + for (int i = 0; i < count; i++) d[(TKey)ReadObject()] = (TValue)ReadObject(); return d; } diff --git a/osu.Game/IO/Legacy/SerializationWriter.cs b/osu.Game/IO/Legacy/SerializationWriter.cs index f30e4492af..c75de93bc8 100644 --- a/osu.Game/IO/Legacy/SerializationWriter.cs +++ b/osu.Game/IO/Legacy/SerializationWriter.cs @@ -102,7 +102,7 @@ namespace osu.Game.IO.Legacy } /// Writes a generic IDictionary to the buffer. - public void Write(IDictionary d) + public void Write(IDictionary d) { if (d == null) { @@ -112,7 +112,7 @@ namespace osu.Game.IO.Legacy { Write(d.Count); - foreach (KeyValuePair kvp in d) + foreach (KeyValuePair kvp in d) { WriteObject(kvp.Key); WriteObject(kvp.Value); diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 94c50185da..9c48ebd09b 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -19,7 +19,7 @@ using osuTK.Graphics; namespace osu.Game.Online.Leaderboards { - public abstract class Leaderboard : Container, IOnlineComponent + public abstract class Leaderboard : Container, IOnlineComponent { private const double fade_duration = 300; @@ -39,9 +39,9 @@ namespace osu.Game.Online.Leaderboards protected override Container Content => content; - private IEnumerable scores; + private IEnumerable scores; - public IEnumerable Scores + public IEnumerable Scores { get => scores; set @@ -288,7 +288,7 @@ namespace osu.Game.Online.Leaderboards /// /// A callback which should be called when fetching is completed. Scheduling is not required. /// An responsible for the fetch operation. This will be queued and performed automatically. - protected abstract APIRequest FetchScores(Action> scoresCallback); + protected abstract APIRequest FetchScores(Action> scoresCallback); private Placeholder currentPlaceholder; @@ -359,6 +359,6 @@ namespace osu.Game.Online.Leaderboards } } - protected abstract LeaderboardScore CreateDrawableScore(ScoreInfo model, int index); + protected abstract LeaderboardScore CreateDrawableScore(TScoreInfo model, int index); } } diff --git a/osu.Game/Overlays/OverlayHeaderTabControl.cs b/osu.Game/Overlays/OverlayHeaderTabControl.cs index 5b56771dc1..7d0cdad6d8 100644 --- a/osu.Game/Overlays/OverlayHeaderTabControl.cs +++ b/osu.Game/Overlays/OverlayHeaderTabControl.cs @@ -12,7 +12,7 @@ namespace osu.Game.Overlays AccentColour = AccentColour, }; - private class OverlayHeaderTabItem : OverlayTabItem + private class OverlayHeaderTabItem : OverlayTabItem { public OverlayHeaderTabItem(string value) : base(value) diff --git a/osu.Game/Overlays/OverlayTabControl.cs b/osu.Game/Overlays/OverlayTabControl.cs index 20649c8a74..4c396eabc1 100644 --- a/osu.Game/Overlays/OverlayTabControl.cs +++ b/osu.Game/Overlays/OverlayTabControl.cs @@ -32,7 +32,7 @@ namespace osu.Game.Overlays foreach (TabItem tabItem in TabContainer) { - ((OverlayTabItem)tabItem).AccentColour = value; + ((OverlayTabItem)tabItem).AccentColour = value; } } } @@ -59,9 +59,9 @@ namespace osu.Game.Overlays protected override Dropdown CreateDropdown() => null; - protected override TabItem CreateTabItem(T value) => new OverlayTabItem(value); + protected override TabItem CreateTabItem(T value) => new OverlayTabItem(value); - protected class OverlayTabItem : TabItem + protected class OverlayTabItem : TabItem { private readonly ExpandingBar bar; @@ -84,7 +84,7 @@ namespace osu.Game.Overlays } } - public OverlayTabItem(U value) + public OverlayTabItem(T value) : base(value) { AutoSizeAxes = Axes.X; diff --git a/osu.Game/Overlays/Settings/SettingsSlider.cs b/osu.Game/Overlays/Settings/SettingsSlider.cs index 20e08c0cd8..96c0279a7b 100644 --- a/osu.Game/Overlays/Settings/SettingsSlider.cs +++ b/osu.Game/Overlays/Settings/SettingsSlider.cs @@ -12,11 +12,11 @@ namespace osu.Game.Overlays.Settings { } - public class SettingsSlider : SettingsItem - where T : struct, IEquatable, IComparable, IConvertible - where U : OsuSliderBar, new() + public class SettingsSlider : SettingsItem + where TValue : struct, IEquatable, IComparable, IConvertible + where TSlider : OsuSliderBar, new() { - protected override Drawable CreateControl() => new U + protected override Drawable CreateControl() => new TSlider { Margin = new MarginPadding { Top = 5, Bottom = 5 }, RelativeSizeAxes = Axes.X @@ -24,14 +24,14 @@ namespace osu.Game.Overlays.Settings public bool TransferValueOnCommit { - get => ((U)Control).TransferValueOnCommit; - set => ((U)Control).TransferValueOnCommit = value; + get => ((TSlider)Control).TransferValueOnCommit; + set => ((TSlider)Control).TransferValueOnCommit = value; } public float KeyboardStep { - get => ((U)Control).KeyboardStep; - set => ((U)Control).KeyboardStep = value; + get => ((TSlider)Control).KeyboardStep; + set => ((TSlider)Control).KeyboardStep = value; } } } diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index b5e7b8bedb..a34fc619a8 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -165,7 +165,7 @@ namespace osu.Game.Overlays AccentColour = colours.Seafoam; } - private class ProfileTabItem : OverlayTabItem + private class ProfileTabItem : OverlayTabItem { public ProfileTabItem(ProfileSection value) : base(value) From 5079feaad16ccd4df3b0e993d4bc7c6705deb686 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Dec 2019 14:04:03 +0900 Subject: [PATCH 087/103] Remove unnecessary string interpolation --- osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs index d5d4c7e5ec..fede99f450 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs @@ -94,7 +94,7 @@ namespace osu.Game.Tests.Visual.Background AddStep("set dim level 0.6", () => userDimContainer.UserDimLevel.Value = test_user_dim); AddUntilStep("dim reached", () => userDimContainer.DimEqual(test_user_dim)); - AddStep($"ignore settings", () => userDimContainer.IgnoreUserSettings.Value = true); + AddStep("ignore settings", () => userDimContainer.IgnoreUserSettings.Value = true); AddUntilStep("no dim", () => userDimContainer.DimEqual(0)); AddStep("set break", () => isBreakTime.Value = true); AddAssert("no dim", () => userDimContainer.DimEqual(0)); From 946a202ee555cc26f12aabca4d9964dea2d99cdf Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 17 Dec 2019 15:34:16 +0900 Subject: [PATCH 088/103] Fix online replays not being available locally --- osu.Game/Beatmaps/BeatmapManager.cs | 4 +++- osu.Game/Database/DownloadableArchiveModelManager.cs | 6 ++++-- osu.Game/Scoring/ScoreManager.cs | 4 +++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index a2e750cac5..a10ad73817 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -158,7 +158,9 @@ namespace osu.Game.Beatmaps void resetIds() => beatmapSet.Beatmaps.ForEach(b => b.OnlineBeatmapID = null); } - protected override bool CheckLocalAvailability(BeatmapSetInfo model, IQueryable items) => items.Any(b => b.OnlineBeatmapSetID == model.OnlineBeatmapSetID); + protected override bool CheckLocalAvailability(BeatmapSetInfo model, IQueryable items) + => base.CheckLocalAvailability(model, items) + || (model.OnlineBeatmapSetID != null && items.Any(b => b.OnlineBeatmapSetID == model.OnlineBeatmapSetID)); /// /// Delete a beatmap difficulty. diff --git a/osu.Game/Database/DownloadableArchiveModelManager.cs b/osu.Game/Database/DownloadableArchiveModelManager.cs index 243060388f..5f688c149d 100644 --- a/osu.Game/Database/DownloadableArchiveModelManager.cs +++ b/osu.Game/Database/DownloadableArchiveModelManager.cs @@ -33,7 +33,8 @@ namespace osu.Game.Database private readonly MutableDatabaseBackedStoreWithFileIncludes modelStore; - protected DownloadableArchiveModelManager(Storage storage, IDatabaseContextFactory contextFactory, IAPIProvider api, MutableDatabaseBackedStoreWithFileIncludes modelStore, IIpcHost importHost = null) + protected DownloadableArchiveModelManager(Storage storage, IDatabaseContextFactory contextFactory, IAPIProvider api, MutableDatabaseBackedStoreWithFileIncludes modelStore, + IIpcHost importHost = null) : base(storage, contextFactory, modelStore, importHost) { this.api = api; @@ -124,7 +125,8 @@ namespace osu.Game.Database /// The whose existence needs to be checked. /// The usable items present in the store. /// Whether the exists. - protected abstract bool CheckLocalAvailability(TModel model, IQueryable items); + protected virtual bool CheckLocalAvailability(TModel model, IQueryable items) + => model.ID > 0 && items.Any(i => i.ID == model.ID && i.Files.Any()); public ArchiveDownloadRequest GetExistingDownload(TModel model) => currentDownloads.Find(r => r.Model.Equals(model)); diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 3279af05b6..5c846e8062 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -69,6 +69,8 @@ namespace osu.Game.Scoring protected override ArchiveDownloadRequest CreateDownloadRequest(ScoreInfo score, bool minimiseDownload) => new DownloadReplayRequest(score); - protected override bool CheckLocalAvailability(ScoreInfo model, IQueryable items) => items.Any(s => s.Equals(model) && s.Files.Any()); + protected override bool CheckLocalAvailability(ScoreInfo model, IQueryable items) + => base.CheckLocalAvailability(model, items) + || (model.OnlineScoreID != null && items.Any(i => i.OnlineScoreID == model.OnlineScoreID)); } } From 59c3b39ed8a7ab63fe653ec60a0d9cdf3e4551af Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 17 Dec 2019 15:46:50 +0900 Subject: [PATCH 089/103] Add test --- osu.Game.Tests/Scores/IO/ImportScoreTest.cs | 47 ++++++++++++++++++++- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs index 89b5db9e1b..a95e699470 100644 --- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs +++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -10,6 +11,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Platform; using osu.Game.Beatmaps; +using osu.Game.IO.Archives; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; @@ -154,7 +156,30 @@ namespace osu.Game.Tests.Scores.IO } } - private async Task loadIntoOsu(OsuGameBase osu, ScoreInfo score) + [Test] + public async Task TestOnlineScoreIsAvailableLocally() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWithDeletedBeatmapSet")) + { + try + { + var osu = await loadOsu(host); + + await loadIntoOsu(osu, new ScoreInfo { OnlineScoreID = 2 }, new TestArchiveReader()); + + var scoreManager = osu.Dependencies.Get(); + + // Note: A new score reference is used here since the import process mutates the original object to set an ID + Assert.That(scoreManager.IsAvailableLocally(new ScoreInfo { OnlineScoreID = 2 })); + } + finally + { + host.Exit(); + } + } + } + + private async Task loadIntoOsu(OsuGameBase osu, ScoreInfo score, ArchiveReader archive = null) { var beatmapManager = osu.Dependencies.Get(); @@ -165,7 +190,7 @@ namespace osu.Game.Tests.Scores.IO score.Ruleset = new OsuRuleset().RulesetInfo; var scoreManager = osu.Dependencies.Get(); - await scoreManager.Import(score); + await scoreManager.Import(score, archive); return scoreManager.GetAllUsableScores().FirstOrDefault(); } @@ -196,5 +221,23 @@ namespace osu.Game.Tests.Scores.IO Assert.IsTrue(task.Wait(timeout), failureMessage); } + + private class TestArchiveReader : ArchiveReader + { + public TestArchiveReader() + : base("test_archive") + { + } + + public override Stream GetStream(string name) => new MemoryStream(); + + public override void Dispose() + { + } + + public override IEnumerable Filenames => new[] { "test_file.osr" }; + + public override Stream GetUnderlyingStream() => new MemoryStream(); + } } } From 927ba4c1133088a67233a32fd56ccbe670c97500 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 17 Dec 2019 15:20:05 +0800 Subject: [PATCH 090/103] Update expected exception in test. --- osu.Game.Tests/NonVisual/LimitedCapacityStackTest.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/NonVisual/LimitedCapacityStackTest.cs b/osu.Game.Tests/NonVisual/LimitedCapacityStackTest.cs index 1c78b63499..d5ac38008e 100644 --- a/osu.Game.Tests/NonVisual/LimitedCapacityStackTest.cs +++ b/osu.Game.Tests/NonVisual/LimitedCapacityStackTest.cs @@ -25,7 +25,7 @@ namespace osu.Game.Tests.NonVisual { Assert.AreEqual(0, stack.Count); - Assert.Throws(() => + Assert.Throws(() => { int unused = stack[0]; }); @@ -55,7 +55,7 @@ namespace osu.Game.Tests.NonVisual // e.g. indices 3, 4, 5, 6 (out of range) for (int i = stack.Count; i < stack.Count + capacity; i++) { - Assert.Throws(() => + Assert.Throws(() => { int unused = stack[i]; }); @@ -80,7 +80,7 @@ namespace osu.Game.Tests.NonVisual // e.g. indices 3, 4, 5, 6 (out of range) for (int i = stack.Count; i < stack.Count + capacity; i++) { - Assert.Throws(() => + Assert.Throws(() => { int unused = stack[i]; }); From 6da581f3fe86047762679905ca3ce14bbccb836b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 17 Dec 2019 16:35:40 +0900 Subject: [PATCH 091/103] Snap based on end position/time of the previous object --- .../Edit/OsuDistanceSnapGrid.cs | 3 ++- .../Edit/OsuHitObjectComposer.cs | 19 ++++++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs index bde86a2890..ff3be97427 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using JetBrains.Annotations; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit.Compose.Components; @@ -10,7 +11,7 @@ namespace osu.Game.Rulesets.Osu.Edit public class OsuDistanceSnapGrid : CircularDistanceSnapGrid { public OsuDistanceSnapGrid(OsuHitObject hitObject, [CanBeNull] OsuHitObject nextHitObject = null) - : base(hitObject.StackedPosition, hitObject.StartTime, nextHitObject?.StartTime) + : base(hitObject.StackedEndPosition, hitObject.GetEndTime(), nextHitObject?.StartTime) { Masking = true; } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 675b09fc6d..a2c1a5f5f4 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -92,7 +92,24 @@ namespace osu.Game.Rulesets.Osu.Edit return null; OsuHitObject sourceObject = EditorBeatmap.HitObjects[sourceIndex]; - OsuHitObject targetObject = sourceIndex + targetOffset < EditorBeatmap.HitObjects.Count ? EditorBeatmap.HitObjects[sourceIndex + targetOffset] : null; + + int targetIndex = sourceIndex + targetOffset; + OsuHitObject targetObject = null; + + // Keep advancing the target object while its start time falls before the end time of the source object + while (true) + { + if (targetIndex >= EditorBeatmap.HitObjects.Count) + break; + + if (EditorBeatmap.HitObjects[targetIndex].StartTime >= sourceObject.GetEndTime()) + { + targetObject = EditorBeatmap.HitObjects[targetIndex]; + break; + } + + targetIndex++; + } return new OsuDistanceSnapGrid(sourceObject, targetObject); } From 1e798a8dbe1c31244e52e7ab7d01ec96a5ce76ff Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 17 Dec 2019 17:53:48 +0900 Subject: [PATCH 092/103] Add abstract implementation of slider path --- .../Objects/Drawables/DrawableSlider.cs | 2 +- .../Drawables/Pieces/DrawableSliderPath.cs | 70 ++++++++++++++ .../Objects/Drawables/Pieces/SliderBody.cs | 92 ++++--------------- 3 files changed, 88 insertions(+), 76 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DrawableSliderPath.cs diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 1e0402d492..e6ab2e580c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -201,7 +201,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.ApplySkin(skin, allowFallback); - Body.BorderSize = skin.GetConfig(OsuSkinConfiguration.SliderBorderSize)?.Value ?? SliderBody.DEFAULT_BORDER_SIZE; + Body.BorderSize = skin.GetConfig(OsuSkinConfiguration.SliderBorderSize)?.Value ?? 1; sliderPathRadius = skin.GetConfig(OsuSkinConfiguration.SliderPathRadius)?.Value ?? OsuHitObject.OBJECT_RADIUS; updatePathRadius(); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DrawableSliderPath.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DrawableSliderPath.cs new file mode 100644 index 0000000000..c31d6beb01 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DrawableSliderPath.cs @@ -0,0 +1,70 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Lines; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces +{ + public abstract class DrawableSliderPath : SmoothPath + { + protected const float BORDER_PORTION = 0.128f; + protected const float GRADIENT_PORTION = 1 - BORDER_PORTION; + + private const float border_max_size = 8f; + private const float border_min_size = 0f; + + private Color4 borderColour = Color4.White; + + public Color4 BorderColour + { + get => borderColour; + set + { + if (borderColour == value) + return; + + borderColour = value; + + InvalidateTexture(); + } + } + + private Color4 accentColour = Color4.White; + + public Color4 AccentColour + { + get => accentColour; + set + { + if (accentColour == value) + return; + + accentColour = value; + + InvalidateTexture(); + } + } + + private float borderSize = 1; + + public float BorderSize + { + get => borderSize; + set + { + if (borderSize == value) + return; + + if (value < border_min_size || value > border_max_size) + return; + + borderSize = value; + + InvalidateTexture(); + } + } + + protected float CalculatedBorderPortion => BorderSize * BORDER_PORTION; + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs index 24a437c20e..dcaa9748e9 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Lines; using osuTK; @@ -12,9 +13,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { public abstract class SliderBody : CompositeDrawable { - public const float DEFAULT_BORDER_SIZE = 1; - - private SliderPath path; + private DrawableSliderPath path; protected Path Path => path; @@ -80,19 +79,19 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces } /// - /// Initialises a new , releasing all resources retained by the old one. + /// Initialises a new , releasing all resources retained by the old one. /// public virtual void RecyclePath() { - InternalChild = path = new SliderPath + InternalChild = path = CreateSliderPath().With(p => { - Position = path?.Position ?? Vector2.Zero, - PathRadius = path?.PathRadius ?? 10, - AccentColour = path?.AccentColour ?? Color4.White, - BorderColour = path?.BorderColour ?? Color4.White, - BorderSize = path?.BorderSize ?? DEFAULT_BORDER_SIZE, - Vertices = path?.Vertices ?? Array.Empty() - }; + p.Position = path?.Position ?? Vector2.Zero; + p.PathRadius = path?.PathRadius ?? 10; + p.AccentColour = path?.AccentColour ?? Color4.White; + p.BorderColour = path?.BorderColour ?? Color4.White; + p.BorderSize = path?.BorderSize ?? 1; + p.Vertices = path?.Vertices ?? Array.Empty(); + }); } public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => path.ReceivePositionalInputAt(screenSpacePos); @@ -103,77 +102,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces /// The vertices protected void SetVertices(IReadOnlyList vertices) => path.Vertices = vertices; - private class SliderPath : SmoothPath + protected virtual DrawableSliderPath CreateSliderPath() => new DefaultDrawableSliderPath(); + + protected class DefaultDrawableSliderPath : DrawableSliderPath { - private const float border_max_size = 8f; - private const float border_min_size = 0f; - - private const float border_portion = 0.128f; - private const float gradient_portion = 1 - border_portion; - private const float opacity_at_centre = 0.3f; private const float opacity_at_edge = 0.8f; - private Color4 borderColour = Color4.White; - - public Color4 BorderColour - { - get => borderColour; - set - { - if (borderColour == value) - return; - - borderColour = value; - - InvalidateTexture(); - } - } - - private Color4 accentColour = Color4.White; - - public Color4 AccentColour - { - get => accentColour; - set - { - if (accentColour == value) - return; - - accentColour = value; - - InvalidateTexture(); - } - } - - private float borderSize = DEFAULT_BORDER_SIZE; - - public float BorderSize - { - get => borderSize; - set - { - if (borderSize == value) - return; - - if (value < border_min_size || value > border_max_size) - return; - - borderSize = value; - - InvalidateTexture(); - } - } - - private float calculatedBorderPortion => BorderSize * border_portion; - protected override Color4 ColourAt(float position) { - if (calculatedBorderPortion != 0f && position <= calculatedBorderPortion) + if (CalculatedBorderPortion != 0f && position <= CalculatedBorderPortion) return BorderColour; - position -= calculatedBorderPortion; - return new Color4(AccentColour.R, AccentColour.G, AccentColour.B, (opacity_at_edge - (opacity_at_edge - opacity_at_centre) * position / gradient_portion) * AccentColour.A); + position -= CalculatedBorderPortion; + return new Color4(AccentColour.R, AccentColour.G, AccentColour.B, (opacity_at_edge - (opacity_at_edge - opacity_at_centre) * position / GRADIENT_PORTION) * AccentColour.A); } } } From 8cd96acffc2e3f3fe61062983804da1a4db33ff9 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 17 Dec 2019 12:05:35 +0300 Subject: [PATCH 093/103] CounterPill implementation --- .../Online/TestSceneProfileCounterPill.cs | 42 +++++++++++++ .../Overlays/Profile/Sections/CounterPill.cs | 61 +++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneProfileCounterPill.cs create mode 100644 osu.Game/Overlays/Profile/Sections/CounterPill.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneProfileCounterPill.cs b/osu.Game.Tests/Visual/Online/TestSceneProfileCounterPill.cs new file mode 100644 index 0000000000..468239cf08 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneProfileCounterPill.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; +using System.Collections.Generic; +using NUnit.Framework; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Overlays.Profile.Sections; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneProfileCounterPill : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(CounterPill) + }; + + private readonly CounterPill pill; + private readonly BindableInt value = new BindableInt(); + + public TestSceneProfileCounterPill() + { + Child = pill = new CounterPill + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Current = { BindTarget = value } + }; + } + + [Test] + public void TestVisibility() + { + AddStep("Set value to 0", () => value.Value = 0); + AddAssert("Check hidden", () => !pill.IsPresent); + AddStep("Set value to 10", () => value.Value = 10); + AddAssert("Check visible", () => pill.IsPresent); + } + } +} diff --git a/osu.Game/Overlays/Profile/Sections/CounterPill.cs b/osu.Game/Overlays/Profile/Sections/CounterPill.cs new file mode 100644 index 0000000000..bd760c4139 --- /dev/null +++ b/osu.Game/Overlays/Profile/Sections/CounterPill.cs @@ -0,0 +1,61 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Framework.Bindables; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Overlays.Profile.Sections +{ + public class CounterPill : CircularContainer + { + private const int duration = 200; + + public readonly BindableInt Current = new BindableInt(); + + private readonly OsuSpriteText counter; + + public CounterPill() + { + AutoSizeAxes = Axes.Both; + Alpha = 0; + Masking = true; + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(0.05f) + }, + counter = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Margin = new MarginPadding { Horizontal = 10, Vertical = 5 }, + Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold) + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + Current.BindValueChanged(onCurrentChanged, true); + } + + private void onCurrentChanged(ValueChangedEvent value) + { + if (value.NewValue == 0) + { + this.FadeOut(duration, Easing.OutQuint); + return; + } + + counter.Text = value.NewValue.ToString(); + this.FadeIn(duration, Easing.OutQuint); + } + } +} From 9caed9e98a25993d6984eb74d541c9e3ad1c6012 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 17 Dec 2019 18:16:25 +0900 Subject: [PATCH 094/103] Add legacy slider body support --- .../Objects/Drawables/DrawableSlider.cs | 6 ++- .../Drawables/Pieces/SnakingSliderBody.cs | 12 ++--- osu.Game.Rulesets.Osu/OsuSkinComponents.cs | 3 +- .../Skinning/LegacySliderBody.cs | 47 +++++++++++++++++++ .../Skinning/OsuLegacySkinTransformer.cs | 6 +++ 5 files changed, 64 insertions(+), 10 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index e6ab2e580c..46a219a69c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -24,9 +24,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public DrawableSliderHead HeadCircle => headContainer.Child; public DrawableSliderTail TailCircle => tailContainer.Child; - public readonly SnakingSliderBody Body; public readonly SliderBall Ball; + public SnakingSliderBody Body => (SnakingSliderBody)skinnedBody.Drawable; + + private readonly SkinnableDrawable skinnedBody; private readonly Container headContainer; private readonly Container tailContainer; private readonly Container tickContainer; @@ -51,7 +53,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables InternalChildren = new Drawable[] { - Body = new SnakingSliderBody(s), + skinnedBody = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderBody), _ => new SnakingSliderBody(), confineMode: ConfineMode.NoScaling), tickContainer = new Container { RelativeSizeAxes = Axes.Both }, repeatContainer = new Container { RelativeSizeAxes = Axes.Both }, Ball = new SliderBall(s, this) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs index f2150280b3..8a8668d6af 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osuTK; @@ -50,16 +51,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces /// private Vector2 snakedPathOffset; - private readonly Slider slider; - - public SnakingSliderBody(Slider slider) - { - this.slider = slider; - } + private Slider slider; [BackgroundDependencyLoader] - private void load() + private void load(DrawableHitObject drawableObject) { + slider = (Slider)drawableObject.HitObject; + Refresh(); } diff --git a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs index 8dd48eace0..4ea4220faf 100644 --- a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs +++ b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs @@ -14,6 +14,7 @@ namespace osu.Game.Rulesets.Osu ReverseArrow, HitCircleText, SliderFollowCircle, - SliderBall + SliderBall, + SliderBody, } } diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs new file mode 100644 index 0000000000..6a26529f4c --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs @@ -0,0 +1,47 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.MathUtils; +using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Skinning +{ + public class LegacySliderBody : SnakingSliderBody + { + protected override DrawableSliderPath CreateSliderPath() => new LegacyDrawableSliderPath(); + + private class LegacyDrawableSliderPath : DrawableSliderPath + { + public new Color4 AccentColour => new Color4(base.AccentColour.R, base.AccentColour.G, base.AccentColour.B, 0.70f); + + protected override Color4 ColourAt(float position) + { + if (CalculatedBorderPortion != 0f && position <= CalculatedBorderPortion) + return BorderColour; + + position -= BORDER_PORTION; + + Color4 outerColour = AccentColour.Darken(0.1f); + Color4 innerColour = lighten(AccentColour, 0.5f); + + return Interpolation.ValueAt(position / GRADIENT_PORTION, outerColour, innerColour, 0, 1); + } + + /// + /// Lightens a colour in a way more friendly to dark or strong colours. + /// + private static Color4 lighten(Color4 color, float amount) + { + amount *= 0.5f; + return new Color4( + Math.Min(1, color.R * (1 + 0.5f * amount) + 1 * amount), + Math.Min(1, color.G * (1 + 0.5f * amount) + 1 * amount), + Math.Min(1, color.B * (1 + 0.5f * amount) + 1 * amount), + color.A); + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs index f5b7d9166f..71770dedce 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs @@ -73,6 +73,12 @@ namespace osu.Game.Rulesets.Osu.Skinning return null; + case OsuSkinComponents.SliderBody: + if (hasHitCircle.Value) + return new LegacySliderBody(); + + return null; + case OsuSkinComponents.HitCircle: if (hasHitCircle.Value) return new LegacyMainCirclePiece(); From 023892738af69c35902c39c43945906fb0b284ea Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 17 Dec 2019 12:36:44 +0300 Subject: [PATCH 095/103] Integration into overlay --- .../Beatmaps/PaginatedBeatmapContainer.cs | 22 +++++++++++++++ .../Profile/Sections/PaginatedContainer.cs | 28 +++++++++++++++++-- .../Sections/Ranks/PaginatedScoreContainer.cs | 12 ++++++++ osu.Game/Users/User.cs | 18 ++++++++++++ 4 files changed, 77 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs index 919f8a2fa0..6684420cf4 100644 --- a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs @@ -38,5 +38,27 @@ namespace osu.Game.Overlays.Profile.Sections.Beatmaps Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, }; + + protected override int GetCount(User user) + { + switch (type) + { + default: + case BeatmapSetType.Favourite: + return user.FavouriteBeatmapsetCount; + + case BeatmapSetType.Graveyard: + return user.GraveyardBeatmapsetCount; + + case BeatmapSetType.Loved: + return user.LovedBeatmapsetCount; + + case BeatmapSetType.RankedAndApproved: + return user.RankedAndApprovedBeatmapsetCount; + + case BeatmapSetType.Unranked: + return user.UnrankedBeatmapsetCount; + } + } } } diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs index dc1a847b14..94d5f99f86 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs @@ -23,6 +23,7 @@ namespace osu.Game.Overlays.Profile.Sections private readonly OsuSpriteText missingText; private APIRequest> retrievalRequest; private CancellationTokenSource loadCancellation; + private readonly BindableInt count = new BindableInt(); [Resolved] private IAPIProvider api { get; set; } @@ -44,11 +45,28 @@ namespace osu.Game.Overlays.Profile.Sections Children = new Drawable[] { - new OsuSpriteText + new FillFlowContainer { - Text = header, - Font = OsuFont.GetFont(size: 20, weight: FontWeight.Bold), + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5, 0), Margin = new MarginPadding { Top = 10, Bottom = 10 }, + Children = new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Text = header, + Font = OsuFont.GetFont(size: 20, weight: FontWeight.Bold), + }, + new CounterPill + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Current = { BindTarget = count } + } + } }, ItemsContainer = new FillFlowContainer { @@ -90,6 +108,8 @@ namespace osu.Game.Overlays.Profile.Sections VisiblePages = 0; ItemsContainer.Clear(); + count.Value = GetCount(e.NewValue); + if (e.NewValue != null) showMore(); } @@ -124,6 +144,8 @@ namespace osu.Game.Overlays.Profile.Sections }, loadCancellation.Token); }); + protected virtual int GetCount(User user) => 0; + protected abstract APIRequest> CreateRequest(); protected abstract Drawable CreateDrawableItem(TModel model); diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs index 5b58fc0930..fa74d76dfc 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -32,6 +32,18 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks protected override APIRequest> CreateRequest() => new GetUserScoresRequest(User.Value.Id, type, VisiblePages++, ItemsPerPage); + protected override int GetCount(User user) + { + switch (type) + { + default: + return 0; + + case ScoreType.Firsts: + return user.ScoresFirstCount; + } + } + protected override Drawable CreateDrawableItem(APILegacyScoreInfo model) { switch (type) diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs index b15789f324..ebd9dbecd1 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -126,6 +126,24 @@ namespace osu.Game.Users [JsonProperty(@"follower_count")] public int FollowerCount; + [JsonProperty(@"favourite_beatmapset_count")] + public int FavouriteBeatmapsetCount; + + [JsonProperty(@"graveyard_beatmapset_count")] + public int GraveyardBeatmapsetCount; + + [JsonProperty(@"loved_beatmapset_count")] + public int LovedBeatmapsetCount; + + [JsonProperty(@"ranked_and_approved_beatmapset_count")] + public int RankedAndApprovedBeatmapsetCount; + + [JsonProperty(@"unranked_beatmapset_count")] + public int UnrankedBeatmapsetCount; + + [JsonProperty(@"scores_first_count")] + public int ScoresFirstCount; + [JsonProperty] private string[] playstyle { From bc9177983adc209fa185cbf549de01bcbab77222 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 17 Dec 2019 12:50:50 +0300 Subject: [PATCH 096/103] Fix possible null --- osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs index 94d5f99f86..a30ff786fb 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs @@ -108,10 +108,11 @@ namespace osu.Game.Overlays.Profile.Sections VisiblePages = 0; ItemsContainer.Clear(); - count.Value = GetCount(e.NewValue); - if (e.NewValue != null) + { showMore(); + count.Value = GetCount(e.NewValue); + } } private void showMore() From 7c2884700eaffe391e37fa79a80deb3a30a431aa Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 17 Dec 2019 19:29:27 +0900 Subject: [PATCH 097/103] Fix various display issues by abstracting further --- osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs | 5 +- .../Objects/Drawables/DrawableRepeatPoint.cs | 3 +- .../Objects/Drawables/DrawableSlider.cs | 52 +++++------------ .../Drawables/Pieces/PlaySliderBody.cs | 57 +++++++++++++++++++ .../Objects/Drawables/Pieces/SliderBody.cs | 2 +- .../Skinning/LegacySliderBody.cs | 2 +- 6 files changed, 78 insertions(+), 43 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/PlaySliderBody.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs index 7e20feba02..0cca3ae40c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs @@ -11,6 +11,7 @@ using osu.Game.Configuration; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; namespace osu.Game.Rulesets.Osu.Mods { @@ -57,8 +58,8 @@ namespace osu.Game.Rulesets.Osu.Mods slider.AccentColour.BindValueChanged(_ => { //will trigger on skin change. - slider.Body.AccentColour = slider.AccentColour.Value.Opacity(0); - slider.Body.BorderColour = slider.AccentColour.Value; + ((PlaySliderBody)slider.Body.Drawable).AccentColour = slider.AccentColour.Value.Opacity(0); + ((PlaySliderBody)slider.Body.Drawable).BorderColour = slider.AccentColour.Value; }, true); break; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs index 71cb9a9691..b81d94a673 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.MathUtils; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; using osu.Game.Rulesets.Scoring; using osuTK; using osu.Game.Skinning; @@ -98,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public void UpdateSnakingPosition(Vector2 start, Vector2 end) { bool isRepeatAtEnd = repeatPoint.RepeatIndex % 2 == 0; - List curve = drawableSlider.Body.CurrentCurve; + List curve = ((PlaySliderBody)drawableSlider.Body.Drawable).CurrentCurve; Position = isRepeatAtEnd ? end : start; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 46a219a69c..03183beff1 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -11,7 +11,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Osu.Configuration; using osu.Game.Rulesets.Osu.Skinning; using osu.Game.Rulesets.Scoring; using osuTK.Graphics; @@ -25,10 +24,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public DrawableSliderTail TailCircle => tailContainer.Child; public readonly SliderBall Ball; + public readonly SkinnableDrawable Body; - public SnakingSliderBody Body => (SnakingSliderBody)skinnedBody.Drawable; + private PlaySliderBody sliderBody => (PlaySliderBody)Body.Drawable; - private readonly SkinnableDrawable skinnedBody; private readonly Container headContainer; private readonly Container tailContainer; private readonly Container tickContainer; @@ -39,10 +38,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private readonly IBindable positionBindable = new Bindable(); private readonly IBindable stackHeightBindable = new Bindable(); private readonly IBindable scaleBindable = new Bindable(); - private readonly IBindable pathVersion = new Bindable(); - - [Resolved(CanBeNull = true)] - private OsuRulesetConfigManager config { get; set; } public DrawableSlider(Slider s) : base(s) @@ -53,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables InternalChildren = new Drawable[] { - skinnedBody = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderBody), _ => new SnakingSliderBody(), confineMode: ConfineMode.NoScaling), + Body = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderBody), _ => new DefaultSliderBody(), confineMode: ConfineMode.NoScaling), tickContainer = new Container { RelativeSizeAxes = Axes.Both }, repeatContainer = new Container { RelativeSizeAxes = Axes.Both }, Ball = new SliderBall(s, this) @@ -72,28 +67,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables [BackgroundDependencyLoader] private void load() { - config?.BindWith(OsuRulesetSetting.SnakingInSliders, Body.SnakingIn); - config?.BindWith(OsuRulesetSetting.SnakingOutSliders, Body.SnakingOut); - positionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition); stackHeightBindable.BindValueChanged(_ => Position = HitObject.StackedPosition); - scaleBindable.BindValueChanged(scale => - { - updatePathRadius(); - Ball.Scale = new Vector2(scale.NewValue); - }); + scaleBindable.BindValueChanged(scale => Ball.Scale = new Vector2(scale.NewValue)); positionBindable.BindTo(HitObject.PositionBindable); stackHeightBindable.BindTo(HitObject.StackHeightBindable); scaleBindable.BindTo(HitObject.ScaleBindable); - pathVersion.BindTo(slider.Path.Version); - - pathVersion.BindValueChanged(_ => Body.Refresh()); AccentColour.BindValueChanged(colour => { - Body.AccentColour = colour.NewValue; - foreach (var drawableHitObject in NestedHitObjects) drawableHitObject.AccentColour.Value = colour.NewValue; }, true); @@ -171,16 +154,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables double completionProgress = Math.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1); Ball.UpdateProgress(completionProgress); - Body.UpdateProgress(completionProgress); + sliderBody.UpdateProgress(completionProgress); foreach (DrawableHitObject hitObject in NestedHitObjects) { - if (hitObject is ITrackSnaking s) s.UpdateSnakingPosition(slider.Path.PositionAt(Body.SnakedStart ?? 0), slider.Path.PositionAt(Body.SnakedEnd ?? 0)); + if (hitObject is ITrackSnaking s) s.UpdateSnakingPosition(slider.Path.PositionAt(sliderBody.SnakedStart ?? 0), slider.Path.PositionAt(sliderBody.SnakedEnd ?? 0)); if (hitObject is IRequireTracking t) t.Tracking = Ball.Tracking; } - Size = Body.Size; - OriginPosition = Body.PathOffset; + Size = sliderBody.Size; + OriginPosition = sliderBody.PathOffset; if (DrawSize != Vector2.Zero) { @@ -194,28 +177,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public override void OnKilled() { base.OnKilled(); - Body.RecyclePath(); + sliderBody.RecyclePath(); } - private float sliderPathRadius; - protected override void ApplySkin(ISkinSource skin, bool allowFallback) { base.ApplySkin(skin, allowFallback); - Body.BorderSize = skin.GetConfig(OsuSkinConfiguration.SliderBorderSize)?.Value ?? 1; - sliderPathRadius = skin.GetConfig(OsuSkinConfiguration.SliderPathRadius)?.Value ?? OsuHitObject.OBJECT_RADIUS; - updatePathRadius(); - - Body.AccentColour = skin.GetConfig(OsuSkinColour.SliderTrackOverride)?.Value ?? AccentColour.Value; - Body.BorderColour = skin.GetConfig(OsuSkinColour.SliderBorder)?.Value ?? Color4.White; - bool allowBallTint = skin.GetConfig(OsuSkinConfiguration.AllowSliderBallTint)?.Value ?? false; Ball.Colour = allowBallTint ? AccentColour.Value : Color4.White; } - private void updatePathRadius() => Body.PathRadius = slider.Scale * sliderPathRadius; - protected override void CheckForResult(bool userTriggered, double timeOffset) { if (userTriggered || Time.Current < slider.EndTime) @@ -266,6 +238,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public Drawable ProxiedLayer => HeadCircle.ProxiedLayer; - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Body.ReceivePositionalInputAt(screenSpacePos); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => sliderBody.ReceivePositionalInputAt(screenSpacePos); + + private class DefaultSliderBody : PlaySliderBody + { + } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/PlaySliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/PlaySliderBody.cs new file mode 100644 index 0000000000..aa9caf193e --- /dev/null +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/PlaySliderBody.cs @@ -0,0 +1,57 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Configuration; +using osu.Game.Rulesets.Osu.Skinning; +using osu.Game.Skinning; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces +{ + public abstract class PlaySliderBody : SnakingSliderBody + { + private IBindable scaleBindable; + private IBindable pathVersion; + private IBindable accentColour; + + [Resolved] + private DrawableHitObject drawableObject { get; set; } + + [Resolved(CanBeNull = true)] + private OsuRulesetConfigManager config { get; set; } + + private Slider slider; + private float defaultPathRadius; + + [BackgroundDependencyLoader] + private void load(ISkinSource skin) + { + slider = (Slider)drawableObject.HitObject; + defaultPathRadius = skin.GetConfig(OsuSkinConfiguration.SliderPathRadius)?.Value ?? OsuHitObject.OBJECT_RADIUS; + + scaleBindable = slider.ScaleBindable.GetBoundCopy(); + scaleBindable.BindValueChanged(_ => updatePathRadius(), true); + + pathVersion = slider.Path.Version.GetBoundCopy(); + pathVersion.BindValueChanged(_ => Refresh()); + + accentColour = drawableObject.AccentColour.GetBoundCopy(); + accentColour.BindValueChanged(accent => updateAccentColour(skin, accent.NewValue), true); + + config?.BindWith(OsuRulesetSetting.SnakingInSliders, SnakingIn); + config?.BindWith(OsuRulesetSetting.SnakingOutSliders, SnakingOut); + + BorderSize = skin.GetConfig(OsuSkinConfiguration.SliderBorderSize)?.Value ?? 1; + BorderColour = skin.GetConfig(OsuSkinColour.SliderBorder)?.Value ?? Color4.White; + } + + private void updatePathRadius() + => PathRadius = defaultPathRadius * scaleBindable.Value; + + private void updateAccentColour(ISkinSource skin, Color4 defaultAccentColour) + => AccentColour = skin.GetConfig(OsuSkinColour.SliderTrackOverride)?.Value ?? defaultAccentColour; + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs index dcaa9748e9..8758a4a066 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs @@ -104,7 +104,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces protected virtual DrawableSliderPath CreateSliderPath() => new DefaultDrawableSliderPath(); - protected class DefaultDrawableSliderPath : DrawableSliderPath + private class DefaultDrawableSliderPath : DrawableSliderPath { private const float opacity_at_centre = 0.3f; private const float opacity_at_edge = 0.8f; diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs index 6a26529f4c..18a5d7a320 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs @@ -9,7 +9,7 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Skinning { - public class LegacySliderBody : SnakingSliderBody + public class LegacySliderBody : PlaySliderBody { protected override DrawableSliderPath CreateSliderPath() => new LegacyDrawableSliderPath(); From f6cde911e2b4a13e57acb84863d9b524668c2985 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 17 Dec 2019 13:41:28 +0300 Subject: [PATCH 098/103] Use switch expressions --- .../Beatmaps/PaginatedBeatmapContainer.cs | 28 ++++++------------- .../Sections/Ranks/PaginatedScoreContainer.cs | 14 +++------- 2 files changed, 12 insertions(+), 30 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs index 6684420cf4..fcd12e2b54 100644 --- a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs @@ -39,26 +39,14 @@ namespace osu.Game.Overlays.Profile.Sections.Beatmaps Origin = Anchor.TopCentre, }; - protected override int GetCount(User user) + protected override int GetCount(User user) => type switch { - switch (type) - { - default: - case BeatmapSetType.Favourite: - return user.FavouriteBeatmapsetCount; - - case BeatmapSetType.Graveyard: - return user.GraveyardBeatmapsetCount; - - case BeatmapSetType.Loved: - return user.LovedBeatmapsetCount; - - case BeatmapSetType.RankedAndApproved: - return user.RankedAndApprovedBeatmapsetCount; - - case BeatmapSetType.Unranked: - return user.UnrankedBeatmapsetCount; - } - } + BeatmapSetType.Favourite => user.FavouriteBeatmapsetCount, + BeatmapSetType.Graveyard => user.GraveyardBeatmapsetCount, + BeatmapSetType.Loved => user.LovedBeatmapsetCount, + BeatmapSetType.RankedAndApproved => user.RankedAndApprovedBeatmapsetCount, + BeatmapSetType.Unranked => user.UnrankedBeatmapsetCount, + _ => 0 + }; } } diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs index fa74d76dfc..e0f1c935da 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -32,17 +32,11 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks protected override APIRequest> CreateRequest() => new GetUserScoresRequest(User.Value.Id, type, VisiblePages++, ItemsPerPage); - protected override int GetCount(User user) + protected override int GetCount(User user) => type switch { - switch (type) - { - default: - return 0; - - case ScoreType.Firsts: - return user.ScoresFirstCount; - } - } + ScoreType.Firsts => user.ScoresFirstCount, + _ => 0 + }; protected override Drawable CreateDrawableItem(APILegacyScoreInfo model) { From 527ab1a72f08ce6c3c5eef7226ec166c9fb726b1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 17 Dec 2019 19:49:13 +0900 Subject: [PATCH 099/103] Fix traceable mod not working on skin change --- osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs | 15 ++++++++------- osu.Game/Skinning/SkinReloadableDrawable.cs | 15 ++++++++++++++- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs index 0cca3ae40c..cf1ce517e8 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs @@ -55,13 +55,8 @@ namespace osu.Game.Rulesets.Osu.Mods break; case DrawableSlider slider: - slider.AccentColour.BindValueChanged(_ => - { - //will trigger on skin change. - ((PlaySliderBody)slider.Body.Drawable).AccentColour = slider.AccentColour.Value.Opacity(0); - ((PlaySliderBody)slider.Body.Drawable).BorderColour = slider.AccentColour.Value; - }, true); - + slider.Body.OnSkinChanged += () => applySliderState(slider); + applySliderState(slider); break; case DrawableSpinner spinner: @@ -70,5 +65,11 @@ namespace osu.Game.Rulesets.Osu.Mods break; } } + + private void applySliderState(DrawableSlider slider) + { + ((PlaySliderBody)slider.Body.Drawable).AccentColour = slider.AccentColour.Value.Opacity(0); + ((PlaySliderBody)slider.Body.Drawable).BorderColour = slider.AccentColour.Value; + } } } diff --git a/osu.Game/Skinning/SkinReloadableDrawable.cs b/osu.Game/Skinning/SkinReloadableDrawable.cs index 6d0b22dd51..4a1aaa62bf 100644 --- a/osu.Game/Skinning/SkinReloadableDrawable.cs +++ b/osu.Game/Skinning/SkinReloadableDrawable.cs @@ -12,6 +12,11 @@ namespace osu.Game.Skinning /// public abstract class SkinReloadableDrawable : CompositeDrawable { + /// + /// Invoked when has changed. + /// + public event Action OnSkinChanged; + /// /// The current skin source. /// @@ -43,12 +48,18 @@ namespace osu.Game.Skinning private void onChange() => // schedule required to avoid calls after disposed. // note that this has the side-effect of components only performing a skin change when they are alive. - Scheduler.AddOnce(() => SkinChanged(CurrentSkin, allowDefaultFallback)); + Scheduler.AddOnce(skinChanged); protected override void LoadAsyncComplete() { base.LoadAsyncComplete(); + skinChanged(); + } + + private void skinChanged() + { SkinChanged(CurrentSkin, allowDefaultFallback); + OnSkinChanged?.Invoke(); } /// @@ -66,6 +77,8 @@ namespace osu.Game.Skinning if (CurrentSkin != null) CurrentSkin.SourceChanged -= onChange; + + OnSkinChanged = null; } } } From 2d85145eeccde1868c1fb843c852450e37c2abf7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 17 Dec 2019 19:52:33 +0900 Subject: [PATCH 100/103] Make legacy accent colour multiplicative --- osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs index 18a5d7a320..dea08f843e 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Osu.Skinning private class LegacyDrawableSliderPath : DrawableSliderPath { - public new Color4 AccentColour => new Color4(base.AccentColour.R, base.AccentColour.G, base.AccentColour.B, 0.70f); + public new Color4 AccentColour => new Color4(base.AccentColour.R, base.AccentColour.G, base.AccentColour.B, base.AccentColour.A * 0.70f); protected override Color4 ColourAt(float position) { From 49bf8d27d10ad2ceaf9feefe737b2f60ce2dffdd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 17 Dec 2019 20:08:13 +0900 Subject: [PATCH 101/103] Move CreateScoreProcessor() to Ruleset --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 5 +++++ osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs | 4 ---- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 6 ++++++ osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs | 4 ---- osu.Game.Rulesets.Osu/OsuRuleset.cs | 6 ++++++ osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs | 4 ---- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 5 +++++ osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs | 4 ---- osu.Game/Rulesets/Ruleset.cs | 7 +++++++ osu.Game/Rulesets/UI/DrawableRuleset.cs | 9 --------- osu.Game/Screens/Play/Player.cs | 2 +- 11 files changed, 30 insertions(+), 26 deletions(-) diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 9fe287d1cc..ba5c1dee92 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -16,7 +16,9 @@ using osu.Game.Rulesets.Replays.Types; using osu.Game.Beatmaps.Legacy; using osu.Game.Rulesets.Catch.Beatmaps; using osu.Game.Rulesets.Catch.Difficulty; +using osu.Game.Rulesets.Catch.Scoring; using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; namespace osu.Game.Rulesets.Catch @@ -24,6 +26,9 @@ namespace osu.Game.Rulesets.Catch public class CatchRuleset : Ruleset { public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableCatchRuleset(this, beatmap, mods); + + public override ScoreProcessor CreateScoreProcessor(IBeatmap beatmap) => new CatchScoreProcessor(beatmap); + public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap); public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new CatchBeatmapProcessor(beatmap); diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs index 278ff97195..fdd820b891 100644 --- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs @@ -10,10 +10,8 @@ using osu.Game.Replays; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawable; using osu.Game.Rulesets.Catch.Replays; -using osu.Game.Rulesets.Catch.Scoring; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; @@ -32,8 +30,6 @@ namespace osu.Game.Rulesets.Catch.UI TimeRange.Value = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450); } - public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(Beatmap); - protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay); protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty, CreateDrawableRepresentation); diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index bcafadb4c5..dc98a063b4 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -25,6 +25,8 @@ using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Difficulty; using osu.Game.Rulesets.Mania.Edit; +using osu.Game.Rulesets.Mania.Scoring; +using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; namespace osu.Game.Rulesets.Mania @@ -32,7 +34,11 @@ namespace osu.Game.Rulesets.Mania public class ManiaRuleset : Ruleset { public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableManiaRuleset(this, beatmap, mods); + + public override ScoreProcessor CreateScoreProcessor(IBeatmap beatmap) => new ManiaScoreProcessor(beatmap); + public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap); + public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new ManiaPerformanceCalculator(this, beatmap, score); public const string SHORT_NAME = "mania"; diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index cf1970c28b..2c497541a8 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -14,11 +14,9 @@ using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Replays; -using osu.Game.Rulesets.Mania.Scoring; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osuTK; @@ -67,8 +65,6 @@ namespace osu.Game.Rulesets.Mania.UI protected override Playfield CreatePlayfield() => new ManiaPlayfield(Beatmap.Stages); - public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(Beatmap); - public override int Variant => (int)(Beatmap.Stages.Count == 1 ? PlayfieldType.Single : PlayfieldType.Dual) + Beatmap.TotalColumns; protected override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo, Variant); diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index beaa788229..ff08d97385 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -23,7 +23,9 @@ using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Configuration; using osu.Game.Rulesets.Osu.Difficulty; +using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Osu.Skinning; +using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Skinning; @@ -32,7 +34,11 @@ namespace osu.Game.Rulesets.Osu public class OsuRuleset : Ruleset { public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableOsuRuleset(this, beatmap, mods); + + public override ScoreProcessor CreateScoreProcessor(IBeatmap beatmap) => new OsuScoreProcessor(beatmap); + public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap); + public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new OsuBeatmapProcessor(beatmap); public const string SHORT_NAME = "osu"; diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index 039d38e4fd..a37ef8d9a0 100644 --- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -14,8 +14,6 @@ using osu.Game.Rulesets.Osu.Configuration; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Replays; -using osu.Game.Rulesets.Osu.Scoring; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; using osuTK; @@ -33,8 +31,6 @@ namespace osu.Game.Rulesets.Osu.UI public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; // always show the gameplay cursor - public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor(Beatmap); - protected override Playfield CreatePlayfield() => new OsuPlayfield(); protected override PassThroughInputManager CreateInputManager() => new OsuInputManager(Ruleset.RulesetInfo); diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 68e6a3b07e..59ce715a1f 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -15,8 +15,10 @@ using osu.Game.Rulesets.Replays.Types; using osu.Game.Rulesets.Taiko.Replays; using osu.Game.Beatmaps.Legacy; using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Beatmaps; using osu.Game.Rulesets.Taiko.Difficulty; +using osu.Game.Rulesets.Taiko.Scoring; using osu.Game.Scoring; namespace osu.Game.Rulesets.Taiko @@ -24,6 +26,9 @@ namespace osu.Game.Rulesets.Taiko public class TaikoRuleset : Ruleset { public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableTaikoRuleset(this, beatmap, mods); + + public override ScoreProcessor CreateScoreProcessor(IBeatmap beatmap) => new TaikoScoreProcessor(beatmap); + public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TaikoBeatmapConverter(beatmap); public const string SHORT_NAME = "taiko"; diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index 2233658428..0c7495aa52 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -5,10 +5,8 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects.Drawables; -using osu.Game.Rulesets.Taiko.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.Taiko.Replays; using osu.Framework.Input; @@ -40,8 +38,6 @@ namespace osu.Game.Rulesets.Taiko.UI new BarLineGenerator(Beatmap).BarLines.ForEach(bar => Playfield.Add(bar.Major ? new DrawableBarLineMajor(bar) : new DrawableBarLine(bar))); } - public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(Beatmap); - public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new TaikoPlayfieldAdjustmentContainer(); protected override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo); diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 45aa904b98..cfe04fc6d1 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -18,6 +18,7 @@ using osu.Game.Beatmaps.Legacy; using osu.Game.Configuration; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Skinning; @@ -62,6 +63,12 @@ namespace osu.Game.Rulesets /// public abstract DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null); + /// + /// Creates a for a beatmap converted to this ruleset. + /// + /// The score processor. + public virtual ScoreProcessor CreateScoreProcessor(IBeatmap beatmap) => new ScoreProcessor(beatmap); + /// /// Creates a to convert a to one that is applicable for this . /// diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 5033fd0686..4ff8ce9e0a 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -305,8 +305,6 @@ namespace osu.Game.Rulesets.UI /// The Playfield. protected abstract Playfield CreatePlayfield(); - public override ScoreProcessor CreateScoreProcessor() => new ScoreProcessor(Beatmap); - /// /// Applies the active mods to this DrawableRuleset. /// @@ -475,13 +473,6 @@ namespace osu.Game.Rulesets.UI /// Invoked when the user requests to pause while the resume overlay is active. /// public abstract void CancelResume(); - - /// - /// Create a for the associated ruleset and link with this - /// . - /// - /// A score processor. - public abstract ScoreProcessor CreateScoreProcessor(); } public class BeatmapInvalidForRulesetException : ArgumentException diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index fa320e9a4f..5dfdeb5ebc 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -128,7 +128,7 @@ namespace osu.Game.Screens.Play DrawableRuleset = ruleset.CreateDrawableRulesetWith(playableBeatmap, Mods.Value); - ScoreProcessor = DrawableRuleset.CreateScoreProcessor(); + ScoreProcessor = ruleset.CreateScoreProcessor(playableBeatmap); ScoreProcessor.Mods.BindTo(Mods); if (!ScoreProcessor.Mode.Disabled) From 35276c37399e9fbfcc0b16c88eb4448760e16470 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 17 Dec 2019 21:26:23 +0900 Subject: [PATCH 102/103] Prevent test scene failures through casting softly --- .../Objects/Drawables/DrawableSlider.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 03183beff1..cd3c572ba0 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public readonly SliderBall Ball; public readonly SkinnableDrawable Body; - private PlaySliderBody sliderBody => (PlaySliderBody)Body.Drawable; + private PlaySliderBody sliderBody => Body.Drawable as PlaySliderBody; private readonly Container headContainer; private readonly Container tailContainer; @@ -154,16 +154,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables double completionProgress = Math.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1); Ball.UpdateProgress(completionProgress); - sliderBody.UpdateProgress(completionProgress); + sliderBody?.UpdateProgress(completionProgress); foreach (DrawableHitObject hitObject in NestedHitObjects) { - if (hitObject is ITrackSnaking s) s.UpdateSnakingPosition(slider.Path.PositionAt(sliderBody.SnakedStart ?? 0), slider.Path.PositionAt(sliderBody.SnakedEnd ?? 0)); + if (hitObject is ITrackSnaking s) s.UpdateSnakingPosition(slider.Path.PositionAt(sliderBody?.SnakedStart ?? 0), slider.Path.PositionAt(sliderBody?.SnakedEnd ?? 0)); if (hitObject is IRequireTracking t) t.Tracking = Ball.Tracking; } - Size = sliderBody.Size; - OriginPosition = sliderBody.PathOffset; + Size = sliderBody?.Size ?? Vector2.Zero; + OriginPosition = sliderBody?.PathOffset ?? Vector2.Zero; if (DrawSize != Vector2.Zero) { @@ -177,7 +177,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public override void OnKilled() { base.OnKilled(); - sliderBody.RecyclePath(); + sliderBody?.RecyclePath(); } protected override void ApplySkin(ISkinSource skin, bool allowFallback) @@ -238,7 +238,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public Drawable ProxiedLayer => HeadCircle.ProxiedLayer; - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => sliderBody.ReceivePositionalInputAt(screenSpacePos); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => sliderBody?.ReceivePositionalInputAt(screenSpacePos) ?? base.ReceivePositionalInputAt(screenSpacePos); private class DefaultSliderBody : PlaySliderBody { From 69da6ed9a16438aa6b9fe034ee127879b4d66c00 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 17 Dec 2019 23:53:18 +0900 Subject: [PATCH 103/103] Fix test re-using the same storage --- osu.Game.Tests/Scores/IO/ImportScoreTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs index a95e699470..a139c3a8c2 100644 --- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs +++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs @@ -159,7 +159,7 @@ namespace osu.Game.Tests.Scores.IO [Test] public async Task TestOnlineScoreIsAvailableLocally() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWithDeletedBeatmapSet")) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestOnlineScoreIsAvailableLocally")) { try {