From dde0756bed9702c95c918af247e9339bc913bb48 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Tue, 24 May 2022 10:23:44 -0400 Subject: [PATCH 001/142] add accuracy challenge mod --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 1 + osu.Game.Rulesets.Mania/ManiaRuleset.cs | 1 + osu.Game.Rulesets.Osu/OsuRuleset.cs | 3 +- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 1 + .../Rulesets/Mods/ModAccuracyChallenge.cs | 59 +++++++++++++++++++ .../Rulesets/Mods/ModEasyWithExtraLives.cs | 3 + osu.Game/Rulesets/Mods/ModPerfect.cs | 2 +- 7 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 80b9436b2c..bcce28adb2 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -109,6 +109,7 @@ namespace osu.Game.Rulesets.Catch new MultiMod(new CatchModDoubleTime(), new CatchModNightcore()), new CatchModHidden(), new CatchModFlashlight(), + //new ModAccuracyChallenge(), }; case ModType.Conversion: diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index bd6a67bf67..8f2d679750 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -222,6 +222,7 @@ namespace osu.Game.Rulesets.Mania new MultiMod(new ManiaModDoubleTime(), new ManiaModNightcore()), new MultiMod(new ManiaModFadeIn(), new ManiaModHidden()), new ManiaModFlashlight(), + //new ModAccuracyChallenge(), }; case ModType.Conversion: diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 207e7a4ab0..15998a48c3 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -159,7 +159,8 @@ namespace osu.Game.Rulesets.Osu new MultiMod(new OsuModDoubleTime(), new OsuModNightcore()), new OsuModHidden(), new MultiMod(new OsuModFlashlight(), new OsuModBlinds()), - new OsuModStrictTracking() + new OsuModStrictTracking(), + new ModAccuracyChallenge(), }; case ModType.Conversion: diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 615fbf093f..655aba88e3 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -128,6 +128,7 @@ namespace osu.Game.Rulesets.Taiko new MultiMod(new TaikoModDoubleTime(), new TaikoModNightcore()), new TaikoModHidden(), new TaikoModFlashlight(), + //new ModAccuracyChallenge(), }; case ModType.Conversion: diff --git a/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs b/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs new file mode 100644 index 0000000000..bb7d3896b2 --- /dev/null +++ b/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs @@ -0,0 +1,59 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using osu.Framework.Bindables; +using osu.Framework.Graphics.Sprites; +using osu.Game.Configuration; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Settings; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Judgements; + +namespace osu.Game.Rulesets.Mods +{ + public class ModAccuracyChallenge : ModFailCondition + { + public override string Name => "Accuracy Challenge"; + public override string Acronym => "AC"; + public override string Description => "Fail the map if you don't maintain a certain accuracy."; + public override IconUsage? Icon => FontAwesome.Solid.Calculator; + public override ModType Type => ModType.DifficultyIncrease; + public override double ScoreMultiplier => 1.0; + public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModEasyWithExtraLives)).Append(typeof(ModPerfect)).ToArray(); + + [SettingSource("Minimum accuracy", "Trigger a failure if your accuracy goes below this value.", SettingControlType = typeof(SettingsSlider))] + public BindableNumber MinimumAccuracy { get; } = new BindableDouble + { + MinValue = 0, + MaxValue = 1, + Precision = 0.01, + Default = 0.9, + Value = 0.9, + }; + + private double baseScore; + private double maxBaseScore; + private double accuracy => maxBaseScore > 0 ? baseScore / maxBaseScore : 1; + + protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result) + { + if (!result.Type.IsScorable() || result.Type.IsBonus()) + return false; + + baseScore += result.Type.IsHit() ? result.Judgement.NumericResultFor(result) : 0; + maxBaseScore += result.Judgement.MaxNumericResult; + + return accuracy < MinimumAccuracy.Value; + } + } + + public class PercentSlider : OsuSliderBar + { + public PercentSlider() + { + DisplayAsPercentage = true; + } + } +} diff --git a/osu.Game/Rulesets/Mods/ModEasyWithExtraLives.cs b/osu.Game/Rulesets/Mods/ModEasyWithExtraLives.cs index 2ac0f59d84..7e6b085f8a 100644 --- a/osu.Game/Rulesets/Mods/ModEasyWithExtraLives.cs +++ b/osu.Game/Rulesets/Mods/ModEasyWithExtraLives.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Linq; using Humanizer; using osu.Framework.Bindables; using osu.Game.Beatmaps; @@ -19,6 +21,7 @@ namespace osu.Game.Rulesets.Mods }; public override string SettingDescription => Retries.IsDefault ? string.Empty : $"{"lives".ToQuantity(Retries.Value)}"; + public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModAccuracyChallenge)).ToArray(); private int retries; diff --git a/osu.Game/Rulesets/Mods/ModPerfect.cs b/osu.Game/Rulesets/Mods/ModPerfect.cs index 9016a24f8d..4c68d0d375 100644 --- a/osu.Game/Rulesets/Mods/ModPerfect.cs +++ b/osu.Game/Rulesets/Mods/ModPerfect.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mods public override double ScoreMultiplier => 1; public override string Description => "SS or quit."; - public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModSuddenDeath)).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModSuddenDeath)).Append(typeof(ModAccuracyChallenge)).ToArray(); protected ModPerfect() { From 252bacc8d4aeee7bacf96598b97a28eef7e884c7 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Tue, 24 May 2022 10:56:31 -0400 Subject: [PATCH 002/142] revert more testing leftovers... --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 2 +- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 +- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index bcce28adb2..9ddafc9315 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -109,7 +109,7 @@ namespace osu.Game.Rulesets.Catch new MultiMod(new CatchModDoubleTime(), new CatchModNightcore()), new CatchModHidden(), new CatchModFlashlight(), - //new ModAccuracyChallenge(), + new ModAccuracyChallenge(), }; case ModType.Conversion: diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 8f2d679750..659d7f242f 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -222,7 +222,7 @@ namespace osu.Game.Rulesets.Mania new MultiMod(new ManiaModDoubleTime(), new ManiaModNightcore()), new MultiMod(new ManiaModFadeIn(), new ManiaModHidden()), new ManiaModFlashlight(), - //new ModAccuracyChallenge(), + new ModAccuracyChallenge(), }; case ModType.Conversion: diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 655aba88e3..a80c87ecd3 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -128,7 +128,7 @@ namespace osu.Game.Rulesets.Taiko new MultiMod(new TaikoModDoubleTime(), new TaikoModNightcore()), new TaikoModHidden(), new TaikoModFlashlight(), - //new ModAccuracyChallenge(), + new ModAccuracyChallenge(), }; case ModType.Conversion: From 5944a15c30abfa9dfc56b169d68955f68bd0edbb Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Tue, 24 May 2022 20:04:57 -0400 Subject: [PATCH 003/142] review pass eins --- osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs | 5 +++-- osu.Game/Rulesets/Mods/ModPerfect.cs | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs b/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs index bb7d3896b2..804ce867af 100644 --- a/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs +++ b/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs @@ -21,12 +21,13 @@ namespace osu.Game.Rulesets.Mods public override IconUsage? Icon => FontAwesome.Solid.Calculator; public override ModType Type => ModType.DifficultyIncrease; public override double ScoreMultiplier => 1.0; - public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModEasyWithExtraLives)).Append(typeof(ModPerfect)).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(ModEasyWithExtraLives), typeof(ModPerfect) }).ToArray(); + public override bool RequiresConfiguration => false; [SettingSource("Minimum accuracy", "Trigger a failure if your accuracy goes below this value.", SettingControlType = typeof(SettingsSlider))] public BindableNumber MinimumAccuracy { get; } = new BindableDouble { - MinValue = 0, + MinValue = 0.01, MaxValue = 1, Precision = 0.01, Default = 0.9, diff --git a/osu.Game/Rulesets/Mods/ModPerfect.cs b/osu.Game/Rulesets/Mods/ModPerfect.cs index 4c68d0d375..c40c651655 100644 --- a/osu.Game/Rulesets/Mods/ModPerfect.cs +++ b/osu.Game/Rulesets/Mods/ModPerfect.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mods public override double ScoreMultiplier => 1; public override string Description => "SS or quit."; - public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModSuddenDeath)).Append(typeof(ModAccuracyChallenge)).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(ModSuddenDeath), typeof(ModAccuracyChallenge) }).ToArray(); protected ModPerfect() { From 58d4aeb4fb1b60fe5c5fe7e701fc5db469e1ad8e Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Wed, 25 May 2022 15:11:06 -0400 Subject: [PATCH 004/142] add accuracy calculation comment --- osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs b/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs index 804ce867af..fe12b72d1e 100644 --- a/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs +++ b/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs @@ -40,6 +40,7 @@ namespace osu.Game.Rulesets.Mods protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result) { + // accuracy calculation logic taken from `ScoreProcessor`. should be updated here if the formula ever changes. if (!result.Type.IsScorable() || result.Type.IsBonus()) return false; From 6710df94a79052689d67b79c3ec55dfd81fd7107 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Mon, 6 Jun 2022 12:05:45 -0400 Subject: [PATCH 005/142] change max accuracy to 99% --- osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs b/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs index fe12b72d1e..307a9594fe 100644 --- a/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs +++ b/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mods public BindableNumber MinimumAccuracy { get; } = new BindableDouble { MinValue = 0.01, - MaxValue = 1, + MaxValue = 0.99, Precision = 0.01, Default = 0.9, Value = 0.9, From fdbec4f8c0c8082445beaef065c7df9aa4a36065 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Wed, 8 Jun 2022 23:44:34 -0400 Subject: [PATCH 006/142] display tooltip accuracy as percentage --- osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs b/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs index 307a9594fe..2c088e880b 100644 --- a/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs +++ b/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs @@ -23,6 +23,7 @@ namespace osu.Game.Rulesets.Mods public override double ScoreMultiplier => 1.0; public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(ModEasyWithExtraLives), typeof(ModPerfect) }).ToArray(); public override bool RequiresConfiguration => false; + public override string SettingDescription => base.SettingDescription.Replace(MinimumAccuracy.Value.ToString(), MinimumAccuracy.Value.ToString("##%")); [SettingSource("Minimum accuracy", "Trigger a failure if your accuracy goes below this value.", SettingControlType = typeof(SettingsSlider))] public BindableNumber MinimumAccuracy { get; } = new BindableDouble From 99dc2fbc3e51a4d8a81eba6c595eeda7079532a9 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Thu, 9 Jun 2022 00:15:15 -0400 Subject: [PATCH 007/142] specify culture --- osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs b/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs index 2c088e880b..891832a353 100644 --- a/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs +++ b/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Globalization; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; @@ -23,7 +24,7 @@ namespace osu.Game.Rulesets.Mods public override double ScoreMultiplier => 1.0; public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(ModEasyWithExtraLives), typeof(ModPerfect) }).ToArray(); public override bool RequiresConfiguration => false; - public override string SettingDescription => base.SettingDescription.Replace(MinimumAccuracy.Value.ToString(), MinimumAccuracy.Value.ToString("##%")); + public override string SettingDescription => base.SettingDescription.Replace(MinimumAccuracy.ToString(), MinimumAccuracy.Value.ToString("##%", NumberFormatInfo.InvariantInfo)); [SettingSource("Minimum accuracy", "Trigger a failure if your accuracy goes below this value.", SettingControlType = typeof(SettingsSlider))] public BindableNumber MinimumAccuracy { get; } = new BindableDouble From 4b73c423bda6c0c8d14b0db3d046c8cd8820fb9f Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Thu, 9 Jun 2022 18:58:22 -0400 Subject: [PATCH 008/142] don't specify icon --- osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs b/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs index 891832a353..74a9a205a0 100644 --- a/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs +++ b/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs @@ -5,7 +5,6 @@ using System; using System.Globalization; using System.Linq; using osu.Framework.Bindables; -using osu.Framework.Graphics.Sprites; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Settings; @@ -19,7 +18,6 @@ namespace osu.Game.Rulesets.Mods public override string Name => "Accuracy Challenge"; public override string Acronym => "AC"; public override string Description => "Fail the map if you don't maintain a certain accuracy."; - public override IconUsage? Icon => FontAwesome.Solid.Calculator; public override ModType Type => ModType.DifficultyIncrease; public override double ScoreMultiplier => 1.0; public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(ModEasyWithExtraLives), typeof(ModPerfect) }).ToArray(); From 0db6c2ada147b5749228d414ff8521e83b69bf96 Mon Sep 17 00:00:00 2001 From: ansel <79257300125@ya.ru> Date: Sat, 3 Dec 2022 22:38:11 +0300 Subject: [PATCH 009/142] Add enum with font types --- osu.Game/Skinning/DefaultFont.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 osu.Game/Skinning/DefaultFont.cs diff --git a/osu.Game/Skinning/DefaultFont.cs b/osu.Game/Skinning/DefaultFont.cs new file mode 100644 index 0000000000..69b6aaaff4 --- /dev/null +++ b/osu.Game/Skinning/DefaultFont.cs @@ -0,0 +1,18 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Skinning.Components; + +namespace osu.Game.Skinning +{ + /// + /// The type of built-in font to use for . + /// + public enum DefaultFont + { + Venera, + Torus, + TorusAlt, + Inter + } +} From b41f30c8689757c3b5850000dccefcb85c475cba Mon Sep 17 00:00:00 2001 From: ansel <79257300125@ya.ru> Date: Sat, 3 Dec 2022 22:44:54 +0300 Subject: [PATCH 010/142] Allow changing font of text elements --- .../Components/BeatmapAttributeText.cs | 10 ++--- .../Components/DefaultTextSkinComponent.cs | 42 +++++++++++++++++++ osu.Game/Skinning/Components/TextElement.cs | 11 ++--- 3 files changed, 52 insertions(+), 11 deletions(-) create mode 100644 osu.Game/Skinning/Components/DefaultTextSkinComponent.cs diff --git a/osu.Game/Skinning/Components/BeatmapAttributeText.cs b/osu.Game/Skinning/Components/BeatmapAttributeText.cs index 0a5f0d22cb..71d8f1a40f 100644 --- a/osu.Game/Skinning/Components/BeatmapAttributeText.cs +++ b/osu.Game/Skinning/Components/BeatmapAttributeText.cs @@ -11,12 +11,11 @@ using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Extensions; -using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Localisation; using osu.Game.Resources.Localisation.Web; @@ -24,10 +23,8 @@ using osu.Game.Resources.Localisation.Web; namespace osu.Game.Skinning.Components { [UsedImplicitly] - public partial class BeatmapAttributeText : Container, ISkinnableDrawable + public partial class BeatmapAttributeText : DefaultTextSkinComponent { - public bool UsesFixedAnchor { get; set; } - [SettingSource("Attribute", "The attribute to be displayed.")] public Bindable Attribute { get; } = new Bindable(BeatmapAttribute.StarRating); @@ -67,7 +64,6 @@ namespace osu.Game.Skinning.Components { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Font = OsuFont.Default.With(size: 40) } }; } @@ -122,6 +118,8 @@ namespace osu.Game.Skinning.Components text.Text = LocalisableString.Format(numberedTemplate, args); } + + protected override void SetFont(FontUsage font) => text.Font = font.With(size: 40); } public enum BeatmapAttribute diff --git a/osu.Game/Skinning/Components/DefaultTextSkinComponent.cs b/osu.Game/Skinning/Components/DefaultTextSkinComponent.cs new file mode 100644 index 0000000000..aff400c798 --- /dev/null +++ b/osu.Game/Skinning/Components/DefaultTextSkinComponent.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 osu.Framework.Bindables; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Configuration; +using osu.Game.Graphics; + +namespace osu.Game.Skinning.Components +{ + /// + /// Skin element that contains text and have ability to control its font. + /// + public abstract partial class DefaultTextSkinComponent : Container, ISkinnableDrawable + { + public bool UsesFixedAnchor { get; set; } + + [SettingSource("Font", "Font to use.")] + public Bindable Font { get; } = new Bindable(DefaultFont.Torus); + + protected abstract void SetFont(FontUsage font); + + protected override void LoadComplete() + { + base.LoadComplete(); + Font.BindValueChanged(e => + { + FontUsage f = e.NewValue switch + { + DefaultFont.Venera => OsuFont.Numeric, + DefaultFont.Torus => OsuFont.Torus, + DefaultFont.TorusAlt => OsuFont.TorusAlternate, + DefaultFont.Inter => OsuFont.Inter, + _ => OsuFont.Default + }; + + SetFont(f); + }, true); + } + } +} diff --git a/osu.Game/Skinning/Components/TextElement.cs b/osu.Game/Skinning/Components/TextElement.cs index 74a0acb979..fb779fdb83 100644 --- a/osu.Game/Skinning/Components/TextElement.cs +++ b/osu.Game/Skinning/Components/TextElement.cs @@ -4,7 +4,7 @@ using JetBrains.Annotations; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -12,17 +12,16 @@ using osu.Game.Graphics.Sprites; namespace osu.Game.Skinning.Components { [UsedImplicitly] - public partial class TextElement : Container, ISkinnableDrawable + public partial class TextElement : DefaultTextSkinComponent { - public bool UsesFixedAnchor { get; set; } - [SettingSource("Text", "The text to be displayed.")] public Bindable Text { get; } = new Bindable("Circles!"); + private readonly OsuSpriteText text; + public TextElement() { AutoSizeAxes = Axes.Both; - OsuSpriteText text; InternalChildren = new Drawable[] { text = new OsuSpriteText @@ -34,5 +33,7 @@ namespace osu.Game.Skinning.Components }; text.Current.BindTo(Text); } + + protected override void SetFont(FontUsage font) => text.Font = font.With(size: 40); } } From 8174f6be64a7877c0e0788028303236ef01e1ec6 Mon Sep 17 00:00:00 2001 From: ansel <79257300125@ya.ru> Date: Sat, 3 Dec 2022 23:32:17 +0300 Subject: [PATCH 011/142] Get rid of dublicated enum --- .../Components/DefaultTextSkinComponent.cs | 12 ++---------- osu.Game/Skinning/DefaultFont.cs | 18 ------------------ 2 files changed, 2 insertions(+), 28 deletions(-) delete mode 100644 osu.Game/Skinning/DefaultFont.cs diff --git a/osu.Game/Skinning/Components/DefaultTextSkinComponent.cs b/osu.Game/Skinning/Components/DefaultTextSkinComponent.cs index aff400c798..1fd29effbd 100644 --- a/osu.Game/Skinning/Components/DefaultTextSkinComponent.cs +++ b/osu.Game/Skinning/Components/DefaultTextSkinComponent.cs @@ -17,7 +17,7 @@ namespace osu.Game.Skinning.Components public bool UsesFixedAnchor { get; set; } [SettingSource("Font", "Font to use.")] - public Bindable Font { get; } = new Bindable(DefaultFont.Torus); + public Bindable Font { get; } = new Bindable(Typeface.Torus); protected abstract void SetFont(FontUsage font); @@ -26,15 +26,7 @@ namespace osu.Game.Skinning.Components base.LoadComplete(); Font.BindValueChanged(e => { - FontUsage f = e.NewValue switch - { - DefaultFont.Venera => OsuFont.Numeric, - DefaultFont.Torus => OsuFont.Torus, - DefaultFont.TorusAlt => OsuFont.TorusAlternate, - DefaultFont.Inter => OsuFont.Inter, - _ => OsuFont.Default - }; - + FontUsage f = OsuFont.GetFont(e.NewValue); SetFont(f); }, true); } diff --git a/osu.Game/Skinning/DefaultFont.cs b/osu.Game/Skinning/DefaultFont.cs deleted file mode 100644 index 69b6aaaff4..0000000000 --- a/osu.Game/Skinning/DefaultFont.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Game.Skinning.Components; - -namespace osu.Game.Skinning -{ - /// - /// The type of built-in font to use for . - /// - public enum DefaultFont - { - Venera, - Torus, - TorusAlt, - Inter - } -} From b240d15731d7a4205825c78122f298d28cb096c7 Mon Sep 17 00:00:00 2001 From: ansel <79257300125@ya.ru> Date: Sat, 3 Dec 2022 23:38:50 +0300 Subject: [PATCH 012/142] Fix numeric font --- osu.Game/Skinning/Components/DefaultTextSkinComponent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/Components/DefaultTextSkinComponent.cs b/osu.Game/Skinning/Components/DefaultTextSkinComponent.cs index 1fd29effbd..abe16918c5 100644 --- a/osu.Game/Skinning/Components/DefaultTextSkinComponent.cs +++ b/osu.Game/Skinning/Components/DefaultTextSkinComponent.cs @@ -26,7 +26,7 @@ namespace osu.Game.Skinning.Components base.LoadComplete(); Font.BindValueChanged(e => { - FontUsage f = OsuFont.GetFont(e.NewValue); + FontUsage f = OsuFont.GetFont(e.NewValue, weight: e.NewValue == Typeface.Venera ? FontWeight.Bold : FontWeight.Regular); SetFont(f); }, true); } From 7d7b824f568ac35602b0e39e38223412c4252ded Mon Sep 17 00:00:00 2001 From: ansel <79257300125@ya.ru> Date: Sat, 3 Dec 2022 23:42:16 +0300 Subject: [PATCH 013/142] Add description for torus alt --- osu.Game/Graphics/OsuFont.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Graphics/OsuFont.cs b/osu.Game/Graphics/OsuFont.cs index 038ea0f5d7..7aa98ece95 100644 --- a/osu.Game/Graphics/OsuFont.cs +++ b/osu.Game/Graphics/OsuFont.cs @@ -3,6 +3,7 @@ #nullable disable +using System.ComponentModel; using osu.Framework.Graphics.Sprites; namespace osu.Game.Graphics @@ -115,6 +116,8 @@ namespace osu.Game.Graphics { Venera, Torus, + + [Description("Torus (alternate)")] TorusAlternate, Inter, } From 9f4bb3e0cacc3248bf4c76396ad6cb97fca82b46 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 7 Dec 2022 09:51:22 +0100 Subject: [PATCH 014/142] Add segmend end completions to SliderPath Fix segmentEnds incorrect on shortened paths Revert "Add segmend end completions to SliderPath" This reverts commit cd46ca31f9af73541e05f515018847a588b52775. Revert "Fix segmentEnds incorrect on shortened paths" This reverts commit 98a312ca9661c8d6a61141a4be57265a93b79a2a. From 10b59007107594402c946ba071164ff8975a717a Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 3 Nov 2022 12:25:23 +0100 Subject: [PATCH 015/142] made PathControlPointVisualiser generic --- .../TestScenePathControlPointVisualiser.cs | 4 +- .../TestSceneSliderControlPointPiece.cs | 14 ++-- .../TestSceneSliderSelectionBlueprint.cs | 2 +- .../Editor/TestSceneSliderSnapping.cs | 8 +- .../Editor/TestSceneSliderSplitting.cs | 8 +- .../PathControlPointConnectionPiece.cs | 28 ++++--- .../Components/PathControlPointPiece.cs | 45 +++++----- .../Components/PathControlPointVisualiser.cs | 82 +++++++++---------- .../Sliders/SliderPlacementBlueprint.cs | 4 +- .../Sliders/SliderSelectionBlueprint.cs | 4 +- .../Editing/TestSceneBlueprintOrdering.cs | 2 +- .../Editing/TestSceneComposerSelection.cs | 2 +- 12 files changed, 103 insertions(+), 100 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs index d1a04e28e5..37561fda85 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor public partial class TestScenePathControlPointVisualiser : OsuManualInputManagerTestScene { private Slider slider; - private PathControlPointVisualiser visualiser; + private PathControlPointVisualiser visualiser; [SetUp] public void Setup() => Schedule(() => @@ -148,7 +148,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor assertControlPointPathType(3, null); } - private void createVisualiser(bool allowSelection) => AddStep("create visualiser", () => Child = visualiser = new PathControlPointVisualiser(slider, allowSelection) + private void createVisualiser(bool allowSelection) => AddStep("create visualiser", () => Child = visualiser = new PathControlPointVisualiser(slider, allowSelection) { Anchor = Anchor.Centre, Origin = Anchor.Centre diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs index 112aab884b..db9eea4127 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderControlPointPiece.cs @@ -159,11 +159,11 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor } private void assertSelectionCount(int count) => - AddAssert($"{count} control point pieces selected", () => this.ChildrenOfType().Count(piece => piece.IsSelected.Value) == count); + AddAssert($"{count} control point pieces selected", () => this.ChildrenOfType>().Count(piece => piece.IsSelected.Value) == count); private void assertSelected(int index) => AddAssert($"{(index + 1).ToOrdinalWords()} control point piece selected", - () => this.ChildrenOfType().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[index]).IsSelected.Value); + () => this.ChildrenOfType>().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[index]).IsSelected.Value); private void moveMouseToRelativePosition(Vector2 relativePosition) => AddStep($"move mouse to {relativePosition}", () => @@ -202,12 +202,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor moveMouseToControlPoint(2); AddStep("hold left mouse", () => InputManager.PressButton(MouseButton.Left)); - AddAssert("three control point pieces selected", () => this.ChildrenOfType().Count(piece => piece.IsSelected.Value) == 3); + AddAssert("three control point pieces selected", () => this.ChildrenOfType>().Count(piece => piece.IsSelected.Value) == 3); addMovementStep(new Vector2(450, 50)); AddStep("release left mouse", () => InputManager.ReleaseButton(MouseButton.Left)); - AddAssert("three control point pieces selected", () => this.ChildrenOfType().Count(piece => piece.IsSelected.Value) == 3); + AddAssert("three control point pieces selected", () => this.ChildrenOfType>().Count(piece => piece.IsSelected.Value) == 3); assertControlPointPosition(2, new Vector2(450, 50)); assertControlPointType(2, PathType.PerfectCurve); @@ -236,12 +236,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor moveMouseToControlPoint(3); AddStep("hold left mouse", () => InputManager.PressButton(MouseButton.Left)); - AddAssert("three control point pieces selected", () => this.ChildrenOfType().Count(piece => piece.IsSelected.Value) == 3); + AddAssert("three control point pieces selected", () => this.ChildrenOfType>().Count(piece => piece.IsSelected.Value) == 3); addMovementStep(new Vector2(550, 50)); AddStep("release left mouse", () => InputManager.ReleaseButton(MouseButton.Left)); - AddAssert("three control point pieces selected", () => this.ChildrenOfType().Count(piece => piece.IsSelected.Value) == 3); + AddAssert("three control point pieces selected", () => this.ChildrenOfType>().Count(piece => piece.IsSelected.Value) == 3); // note: if the head is part of the selection being moved, the entire slider is moved. // the unselected nodes will therefore change position relative to the slider head. @@ -354,7 +354,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor public new SliderBodyPiece BodyPiece => base.BodyPiece; public new TestSliderCircleOverlay HeadOverlay => (TestSliderCircleOverlay)base.HeadOverlay; public new TestSliderCircleOverlay TailOverlay => (TestSliderCircleOverlay)base.TailOverlay; - public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser; + public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser; public TestSliderBlueprint(Slider slider) : base(slider) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs index ad740b2977..8ed77d45d7 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSelectionBlueprint.cs @@ -199,7 +199,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor public new SliderBodyPiece BodyPiece => base.BodyPiece; public new TestSliderCircleOverlay HeadOverlay => (TestSliderCircleOverlay)base.HeadOverlay; public new TestSliderCircleOverlay TailOverlay => (TestSliderCircleOverlay)base.TailOverlay; - public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser; + public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser; public TestSliderBlueprint(Slider slider) : base(slider) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs index e9d50d5118..f262a4334a 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSnapping.cs @@ -72,14 +72,14 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor [Test] public void TestMovingUnsnappedSliderNodesSnaps() { - PathControlPointPiece sliderEnd = null; + PathControlPointPiece sliderEnd = null; assertSliderSnapped(false); AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider)); AddStep("select slider end", () => { - sliderEnd = this.ChildrenOfType().Single(piece => piece.ControlPoint == slider.Path.ControlPoints.Last()); + sliderEnd = this.ChildrenOfType>().Single(piece => piece.ControlPoint == slider.Path.ControlPoints.Last()); InputManager.MoveMouseTo(sliderEnd.ScreenSpaceDrawQuad.Centre); }); AddStep("move slider end", () => @@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider)); AddStep("move mouse to new point location", () => { - var firstPiece = this.ChildrenOfType().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[0]); + var firstPiece = this.ChildrenOfType>().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[0]); var pos = slider.Path.PositionAt(0.25d) + slider.Position; InputManager.MoveMouseTo(firstPiece.Parent.ToScreenSpace(pos)); }); @@ -120,7 +120,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider)); AddStep("move mouse to second control point", () => { - var secondPiece = this.ChildrenOfType().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[1]); + var secondPiece = this.ChildrenOfType>().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[1]); InputManager.MoveMouseTo(secondPiece); }); AddStep("quick delete", () => diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs index b2ac462c8f..6cb77c7b92 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderSplitting.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor => Editor.ChildrenOfType().First(); private Slider? slider; - private PathControlPointVisualiser? visualiser; + private PathControlPointVisualiser? visualiser; private const double split_gap = 100; @@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("select added slider", () => { EditorBeatmap.SelectedHitObjects.Add(slider); - visualiser = blueprintContainer.SelectionBlueprints.First(o => o.Item == slider).ChildrenOfType().First(); + visualiser = blueprintContainer.SelectionBlueprints.First(o => o.Item == slider).ChildrenOfType>().First(); }); moveMouseToControlPoint(2); @@ -122,7 +122,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("select added slider", () => { EditorBeatmap.SelectedHitObjects.Add(slider); - visualiser = blueprintContainer.SelectionBlueprints.First(o => o.Item == slider).ChildrenOfType().First(); + visualiser = blueprintContainer.SelectionBlueprints.First(o => o.Item == slider).ChildrenOfType>().First(); }); moveMouseToControlPoint(2); @@ -190,7 +190,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("select added slider", () => { EditorBeatmap.SelectedHitObjects.Add(slider); - visualiser = blueprintContainer.SelectionBlueprints.First(o => o.Item == slider).ChildrenOfType().First(); + visualiser = blueprintContainer.SelectionBlueprints.First(o => o.Item == slider).ChildrenOfType>().First(); }); moveMouseToControlPoint(2); 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 28e0d650c4..67685d21a7 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs @@ -8,34 +8,36 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Lines; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osuTK; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { /// - /// A visualisation of the line between two s. + /// A visualisation of the line between two s. /// - public partial class PathControlPointConnectionPiece : CompositeDrawable + /// The type of which this visualises. + public partial class PathControlPointConnectionPiece : CompositeDrawable where T : OsuHitObject, IHasPath { public readonly PathControlPoint ControlPoint; private readonly Path path; - private readonly Slider slider; + private readonly T hitObject; public int ControlPointIndex { get; set; } - private IBindable sliderPosition; + private IBindable hitObjectPosition; private IBindable pathVersion; - public PathControlPointConnectionPiece(Slider slider, int controlPointIndex) + public PathControlPointConnectionPiece(T hitObject, int controlPointIndex) { - this.slider = slider; + this.hitObject = hitObject; ControlPointIndex = controlPointIndex; Origin = Anchor.Centre; AutoSizeAxes = Axes.Both; - ControlPoint = slider.Path.ControlPoints[controlPointIndex]; + ControlPoint = hitObject.Path.ControlPoints[controlPointIndex]; InternalChild = path = new SmoothPath { @@ -48,10 +50,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { base.LoadComplete(); - sliderPosition = slider.PositionBindable.GetBoundCopy(); - sliderPosition.BindValueChanged(_ => updateConnectingPath()); + hitObjectPosition = hitObject.PositionBindable.GetBoundCopy(); + hitObjectPosition.BindValueChanged(_ => updateConnectingPath()); - pathVersion = slider.Path.Version.GetBoundCopy(); + pathVersion = hitObject.Path.Version.GetBoundCopy(); pathVersion.BindValueChanged(_ => updateConnectingPath()); updateConnectingPath(); @@ -62,16 +64,16 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components /// private void updateConnectingPath() { - Position = slider.StackedPosition + ControlPoint.Position; + Position = hitObject.StackedPosition + ControlPoint.Position; path.ClearVertices(); int nextIndex = ControlPointIndex + 1; - if (nextIndex == 0 || nextIndex >= slider.Path.ControlPoints.Count) + if (nextIndex == 0 || nextIndex >= hitObject.Path.ControlPoints.Count) return; path.AddVertex(Vector2.Zero); - path.AddVertex(slider.Path.ControlPoints[nextIndex].Position - ControlPoint.Position); + path.AddVertex(hitObject.Path.ControlPoints[nextIndex].Position - ControlPoint.Position); 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 d83f35d13f..a4d5c08b8a 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -29,11 +29,12 @@ using osuTK.Input; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { /// - /// A visualisation of a single in a . + /// A visualisation of a single in an osu hit object with a path. /// - public partial class PathControlPointPiece : BlueprintPiece, IHasTooltip + /// The type of which this visualises. + public partial class PathControlPointPiece : BlueprintPiece, IHasTooltip where T : OsuHitObject, IHasPath { - public Action RequestSelection; + public Action, MouseButtonEvent> RequestSelection; public Action DragStarted; public Action DragInProgress; @@ -44,34 +45,34 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components public readonly BindableBool IsSelected = new BindableBool(); public readonly PathControlPoint ControlPoint; - private readonly Slider slider; + private readonly T hitObject; private readonly Container marker; private readonly Drawable markerRing; [Resolved] private OsuColour colours { get; set; } - private IBindable sliderPosition; - private IBindable sliderScale; + private IBindable hitObjectPosition; + private IBindable hitObjectScale; [UsedImplicitly] - private readonly IBindable sliderVersion; + private readonly IBindable hitObjectVersion; - public PathControlPointPiece(Slider slider, PathControlPoint controlPoint) + public PathControlPointPiece(T hitObject, PathControlPoint controlPoint) { - this.slider = slider; + this.hitObject = hitObject; ControlPoint = controlPoint; - // we don't want to run the path type update on construction as it may inadvertently change the slider. - cachePoints(slider); + // we don't want to run the path type update on construction as it may inadvertently change the hit object. + cachePoints(hitObject); - sliderVersion = slider.Path.Version.GetBoundCopy(); + hitObjectVersion = hitObject.Path.Version.GetBoundCopy(); // schedule ensure that updates are only applied after all operations from a single frame are applied. - // this avoids inadvertently changing the slider path type for batch operations. - sliderVersion.BindValueChanged(_ => Scheduler.AddOnce(() => + // this avoids inadvertently changing the hit object path type for batch operations. + hitObjectVersion.BindValueChanged(_ => Scheduler.AddOnce(() => { - cachePoints(slider); + cachePoints(hitObject); updatePathType(); })); @@ -120,11 +121,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { base.LoadComplete(); - sliderPosition = slider.PositionBindable.GetBoundCopy(); - sliderPosition.BindValueChanged(_ => updateMarkerDisplay()); + hitObjectPosition = hitObject.PositionBindable.GetBoundCopy(); + hitObjectPosition.BindValueChanged(_ => updateMarkerDisplay()); - sliderScale = slider.ScaleBindable.GetBoundCopy(); - sliderScale.BindValueChanged(_ => updateMarkerDisplay()); + hitObjectScale = hitObject.ScaleBindable.GetBoundCopy(); + hitObjectScale.BindValueChanged(_ => updateMarkerDisplay()); IsSelected.BindValueChanged(_ => updateMarkerDisplay()); @@ -212,7 +213,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components protected override void OnDragEnd(DragEndEvent e) => DragEnded?.Invoke(); - private void cachePoints(Slider slider) => PointsInSegment = slider.Path.PointsInSegment(ControlPoint); + private void cachePoints(T hitObject) => PointsInSegment = hitObject.Path.PointsInSegment(ControlPoint); /// /// Handles correction of invalid path types. @@ -239,7 +240,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components /// private void updateMarkerDisplay() { - Position = slider.StackedPosition + ControlPoint.Position; + Position = hitObject.StackedPosition + ControlPoint.Position; markerRing.Alpha = IsSelected.Value ? 1 : 0; @@ -249,7 +250,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components colour = colour.Lighten(1); marker.Colour = colour; - marker.Scale = new Vector2(slider.Scale); + marker.Scale = new Vector2(hitObject.Scale); } private Color4 getColourFromNodeType() 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 3a175888d9..65b212e976 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -29,15 +29,15 @@ using osuTK.Input; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { - public partial class PathControlPointVisualiser : CompositeDrawable, IKeyBindingHandler, IHasContextMenu + public partial class PathControlPointVisualiser : CompositeDrawable, IKeyBindingHandler, IHasContextMenu where T : OsuHitObject, IHasPath { public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; // allow context menu to appear outside of the playfield. - internal readonly Container Pieces; - internal readonly Container Connections; + internal readonly Container> Pieces; + internal readonly Container> Connections; private readonly IBindableList controlPoints = new BindableList(); - private readonly Slider slider; + private readonly T hitObject; private readonly bool allowSelection; private InputManager inputManager; @@ -48,17 +48,17 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components [Resolved(CanBeNull = true)] private IDistanceSnapProvider snapProvider { get; set; } - public PathControlPointVisualiser(Slider slider, bool allowSelection) + public PathControlPointVisualiser(T hitObject, bool allowSelection) { - this.slider = slider; + this.hitObject = hitObject; this.allowSelection = allowSelection; RelativeSizeAxes = Axes.Both; InternalChildren = new Drawable[] { - Connections = new Container { RelativeSizeAxes = Axes.Both }, - Pieces = new Container { RelativeSizeAxes = Axes.Both } + Connections = new Container> { RelativeSizeAxes = Axes.Both }, + Pieces = new Container> { RelativeSizeAxes = Axes.Both } }; } @@ -69,12 +69,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components inputManager = GetContainingInputManager(); controlPoints.CollectionChanged += onControlPointsChanged; - controlPoints.BindTo(slider.Path.ControlPoints); + controlPoints.BindTo(hitObject.Path.ControlPoints); } /// - /// Selects the corresponding to the given , - /// and deselects all other s. + /// Selects the corresponding to the given , + /// and deselects all other s. /// public void SetSelectionTo(PathControlPoint pathControlPoint) { @@ -124,8 +124,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components return true; } - private bool isSplittable(PathControlPointPiece p) => - // A slider can only be split on control points which connect two different slider segments. + private bool isSplittable(PathControlPointPiece p) => + // A hit object can only be split on control points which connect two different path segments. p.ControlPoint.Type.HasValue && p != Pieces.FirstOrDefault() && p != Pieces.LastOrDefault(); private void onControlPointsChanged(object sender, NotifyCollectionChangedEventArgs e) @@ -148,7 +148,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { var point = (PathControlPoint)e.NewItems[i]; - Pieces.Add(new PathControlPointPiece(slider, point).With(d => + Pieces.Add(new PathControlPointPiece(hitObject, point).With(d => { if (allowSelection) d.RequestSelection = selectionRequested; @@ -158,7 +158,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components d.DragEnded = dragEnded; })); - Connections.Add(new PathControlPointConnectionPiece(slider, e.NewStartingIndex + i)); + Connections.Add(new PathControlPointConnectionPiece(hitObject, e.NewStartingIndex + i)); } break; @@ -215,7 +215,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { } - private void selectionRequested(PathControlPointPiece piece, MouseButtonEvent e) + private void selectionRequested(PathControlPointPiece piece, MouseButtonEvent e) { if (e.Button == MouseButton.Left && inputManager.CurrentState.Keyboard.ControlPressed) piece.IsSelected.Toggle(); @@ -230,7 +230,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components /// /// The control point piece that we want to change the path type of. /// The path type we want to assign to the given control point piece. - private void updatePathType(PathControlPointPiece piece, PathType? type) + private void updatePathType(PathControlPointPiece piece, PathType? type) { int indexInSegment = piece.PointsInSegment.IndexOf(piece.ControlPoint); @@ -264,9 +264,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private void dragStarted(PathControlPoint controlPoint) { - dragStartPositions = slider.Path.ControlPoints.Select(point => point.Position).ToArray(); - dragPathTypes = slider.Path.ControlPoints.Select(point => point.Type).ToArray(); - draggedControlPointIndex = slider.Path.ControlPoints.IndexOf(controlPoint); + dragStartPositions = hitObject.Path.ControlPoints.Select(point => point.Position).ToArray(); + dragPathTypes = hitObject.Path.ControlPoints.Select(point => point.Type).ToArray(); + draggedControlPointIndex = hitObject.Path.ControlPoints.IndexOf(controlPoint); selectedControlPoints = new HashSet(Pieces.Where(piece => piece.IsSelected.Value).Select(piece => piece.ControlPoint)); Debug.Assert(draggedControlPointIndex >= 0); @@ -276,25 +276,25 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private void dragInProgress(DragEvent e) { - Vector2[] oldControlPoints = slider.Path.ControlPoints.Select(cp => cp.Position).ToArray(); - var oldPosition = slider.Position; - double oldStartTime = slider.StartTime; + Vector2[] oldControlPoints = hitObject.Path.ControlPoints.Select(cp => cp.Position).ToArray(); + var oldPosition = hitObject.Position; + double oldStartTime = hitObject.StartTime; - if (selectedControlPoints.Contains(slider.Path.ControlPoints[0])) + if (selectedControlPoints.Contains(hitObject.Path.ControlPoints[0])) { - // Special handling for selections containing head control point - the position of the slider changes which means the snapped position and time have to be taken into account + // Special handling for selections containing head control point - the position of the hit object changes which means the snapped position and time have to be taken into account Vector2 newHeadPosition = Parent.ToScreenSpace(e.MousePosition + (dragStartPositions[0] - dragStartPositions[draggedControlPointIndex])); var result = snapProvider?.FindSnappedPositionAndTime(newHeadPosition); - Vector2 movementDelta = Parent.ToLocalSpace(result?.ScreenSpacePosition ?? newHeadPosition) - slider.Position; + Vector2 movementDelta = Parent.ToLocalSpace(result?.ScreenSpacePosition ?? newHeadPosition) - hitObject.Position; - slider.Position += movementDelta; - slider.StartTime = result?.Time ?? slider.StartTime; + hitObject.Position += movementDelta; + hitObject.StartTime = result?.Time ?? hitObject.StartTime; - for (int i = 1; i < slider.Path.ControlPoints.Count; i++) + for (int i = 1; i < hitObject.Path.ControlPoints.Count; i++) { - var controlPoint = slider.Path.ControlPoints[i]; - // Since control points are relative to the position of the slider, all points that are _not_ selected + var controlPoint = hitObject.Path.ControlPoints[i]; + // Since control points are relative to the position of the hit object, all points that are _not_ selected // need to be offset _back_ by the delta corresponding to the movement of the head point. // All other selected control points (if any) will move together with the head point // (and so they will not move at all, relative to each other). @@ -306,7 +306,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { var result = snapProvider?.FindSnappedPositionAndTime(Parent.ToScreenSpace(e.MousePosition)); - Vector2 movementDelta = Parent.ToLocalSpace(result?.ScreenSpacePosition ?? Parent.ToScreenSpace(e.MousePosition)) - dragStartPositions[draggedControlPointIndex] - slider.Position; + Vector2 movementDelta = Parent.ToLocalSpace(result?.ScreenSpacePosition ?? Parent.ToScreenSpace(e.MousePosition)) - dragStartPositions[draggedControlPointIndex] - hitObject.Position; for (int i = 0; i < controlPoints.Count; ++i) { @@ -317,23 +317,23 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components } // Snap the path to the current beat divisor before checking length validity. - slider.SnapTo(snapProvider); + hitObject.SnapTo(snapProvider); - if (!slider.Path.HasValidLength) + if (!hitObject.Path.HasValidLength) { - for (int i = 0; i < slider.Path.ControlPoints.Count; i++) - slider.Path.ControlPoints[i].Position = oldControlPoints[i]; + for (int i = 0; i < hitObject.Path.ControlPoints.Count; i++) + hitObject.Path.ControlPoints[i].Position = oldControlPoints[i]; - slider.Position = oldPosition; - slider.StartTime = oldStartTime; + hitObject.Position = oldPosition; + hitObject.StartTime = oldStartTime; // Snap the path length again to undo the invalid length. - slider.SnapTo(snapProvider); + hitObject.SnapTo(snapProvider); return; } // Maintain the path types in case they got defaulted to bezier at some point during the drag. - for (int i = 0; i < slider.Path.ControlPoints.Count; i++) - slider.Path.ControlPoints[i].Type = dragPathTypes[i]; + for (int i = 0; i < hitObject.Path.ControlPoints.Count; i++) + hitObject.Path.ControlPoints[i].Type = dragPathTypes[i]; } private void dragEnded() => changeHandler?.EndChange(); diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index f91d35e2e1..77393efeb3 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private SliderBodyPiece bodyPiece; private HitCirclePiece headCirclePiece; private HitCirclePiece tailCirclePiece; - private PathControlPointVisualiser controlPointVisualiser; + private PathControlPointVisualiser controlPointVisualiser; private InputManager inputManager; @@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders bodyPiece = new SliderBodyPiece(), headCirclePiece = new HitCirclePiece(), tailCirclePiece = new HitCirclePiece(), - controlPointVisualiser = new PathControlPointVisualiser(HitObject, false) + controlPointVisualiser = new PathControlPointVisualiser(HitObject, false) }; setState(SliderPlacementState.Initial); diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index a51c223785..b37041674e 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders protected SliderCircleOverlay TailOverlay { get; private set; } [CanBeNull] - protected PathControlPointVisualiser ControlPointVisualiser { get; private set; } + protected PathControlPointVisualiser ControlPointVisualiser { get; private set; } [Resolved(CanBeNull = true)] private IDistanceSnapProvider snapProvider { get; set; } @@ -147,7 +147,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { if (ControlPointVisualiser == null) { - AddInternal(ControlPointVisualiser = new PathControlPointVisualiser(HitObject, true) + AddInternal(ControlPointVisualiser = new PathControlPointVisualiser(HitObject, true) { RemoveControlPointsRequested = removeControlPoints, SplitControlPointsRequested = splitControlPoints diff --git a/osu.Game.Tests/Visual/Editing/TestSceneBlueprintOrdering.cs b/osu.Game.Tests/Visual/Editing/TestSceneBlueprintOrdering.cs index 7728adecae..8b598a6a24 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneBlueprintOrdering.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneBlueprintOrdering.cs @@ -66,7 +66,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("move mouse to common point", () => { - var pos = blueprintContainer.ChildrenOfType().ElementAt(1).ScreenSpaceDrawQuad.Centre; + var pos = blueprintContainer.ChildrenOfType>().ElementAt(1).ScreenSpaceDrawQuad.Centre; InputManager.MoveMouseTo(pos); }); AddStep("right click", () => InputManager.Click(MouseButton.Right)); diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs index ffb8a67c68..b14025c9d8 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs @@ -286,7 +286,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("move mouse to controlpoint", () => { - var pos = blueprintContainer.ChildrenOfType().ElementAt(1).ScreenSpaceDrawQuad.Centre; + var pos = blueprintContainer.ChildrenOfType>().ElementAt(1).ScreenSpaceDrawQuad.Centre; InputManager.MoveMouseTo(pos); }); AddStep("hold shift", () => InputManager.PressKey(Key.ShiftLeft)); From d458c3a012264fa8095db2d24345ce31dfbf3fe1 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Wed, 7 Dec 2022 10:11:57 +0100 Subject: [PATCH 016/142] Fix variable which didnt get renamed --- .../Blueprints/Sliders/Components/PathControlPointVisualiser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 65b212e976..10ee7a9a05 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -248,7 +248,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components break; } - slider.Path.ExpectedDistance.Value = null; + hitObject.Path.ExpectedDistance.Value = null; piece.ControlPoint.Type = type; } From 39221a52dacdc4232f071ab03cac9c70aea8ada6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 5 Jan 2023 12:49:45 +0300 Subject: [PATCH 017/142] Fix advanced statistics display using decoupled ruleset bindable for difficulty calculation --- osu.Game/OsuGameBase.cs | 5 ++++- .../Screens/Select/Details/AdvancedStats.cs | 20 +++++++++++++------ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 36e248c1f2..83a32dd557 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -160,9 +160,12 @@ namespace osu.Game protected Bindable Beatmap { get; private set; } // cached via load() method + /// + /// The current ruleset selection for the local user. + /// [Cached] [Cached(typeof(IBindable))] - protected readonly Bindable Ruleset = new Bindable(); + protected internal readonly Bindable Ruleset = new Bindable(); /// /// The current mod selection for the local user. diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 5d0588e67b..3d45679604 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -30,14 +30,16 @@ namespace osu.Game.Screens.Select.Details { public partial class AdvancedStats : Container { + [Resolved] + private BeatmapDifficultyCache difficultyCache { get; set; } + [Resolved] private IBindable> mods { get; set; } [Resolved] - private IBindable ruleset { get; set; } + private OsuGameBase game { get; set; } - [Resolved] - private BeatmapDifficultyCache difficultyCache { get; set; } + private IBindable gameRuleset; protected readonly StatisticRow FirstValue, HpDrain, Accuracy, ApproachRate; private readonly StatisticRow starDifficulty; @@ -84,7 +86,13 @@ namespace osu.Game.Screens.Select.Details { base.LoadComplete(); - ruleset.BindValueChanged(_ => updateStatistics()); + // the cached ruleset bindable might be a decoupled bindable provided by SongSelect, + // which we can't rely on in combination with the game-wide selected mods list, + // since mods could be updated to the new ruleset instances while the decoupled bindable is held behind, + // therefore resulting in performing difficulty calculation with invalid states. + gameRuleset = game.Ruleset.GetBoundCopy(); + gameRuleset.BindValueChanged(_ => updateStatistics()); + mods.BindValueChanged(modsChanged, true); } @@ -151,8 +159,8 @@ namespace osu.Game.Screens.Select.Details starDifficultyCancellationSource = new CancellationTokenSource(); - var normalStarDifficultyTask = difficultyCache.GetDifficultyAsync(BeatmapInfo, ruleset.Value, null, starDifficultyCancellationSource.Token); - var moddedStarDifficultyTask = difficultyCache.GetDifficultyAsync(BeatmapInfo, ruleset.Value, mods.Value, starDifficultyCancellationSource.Token); + var normalStarDifficultyTask = difficultyCache.GetDifficultyAsync(BeatmapInfo, gameRuleset.Value, null, starDifficultyCancellationSource.Token); + var moddedStarDifficultyTask = difficultyCache.GetDifficultyAsync(BeatmapInfo, gameRuleset.Value, mods.Value, starDifficultyCancellationSource.Token); Task.WhenAll(normalStarDifficultyTask, moddedStarDifficultyTask).ContinueWith(_ => Schedule(() => { From 8f37e69dc4e8c7804098a7415deb838a33f4933e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 5 Jan 2023 12:50:38 +0300 Subject: [PATCH 018/142] Schedule difficulty calculation to avoid performing with incomplete state updates --- osu.Game/Screens/Select/Details/AdvancedStats.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 3d45679604..a383298faa 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -150,7 +150,14 @@ namespace osu.Game.Screens.Select.Details private CancellationTokenSource starDifficultyCancellationSource; - private void updateStarDifficulty() + /// + /// Updates the displayed star difficulty statistics with the values provided by the currently-selected beatmap, ruleset, and selected mods. + /// + /// + /// This is scheduled to avoid scenarios wherein a ruleset changes first before selected mods do, + /// potentially resulting in failure during difficulty calculation due to incomplete bindable state updates. + /// + private void updateStarDifficulty() => Scheduler.AddOnce(() => { starDifficultyCancellationSource?.Cancel(); @@ -172,7 +179,7 @@ namespace osu.Game.Screens.Select.Details starDifficulty.Value = ((float)normalDifficulty.Value.Stars, (float)moddedDifficulty.Value.Stars); }), starDifficultyCancellationSource.Token, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Current); - } + }); protected override void Dispose(bool isDisposing) { From 38bb7ac0c7901cbca12a4f787fe2d28eaa0591a8 Mon Sep 17 00:00:00 2001 From: Wleter Date: Tue, 10 Jan 2023 21:16:34 +0100 Subject: [PATCH 019/142] add fields for path's end location --- .../Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs | 4 ++++ osu.Game.Rulesets.Osu/Skinning/SliderBody.cs | 6 ++++++ osu.Game.Rulesets.Osu/Skinning/SnakingSliderBody.cs | 8 ++++++++ 3 files changed, 18 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs index ecd840dda6..c44bbbfa03 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs @@ -22,6 +22,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components /// public Vector2 PathStartLocation => body.PathOffset; + /// + /// Offset in absolute (local) coordinates from the end of the curve. + /// + public Vector2 PathEndLocation => body.PathEndOffset; public SliderBodyPiece() { InternalChild = body = new ManualSliderBody diff --git a/osu.Game.Rulesets.Osu/Skinning/SliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/SliderBody.cs index 283687adfd..8a54723a8e 100644 --- a/osu.Game.Rulesets.Osu/Skinning/SliderBody.cs +++ b/osu.Game.Rulesets.Osu/Skinning/SliderBody.cs @@ -31,6 +31,12 @@ namespace osu.Game.Rulesets.Osu.Skinning /// public virtual Vector2 PathOffset => path.PositionInBoundingBox(path.Vertices[0]); + /// + /// Offset in absolute coordinates from the end of the curve. + /// + public virtual Vector2 PathEndOffset => path.PositionInBoundingBox(path.Vertices[^1]); + + /// /// Used to colour the path. /// diff --git a/osu.Game.Rulesets.Osu/Skinning/SnakingSliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/SnakingSliderBody.cs index f8ee465cd6..0b7acc1f47 100644 --- a/osu.Game.Rulesets.Osu/Skinning/SnakingSliderBody.cs +++ b/osu.Game.Rulesets.Osu/Skinning/SnakingSliderBody.cs @@ -43,6 +43,8 @@ namespace osu.Game.Rulesets.Osu.Skinning public override Vector2 PathOffset => snakedPathOffset; + public override Vector2 PathEndOffset => snakedPathEndOffset; + /// /// The top-left position of the path when fully snaked. /// @@ -53,6 +55,11 @@ namespace osu.Game.Rulesets.Osu.Skinning /// private Vector2 snakedPathOffset; + /// + /// The offset of the end of path from when fully snaked. + /// + private Vector2 snakedPathEndOffset; + private DrawableSlider drawableSlider = null!; [BackgroundDependencyLoader] @@ -109,6 +116,7 @@ namespace osu.Game.Rulesets.Osu.Skinning snakedPosition = Path.PositionInBoundingBox(Vector2.Zero); snakedPathOffset = Path.PositionInBoundingBox(Path.Vertices[0]); + snakedPathEndOffset = Path.PositionInBoundingBox(Path.Vertices[^1]); double lastSnakedStart = SnakedStart ?? 0; double lastSnakedEnd = SnakedEnd ?? 0; From e5863fbaf1f4f9f7918459aff1a38e0941752301 Mon Sep 17 00:00:00 2001 From: Wleter Date: Tue, 10 Jan 2023 21:20:09 +0100 Subject: [PATCH 020/142] add ScreenSpaceEndPoint field --- .../Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs | 4 ++++ osu.Game/Rulesets/Edit/HitObjectSelectionBlueprint.cs | 2 ++ osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 5 +++++ 3 files changed, 11 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index a51c223785..3737a44ad2 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -409,6 +409,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders public override Vector2 ScreenSpaceSelectionPoint => DrawableObject.SliderBody?.ToScreenSpace(DrawableObject.SliderBody.PathOffset) ?? BodyPiece.ToScreenSpace(BodyPiece.PathStartLocation); + + public override Vector2 ScreenSpaceEndPoint => DrawableObject.SliderBody?.ToScreenSpace(DrawableObject.SliderBody.PathEndOffset) + ?? BodyPiece.ToScreenSpace(BodyPiece.PathEndLocation); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => BodyPiece.ReceivePositionalInputAt(screenSpacePos) || ControlPointVisualiser?.Pieces.Any(p => p.ReceivePositionalInputAt(screenSpacePos)) == true; diff --git a/osu.Game/Rulesets/Edit/HitObjectSelectionBlueprint.cs b/osu.Game/Rulesets/Edit/HitObjectSelectionBlueprint.cs index 93b889792b..3aa086582f 100644 --- a/osu.Game/Rulesets/Edit/HitObjectSelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/HitObjectSelectionBlueprint.cs @@ -48,6 +48,8 @@ namespace osu.Game.Rulesets.Edit public override Vector2 ScreenSpaceSelectionPoint => DrawableObject.ScreenSpaceDrawQuad.Centre; + public override Vector2 ScreenSpaceEndPoint => DrawableObject.ScreenSpaceDrawQuad.Centre; + public override Quad SelectionQuad => DrawableObject.ScreenSpaceDrawQuad; } diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index 4e0e45e0f5..a27a59be34 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -129,6 +129,11 @@ namespace osu.Game.Rulesets.Edit /// public virtual Vector2 ScreenSpaceSelectionPoint => ScreenSpaceDrawQuad.Centre; + /// + /// The screen-space point that mark end of this . + /// + public virtual Vector2 ScreenSpaceEndPoint => ScreenSpaceDrawQuad.Centre; + /// /// The screen-space quad that outlines this for selections. /// From 88060a3ea097d3dbad513e08f582288825cbaf9b Mon Sep 17 00:00:00 2001 From: Wleter Date: Tue, 10 Jan 2023 21:21:09 +0100 Subject: [PATCH 021/142] add snapping for slider's end --- .../Compose/Components/BlueprintContainer.cs | 60 +++++++++++++------ 1 file changed, 42 insertions(+), 18 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 77892f21e6..826a16b83b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -440,6 +440,7 @@ namespace osu.Game.Screens.Edit.Compose.Components #region Selection Movement private Vector2[] movementBlueprintOriginalPositions; + private Vector2[] movementBlueprintEndPositions; private SelectionBlueprint[] movementBlueprints; private bool isDraggingBlueprint; @@ -459,7 +460,12 @@ namespace osu.Game.Screens.Edit.Compose.Components // Movement is tracked from the blueprint of the earliest item, since it only makes sense to distance snap from that item movementBlueprints = SortForMovement(SelectionHandler.SelectedBlueprints).ToArray(); - movementBlueprintOriginalPositions = movementBlueprints.Select(m => m.ScreenSpaceSelectionPoint).ToArray(); + movementBlueprintOriginalPositions = movementBlueprints.Select(m => m.ScreenSpaceSelectionPoint) + .ToArray(); + movementBlueprintEndPositions = movementBlueprints.Where(m => m.ScreenSpaceSelectionPoint != m.ScreenSpaceEndPoint) + .Select(m => m.ScreenSpaceEndPoint) + .ToArray(); + return true; } @@ -470,6 +476,33 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Sorted blueprints. protected virtual IEnumerable> SortForMovement(IReadOnlyList> blueprints) => blueprints; + /// + /// Check for positional snap for every given positions. + /// + /// Distance traveled since start of dragging action. + /// The positions to check for snapping before start of dragging action. + /// The positions to check for snapping at the current time. + /// Whether found object to snap to. + private bool checkSnappingForNearbyObjects(Vector2 distanceTraveled, Vector2[] originalPositions, Vector2[] currentPositions) + { + for (int i = 0; i < originalPositions.Length; i++) + { + Vector2 originalPosition = originalPositions[i]; + var testPosition = originalPosition + distanceTraveled; + + var positionalResult = snapProvider.FindSnappedPositionAndTime(testPosition, SnapType.NearbyObjects); + + if (positionalResult.ScreenSpacePosition == testPosition) continue; + + var delta = positionalResult.ScreenSpacePosition - currentPositions[i]; + + // attempt to move the objects, and abort any time based snapping if we can. + if (SelectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprints[i], delta))) + return true; + } + return false; + } + /// /// Moves the current selected blueprints. /// @@ -482,33 +515,24 @@ namespace osu.Game.Screens.Edit.Compose.Components Debug.Assert(movementBlueprintOriginalPositions != null); - Vector2 distanceTravelled = e.ScreenSpaceMousePosition - e.ScreenSpaceMouseDownPosition; + Vector2 distanceTraveled = e.ScreenSpaceMousePosition - e.ScreenSpaceMouseDownPosition; if (snapProvider != null) { - // check for positional snap for every object in selection (for things like object-object snapping) - for (int i = 0; i < movementBlueprintOriginalPositions.Length; i++) - { - Vector2 originalPosition = movementBlueprintOriginalPositions[i]; - var testPosition = originalPosition + distanceTravelled; + var currentSelectionPointPositions = movementBlueprints.Select(m => m.ScreenSpaceSelectionPoint).ToArray(); + if (checkSnappingForNearbyObjects(distanceTraveled, movementBlueprintOriginalPositions, currentSelectionPointPositions)) + return true; - var positionalResult = snapProvider.FindSnappedPositionAndTime(testPosition, SnapType.NearbyObjects); - - if (positionalResult.ScreenSpacePosition == testPosition) continue; - - var delta = positionalResult.ScreenSpacePosition - movementBlueprints[i].ScreenSpaceSelectionPoint; - - // attempt to move the objects, and abort any time based snapping if we can. - if (SelectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprints[i], delta))) - return true; - } + var currentEndPointPositions = movementBlueprints.Select(m => m.ScreenSpaceEndPoint).ToArray(); + if (checkSnappingForNearbyObjects(distanceTraveled, movementBlueprintEndPositions, currentEndPointPositions)) + return true; } // if no positional snapping could be performed, try unrestricted snapping from the earliest // item in the selection. // The final movement position, relative to movementBlueprintOriginalPosition. - Vector2 movePosition = movementBlueprintOriginalPositions.First() + distanceTravelled; + Vector2 movePosition = movementBlueprintOriginalPositions.First() + distanceTraveled; // Retrieve a snapped position. var result = snapProvider?.FindSnappedPositionAndTime(movePosition, ~SnapType.NearbyObjects); From 23ea77cb74034f2e19071a4758329efc8fad4ba0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Jan 2023 17:03:04 +0900 Subject: [PATCH 022/142] Use `ScoreProcessor` to fetch accuracy rather than calculating manually --- .../Rulesets/Mods/ModAccuracyChallenge.cs | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs b/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs index 74a9a205a0..732f54e356 100644 --- a/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs +++ b/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs @@ -5,23 +5,32 @@ using System; using System.Globalization; using System.Linq; using osu.Framework.Bindables; +using osu.Framework.Localisation; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Judgements; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Mods { - public class ModAccuracyChallenge : ModFailCondition + public class ModAccuracyChallenge : ModFailCondition, IApplicableToScoreProcessor { public override string Name => "Accuracy Challenge"; + public override string Acronym => "AC"; - public override string Description => "Fail the map if you don't maintain a certain accuracy."; + + public override LocalisableString Description => "Fail if your accuracy drops too low!"; + public override ModType Type => ModType.DifficultyIncrease; + public override double ScoreMultiplier => 1.0; + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(ModEasyWithExtraLives), typeof(ModPerfect) }).ToArray(); + public override bool RequiresConfiguration => false; + public override string SettingDescription => base.SettingDescription.Replace(MinimumAccuracy.ToString(), MinimumAccuracy.Value.ToString("##%", NumberFormatInfo.InvariantInfo)); [SettingSource("Minimum accuracy", "Trigger a failure if your accuracy goes below this value.", SettingControlType = typeof(SettingsSlider))] @@ -34,9 +43,14 @@ namespace osu.Game.Rulesets.Mods Value = 0.9, }; - private double baseScore; - private double maxBaseScore; - private double accuracy => maxBaseScore > 0 ? baseScore / maxBaseScore : 1; + private ScoreProcessor scoreProcessor = null!; + + public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) + { + this.scoreProcessor = scoreProcessor; + } + + public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank; protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result) { @@ -44,14 +58,11 @@ namespace osu.Game.Rulesets.Mods if (!result.Type.IsScorable() || result.Type.IsBonus()) return false; - baseScore += result.Type.IsHit() ? result.Judgement.NumericResultFor(result) : 0; - maxBaseScore += result.Judgement.MaxNumericResult; - - return accuracy < MinimumAccuracy.Value; + return scoreProcessor.Accuracy.Value < MinimumAccuracy.Value; } } - public class PercentSlider : OsuSliderBar + public partial class PercentSlider : OsuSliderBar { public PercentSlider() { From dfbbc4002c0fccf6ecdedea8a609972efa0fdd45 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Tue, 17 Jan 2023 10:22:58 -0500 Subject: [PATCH 023/142] address test failure --- .../Mods/OsuModAccuracyChallenge.cs | 14 ++++++++++++++ osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Osu/Mods/OsuModAccuracyChallenge.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAccuracyChallenge.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAccuracyChallenge.cs new file mode 100644 index 0000000000..5b79753632 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAccuracyChallenge.cs @@ -0,0 +1,14 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Osu.Mods +{ + public class OsuModAccuracyChallenge : ModAccuracyChallenge + { + public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).ToArray(); + } +} diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 6417b557f4..48056a49de 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -165,7 +165,7 @@ namespace osu.Game.Rulesets.Osu new OsuModHidden(), new MultiMod(new OsuModFlashlight(), new OsuModBlinds()), new OsuModStrictTracking(), - new ModAccuracyChallenge(), + new OsuModAccuracyChallenge(), }; case ModType.Conversion: From 0b1e5c0f53f4470ab9ead7d1b99c35d5fca2c790 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Tue, 17 Jan 2023 10:23:57 -0500 Subject: [PATCH 024/142] lambdaify function --- osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs b/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs index 732f54e356..78e6acf23a 100644 --- a/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs +++ b/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs @@ -45,10 +45,7 @@ namespace osu.Game.Rulesets.Mods private ScoreProcessor scoreProcessor = null!; - public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) - { - this.scoreProcessor = scoreProcessor; - } + public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) => this.scoreProcessor = scoreProcessor; public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank; From b8e6a2d87b86c88f243bd67ee6379d572ca1c5b3 Mon Sep 17 00:00:00 2001 From: Wleter Date: Tue, 17 Jan 2023 20:41:49 +0100 Subject: [PATCH 025/142] filter currentEndPointPositions --- .../Compose/Components/BlueprintContainer.cs | 64 ++++++++++--------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 826a16b83b..07cfd9fe0a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -440,7 +440,7 @@ namespace osu.Game.Screens.Edit.Compose.Components #region Selection Movement private Vector2[] movementBlueprintOriginalPositions; - private Vector2[] movementBlueprintEndPositions; + private Vector2[] movementBlueprintOriginalEndPositions; private SelectionBlueprint[] movementBlueprints; private bool isDraggingBlueprint; @@ -462,7 +462,7 @@ namespace osu.Game.Screens.Edit.Compose.Components movementBlueprints = SortForMovement(SelectionHandler.SelectedBlueprints).ToArray(); movementBlueprintOriginalPositions = movementBlueprints.Select(m => m.ScreenSpaceSelectionPoint) .ToArray(); - movementBlueprintEndPositions = movementBlueprints.Where(m => m.ScreenSpaceSelectionPoint != m.ScreenSpaceEndPoint) + movementBlueprintOriginalEndPositions = movementBlueprints.Where(m => m.ScreenSpaceSelectionPoint != m.ScreenSpaceEndPoint) .Select(m => m.ScreenSpaceEndPoint) .ToArray(); @@ -476,33 +476,6 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Sorted blueprints. protected virtual IEnumerable> SortForMovement(IReadOnlyList> blueprints) => blueprints; - /// - /// Check for positional snap for every given positions. - /// - /// Distance traveled since start of dragging action. - /// The positions to check for snapping before start of dragging action. - /// The positions to check for snapping at the current time. - /// Whether found object to snap to. - private bool checkSnappingForNearbyObjects(Vector2 distanceTraveled, Vector2[] originalPositions, Vector2[] currentPositions) - { - for (int i = 0; i < originalPositions.Length; i++) - { - Vector2 originalPosition = originalPositions[i]; - var testPosition = originalPosition + distanceTraveled; - - var positionalResult = snapProvider.FindSnappedPositionAndTime(testPosition, SnapType.NearbyObjects); - - if (positionalResult.ScreenSpacePosition == testPosition) continue; - - var delta = positionalResult.ScreenSpacePosition - currentPositions[i]; - - // attempt to move the objects, and abort any time based snapping if we can. - if (SelectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprints[i], delta))) - return true; - } - return false; - } - /// /// Moves the current selected blueprints. /// @@ -523,8 +496,10 @@ namespace osu.Game.Screens.Edit.Compose.Components if (checkSnappingForNearbyObjects(distanceTraveled, movementBlueprintOriginalPositions, currentSelectionPointPositions)) return true; - var currentEndPointPositions = movementBlueprints.Select(m => m.ScreenSpaceEndPoint).ToArray(); - if (checkSnappingForNearbyObjects(distanceTraveled, movementBlueprintEndPositions, currentEndPointPositions)) + var currentEndPointPositions = movementBlueprints.Where(m => m.ScreenSpaceSelectionPoint != m.ScreenSpaceEndPoint) + .Select(m => m.ScreenSpaceEndPoint) + .ToArray(); + if (checkSnappingForNearbyObjects(distanceTraveled, movementBlueprintOriginalEndPositions, currentEndPointPositions)) return true; } @@ -545,6 +520,33 @@ namespace osu.Game.Screens.Edit.Compose.Components return ApplySnapResult(movementBlueprints, result); } + /// + /// Check for positional snap for every given positions. + /// + /// Distance traveled since start of dragging action. + /// The position of objects before start of dragging action. + /// The positions of object at the current time. + /// Whether found object to snap to. + private bool checkSnappingForNearbyObjects(Vector2 distanceTraveled, Vector2[] originalPositions, Vector2[] currentPositions) + { + for (int i = 0; i < originalPositions.Length; i++) + { + Vector2 originalPosition = originalPositions[i]; + var testPosition = originalPosition + distanceTraveled; + + var positionalResult = snapProvider.FindSnappedPositionAndTime(testPosition, SnapType.NearbyObjects); + + if (positionalResult.ScreenSpacePosition == testPosition) continue; + + var delta = positionalResult.ScreenSpacePosition - currentPositions[i]; + + // attempt to move the objects, and abort any time based snapping if we can. + if (SelectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprints[i], delta))) + return true; + } + return false; + } + protected virtual bool ApplySnapResult(SelectionBlueprint[] blueprints, SnapResult result) => SelectionHandler.HandleMovement(new MoveSelectionEvent(blueprints.First(), result.ScreenSpacePosition - blueprints.First().ScreenSpaceSelectionPoint)); From 00f15d19f969d1977b9834a75ff781b8a4d0dc19 Mon Sep 17 00:00:00 2001 From: Wleter Date: Tue, 17 Jan 2023 21:11:21 +0100 Subject: [PATCH 026/142] fix double newlines --- .../Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs | 1 + .../Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs | 1 - osu.Game.Rulesets.Osu/Skinning/SliderBody.cs | 1 - 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs index c44bbbfa03..4ff38cd1f8 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs @@ -26,6 +26,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components /// Offset in absolute (local) coordinates from the end of the curve. /// public Vector2 PathEndLocation => body.PathEndOffset; + public SliderBodyPiece() { InternalChild = body = new ManualSliderBody diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 3737a44ad2..90c97e2752 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -409,7 +409,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders public override Vector2 ScreenSpaceSelectionPoint => DrawableObject.SliderBody?.ToScreenSpace(DrawableObject.SliderBody.PathOffset) ?? BodyPiece.ToScreenSpace(BodyPiece.PathStartLocation); - public override Vector2 ScreenSpaceEndPoint => DrawableObject.SliderBody?.ToScreenSpace(DrawableObject.SliderBody.PathEndOffset) ?? BodyPiece.ToScreenSpace(BodyPiece.PathEndLocation); diff --git a/osu.Game.Rulesets.Osu/Skinning/SliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/SliderBody.cs index 8a54723a8e..e7885e65de 100644 --- a/osu.Game.Rulesets.Osu/Skinning/SliderBody.cs +++ b/osu.Game.Rulesets.Osu/Skinning/SliderBody.cs @@ -35,7 +35,6 @@ namespace osu.Game.Rulesets.Osu.Skinning /// Offset in absolute coordinates from the end of the curve. /// public virtual Vector2 PathEndOffset => path.PositionInBoundingBox(path.Vertices[^1]); - /// /// Used to colour the path. From 788033e1b89997142f9d74089b19bd7821b08119 Mon Sep 17 00:00:00 2001 From: Wleter Date: Tue, 17 Jan 2023 21:13:54 +0100 Subject: [PATCH 027/142] fix unnecessary newline --- .../Screens/Edit/Compose/Components/BlueprintContainer.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 07cfd9fe0a..113dffbcb0 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -460,11 +460,9 @@ namespace osu.Game.Screens.Edit.Compose.Components // Movement is tracked from the blueprint of the earliest item, since it only makes sense to distance snap from that item movementBlueprints = SortForMovement(SelectionHandler.SelectedBlueprints).ToArray(); - movementBlueprintOriginalPositions = movementBlueprints.Select(m => m.ScreenSpaceSelectionPoint) - .ToArray(); + movementBlueprintOriginalPositions = movementBlueprints.Select(m => m.ScreenSpaceSelectionPoint).ToArray(); movementBlueprintOriginalEndPositions = movementBlueprints.Where(m => m.ScreenSpaceSelectionPoint != m.ScreenSpaceEndPoint) - .Select(m => m.ScreenSpaceEndPoint) - .ToArray(); + .Select(m => m.ScreenSpaceEndPoint).ToArray(); return true; } From ecb4727aeca1d1011a793ccab2b85f8f67bc6dc8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Jan 2023 17:33:14 +0900 Subject: [PATCH 028/142] Fix formatting issues --- .../Blueprints/Sliders/Components/SliderBodyPiece.cs | 2 +- .../Blueprints/Sliders/SliderSelectionBlueprint.cs | 2 +- .../Edit/Compose/Components/BlueprintContainer.cs | 11 +++++++---- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs index 4ff38cd1f8..68a44eb2f8 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components /// Offset in absolute (local) coordinates from the end of the curve. /// public Vector2 PathEndLocation => body.PathEndOffset; - + public SliderBodyPiece() { InternalChild = body = new ManualSliderBody diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 90c97e2752..634897e3d5 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -410,7 +410,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders ?? BodyPiece.ToScreenSpace(BodyPiece.PathStartLocation); public override Vector2 ScreenSpaceEndPoint => DrawableObject.SliderBody?.ToScreenSpace(DrawableObject.SliderBody.PathEndOffset) - ?? BodyPiece.ToScreenSpace(BodyPiece.PathEndLocation); + ?? BodyPiece.ToScreenSpace(BodyPiece.PathEndLocation); public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => BodyPiece.ReceivePositionalInputAt(screenSpacePos) || ControlPointVisualiser?.Pieces.Any(p => p.ReceivePositionalInputAt(screenSpacePos)) == true; diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 113dffbcb0..c9d9f2266a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -462,8 +462,8 @@ namespace osu.Game.Screens.Edit.Compose.Components movementBlueprints = SortForMovement(SelectionHandler.SelectedBlueprints).ToArray(); movementBlueprintOriginalPositions = movementBlueprints.Select(m => m.ScreenSpaceSelectionPoint).ToArray(); movementBlueprintOriginalEndPositions = movementBlueprints.Where(m => m.ScreenSpaceSelectionPoint != m.ScreenSpaceEndPoint) - .Select(m => m.ScreenSpaceEndPoint).ToArray(); - + .Select(m => m.ScreenSpaceEndPoint).ToArray(); + return true; } @@ -491,12 +491,14 @@ namespace osu.Game.Screens.Edit.Compose.Components if (snapProvider != null) { var currentSelectionPointPositions = movementBlueprints.Select(m => m.ScreenSpaceSelectionPoint).ToArray(); + if (checkSnappingForNearbyObjects(distanceTraveled, movementBlueprintOriginalPositions, currentSelectionPointPositions)) return true; var currentEndPointPositions = movementBlueprints.Where(m => m.ScreenSpaceSelectionPoint != m.ScreenSpaceEndPoint) - .Select(m => m.ScreenSpaceEndPoint) - .ToArray(); + .Select(m => m.ScreenSpaceEndPoint) + .ToArray(); + if (checkSnappingForNearbyObjects(distanceTraveled, movementBlueprintOriginalEndPositions, currentEndPointPositions)) return true; } @@ -542,6 +544,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (SelectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprints[i], delta))) return true; } + return false; } From 522bb8bccafc93603d068775b6c3a28cd3b58c25 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Jan 2023 18:08:58 +0900 Subject: [PATCH 029/142] Use `ComputeAccuracy` to get imminent accuracy --- osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs | 15 +++++++++++++-- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 18 +++++++++++------- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs b/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs index 78e6acf23a..02d4fb4d67 100644 --- a/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs +++ b/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs @@ -51,11 +51,22 @@ namespace osu.Game.Rulesets.Mods protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result) { - // accuracy calculation logic taken from `ScoreProcessor`. should be updated here if the formula ever changes. if (!result.Type.IsScorable() || result.Type.IsBonus()) return false; - return scoreProcessor.Accuracy.Value < MinimumAccuracy.Value; + return getAccuracyWithImminentResultAdded(result) < MinimumAccuracy.Value; + } + + private double getAccuracyWithImminentResultAdded(JudgementResult result) + { + var score = new ScoreInfo { Ruleset = scoreProcessor.Ruleset.RulesetInfo }; + + // This is super ugly, but if we don't do it this way we will not have the most recent result added to the accuracy value. + // Hopefully we can improve this in the future. + scoreProcessor.PopulateScore(score); + score.Statistics[result.Type]++; + + return scoreProcessor.ComputeAccuracy(score); } } diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index ff0d91c0dd..3ff1b69612 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -97,7 +97,11 @@ namespace osu.Game.Rulesets.Scoring /// protected virtual double ClassicScoreMultiplier => 36; - private readonly Ruleset ruleset; + /// + /// The ruleset this score processor is valid for. + /// + public readonly Ruleset Ruleset; + private readonly double accuracyPortion; private readonly double comboPortion; @@ -145,7 +149,7 @@ namespace osu.Game.Rulesets.Scoring public ScoreProcessor(Ruleset ruleset) { - this.ruleset = ruleset; + this.Ruleset = ruleset; accuracyPortion = DefaultAccuracyPortion; comboPortion = DefaultComboPortion; @@ -291,8 +295,8 @@ namespace osu.Game.Rulesets.Scoring [Pure] public double ComputeAccuracy(ScoreInfo scoreInfo) { - if (!ruleset.RulesetInfo.Equals(scoreInfo.Ruleset)) - throw new ArgumentException($"Unexpected score ruleset. Expected \"{ruleset.RulesetInfo.ShortName}\" but was \"{scoreInfo.Ruleset.ShortName}\"."); + if (!Ruleset.RulesetInfo.Equals(scoreInfo.Ruleset)) + throw new ArgumentException($"Unexpected score ruleset. Expected \"{Ruleset.RulesetInfo.ShortName}\" but was \"{scoreInfo.Ruleset.ShortName}\"."); // We only extract scoring values from the score's statistics. This is because accuracy is always relative to the point of pass or fail rather than relative to the whole beatmap. extractScoringValues(scoreInfo.Statistics, out var current, out var maximum); @@ -312,8 +316,8 @@ namespace osu.Game.Rulesets.Scoring [Pure] public long ComputeScore(ScoringMode mode, ScoreInfo scoreInfo) { - if (!ruleset.RulesetInfo.Equals(scoreInfo.Ruleset)) - throw new ArgumentException($"Unexpected score ruleset. Expected \"{ruleset.RulesetInfo.ShortName}\" but was \"{scoreInfo.Ruleset.ShortName}\"."); + if (!Ruleset.RulesetInfo.Equals(scoreInfo.Ruleset)) + throw new ArgumentException($"Unexpected score ruleset. Expected \"{Ruleset.RulesetInfo.ShortName}\" but was \"{scoreInfo.Ruleset.ShortName}\"."); extractScoringValues(scoreInfo, out var current, out var maximum); @@ -552,7 +556,7 @@ namespace osu.Game.Rulesets.Scoring break; default: - maxResult = maxBasicResult ??= ruleset.GetHitResults().MaxBy(kvp => Judgement.ToNumericResult(kvp.result)).result; + maxResult = maxBasicResult ??= Ruleset.GetHitResults().MaxBy(kvp => Judgement.ToNumericResult(kvp.result)).result; break; } From 24ed84aad04b1e2c6d04375f5553e6061836f97d Mon Sep 17 00:00:00 2001 From: StanR Date: Wed, 18 Jan 2023 16:25:11 +0300 Subject: [PATCH 030/142] Add tiered level badge colouring --- .../Visual/Online/TestSceneLevelBadge.cs | 58 +++++++++++++++++++ osu.Game/Graphics/OsuColour.cs | 36 ++++++++++++ .../Profile/Header/Components/LevelBadge.cs | 39 +++++++++++-- osu.Game/Scoring/RankingTier.cs | 17 ++++++ 4 files changed, 146 insertions(+), 4 deletions(-) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneLevelBadge.cs create mode 100644 osu.Game/Scoring/RankingTier.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneLevelBadge.cs b/osu.Game.Tests/Visual/Online/TestSceneLevelBadge.cs new file mode 100644 index 0000000000..71c57896d2 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneLevelBadge.cs @@ -0,0 +1,58 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Overlays.Profile.Header.Components; +using osu.Game.Users; +using osuTK; + +namespace osu.Game.Tests.Visual.Online +{ + [TestFixture] + public partial class TestSceneLevelBadge : OsuTestScene + { + public TestSceneLevelBadge() + { + var levels = new List(); + + for (int i = 0; i < 11; i++) + { + levels.Add(new UserStatistics.LevelInfo + { + Current = i * 10 + }); + } + + levels.Add(new UserStatistics.LevelInfo { Current = 101 }); + levels.Add(new UserStatistics.LevelInfo { Current = 105 }); + levels.Add(new UserStatistics.LevelInfo { Current = 110 }); + levels.Add(new UserStatistics.LevelInfo { Current = 115 }); + levels.Add(new UserStatistics.LevelInfo { Current = 120 }); + + Children = new Drawable[] + { + new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5), + ChildrenEnumerable = levels.Select(l => new LevelBadge + { + Size = new Vector2(60), + LevelInfo = { Value = l } + }) + } + }; + } + } +} diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index c5659aaf57..5e2649377a 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics.Colour; using osu.Game.Beatmaps; using osu.Game.Online.Rooms; using osu.Game.Overlays; @@ -187,6 +188,41 @@ namespace osu.Game.Graphics } } + /// + /// Retrieves colour for a . + /// See https://www.figma.com/file/YHWhp9wZ089YXgB7pe6L1k/Tier-Colours + /// + public ColourInfo ForRankingTiers(RankingTier tier) + { + switch (tier) + { + default: + case RankingTier.Iron: + return Color4Extensions.FromHex(@"BAB3AB"); + + case RankingTier.Bronze: + return ColourInfo.GradientVertical(Color4Extensions.FromHex(@"B88F7A"), Color4Extensions.FromHex(@"855C47")); + + case RankingTier.Silver: + return ColourInfo.GradientVertical(Color4Extensions.FromHex(@"E0E0EB"), Color4Extensions.FromHex(@"A3A3C2")); + + case RankingTier.Gold: + return ColourInfo.GradientVertical(Color4Extensions.FromHex(@"F0E4A8"), Color4Extensions.FromHex(@"E0C952")); + + case RankingTier.Platinum: + return ColourInfo.GradientVertical(Color4Extensions.FromHex(@"A8F0EF"), Color4Extensions.FromHex(@"52E0DF")); + + case RankingTier.Rhodium: + return ColourInfo.GradientVertical(Color4Extensions.FromHex(@"D9F8D3"), Color4Extensions.FromHex(@"A0CF96")); + + case RankingTier.Radiant: + return ColourInfo.GradientVertical(Color4Extensions.FromHex(@"97DCFF"), Color4Extensions.FromHex(@"ED82FF")); + + case RankingTier.Lustrous: + return ColourInfo.GradientVertical(Color4Extensions.FromHex(@"FFE600"), Color4Extensions.FromHex(@"ED82FF")); + } + } + /// /// Returns a foreground text colour that is supposed to contrast well with /// the supplied . diff --git a/osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs b/osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs index 00bb8cbc75..ddc084a600 100644 --- a/osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs +++ b/osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; @@ -12,6 +13,7 @@ using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Resources.Localisation.Web; +using osu.Game.Scoring; using osu.Game.Users; namespace osu.Game.Overlays.Profile.Header.Components @@ -23,6 +25,10 @@ namespace osu.Game.Overlays.Profile.Header.Components public LocalisableString TooltipText { get; private set; } private OsuSpriteText levelText = null!; + private Sprite sprite = null!; + + [Resolved] + private OsuColour osuColour { get; set; } = null!; public LevelBadge() { @@ -34,7 +40,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { InternalChildren = new Drawable[] { - new Sprite + sprite = new Sprite { RelativeSizeAxes = Axes.Both, Texture = textures.Get("Profile/levelbadge"), @@ -58,9 +64,34 @@ namespace osu.Game.Overlays.Profile.Header.Components private void updateLevel(UserStatistics.LevelInfo? levelInfo) { - string level = levelInfo?.Current.ToString() ?? "0"; - levelText.Text = level; - TooltipText = UsersStrings.ShowStatsLevel(level); + int level = levelInfo?.Current ?? 0; + + levelText.Text = level.ToString(); + TooltipText = UsersStrings.ShowStatsLevel(level.ToString()); + + sprite.Colour = mapLevelToTierColour(level); + } + + private ColourInfo mapLevelToTierColour(int level) + { + var tier = RankingTier.Iron; + + if (level > 0) + { + tier = (RankingTier)(level / 20); + } + + if (level >= 105) + { + tier = RankingTier.Radiant; + } + + if (level >= 110) + { + tier = RankingTier.Lustrous; + } + + return osuColour.ForRankingTiers(tier); } } } diff --git a/osu.Game/Scoring/RankingTier.cs b/osu.Game/Scoring/RankingTier.cs new file mode 100644 index 0000000000..e57c241515 --- /dev/null +++ b/osu.Game/Scoring/RankingTier.cs @@ -0,0 +1,17 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Scoring +{ + public enum RankingTier + { + Iron, + Bronze, + Silver, + Gold, + Platinum, + Rhodium, + Radiant, + Lustrous + } +} From 8ae82484b5ec550261366337eb5fcec04549495d Mon Sep 17 00:00:00 2001 From: StanR Date: Wed, 18 Jan 2023 16:38:58 +0300 Subject: [PATCH 031/142] Usings --- osu.Game.Tests/Visual/Online/TestSceneLevelBadge.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneLevelBadge.cs b/osu.Game.Tests/Visual/Online/TestSceneLevelBadge.cs index 71c57896d2..fef6dd0477 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneLevelBadge.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneLevelBadge.cs @@ -6,9 +6,6 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; using osu.Game.Overlays.Profile.Header.Components; using osu.Game.Users; using osuTK; From c9375c056e3444b4e0fe9730b31ee025ddf3aa95 Mon Sep 17 00:00:00 2001 From: Wleter Date: Wed, 18 Jan 2023 15:54:24 +0100 Subject: [PATCH 032/142] revert back name change --- .../Edit/Compose/Components/BlueprintContainer.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index c9d9f2266a..49e4ab5300 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -486,20 +486,20 @@ namespace osu.Game.Screens.Edit.Compose.Components Debug.Assert(movementBlueprintOriginalPositions != null); - Vector2 distanceTraveled = e.ScreenSpaceMousePosition - e.ScreenSpaceMouseDownPosition; + Vector2 distanceTravelled = e.ScreenSpaceMousePosition - e.ScreenSpaceMouseDownPosition; if (snapProvider != null) { var currentSelectionPointPositions = movementBlueprints.Select(m => m.ScreenSpaceSelectionPoint).ToArray(); - if (checkSnappingForNearbyObjects(distanceTraveled, movementBlueprintOriginalPositions, currentSelectionPointPositions)) + if (checkSnappingForNearbyObjects(distanceTravelled, movementBlueprintOriginalPositions, currentSelectionPointPositions)) return true; var currentEndPointPositions = movementBlueprints.Where(m => m.ScreenSpaceSelectionPoint != m.ScreenSpaceEndPoint) .Select(m => m.ScreenSpaceEndPoint) .ToArray(); - if (checkSnappingForNearbyObjects(distanceTraveled, movementBlueprintOriginalEndPositions, currentEndPointPositions)) + if (checkSnappingForNearbyObjects(distanceTravelled, movementBlueprintOriginalEndPositions, currentEndPointPositions)) return true; } @@ -507,7 +507,7 @@ namespace osu.Game.Screens.Edit.Compose.Components // item in the selection. // The final movement position, relative to movementBlueprintOriginalPosition. - Vector2 movePosition = movementBlueprintOriginalPositions.First() + distanceTraveled; + Vector2 movePosition = movementBlueprintOriginalPositions.First() + distanceTravelled; // Retrieve a snapped position. var result = snapProvider?.FindSnappedPositionAndTime(movePosition, ~SnapType.NearbyObjects); @@ -523,16 +523,16 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// Check for positional snap for every given positions. /// - /// Distance traveled since start of dragging action. + /// Distance traveled since start of dragging action. /// The position of objects before start of dragging action. /// The positions of object at the current time. /// Whether found object to snap to. - private bool checkSnappingForNearbyObjects(Vector2 distanceTraveled, Vector2[] originalPositions, Vector2[] currentPositions) + private bool checkSnappingForNearbyObjects(Vector2 distanceTravelled, Vector2[] originalPositions, Vector2[] currentPositions) { for (int i = 0; i < originalPositions.Length; i++) { Vector2 originalPosition = originalPositions[i]; - var testPosition = originalPosition + distanceTraveled; + var testPosition = originalPosition + distanceTravelled; var positionalResult = snapProvider.FindSnappedPositionAndTime(testPosition, SnapType.NearbyObjects); From e0f3fa1af61734545767fac51baa9ff7a1fb1838 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Wed, 18 Jan 2023 12:22:27 -0500 Subject: [PATCH 033/142] remove `this` in ruleset assignment --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 3ff1b69612..b5f42ec2cc 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -149,7 +149,7 @@ namespace osu.Game.Rulesets.Scoring public ScoreProcessor(Ruleset ruleset) { - this.Ruleset = ruleset; + Ruleset = ruleset; accuracyPortion = DefaultAccuracyPortion; comboPortion = DefaultComboPortion; From 150195b8878bd9ca5ed83fe71f92487e92b52403 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Wed, 18 Jan 2023 12:24:41 -0500 Subject: [PATCH 034/142] use extension method to check accuracy impact --- osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs b/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs index 02d4fb4d67..e18164db89 100644 --- a/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs +++ b/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Mods protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result) { - if (!result.Type.IsScorable() || result.Type.IsBonus()) + if (!result.Type.AffectsAccuracy()) return false; return getAccuracyWithImminentResultAdded(result) < MinimumAccuracy.Value; From ab78dd0436edd06dba957b54634b30c6ad57807c Mon Sep 17 00:00:00 2001 From: Wleter Date: Wed, 18 Jan 2023 21:34:23 +0100 Subject: [PATCH 035/142] add collection of selection points. --- .../Sliders/SliderSelectionBlueprint.cs | 8 +++- .../Edit/HitObjectSelectionBlueprint.cs | 2 - osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 8 ++-- .../Compose/Components/BlueprintContainer.cs | 41 ++++++++----------- 4 files changed, 28 insertions(+), 31 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 634897e3d5..66adcd62a6 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -409,8 +409,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders public override Vector2 ScreenSpaceSelectionPoint => DrawableObject.SliderBody?.ToScreenSpace(DrawableObject.SliderBody.PathOffset) ?? BodyPiece.ToScreenSpace(BodyPiece.PathStartLocation); - public override Vector2 ScreenSpaceEndPoint => DrawableObject.SliderBody?.ToScreenSpace(DrawableObject.SliderBody.PathEndOffset) - ?? BodyPiece.ToScreenSpace(BodyPiece.PathEndLocation); + public override Vector2[] ScreenSpaceSelectionPoints => new Vector2[] + { + ScreenSpaceSelectionPoint, + DrawableObject.SliderBody?.ToScreenSpace(DrawableObject.SliderBody.PathEndOffset) + ?? BodyPiece.ToScreenSpace(BodyPiece.PathEndLocation) + }; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => BodyPiece.ReceivePositionalInputAt(screenSpacePos) || ControlPointVisualiser?.Pieces.Any(p => p.ReceivePositionalInputAt(screenSpacePos)) == true; diff --git a/osu.Game/Rulesets/Edit/HitObjectSelectionBlueprint.cs b/osu.Game/Rulesets/Edit/HitObjectSelectionBlueprint.cs index 3aa086582f..93b889792b 100644 --- a/osu.Game/Rulesets/Edit/HitObjectSelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/HitObjectSelectionBlueprint.cs @@ -48,8 +48,6 @@ namespace osu.Game.Rulesets.Edit public override Vector2 ScreenSpaceSelectionPoint => DrawableObject.ScreenSpaceDrawQuad.Centre; - public override Vector2 ScreenSpaceEndPoint => DrawableObject.ScreenSpaceDrawQuad.Centre; - public override Quad SelectionQuad => DrawableObject.ScreenSpaceDrawQuad; } diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index a27a59be34..3b86dbbe81 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -4,6 +4,7 @@ #nullable disable using System; +using System.Collections.Generic; using osu.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -125,14 +126,15 @@ namespace osu.Game.Rulesets.Edit public virtual MenuItem[] ContextMenuItems => Array.Empty(); /// - /// The screen-space point that causes this to be selected via a drag. + /// The screen-space main point that causes this to be selected via a drag. /// public virtual Vector2 ScreenSpaceSelectionPoint => ScreenSpaceDrawQuad.Centre; /// - /// The screen-space point that mark end of this . + /// The screen-space collection of base points that cause this to be selected via a drag. + /// The first element of this collection is /// - public virtual Vector2 ScreenSpaceEndPoint => ScreenSpaceDrawQuad.Centre; + public virtual Vector2[] ScreenSpaceSelectionPoints => new Vector2[] { ScreenSpaceSelectionPoint }; /// /// The screen-space quad that outlines this for selections. diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 49e4ab5300..750b7697b8 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -439,8 +439,7 @@ namespace osu.Game.Screens.Edit.Compose.Components #region Selection Movement - private Vector2[] movementBlueprintOriginalPositions; - private Vector2[] movementBlueprintOriginalEndPositions; + private Vector2[][] movementBlueprintsOriginalPositions; private SelectionBlueprint[] movementBlueprints; private bool isDraggingBlueprint; @@ -460,9 +459,7 @@ namespace osu.Game.Screens.Edit.Compose.Components // Movement is tracked from the blueprint of the earliest item, since it only makes sense to distance snap from that item movementBlueprints = SortForMovement(SelectionHandler.SelectedBlueprints).ToArray(); - movementBlueprintOriginalPositions = movementBlueprints.Select(m => m.ScreenSpaceSelectionPoint).ToArray(); - movementBlueprintOriginalEndPositions = movementBlueprints.Where(m => m.ScreenSpaceSelectionPoint != m.ScreenSpaceEndPoint) - .Select(m => m.ScreenSpaceEndPoint).ToArray(); + movementBlueprintsOriginalPositions = movementBlueprints.Select(m => m.ScreenSpaceSelectionPoints).ToArray(); return true; } @@ -484,30 +481,24 @@ namespace osu.Game.Screens.Edit.Compose.Components if (movementBlueprints == null) return false; - Debug.Assert(movementBlueprintOriginalPositions != null); + Debug.Assert(movementBlueprintsOriginalPositions != null); Vector2 distanceTravelled = e.ScreenSpaceMousePosition - e.ScreenSpaceMouseDownPosition; if (snapProvider != null) { - var currentSelectionPointPositions = movementBlueprints.Select(m => m.ScreenSpaceSelectionPoint).ToArray(); - - if (checkSnappingForNearbyObjects(distanceTravelled, movementBlueprintOriginalPositions, currentSelectionPointPositions)) - return true; - - var currentEndPointPositions = movementBlueprints.Where(m => m.ScreenSpaceSelectionPoint != m.ScreenSpaceEndPoint) - .Select(m => m.ScreenSpaceEndPoint) - .ToArray(); - - if (checkSnappingForNearbyObjects(distanceTravelled, movementBlueprintOriginalEndPositions, currentEndPointPositions)) - return true; + for (int i = 0; i < movementBlueprints.Length; i++) + { + if (checkSnappingBlueprintToNearbyObjects(movementBlueprints[i], distanceTravelled, movementBlueprintsOriginalPositions[i])) + return true; + } } // if no positional snapping could be performed, try unrestricted snapping from the earliest // item in the selection. // The final movement position, relative to movementBlueprintOriginalPosition. - Vector2 movePosition = movementBlueprintOriginalPositions.First() + distanceTravelled; + Vector2 movePosition = movementBlueprintsOriginalPositions.First().First() + distanceTravelled; // Retrieve a snapped position. var result = snapProvider?.FindSnappedPositionAndTime(movePosition, ~SnapType.NearbyObjects); @@ -521,14 +512,16 @@ namespace osu.Game.Screens.Edit.Compose.Components } /// - /// Check for positional snap for every given positions. + /// Check for positional snap for given blueprint. /// + /// The blueprint to check for snapping /// Distance traveled since start of dragging action. - /// The position of objects before start of dragging action. - /// The positions of object at the current time. + /// The selection positions of blueprint before start of dragging action. /// Whether found object to snap to. - private bool checkSnappingForNearbyObjects(Vector2 distanceTravelled, Vector2[] originalPositions, Vector2[] currentPositions) + private bool checkSnappingBlueprintToNearbyObjects(SelectionBlueprint blueprint, Vector2 distanceTravelled, Vector2[] originalPositions) { + var currentPositions = blueprint.ScreenSpaceSelectionPoints; + for (int i = 0; i < originalPositions.Length; i++) { Vector2 originalPosition = originalPositions[i]; @@ -541,7 +534,7 @@ namespace osu.Game.Screens.Edit.Compose.Components var delta = positionalResult.ScreenSpacePosition - currentPositions[i]; // attempt to move the objects, and abort any time based snapping if we can. - if (SelectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprints[i], delta))) + if (SelectionHandler.HandleMovement(new MoveSelectionEvent(blueprint, delta))) return true; } @@ -560,7 +553,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (movementBlueprints == null) return false; - movementBlueprintOriginalPositions = null; + movementBlueprintsOriginalPositions = null; movementBlueprints = null; return true; From 40e99069fcbd31174aefbffb03209a247cce5139 Mon Sep 17 00:00:00 2001 From: Wleter Date: Wed, 18 Jan 2023 21:43:09 +0100 Subject: [PATCH 036/142] fix typos and newlines --- .../Screens/Edit/Compose/Components/BlueprintContainer.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 750b7697b8..40aa92a53f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -460,7 +460,6 @@ namespace osu.Game.Screens.Edit.Compose.Components // Movement is tracked from the blueprint of the earliest item, since it only makes sense to distance snap from that item movementBlueprints = SortForMovement(SelectionHandler.SelectedBlueprints).ToArray(); movementBlueprintsOriginalPositions = movementBlueprints.Select(m => m.ScreenSpaceSelectionPoints).ToArray(); - return true; } @@ -515,7 +514,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Check for positional snap for given blueprint. /// /// The blueprint to check for snapping - /// Distance traveled since start of dragging action. + /// Distance travelled since start of dragging action. /// The selection positions of blueprint before start of dragging action. /// Whether found object to snap to. private bool checkSnappingBlueprintToNearbyObjects(SelectionBlueprint blueprint, Vector2 distanceTravelled, Vector2[] originalPositions) @@ -537,7 +536,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (SelectionHandler.HandleMovement(new MoveSelectionEvent(blueprint, delta))) return true; } - + return false; } From f8d8a627b8d5c75d6fdb2417166be0c537afc731 Mon Sep 17 00:00:00 2001 From: Wleter Date: Wed, 18 Jan 2023 22:00:39 +0100 Subject: [PATCH 037/142] change property name --- .../Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs | 2 +- osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 2 +- .../Screens/Edit/Compose/Components/BlueprintContainer.cs | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 66adcd62a6..660976ba33 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -409,7 +409,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders public override Vector2 ScreenSpaceSelectionPoint => DrawableObject.SliderBody?.ToScreenSpace(DrawableObject.SliderBody.PathOffset) ?? BodyPiece.ToScreenSpace(BodyPiece.PathStartLocation); - public override Vector2[] ScreenSpaceSelectionPoints => new Vector2[] + public override Vector2[] ScreenSpaceSnapPoints => new Vector2[] { ScreenSpaceSelectionPoint, DrawableObject.SliderBody?.ToScreenSpace(DrawableObject.SliderBody.PathEndOffset) diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index 3b86dbbe81..b2f2fa8a9b 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -134,7 +134,7 @@ namespace osu.Game.Rulesets.Edit /// The screen-space collection of base points that cause this to be selected via a drag. /// The first element of this collection is /// - public virtual Vector2[] ScreenSpaceSelectionPoints => new Vector2[] { ScreenSpaceSelectionPoint }; + public virtual Vector2[] ScreenSpaceSnapPoints => new Vector2[] { ScreenSpaceSelectionPoint }; /// /// The screen-space quad that outlines this for selections. diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 40aa92a53f..8cb5a1face 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -459,7 +459,7 @@ namespace osu.Game.Screens.Edit.Compose.Components // Movement is tracked from the blueprint of the earliest item, since it only makes sense to distance snap from that item movementBlueprints = SortForMovement(SelectionHandler.SelectedBlueprints).ToArray(); - movementBlueprintsOriginalPositions = movementBlueprints.Select(m => m.ScreenSpaceSelectionPoints).ToArray(); + movementBlueprintsOriginalPositions = movementBlueprints.Select(m => m.ScreenSpaceSnapPoints).ToArray(); return true; } @@ -519,7 +519,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Whether found object to snap to. private bool checkSnappingBlueprintToNearbyObjects(SelectionBlueprint blueprint, Vector2 distanceTravelled, Vector2[] originalPositions) { - var currentPositions = blueprint.ScreenSpaceSelectionPoints; + var currentPositions = blueprint.ScreenSpaceSnapPoints; for (int i = 0; i < originalPositions.Length; i++) { @@ -536,7 +536,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (SelectionHandler.HandleMovement(new MoveSelectionEvent(blueprint, delta))) return true; } - + return false; } From a1b5c9d910593cff6d7b6c903eacf632a60ec0b0 Mon Sep 17 00:00:00 2001 From: naoey Date: Sun, 22 Jan 2023 01:10:14 +0900 Subject: [PATCH 038/142] Don't transfer MD5 hashes in collections when copying beatmaps Fixes #22306. Changes beatmap saving so that by default it does not transfer the hashes in collections, and only transfers them when saving the same difficulty in the editor. Issue seems to have been introduced in https://github.com/ppy/osu/pull/20641. --- .../Editing/TestSceneEditorBeatmapCreation.cs | 57 ++++++++++++++++++- osu.Game/Beatmaps/BeatmapManager.cs | 11 +++- osu.Game/Screens/Edit/Editor.cs | 2 +- osu.Game/Tests/Visual/EditorTestScene.cs | 2 +- 4 files changed, 66 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 3f89bf9e9c..5aa2dd2ebf 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -13,6 +13,7 @@ using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Collections; using osu.Game.Database; using osu.Game.Overlays.Dialog; using osu.Game.Rulesets; @@ -42,6 +43,9 @@ namespace osu.Game.Tests.Visual.Editing [Resolved] private BeatmapManager beatmapManager { get; set; } = null!; + [Resolved] + private RealmAccess realm { get; set; } = null!; + private Guid currentBeatmapSetID => EditorBeatmap.BeatmapInfo.BeatmapSet?.ID ?? Guid.Empty; public override void SetUpSteps() @@ -224,7 +228,8 @@ namespace osu.Game.Tests.Visual.Editing return beatmap != null && beatmap.DifficultyName == secondDifficultyName && set != null - && set.PerformRead(s => s.Beatmaps.Count == 2 && s.Beatmaps.Any(b => b.DifficultyName == secondDifficultyName) && s.Beatmaps.All(b => s.Status == BeatmapOnlineStatus.LocallyModified)); + && set.PerformRead(s => + s.Beatmaps.Count == 2 && s.Beatmaps.Any(b => b.DifficultyName == secondDifficultyName) && s.Beatmaps.All(b => s.Status == BeatmapOnlineStatus.LocallyModified)); }); } @@ -327,6 +332,56 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("old beatmap file not deleted", () => refetchedBeatmapSet.AsNonNull().PerformRead(s => s.Files.Count == 2)); } + [Test] + public void TestCopyDifficultyDoesNotChangeCollections() + { + string originalDifficultyName = Guid.NewGuid().ToString(); + + AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = originalDifficultyName); + AddStep("save beatmap", () => Editor.Save()); + + string originalMd5 = string.Empty; + BeatmapCollection collection = null!; + + AddStep("setup a collection with original beatmap", () => + { + collection = new BeatmapCollection("test copy"); + collection.BeatmapMD5Hashes.Add(originalMd5 = EditorBeatmap.BeatmapInfo.MD5Hash); + + realm.Write(r => + { + r.Add(collection); + }); + }); + + AddAssert("collection contains original beatmap", () => + !string.IsNullOrEmpty(originalMd5) && collection.BeatmapMD5Hashes.Contains(originalMd5)); + + AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); + + AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); + AddStep("confirm creation as a copy", () => DialogOverlay.CurrentDialog.Buttons.ElementAt(1).TriggerClick()); + + AddUntilStep("wait for created", () => + { + string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; + return difficultyName != null && difficultyName != originalDifficultyName; + }); + + AddStep("save without changes", () => Editor.Save()); + + AddAssert("collection still points to old beatmap", () => !collection.BeatmapMD5Hashes.Contains(EditorBeatmap.BeatmapInfo.MD5Hash) + && collection.BeatmapMD5Hashes.Contains(originalMd5)); + + AddStep("clean up collection", () => + { + realm.Write(r => + { + r.Remove(collection); + }); + }); + } + [Test] public void TestCreateMultipleNewDifficultiesSucceeds() { diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 34637501fa..db9c450bbe 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -186,7 +186,10 @@ namespace osu.Game.Beatmaps targetBeatmapSet.Beatmaps.Add(newBeatmap.BeatmapInfo); newBeatmap.BeatmapInfo.BeatmapSet = targetBeatmapSet; - Save(newBeatmap.BeatmapInfo, newBeatmap, beatmapSkin); + // make sure that collections don't get transferred when adding new difficulties to a set (that function + // was added for the scenario of saving the same difficulty), since this path is invoked from copying + // an existing difficulty as well. + Save(newBeatmap.BeatmapInfo, newBeatmap, beatmapSkin, false); workingBeatmapCache.Invalidate(targetBeatmapSet); return GetWorkingBeatmap(newBeatmap.BeatmapInfo); @@ -285,7 +288,8 @@ namespace osu.Game.Beatmaps /// The to save the content against. The file referenced by will be replaced. /// The content to write. /// The beatmap content to write, null if to be omitted. - public virtual void Save(BeatmapInfo beatmapInfo, IBeatmap beatmapContent, ISkin? beatmapSkin = null) + /// Whether to transfer the MD5 hashes in collections referencing this beatmap. + public virtual void Save(BeatmapInfo beatmapInfo, IBeatmap beatmapContent, ISkin? beatmapSkin = null, bool transferCollections = false) { var setInfo = beatmapInfo.BeatmapSet; Debug.Assert(setInfo != null); @@ -337,7 +341,8 @@ namespace osu.Game.Beatmaps setInfo.CopyChangesToRealm(liveBeatmapSet); - beatmapInfo.TransferCollectionReferences(r, oldMd5Hash); + if (transferCollections) + beatmapInfo.TransferCollectionReferences(r, oldMd5Hash); ProcessBeatmap?.Invoke((liveBeatmapSet, false)); }); diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 74ea933255..7161f34df2 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -429,7 +429,7 @@ namespace osu.Game.Screens.Edit try { // save the loaded beatmap's data stream. - beatmapManager.Save(editorBeatmap.BeatmapInfo, editorBeatmap.PlayableBeatmap, editorBeatmap.BeatmapSkin); + beatmapManager.Save(editorBeatmap.BeatmapInfo, editorBeatmap.PlayableBeatmap, editorBeatmap.BeatmapSkin, true); } catch (Exception ex) { diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index 6e2f1e99cd..e9d186fe35 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -178,7 +178,7 @@ namespace osu.Game.Tests.Visual => testBeatmapManager.TestBeatmap; } - public override void Save(BeatmapInfo info, IBeatmap beatmapContent, ISkin beatmapSkin = null) + public override void Save(BeatmapInfo info, IBeatmap beatmapContent, ISkin beatmapSkin = null, bool transferCollections = false) { // don't actually care about saving for this context. } From d4e5d7a8735bcb41792f77edf5a2a4fbdcda5c0c Mon Sep 17 00:00:00 2001 From: sw1tchbl4d3 Date: Sat, 21 Jan 2023 22:29:28 +0100 Subject: [PATCH 039/142] Move fixed scroll speed change out of classic mod for taiko --- .../Mods/TaikoModClassic.cs | 20 ++----------------- .../UI/DrawableTaikoRuleset.cs | 14 ++++++++++++- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs index 3b3b3e606c..2ccdfd40e5 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.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.Diagnostics; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.UI; @@ -9,30 +8,15 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Taiko.Mods { - public class TaikoModClassic : ModClassic, IApplicableToDrawableRuleset, IUpdatableByPlayfield + public class TaikoModClassic : ModClassic, IApplicableToDrawableRuleset { - private DrawableTaikoRuleset? drawableTaikoRuleset; - public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - drawableTaikoRuleset = (DrawableTaikoRuleset)drawableRuleset; + var drawableTaikoRuleset = (DrawableTaikoRuleset)drawableRuleset; drawableTaikoRuleset.LockPlayfieldMaxAspect.Value = false; var playfield = (TaikoPlayfield)drawableRuleset.Playfield; playfield.ClassicHitTargetPosition.Value = true; } - - public void Update(Playfield playfield) - { - Debug.Assert(drawableTaikoRuleset != null); - - // Classic taiko scrolls at a constant 100px per 1000ms. More notes become visible as the playfield is lengthened. - const float scroll_rate = 10; - - // Since the time range will depend on a positional value, it is referenced to the x480 pixel space. - float ratio = drawableTaikoRuleset.DrawHeight / 480; - - drawableTaikoRuleset.TimeRange.Value = (playfield.HitObjectContainer.DrawWidth / ratio) * scroll_rate; - } } } diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index 146daa8c27..40203440c5 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -43,7 +43,6 @@ namespace osu.Game.Rulesets.Taiko.UI : base(ruleset, beatmap, mods) { Direction.Value = ScrollingDirection.Left; - TimeRange.Value = 7000; } [BackgroundDependencyLoader] @@ -60,6 +59,19 @@ namespace osu.Game.Rulesets.Taiko.UI KeyBindingInputManager.Add(new DrumTouchInputArea()); } + protected override void Update() + { + base.Update(); + + // Taiko scrolls at a constant 100px per 1000ms. More notes become visible as the playfield is lengthened. + const float scroll_rate = 10; + + // Since the time range will depend on a positional value, it is referenced to the x480 pixel space. + float ratio = DrawHeight / 480; + + TimeRange.Value = (Playfield.HitObjectContainer.DrawWidth / ratio) * scroll_rate; + } + protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); From 718cbf9382a20351dc4fc127b91e63f1331dd19d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 21 Jan 2023 23:19:34 +0100 Subject: [PATCH 040/142] Fix `SegmentedGraph` draw node calculating segment colours in unsafe manner The `SegmentedGraph`'s draw node would call `getSegmentColour()` on the drawable, which would query the `DrawColourInfo` and `tierColours` properties of the drawable. This is a cross-thread access and as such completely unsafe, as due to being cross-thread it can die on invalidations or out-of-bounds accesses. Fix by transferring everything to the draw node first before attempting to draw. `SegmentedGraph.TierColours` setter already correctly invalidates the draw node via `graphNeedsUpdate`, so no further intervention was required there. Closes #22326. --- .../Graphics/UserInterface/SegmentedGraph.cs | 39 +++++++++++-------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/SegmentedGraph.cs b/osu.Game/Graphics/UserInterface/SegmentedGraph.cs index 6ca0c588e8..e8817d5275 100644 --- a/osu.Game/Graphics/UserInterface/SegmentedGraph.cs +++ b/osu.Game/Graphics/UserInterface/SegmentedGraph.cs @@ -152,22 +152,6 @@ namespace osu.Game.Graphics.UserInterface segments.Sort(); } - private ColourInfo getSegmentColour(SegmentInfo segment) - { - var segmentColour = new ColourInfo - { - TopLeft = DrawColourInfo.Colour.Interpolate(new Vector2(segment.Start, 0f)), - TopRight = DrawColourInfo.Colour.Interpolate(new Vector2(segment.End, 0f)), - BottomLeft = DrawColourInfo.Colour.Interpolate(new Vector2(segment.Start, 1f)), - BottomRight = DrawColourInfo.Colour.Interpolate(new Vector2(segment.End, 1f)) - }; - - var tierColour = segment.Tier >= 0 ? tierColours[segment.Tier] : new Colour4(0, 0, 0, 0); - segmentColour.ApplyChild(tierColour); - - return segmentColour; - } - protected override DrawNode CreateDrawNode() => new SegmentedGraphDrawNode(this); protected struct SegmentInfo @@ -215,6 +199,7 @@ namespace osu.Game.Graphics.UserInterface private IShader shader = null!; private readonly List segments = new List(); private Vector2 drawSize; + private readonly List tierColours = new List(); public SegmentedGraphDrawNode(SegmentedGraph source) : base(source) @@ -228,8 +213,12 @@ namespace osu.Game.Graphics.UserInterface texture = Source.texture; shader = Source.shader; drawSize = Source.DrawSize; + segments.Clear(); segments.AddRange(Source.segments.Where(s => s.Length * drawSize.X > 1)); + + tierColours.Clear(); + tierColours.AddRange(Source.tierColours); } public override void Draw(IRenderer renderer) @@ -252,11 +241,27 @@ namespace osu.Game.Graphics.UserInterface Vector2Extensions.Transform(topRight, DrawInfo.Matrix), Vector2Extensions.Transform(bottomLeft, DrawInfo.Matrix), Vector2Extensions.Transform(bottomRight, DrawInfo.Matrix)), - Source.getSegmentColour(segment)); + getSegmentColour(segment)); } shader.Unbind(); } + + private ColourInfo getSegmentColour(SegmentInfo segment) + { + var segmentColour = new ColourInfo + { + TopLeft = DrawColourInfo.Colour.Interpolate(new Vector2(segment.Start, 0f)), + TopRight = DrawColourInfo.Colour.Interpolate(new Vector2(segment.End, 0f)), + BottomLeft = DrawColourInfo.Colour.Interpolate(new Vector2(segment.Start, 1f)), + BottomRight = DrawColourInfo.Colour.Interpolate(new Vector2(segment.End, 1f)) + }; + + var tierColour = segment.Tier >= 0 ? tierColours[segment.Tier] : new Colour4(0, 0, 0, 0); + segmentColour.ApplyChild(tierColour); + + return segmentColour; + } } protected class SegmentManager : IEnumerable From 1c1c9915bb259d0021cd3e597609f714004594f8 Mon Sep 17 00:00:00 2001 From: naoey Date: Sun, 22 Jan 2023 10:27:33 +0900 Subject: [PATCH 041/142] Split saving new and existing beatmaps into separate flows --- .../Beatmaps/WorkingBeatmapManagerTest.cs | 2 +- osu.Game/Beatmaps/BeatmapManager.cs | 148 ++++++++++-------- osu.Game/Screens/Edit/Editor.cs | 5 +- osu.Game/Tests/Visual/EditorTestScene.cs | 7 +- 4 files changed, 91 insertions(+), 71 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/WorkingBeatmapManagerTest.cs b/osu.Game.Tests/Beatmaps/WorkingBeatmapManagerTest.cs index 89b8c8927d..4cd866392b 100644 --- a/osu.Game.Tests/Beatmaps/WorkingBeatmapManagerTest.cs +++ b/osu.Game.Tests/Beatmaps/WorkingBeatmapManagerTest.cs @@ -124,7 +124,7 @@ namespace osu.Game.Tests.Beatmaps Assert.That(preserveCollection.BeatmapMD5Hashes, Does.Contain(initialHash)); Assert.That(noNewCollection.BeatmapMD5Hashes, Does.Not.Contain(initialHash)); - beatmaps.Save(working.BeatmapInfo, working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo)); + beatmaps.SaveExistingBeatmap(working.BeatmapInfo, working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo)); string finalHash = working.BeatmapInfo.MD5Hash; diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index db9c450bbe..d644031fe1 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -186,10 +186,7 @@ namespace osu.Game.Beatmaps targetBeatmapSet.Beatmaps.Add(newBeatmap.BeatmapInfo); newBeatmap.BeatmapInfo.BeatmapSet = targetBeatmapSet; - // make sure that collections don't get transferred when adding new difficulties to a set (that function - // was added for the scenario of saving the same difficulty), since this path is invoked from copying - // an existing difficulty as well. - Save(newBeatmap.BeatmapInfo, newBeatmap, beatmapSkin, false); + SaveNewBeatmap(newBeatmap.BeatmapInfo, newBeatmap, beatmapSkin); workingBeatmapCache.Invalidate(targetBeatmapSet); return GetWorkingBeatmap(newBeatmap.BeatmapInfo); @@ -283,78 +280,30 @@ namespace osu.Game.Beatmaps public IWorkingBeatmap DefaultBeatmap => workingBeatmapCache.DefaultBeatmap; /// - /// Saves an file against a given . + /// Saves an existing file against a given , also transferring the beatmap + /// hashes in any collections referencing it. /// /// The to save the content against. The file referenced by will be replaced. /// The content to write. /// The beatmap content to write, null if to be omitted. - /// Whether to transfer the MD5 hashes in collections referencing this beatmap. - public virtual void Save(BeatmapInfo beatmapInfo, IBeatmap beatmapContent, ISkin? beatmapSkin = null, bool transferCollections = false) + public virtual void SaveExistingBeatmap(BeatmapInfo beatmapInfo, IBeatmap beatmapContent, ISkin? beatmapSkin = null) { - var setInfo = beatmapInfo.BeatmapSet; - Debug.Assert(setInfo != null); + string oldMd5Hash = beatmapInfo.MD5Hash; - // Difficulty settings must be copied first due to the clone in `Beatmap<>.BeatmapInfo_Set`. - // This should hopefully be temporary, assuming said clone is eventually removed. + save(beatmapInfo, beatmapContent, beatmapSkin); - // Warning: The directionality here is important. Changes have to be copied *from* beatmapContent (which comes from editor and is being saved) - // *to* the beatmapInfo (which is a database model and needs to receive values without the taiko slider velocity multiplier for correct operation). - // CopyTo() will undo such adjustments, while CopyFrom() will not. - beatmapContent.Difficulty.CopyTo(beatmapInfo.Difficulty); + Realm.Write(r => beatmapInfo.TransferCollectionReferences(r, oldMd5Hash)); + } - // All changes to metadata are made in the provided beatmapInfo, so this should be copied to the `IBeatmap` before encoding. - beatmapContent.BeatmapInfo = beatmapInfo; - - using (var stream = new MemoryStream()) - { - using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) - new LegacyBeatmapEncoder(beatmapContent, beatmapSkin).Encode(sw); - - stream.Seek(0, SeekOrigin.Begin); - - // AddFile generally handles updating/replacing files, but this is a case where the filename may have also changed so let's delete for simplicity. - var existingFileInfo = beatmapInfo.Path != null ? setInfo.GetFile(beatmapInfo.Path) : null; - string targetFilename = createBeatmapFilenameFromMetadata(beatmapInfo); - - // ensure that two difficulties from the set don't point at the same beatmap file. - if (setInfo.Beatmaps.Any(b => b.ID != beatmapInfo.ID && string.Equals(b.Path, targetFilename, StringComparison.OrdinalIgnoreCase))) - throw new InvalidOperationException($"{setInfo.GetDisplayString()} already has a difficulty with the name of '{beatmapInfo.DifficultyName}'."); - - if (existingFileInfo != null) - DeleteFile(setInfo, existingFileInfo); - - string oldMd5Hash = beatmapInfo.MD5Hash; - - beatmapInfo.MD5Hash = stream.ComputeMD5Hash(); - beatmapInfo.Hash = stream.ComputeSHA2Hash(); - - beatmapInfo.LastLocalUpdate = DateTimeOffset.Now; - beatmapInfo.Status = BeatmapOnlineStatus.LocallyModified; - - AddFile(setInfo, stream, createBeatmapFilenameFromMetadata(beatmapInfo)); - - updateHashAndMarkDirty(setInfo); - - Realm.Write(r => - { - var liveBeatmapSet = r.Find(setInfo.ID); - - setInfo.CopyChangesToRealm(liveBeatmapSet); - - if (transferCollections) - beatmapInfo.TransferCollectionReferences(r, oldMd5Hash); - - ProcessBeatmap?.Invoke((liveBeatmapSet, false)); - }); - } - - Debug.Assert(beatmapInfo.BeatmapSet != null); - - static string createBeatmapFilenameFromMetadata(BeatmapInfo beatmapInfo) - { - var metadata = beatmapInfo.Metadata; - return $"{metadata.Artist} - {metadata.Title} ({metadata.Author.Username}) [{beatmapInfo.DifficultyName}].osu".GetValidFilename(); - } + /// + /// Saves a new file against a given . + /// + /// The to save the content against. The file referenced by will be replaced. + /// The content to write. + /// The beatmap content to write, null if to be omitted. + public virtual void SaveNewBeatmap(BeatmapInfo beatmapInfo, IBeatmap beatmapContent, ISkin? beatmapSkin = null) + { + save(beatmapInfo, beatmapContent, beatmapSkin); } public void DeleteAllVideos() @@ -465,6 +414,69 @@ namespace osu.Game.Beatmaps setInfo.Status = BeatmapOnlineStatus.LocallyModified; } + private void save(BeatmapInfo beatmapInfo, IBeatmap beatmapContent, ISkin? beatmapSkin) + { + var setInfo = beatmapInfo.BeatmapSet; + Debug.Assert(setInfo != null); + + // Difficulty settings must be copied first due to the clone in `Beatmap<>.BeatmapInfo_Set`. + // This should hopefully be temporary, assuming said clone is eventually removed. + + // Warning: The directionality here is important. Changes have to be copied *from* beatmapContent (which comes from editor and is being saved) + // *to* the beatmapInfo (which is a database model and needs to receive values without the taiko slider velocity multiplier for correct operation). + // CopyTo() will undo such adjustments, while CopyFrom() will not. + beatmapContent.Difficulty.CopyTo(beatmapInfo.Difficulty); + + // All changes to metadata are made in the provided beatmapInfo, so this should be copied to the `IBeatmap` before encoding. + beatmapContent.BeatmapInfo = beatmapInfo; + + using (var stream = new MemoryStream()) + { + using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) + new LegacyBeatmapEncoder(beatmapContent, beatmapSkin).Encode(sw); + + stream.Seek(0, SeekOrigin.Begin); + + // AddFile generally handles updating/replacing files, but this is a case where the filename may have also changed so let's delete for simplicity. + var existingFileInfo = beatmapInfo.Path != null ? setInfo.GetFile(beatmapInfo.Path) : null; + string targetFilename = createBeatmapFilenameFromMetadata(beatmapInfo); + + // ensure that two difficulties from the set don't point at the same beatmap file. + if (setInfo.Beatmaps.Any(b => b.ID != beatmapInfo.ID && string.Equals(b.Path, targetFilename, StringComparison.OrdinalIgnoreCase))) + throw new InvalidOperationException($"{setInfo.GetDisplayString()} already has a difficulty with the name of '{beatmapInfo.DifficultyName}'."); + + if (existingFileInfo != null) + DeleteFile(setInfo, existingFileInfo); + + beatmapInfo.MD5Hash = stream.ComputeMD5Hash(); + beatmapInfo.Hash = stream.ComputeSHA2Hash(); + + beatmapInfo.LastLocalUpdate = DateTimeOffset.Now; + beatmapInfo.Status = BeatmapOnlineStatus.LocallyModified; + + AddFile(setInfo, stream, createBeatmapFilenameFromMetadata(beatmapInfo)); + + updateHashAndMarkDirty(setInfo); + + Realm.Write(r => + { + var liveBeatmapSet = r.Find(setInfo.ID); + + setInfo.CopyChangesToRealm(liveBeatmapSet); + + ProcessBeatmap?.Invoke((liveBeatmapSet, false)); + }); + } + + Debug.Assert(beatmapInfo.BeatmapSet != null); + + static string createBeatmapFilenameFromMetadata(BeatmapInfo beatmapInfo) + { + var metadata = beatmapInfo.Metadata; + return $"{metadata.Artist} - {metadata.Title} ({metadata.Author.Username}) [{beatmapInfo.DifficultyName}].osu".GetValidFilename(); + } + } + #region Implementation of ICanAcceptFiles public Task Import(params string[] paths) => beatmapImporter.Import(paths); diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 7161f34df2..915f3d4a2a 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -429,7 +429,10 @@ namespace osu.Game.Screens.Edit try { // save the loaded beatmap's data stream. - beatmapManager.Save(editorBeatmap.BeatmapInfo, editorBeatmap.PlayableBeatmap, editorBeatmap.BeatmapSkin, true); + if (isNewBeatmap) + beatmapManager.SaveNewBeatmap(editorBeatmap.BeatmapInfo, editorBeatmap.PlayableBeatmap, editorBeatmap.BeatmapSkin); + else + beatmapManager.SaveExistingBeatmap(editorBeatmap.BeatmapInfo, editorBeatmap.PlayableBeatmap, editorBeatmap.BeatmapSkin); } catch (Exception ex) { diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index e9d186fe35..8834911ff5 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -178,7 +178,12 @@ namespace osu.Game.Tests.Visual => testBeatmapManager.TestBeatmap; } - public override void Save(BeatmapInfo info, IBeatmap beatmapContent, ISkin beatmapSkin = null, bool transferCollections = false) + public override void SaveExistingBeatmap(BeatmapInfo info, IBeatmap beatmapContent, ISkin beatmapSkin = null) + { + // don't actually care about saving for this context. + } + + public override void SaveNewBeatmap(BeatmapInfo info, IBeatmap beatmapContent, ISkin beatmapSkin = null) { // don't actually care about saving for this context. } From da03abc81235b04718fd64211b3b344ffe7dab77 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 21 Jan 2023 17:08:42 -0800 Subject: [PATCH 042/142] Fix comment editor text boxes not having sound feedback --- osu.Game.Tests/Visual/Online/TestSceneCommentActions.cs | 2 +- osu.Game/Graphics/UserInterface/OsuTextBox.cs | 7 +++++-- osu.Game/Overlays/Comments/CommentEditor.cs | 8 +------- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneCommentActions.cs b/osu.Game.Tests/Visual/Online/TestSceneCommentActions.cs index 124d0c239a..dbf3b52572 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneCommentActions.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneCommentActions.cs @@ -262,7 +262,7 @@ namespace osu.Game.Tests.Visual.Online AddAssert("Nothing happened", () => this.ChildrenOfType().Any()); AddStep("Set report data", () => { - var field = this.ChildrenOfType().Single(); + var field = this.ChildrenOfType().Single().ChildrenOfType().Single(); field.Current.Value = report_text; var reason = this.ChildrenOfType>().Single(); reason.Current.Value = CommentReportReason.Other; diff --git a/osu.Game/Graphics/UserInterface/OsuTextBox.cs b/osu.Game/Graphics/UserInterface/OsuTextBox.cs index a65204e6b3..99803e2956 100644 --- a/osu.Game/Graphics/UserInterface/OsuTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTextBox.cs @@ -250,13 +250,16 @@ namespace osu.Game.Graphics.UserInterface protected override void OnFocus(FocusEvent e) { - BorderThickness = 3; + if (Masking) + BorderThickness = 3; + base.OnFocus(e); } protected override void OnFocusLost(FocusLostEvent e) { - BorderThickness = 0; + if (Masking) + BorderThickness = 0; base.OnFocusLost(e); } diff --git a/osu.Game/Overlays/Comments/CommentEditor.cs b/osu.Game/Overlays/Comments/CommentEditor.cs index ff417a2e15..2af7dd3093 100644 --- a/osu.Game/Overlays/Comments/CommentEditor.cs +++ b/osu.Game/Overlays/Comments/CommentEditor.cs @@ -147,7 +147,7 @@ namespace osu.Game.Overlays.Comments private void updateCommitButtonState() => commitButton.Enabled.Value = loadingSpinner.State.Value == Visibility.Hidden && !string.IsNullOrEmpty(Current.Value); - private partial class EditorTextBox : BasicTextBox + private partial class EditorTextBox : OsuTextBox { protected override float LeftRightPadding => side_padding; @@ -173,12 +173,6 @@ namespace osu.Game.Overlays.Comments { Font = OsuFont.GetFont(weight: FontWeight.Regular), }; - - protected override Drawable GetDrawableCharacter(char c) => new FallingDownContainer - { - AutoSizeAxes = Axes.Both, - Child = new OsuSpriteText { Text = c.ToString(), Font = OsuFont.GetFont(size: CalculatedTextSize) } - }; } protected partial class EditorButton : RoundedButton From a746cbc6ed471455583f1df5659014fd98214e4e Mon Sep 17 00:00:00 2001 From: Matheus Filipe dos Santos Reinert Date: Sun, 22 Jan 2023 01:14:33 -0300 Subject: [PATCH 043/142] Fix failSample still playing after player left FailOverlay --- osu.Game/Screens/Play/FailAnimation.cs | 8 +++++++- osu.Game/Screens/Play/Player.cs | 3 +++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/FailAnimation.cs b/osu.Game/Screens/Play/FailAnimation.cs index 3f21f811c1..1eb1e69581 100644 --- a/osu.Game/Screens/Play/FailAnimation.cs +++ b/osu.Game/Screens/Play/FailAnimation.cs @@ -51,6 +51,7 @@ namespace osu.Game.Screens.Play private const float duration = 2500; private ISample? failSample; + private SampleChannel? failSampleChannel; [Resolved] private OsuConfigManager config { get; set; } = null!; @@ -125,7 +126,7 @@ namespace osu.Game.Screens.Play failHighPassFilter.CutoffTo(300); failLowPassFilter.CutoffTo(300, duration, Easing.OutCubic); - failSample?.Play(); + failSampleChannel = failSample?.Play(); track.AddAdjustment(AdjustableProperty.Frequency, trackFreq); track.AddAdjustment(AdjustableProperty.Volume, volumeAdjustment); @@ -153,6 +154,11 @@ namespace osu.Game.Screens.Play Background?.FadeColour(OsuColour.Gray(0.3f), 60); } + public void StopSample() + { + failSampleChannel?.Stop(); + } + public void RemoveFilters(bool resetTrackFrequency = true) { filtersRemoved = true; diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 77c16718f5..d7ac91fcea 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -612,6 +612,8 @@ namespace osu.Game.Screens.Play // if an exit has been requested, cancel any pending completion (the user has shown intention to exit). resultsDisplayDelegate?.Cancel(); + failAnimationLayer.StopSample(); + // The actual exit is performed if // - the pause / fail dialog was not requested // - the pause / fail dialog was requested but is already displayed (user showing intention to exit). @@ -678,6 +680,7 @@ namespace osu.Game.Screens.Play sampleRestart?.Play(); RestartRequested?.Invoke(quickRestart); + failAnimationLayer.StopSample(); PerformExit(false); } From 05f77d2cabfa19201b98ee26f911261bf916642a Mon Sep 17 00:00:00 2001 From: EXtremeExploit Date: Sun, 22 Jan 2023 01:36:53 -0300 Subject: [PATCH 044/142] Add modes text to groups tooltip --- .../Visual/Online/TestSceneUserProfileOverlay.cs | 1 + .../Overlays/Profile/Header/Components/GroupBadge.cs | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs index f7e88e4437..fc8c2c0b6e 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs @@ -89,6 +89,7 @@ namespace osu.Game.Tests.Visual.Online Groups = new[] { new APIUserGroup { Colour = "#EB47D0", ShortName = "DEV", Name = "Developers" }, + new APIUserGroup { Colour = "#A347EB", ShortName = "BN", Name = "Beatmap Nominators", Playmodes = new[] { "mania" } }, new APIUserGroup { Colour = "#A347EB", ShortName = "BN", Name = "Beatmap Nominators", Playmodes = new[] { "osu", "taiko" } } }, ProfileOrder = new[] diff --git a/osu.Game/Overlays/Profile/Header/Components/GroupBadge.cs b/osu.Game/Overlays/Profile/Header/Components/GroupBadge.cs index e62f1cebf0..654d2e9ef4 100644 --- a/osu.Game/Overlays/Profile/Header/Components/GroupBadge.cs +++ b/osu.Game/Overlays/Profile/Header/Components/GroupBadge.cs @@ -20,7 +20,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { public partial class GroupBadge : Container, IHasTooltip { - public LocalisableString TooltipText { get; } + public LocalisableString TooltipText { get; set; } public int TextSize { get; set; } = 12; @@ -34,7 +34,9 @@ namespace osu.Game.Overlays.Profile.Header.Components Masking = true; CornerRadius = 8; + TooltipText = group.Name; + } [BackgroundDependencyLoader] @@ -79,6 +81,14 @@ namespace osu.Game.Overlays.Profile.Header.Components })).ToList() ); } + + if (group.Playmodes?.Length > 0) + { + var badgeModesList = group.Playmodes.Select(p => rulesets.GetRuleset(p)?.Name).ToList(); + + string modesDisplay = string.Join(", ", badgeModesList); + this.TooltipText += $" ({modesDisplay})"; + } } } } From 8c208da3242d44c3407ff1d17e4830e3148152de Mon Sep 17 00:00:00 2001 From: EXtremeExploit Date: Sun, 22 Jan 2023 01:39:01 -0300 Subject: [PATCH 045/142] Cleanup --- osu.Game/Overlays/Profile/Header/Components/GroupBadge.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/GroupBadge.cs b/osu.Game/Overlays/Profile/Header/Components/GroupBadge.cs index 654d2e9ef4..9063bc82ec 100644 --- a/osu.Game/Overlays/Profile/Header/Components/GroupBadge.cs +++ b/osu.Game/Overlays/Profile/Header/Components/GroupBadge.cs @@ -34,9 +34,7 @@ namespace osu.Game.Overlays.Profile.Header.Components Masking = true; CornerRadius = 8; - TooltipText = group.Name; - } [BackgroundDependencyLoader] From 2f3971b6fbf880aec0a95f18b31796c48f804a58 Mon Sep 17 00:00:00 2001 From: EXtremeExploit Date: Sun, 22 Jan 2023 01:40:00 -0300 Subject: [PATCH 046/142] Move code to already existing if block --- osu.Game/Overlays/Profile/Header/Components/GroupBadge.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/GroupBadge.cs b/osu.Game/Overlays/Profile/Header/Components/GroupBadge.cs index 9063bc82ec..b94774da26 100644 --- a/osu.Game/Overlays/Profile/Header/Components/GroupBadge.cs +++ b/osu.Game/Overlays/Profile/Header/Components/GroupBadge.cs @@ -78,10 +78,7 @@ namespace osu.Game.Overlays.Profile.Header.Components icon.Size = new Vector2(TextSize - 1); })).ToList() ); - } - if (group.Playmodes?.Length > 0) - { var badgeModesList = group.Playmodes.Select(p => rulesets.GetRuleset(p)?.Name).ToList(); string modesDisplay = string.Join(", ", badgeModesList); From 2e1ba6ef49d9944bc79e6ed04ef602720556f4a6 Mon Sep 17 00:00:00 2001 From: Matheus Filipe dos Santos Reinert Date: Sun, 22 Jan 2023 01:43:40 -0300 Subject: [PATCH 047/142] Create StopSampleAndRemoveFilters method and change RemoveFilters to private --- osu.Game/Screens/Play/FailAnimation.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/FailAnimation.cs b/osu.Game/Screens/Play/FailAnimation.cs index 1eb1e69581..b35bd9db8c 100644 --- a/osu.Game/Screens/Play/FailAnimation.cs +++ b/osu.Game/Screens/Play/FailAnimation.cs @@ -120,7 +120,7 @@ namespace osu.Game.Screens.Play this.TransformBindableTo(trackFreq, 0, duration).OnComplete(_ => { // Don't reset frequency as the pause screen may appear post transform, causing a second frequency sweep. - RemoveFilters(false); + removeFilters(false); OnComplete?.Invoke(); }); @@ -159,7 +159,13 @@ namespace osu.Game.Screens.Play failSampleChannel?.Stop(); } - public void RemoveFilters(bool resetTrackFrequency = true) + public void StopSampleAndRemoveFilters() + { + StopSample(); + removeFilters(); + } + + private void removeFilters(bool resetTrackFrequency = true) { filtersRemoved = true; From 5b1a23c697b2eb80732ee416c32febccae6a1540 Mon Sep 17 00:00:00 2001 From: Matheus Filipe dos Santos Reinert Date: Sun, 22 Jan 2023 01:44:16 -0300 Subject: [PATCH 048/142] Replace RemoveFilters call with StopSampleAndRemoveFilters --- osu.Game/Screens/Play/Player.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index d7ac91fcea..654dc2ccb6 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -612,8 +612,6 @@ namespace osu.Game.Screens.Play // if an exit has been requested, cancel any pending completion (the user has shown intention to exit). resultsDisplayDelegate?.Cancel(); - failAnimationLayer.StopSample(); - // The actual exit is performed if // - the pause / fail dialog was not requested // - the pause / fail dialog was requested but is already displayed (user showing intention to exit). @@ -1075,7 +1073,7 @@ namespace osu.Game.Screens.Play public override bool OnExiting(ScreenExitEvent e) { screenSuspension?.RemoveAndDisposeImmediately(); - failAnimationLayer?.RemoveFilters(); + failAnimationLayer?.StopSampleAndRemoveFilters(); if (LoadedBeatmapSuccessfully) { From 704074324959abb62edd912c43dbec43708f410f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 22 Jan 2023 13:47:31 +0900 Subject: [PATCH 049/142] Add search keywords for screen scaling sub-settings --- .../Overlays/Settings/Sections/Graphics/LayoutSettings.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index bad06732d0..f140c14a0b 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -133,6 +133,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics new SettingsSlider { LabelText = GraphicsSettingsStrings.HorizontalPosition, + Keywords = new[] { "screen", "scaling" }, Current = scalingPositionX, KeyboardStep = 0.01f, DisplayAsPercentage = true @@ -140,6 +141,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics new SettingsSlider { LabelText = GraphicsSettingsStrings.VerticalPosition, + Keywords = new[] { "screen", "scaling" }, Current = scalingPositionY, KeyboardStep = 0.01f, DisplayAsPercentage = true @@ -147,6 +149,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics new SettingsSlider { LabelText = GraphicsSettingsStrings.HorizontalScale, + Keywords = new[] { "screen", "scaling" }, Current = scalingSizeX, KeyboardStep = 0.01f, DisplayAsPercentage = true @@ -154,6 +157,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics new SettingsSlider { LabelText = GraphicsSettingsStrings.VerticalScale, + Keywords = new[] { "screen", "scaling" }, Current = scalingSizeY, KeyboardStep = 0.01f, DisplayAsPercentage = true From 0edfd24410ed72a826dada58021bc414fdefe543 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 22 Jan 2023 09:24:12 +0100 Subject: [PATCH 050/142] Remove unnecessary sample stop in `Restart()` It is unnecessary, as a successful restart will exit the current player screen, and `OnExiting()` has another `StopSampleAndRemoveFilters()` call, which means that in the restart flow the sample was actually getting stopped twice. Standard exit flow is fine, it only stopped the sample once. --- osu.Game/Screens/Play/Player.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 654dc2ccb6..a0b9e9352a 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -678,7 +678,6 @@ namespace osu.Game.Screens.Play sampleRestart?.Play(); RestartRequested?.Invoke(quickRestart); - failAnimationLayer.StopSample(); PerformExit(false); } From 9e4e85e3e3512022697b6b64402b371faafaa14b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 22 Jan 2023 09:26:01 +0100 Subject: [PATCH 051/142] Inline `StopSample()` into `StopSampleAndRemoveFilters()` The first method no longer has any callers except for the second one. --- osu.Game/Screens/Play/FailAnimation.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/FailAnimation.cs b/osu.Game/Screens/Play/FailAnimation.cs index b35bd9db8c..e01d668f5d 100644 --- a/osu.Game/Screens/Play/FailAnimation.cs +++ b/osu.Game/Screens/Play/FailAnimation.cs @@ -154,14 +154,9 @@ namespace osu.Game.Screens.Play Background?.FadeColour(OsuColour.Gray(0.3f), 60); } - public void StopSample() - { - failSampleChannel?.Stop(); - } - public void StopSampleAndRemoveFilters() { - StopSample(); + failSampleChannel?.Stop(); removeFilters(); } From 06aa3f779811c3b4506faa909717b6ff1cd03f30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 22 Jan 2023 09:27:06 +0100 Subject: [PATCH 052/142] Rename `Stop{SampleAndRemoveFilters -> }()` Now that just one method for stopping samples is left, let's just repurpose st as the general "stop global effects" method rather than have it there with a hyperspecific name. It also has good symmetry, as there already was a `Start()` method in the class. --- osu.Game/Screens/Play/FailAnimation.cs | 5 ++++- osu.Game/Screens/Play/Player.cs | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/FailAnimation.cs b/osu.Game/Screens/Play/FailAnimation.cs index e01d668f5d..0214d33549 100644 --- a/osu.Game/Screens/Play/FailAnimation.cs +++ b/osu.Game/Screens/Play/FailAnimation.cs @@ -154,7 +154,10 @@ namespace osu.Game.Screens.Play Background?.FadeColour(OsuColour.Gray(0.3f), 60); } - public void StopSampleAndRemoveFilters() + /// + /// Stops any and all persistent effects added by the ongoing fail animation. + /// + public void Stop() { failSampleChannel?.Stop(); removeFilters(); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index a0b9e9352a..0d208e6d9b 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -1072,7 +1072,7 @@ namespace osu.Game.Screens.Play public override bool OnExiting(ScreenExitEvent e) { screenSuspension?.RemoveAndDisposeImmediately(); - failAnimationLayer?.StopSampleAndRemoveFilters(); + failAnimationLayer?.Stop(); if (LoadedBeatmapSuccessfully) { From b98da506c1a15c87be7961b7879e719f6b87a24f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 22 Jan 2023 10:07:47 +0100 Subject: [PATCH 053/142] Fix code quality inspection --- osu.Game/Overlays/Profile/Header/Components/GroupBadge.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/GroupBadge.cs b/osu.Game/Overlays/Profile/Header/Components/GroupBadge.cs index b94774da26..3aa5828439 100644 --- a/osu.Game/Overlays/Profile/Header/Components/GroupBadge.cs +++ b/osu.Game/Overlays/Profile/Header/Components/GroupBadge.cs @@ -82,7 +82,7 @@ namespace osu.Game.Overlays.Profile.Header.Components var badgeModesList = group.Playmodes.Select(p => rulesets.GetRuleset(p)?.Name).ToList(); string modesDisplay = string.Join(", ", badgeModesList); - this.TooltipText += $" ({modesDisplay})"; + TooltipText += $" ({modesDisplay})"; } } } From 9bde1ef9bfde013184de39cfe06243b2e2ad0f5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 22 Jan 2023 10:09:08 +0100 Subject: [PATCH 054/142] Privatise setter --- osu.Game/Overlays/Profile/Header/Components/GroupBadge.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/GroupBadge.cs b/osu.Game/Overlays/Profile/Header/Components/GroupBadge.cs index 3aa5828439..4d6ee36254 100644 --- a/osu.Game/Overlays/Profile/Header/Components/GroupBadge.cs +++ b/osu.Game/Overlays/Profile/Header/Components/GroupBadge.cs @@ -20,7 +20,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { public partial class GroupBadge : Container, IHasTooltip { - public LocalisableString TooltipText { get; set; } + public LocalisableString TooltipText { get; private set; } public int TextSize { get; set; } = 12; From 73f083a3166e543cbada59d2cae13bfaa71c5596 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Jan 2023 14:13:46 +0900 Subject: [PATCH 055/142] Refactor how additional points are provided to avoid confusion --- .../Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs | 8 +++----- osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 9 +++++++-- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 660976ba33..b502839e22 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -409,11 +409,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders public override Vector2 ScreenSpaceSelectionPoint => DrawableObject.SliderBody?.ToScreenSpace(DrawableObject.SliderBody.PathOffset) ?? BodyPiece.ToScreenSpace(BodyPiece.PathStartLocation); - public override Vector2[] ScreenSpaceSnapPoints => new Vector2[] - { - ScreenSpaceSelectionPoint, - DrawableObject.SliderBody?.ToScreenSpace(DrawableObject.SliderBody.PathEndOffset) - ?? BodyPiece.ToScreenSpace(BodyPiece.PathEndLocation) + protected override Vector2[] ScreenSpaceAdditionalNodes => new[] + { + DrawableObject.SliderBody?.ToScreenSpace(DrawableObject.SliderBody.PathEndOffset) ?? BodyPiece.ToScreenSpace(BodyPiece.PathEndLocation) }; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index b2f2fa8a9b..d36ed02210 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -4,7 +4,7 @@ #nullable disable using System; -using System.Collections.Generic; +using System.Linq; using osu.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -130,11 +130,16 @@ namespace osu.Game.Rulesets.Edit /// public virtual Vector2 ScreenSpaceSelectionPoint => ScreenSpaceDrawQuad.Centre; + /// + /// Any points that should be used for snapping purposes in addition to . Exposed via . + /// + protected virtual Vector2[] ScreenSpaceAdditionalNodes => Array.Empty(); + /// /// The screen-space collection of base points that cause this to be selected via a drag. /// The first element of this collection is /// - public virtual Vector2[] ScreenSpaceSnapPoints => new Vector2[] { ScreenSpaceSelectionPoint }; + public Vector2[] ScreenSpaceSnapPoints => ScreenSpaceAdditionalNodes.Prepend(ScreenSpaceSelectionPoint).ToArray(); /// /// The screen-space quad that outlines this for selections. From 2ed9fe3747cc743bbf6ec1410c21ab68349edc08 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Jan 2023 15:10:26 +0900 Subject: [PATCH 056/142] Add support for externally specified keywords in `SettingsButton`s --- osu.Game/Overlays/Settings/SettingsButton.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Settings/SettingsButton.cs b/osu.Game/Overlays/Settings/SettingsButton.cs index 5091ddc2d0..a837444758 100644 --- a/osu.Game/Overlays/Settings/SettingsButton.cs +++ b/osu.Game/Overlays/Settings/SettingsButton.cs @@ -1,8 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; -using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -22,6 +22,8 @@ namespace osu.Game.Overlays.Settings public LocalisableString TooltipText { get; set; } + public IEnumerable Keywords { get; set; } = Array.Empty(); + public BindableBool CanBeShown { get; } = new BindableBool(true); IBindable IConditionalFilterable.CanBeShown => CanBeShown; @@ -30,9 +32,13 @@ namespace osu.Game.Overlays.Settings get { if (TooltipText != default) - return base.FilterTerms.Append(TooltipText); + yield return TooltipText; - return base.FilterTerms; + foreach (string s in Keywords) + yield return s; + + foreach (LocalisableString s in base.FilterTerms) + yield return s; } } } From 7ebd31d42f2ec46a210dd0a2bff7955c6ee191a2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Jan 2023 15:10:34 +0900 Subject: [PATCH 057/142] Add more keywords to settings based on feedback --- osu.Game/Overlays/Settings/Sections/Gameplay/InputSettings.cs | 1 + osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs | 1 + osu.Game/Overlays/Settings/Sections/GeneralSection.cs | 1 + osu.Game/Overlays/Settings/Sections/Input/BindingSettings.cs | 2 +- 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/InputSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/InputSettings.cs index 4d027cf8cb..9291dfe923 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/InputSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/InputSettings.cs @@ -35,6 +35,7 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay new SettingsCheckbox { LabelText = SkinSettingsStrings.GameplayCursorDuringTouch, + Keywords = new[] { @"touchscreen" }, Current = config.GetBindable(OsuSetting.GameplayCursorDuringTouch) }, }; diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs index 55be06c765..2f68b3a82f 100644 --- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs @@ -70,6 +70,7 @@ namespace osu.Game.Overlays.Settings.Sections.General Add(new SettingsButton { Text = GeneralSettingsStrings.OpenOsuFolder, + Keywords = new[] { @"logs", @"files", @"access", "directory" }, Action = () => storage.PresentExternally(), }); diff --git a/osu.Game/Overlays/Settings/Sections/GeneralSection.cs b/osu.Game/Overlays/Settings/Sections/GeneralSection.cs index d4fd78f0c8..c5274d6223 100644 --- a/osu.Game/Overlays/Settings/Sections/GeneralSection.cs +++ b/osu.Game/Overlays/Settings/Sections/GeneralSection.cs @@ -34,6 +34,7 @@ namespace osu.Game.Overlays.Settings.Sections new SettingsButton { Text = GeneralSettingsStrings.RunSetupWizard, + Keywords = new[] { @"first run", @"initial", @"getting started" }, TooltipText = FirstRunSetupOverlayStrings.FirstRunSetupDescription, Action = () => firstRunSetupOverlay?.Show(), }, diff --git a/osu.Game/Overlays/Settings/Sections/Input/BindingSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/BindingSettings.cs index dbd7949206..2b478f6af3 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/BindingSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/BindingSettings.cs @@ -15,7 +15,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input { protected override LocalisableString Header => BindingSettingsStrings.ShortcutAndGameplayBindings; - public override IEnumerable FilterTerms => base.FilterTerms.Concat(new LocalisableString[] { "keybindings" }); + public override IEnumerable FilterTerms => base.FilterTerms.Concat(new LocalisableString[] { @"keybindings", @"controls", @"keyboard", @"keys" }); public BindingSettings(KeyBindingPanel keyConfig) { From 9c2494383fda4c2a6b92205946e495355c444a8e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Jan 2023 15:43:52 +0900 Subject: [PATCH 058/142] Rename fetch method to be non-plural to match all others --- osu.Game/Graphics/OsuColour.cs | 2 +- osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index 5e2649377a..e06f6b3fd0 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -192,7 +192,7 @@ namespace osu.Game.Graphics /// Retrieves colour for a . /// See https://www.figma.com/file/YHWhp9wZ089YXgB7pe6L1k/Tier-Colours /// - public ColourInfo ForRankingTiers(RankingTier tier) + public ColourInfo ForRankingTier(RankingTier tier) { switch (tier) { diff --git a/osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs b/osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs index ddc084a600..9b4df7672d 100644 --- a/osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs +++ b/osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs @@ -91,7 +91,7 @@ namespace osu.Game.Overlays.Profile.Header.Components tier = RankingTier.Lustrous; } - return osuColour.ForRankingTiers(tier); + return osuColour.ForRankingTier(tier); } } } From 736965e0099232f64d34bdc50eaa96de3a078701 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Jan 2023 15:45:44 +0900 Subject: [PATCH 059/142] Fix test scene potentially overflowing visible region --- osu.Game.Tests/Visual/Online/TestSceneLevelBadge.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneLevelBadge.cs b/osu.Game.Tests/Visual/Online/TestSceneLevelBadge.cs index fef6dd0477..1fd54ca50f 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneLevelBadge.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneLevelBadge.cs @@ -37,16 +37,15 @@ namespace osu.Game.Tests.Visual.Online { new FillFlowContainer { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Horizontal, + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Full, Spacing = new Vector2(5), - ChildrenEnumerable = levels.Select(l => new LevelBadge + ChildrenEnumerable = levels.Select(level => new LevelBadge { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, Size = new Vector2(60), - LevelInfo = { Value = l } + LevelInfo = { Value = level } }) } }; From c4d5957ac3238009529833229ee63ac16377b1a2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Jan 2023 17:07:27 +0900 Subject: [PATCH 060/142] Add empty space tap-streaming support for osu! ruleset on touchscreen devices --- .../TestSceneTouchInput.cs | 147 +++++++++++++++++- osu.Game.Rulesets.Osu/OsuInputManager.cs | 5 + .../UI/OsuTouchInputMapper.cs | 40 ++++- 3 files changed, 186 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneTouchInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneTouchInput.cs index 10d0143351..5530bd4b99 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneTouchInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneTouchInput.cs @@ -13,7 +13,12 @@ using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Input.States; using osu.Framework.Testing; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Configuration; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Screens.Play; using osu.Game.Tests.Visual; using osuTK; @@ -33,6 +38,8 @@ namespace osu.Game.Rulesets.Osu.Tests private OsuInputManager osuInputManager = null!; + private Container mainContent = null!; + [SetUpSteps] public void SetUpSteps() { @@ -44,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.Tests { osuInputManager = new OsuInputManager(new OsuRuleset().RulesetInfo) { - Child = new Container + Child = mainContent = new Container { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -54,12 +61,14 @@ namespace osu.Game.Rulesets.Osu.Tests { Anchor = Anchor.Centre, Origin = Anchor.CentreRight, + Depth = float.MinValue, X = -100, }, rightKeyCounter = new TestActionKeyCounter(OsuAction.RightButton) { Anchor = Anchor.Centre, Origin = Anchor.CentreLeft, + Depth = float.MinValue, X = 100, } }, @@ -116,9 +125,127 @@ namespace osu.Game.Rulesets.Osu.Tests endTouch(TouchSource.Touch2); checkPosition(TouchSource.Touch2); - // note that touch1 was never ended, but becomes active for tracking again. + // note that touch1 was never ended, but is no longer valid for touch input due to touch 2 occurring. beginTouch(TouchSource.Touch1); + checkPosition(TouchSource.Touch2); + } + + [Test] + public void TestStreamInput() + { + // In this scenario, the user is tapping on the first object in a stream, + // then using one or two fingers in empty space to continue the stream. + + addHitCircleAt(TouchSource.Touch1); + beginTouch(TouchSource.Touch1); + + // The first touch is handled as normal. + assertKeyCounter(1, 0); + checkPressed(OsuAction.LeftButton); checkPosition(TouchSource.Touch1); + + // The second touch should release the first, and also act as a right button. + beginTouch(TouchSource.Touch2); + + assertKeyCounter(1, 1); + // Importantly, this is different from the simple case because an object was interacted with + // in the first touch, but not the second touch. + checkNotPressed(OsuAction.LeftButton); + checkPressed(OsuAction.RightButton); + // Also importantly, the positional part of the second touch is ignored. + checkPosition(TouchSource.Touch1); + + // In this scenario, a third touch should be allowed, and handled similarly to the second. + beginTouch(TouchSource.Touch3); + + assertKeyCounter(2, 1); + checkPressed(OsuAction.LeftButton); + checkPressed(OsuAction.RightButton); + // Position is still ignored. + checkPosition(TouchSource.Touch1); + + endTouch(TouchSource.Touch2); + + checkPressed(OsuAction.LeftButton); + checkNotPressed(OsuAction.RightButton); + // Position is still ignored. + checkPosition(TouchSource.Touch1); + + // User continues streaming + beginTouch(TouchSource.Touch2); + + assertKeyCounter(2, 2); + checkPressed(OsuAction.LeftButton); + checkPressed(OsuAction.RightButton); + // Position is still ignored. + checkPosition(TouchSource.Touch1); + + // In this mode a maximum of three touches should be supported. + // A fourth touch should result in no changes anywhere. + beginTouch(TouchSource.Touch4); + assertKeyCounter(2, 2); + checkPressed(OsuAction.LeftButton); + checkPressed(OsuAction.RightButton); + checkPosition(TouchSource.Touch1); + endTouch(TouchSource.Touch4); + } + + [Test] + public void TestNonStreamOverlappingDirectTouchesWithRelease() + { + // In this scenario, the user is tapping on three circles directly while correctly releasing the first touch. + // All three should be recognised. + + addHitCircleAt(TouchSource.Touch1); + addHitCircleAt(TouchSource.Touch2); + addHitCircleAt(TouchSource.Touch3); + + beginTouch(TouchSource.Touch1); + assertKeyCounter(1, 0); + checkPressed(OsuAction.LeftButton); + checkPosition(TouchSource.Touch1); + + beginTouch(TouchSource.Touch2); + assertKeyCounter(1, 1); + checkPressed(OsuAction.LeftButton); + checkPressed(OsuAction.RightButton); + checkPosition(TouchSource.Touch2); + + endTouch(TouchSource.Touch1); + + beginTouch(TouchSource.Touch3); + assertKeyCounter(2, 1); + checkPressed(OsuAction.LeftButton); + checkPressed(OsuAction.RightButton); + checkPosition(TouchSource.Touch3); + } + + [Test] + public void TestNonStreamOverlappingDirectTouchesWithoutRelease() + { + // In this scenario, the user is tapping on three circles directly without releasing any touches. + // The first two should be recognised, but a third should not (as the user already has two fingers down). + + addHitCircleAt(TouchSource.Touch1); + addHitCircleAt(TouchSource.Touch2); + addHitCircleAt(TouchSource.Touch3); + + beginTouch(TouchSource.Touch1); + assertKeyCounter(1, 0); + checkPressed(OsuAction.LeftButton); + checkPosition(TouchSource.Touch1); + + beginTouch(TouchSource.Touch2); + assertKeyCounter(1, 1); + checkPressed(OsuAction.LeftButton); + checkPressed(OsuAction.RightButton); + checkPosition(TouchSource.Touch2); + + beginTouch(TouchSource.Touch3); + assertKeyCounter(1, 1); + checkPressed(OsuAction.LeftButton); + checkPressed(OsuAction.RightButton); + checkPosition(TouchSource.Touch3); } [Test] @@ -263,6 +390,22 @@ namespace osu.Game.Rulesets.Osu.Tests assertKeyCounter(1, 1); } + private void addHitCircleAt(TouchSource source) + { + AddStep($"Add circle at {source}", () => + { + var hitCircle = new HitCircle(); + + hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + mainContent.Add(new DrawableHitCircle(hitCircle) + { + Clock = new FramedClock(new ManualClock()), + Position = mainContent.ToLocalSpace(getSanePositionForSource(source)), + }); + }); + } + private void beginTouch(TouchSource source, Vector2? screenSpacePosition = null) => AddStep($"Begin touch for {source}", () => InputManager.BeginTouch(new Touch(source, screenSpacePosition ??= getSanePositionForSource(source)))); diff --git a/osu.Game.Rulesets.Osu/OsuInputManager.cs b/osu.Game.Rulesets.Osu/OsuInputManager.cs index 99ae706369..465dd13362 100644 --- a/osu.Game.Rulesets.Osu/OsuInputManager.cs +++ b/osu.Game.Rulesets.Osu/OsuInputManager.cs @@ -9,8 +9,10 @@ using osu.Framework.Graphics; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Input.Bindings; +using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.UI; +using osuTK; namespace osu.Game.Rulesets.Osu { @@ -40,6 +42,9 @@ namespace osu.Game.Rulesets.Osu protected override KeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) => new OsuKeyBindingContainer(ruleset, variant, unique); + public bool CheckScreenSpaceActionPressJudgeable(Vector2 screenSpacePosition) => + NonPositionalInputQueue.OfType().Any(c => c.ReceivePositionalInputAt(screenSpacePosition)); + public OsuInputManager(RulesetInfo ruleset) : base(ruleset, 0, SimultaneousBindingMode.Unique) { diff --git a/osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs b/osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs index c75e179443..9842e24d05 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs @@ -44,6 +44,8 @@ namespace osu.Game.Rulesets.Osu.UI handleTouchMovement(e); } + private TrackedTouch? positionTrackingTouch; + protected override bool OnTouchDown(TouchDownEvent e) { OsuAction action = trackedTouches.Any(t => t.Action == OsuAction.LeftButton) @@ -53,7 +55,31 @@ namespace osu.Game.Rulesets.Osu.UI // Ignore any taps which trigger an action which is already handled. But track them for potential positional input in the future. bool shouldResultInAction = osuInputManager.AllowGameplayInputs && !mouseDisabled.Value && trackedTouches.All(t => t.Action != action); - trackedTouches.Add(new TrackedTouch(e.Touch.Source, shouldResultInAction ? action : null)); + // If we can actually accept as an action, check whether this tap was on a circle's receptor. + // This case gets special handling to allow for empty-space stream tapping. + bool isDirectCircleTouch = osuInputManager.CheckScreenSpaceActionPressJudgeable(e.ScreenSpaceTouchDownPosition); + + var trackedTouch = new TrackedTouch(e.Touch.Source, shouldResultInAction ? action : null, isDirectCircleTouch); + + if (isDirectCircleTouch) + positionTrackingTouch = trackedTouch; + else + { + // If no direct touch is registered, we always use the new (latest) touch for positional tracking. + if (positionTrackingTouch?.DirectTouch != true) + positionTrackingTouch = trackedTouch; + else + { + // If not a direct circle touch, consider whether to release the an original direct touch. + if (positionTrackingTouch.DirectTouch && positionTrackingTouch.Action is OsuAction directTouchAction) + { + osuInputManager.KeyBindingContainer.TriggerReleased(directTouchAction); + positionTrackingTouch.Action = null; + } + } + } + + trackedTouches.Add(trackedTouch); // Important to update position before triggering the pressed action. handleTouchMovement(e); @@ -67,7 +93,7 @@ namespace osu.Game.Rulesets.Osu.UI private void handleTouchMovement(TouchEvent touchEvent) { // Movement should only be tracked for the most recent touch. - if (touchEvent.Touch.Source != trackedTouches.Last().Source) + if (touchEvent.Touch.Source != positionTrackingTouch?.Source) return; if (!osuInputManager.AllowUserCursorMovement) @@ -83,6 +109,9 @@ namespace osu.Game.Rulesets.Osu.UI if (tracked.Action is OsuAction action) osuInputManager.KeyBindingContainer.TriggerReleased(action); + if (positionTrackingTouch == tracked) + positionTrackingTouch = null; + trackedTouches.Remove(tracked); base.OnTouchUp(e); @@ -92,12 +121,15 @@ namespace osu.Game.Rulesets.Osu.UI { public readonly TouchSource Source; - public readonly OsuAction? Action; + public OsuAction? Action; - public TrackedTouch(TouchSource source, OsuAction? action) + public readonly bool DirectTouch; + + public TrackedTouch(TouchSource source, OsuAction? action, bool directTouch) { Source = source; Action = action; + DirectTouch = directTouch; } } } From 238a3833e23ba829d913e73872ac6aa1b1883e80 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Jan 2023 17:28:01 +0900 Subject: [PATCH 061/142] Add test coverage of stream scenario with an initial finger down --- .../TestSceneTouchInput.cs | 50 ++++++++++++++++++- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneTouchInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneTouchInput.cs index 5530bd4b99..4d119bc2ea 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneTouchInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneTouchInput.cs @@ -148,8 +148,8 @@ namespace osu.Game.Rulesets.Osu.Tests beginTouch(TouchSource.Touch2); assertKeyCounter(1, 1); - // Importantly, this is different from the simple case because an object was interacted with - // in the first touch, but not the second touch. + // Importantly, this is different from the simple case because an object was interacted with in the first touch, but not the second touch. + // left button is automatically released. checkNotPressed(OsuAction.LeftButton); checkPressed(OsuAction.RightButton); // Also importantly, the positional part of the second touch is ignored. @@ -190,6 +190,52 @@ namespace osu.Game.Rulesets.Osu.Tests endTouch(TouchSource.Touch4); } + [Test] + public void TestStreamInputWithInitialTouchDown() + { + // In this scenario, the user is wanting to use stream input but we start with one finger still on the screen. + + addHitCircleAt(TouchSource.Touch2); + + beginTouch(TouchSource.Touch1); + assertKeyCounter(1, 0); + checkPressed(OsuAction.LeftButton); + checkPosition(TouchSource.Touch1); + + // hits circle + beginTouch(TouchSource.Touch2); + assertKeyCounter(1, 1); + checkPressed(OsuAction.LeftButton); + checkPressed(OsuAction.RightButton); + checkPosition(TouchSource.Touch2); + + endTouch(TouchSource.Touch1); + checkNotPressed(OsuAction.LeftButton); + + // stream using other two fingers while touch2 tracks + beginTouch(TouchSource.Touch1); + assertKeyCounter(2, 1); + checkPressed(OsuAction.LeftButton); + // right button is automatically released + checkNotPressed(OsuAction.RightButton); + checkPosition(TouchSource.Touch2); + + beginTouch(TouchSource.Touch3); + assertKeyCounter(2, 2); + checkPressed(OsuAction.LeftButton); + checkPressed(OsuAction.RightButton); + checkPosition(TouchSource.Touch2); + + endTouch(TouchSource.Touch1); + checkNotPressed(OsuAction.LeftButton); + + beginTouch(TouchSource.Touch1); + assertKeyCounter(3, 2); + checkPressed(OsuAction.LeftButton); + checkPressed(OsuAction.RightButton); + checkPosition(TouchSource.Touch2); + } + [Test] public void TestNonStreamOverlappingDirectTouchesWithRelease() { From b436b7b99b475348adf7914d27f1e801275c47f9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Jan 2023 17:39:41 +0900 Subject: [PATCH 062/142] Add test coverage of more streaming scenarios --- .../TestSceneTouchInput.cs | 55 ++++++++++++++++++- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneTouchInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneTouchInput.cs index 4d119bc2ea..22707d218c 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneTouchInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneTouchInput.cs @@ -191,9 +191,10 @@ namespace osu.Game.Rulesets.Osu.Tests } [Test] - public void TestStreamInputWithInitialTouchDown() + public void TestStreamInputWithInitialTouchDownLeft() { // In this scenario, the user is wanting to use stream input but we start with one finger still on the screen. + // That finger is mapped to a left action. addHitCircleAt(TouchSource.Touch2); @@ -202,7 +203,7 @@ namespace osu.Game.Rulesets.Osu.Tests checkPressed(OsuAction.LeftButton); checkPosition(TouchSource.Touch1); - // hits circle + // hits circle as right action beginTouch(TouchSource.Touch2); assertKeyCounter(1, 1); checkPressed(OsuAction.LeftButton); @@ -236,6 +237,56 @@ namespace osu.Game.Rulesets.Osu.Tests checkPosition(TouchSource.Touch2); } + [Test] + public void TestStreamInputWithInitialTouchDownRight() + { + // In this scenario, the user is wanting to use stream input but we start with one finger still on the screen. + // That finger is mapped to a right action. + + beginTouch(TouchSource.Touch1); + beginTouch(TouchSource.Touch2); + + assertKeyCounter(1, 1); + checkPressed(OsuAction.LeftButton); + checkPressed(OsuAction.RightButton); + + endTouch(TouchSource.Touch1); + + addHitCircleAt(TouchSource.Touch1); + + // hits circle as left action + beginTouch(TouchSource.Touch1); + assertKeyCounter(2, 1); + checkPressed(OsuAction.LeftButton); + checkPressed(OsuAction.RightButton); + checkPosition(TouchSource.Touch1); + + endTouch(TouchSource.Touch2); + + // stream using other two fingers while touch1 tracks + beginTouch(TouchSource.Touch2); + assertKeyCounter(2, 2); + checkPressed(OsuAction.RightButton); + // left button is automatically released + checkNotPressed(OsuAction.LeftButton); + checkPosition(TouchSource.Touch1); + + beginTouch(TouchSource.Touch3); + assertKeyCounter(3, 2); + checkPressed(OsuAction.LeftButton); + checkPressed(OsuAction.RightButton); + checkPosition(TouchSource.Touch1); + + endTouch(TouchSource.Touch2); + checkNotPressed(OsuAction.RightButton); + + beginTouch(TouchSource.Touch2); + assertKeyCounter(3, 3); + checkPressed(OsuAction.LeftButton); + checkPressed(OsuAction.RightButton); + checkPosition(TouchSource.Touch1); + } + [Test] public void TestNonStreamOverlappingDirectTouchesWithRelease() { From b9ed6a7a7c97d014f08b3d39c0906d78f640708f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Jan 2023 17:54:17 +0900 Subject: [PATCH 063/142] Add visual demonstration of streaming that runs a bit faster than other tests --- .../TestSceneTouchInput.cs | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneTouchInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneTouchInput.cs index 22707d218c..4589eb88c0 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneTouchInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneTouchInput.cs @@ -14,11 +14,13 @@ using osu.Framework.Input.Events; using osu.Framework.Input.States; using osu.Framework.Testing; using osu.Framework.Timing; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Configuration; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.UI.Cursor; using osu.Game.Screens.Play; using osu.Game.Tests.Visual; using osuTK; @@ -70,6 +72,10 @@ namespace osu.Game.Rulesets.Osu.Tests Origin = Anchor.CentreLeft, Depth = float.MinValue, X = 100, + }, + new OsuCursorContainer + { + Depth = float.MinValue, } }, } @@ -79,6 +85,40 @@ namespace osu.Game.Rulesets.Osu.Tests }); } + [Test] + public void TestStreamInputVisual() + { + addHitCircleAt(TouchSource.Touch1); + addHitCircleAt(TouchSource.Touch2); + + beginTouch(TouchSource.Touch1); + beginTouch(TouchSource.Touch2); + + endTouch(TouchSource.Touch1); + + int i = 0; + + AddRepeatStep("Alternate", () => + { + TouchSource down = i % 2 == 0 ? TouchSource.Touch3 : TouchSource.Touch4; + TouchSource up = i % 2 == 0 ? TouchSource.Touch4 : TouchSource.Touch3; + + // sometimes the user will end the previous touch before touching again, sometimes not. + if (RNG.NextBool()) + { + InputManager.BeginTouch(new Touch(down, getSanePositionForSource(down))); + InputManager.EndTouch(new Touch(up, getSanePositionForSource(up))); + } + else + { + InputManager.EndTouch(new Touch(up, getSanePositionForSource(up))); + InputManager.BeginTouch(new Touch(down, getSanePositionForSource(down))); + } + + i++; + }, 100); + } + [Test] public void TestSimpleInput() { From 6daa36477942d6179683ec59387ae898f0e0936b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9miah=20D=C3=89COMBE?= Date: Mon, 23 Jan 2023 13:53:31 +0100 Subject: [PATCH 064/142] adding setting to adjust blur of the background of the song select screen --- osu.Game/Configuration/OsuConfigManager.cs | 3 ++ osu.Game/Localisation/UserInterfaceStrings.cs | 6 ++++ .../UserInterface/SongSelectSettings.cs | 5 +++ osu.Game/Screens/Select/SongSelect.cs | 33 +++++++++++++++++-- 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 6cbb677a64..2b7c2102d4 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -60,6 +60,8 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.ToolbarClockDisplayMode, ToolbarClockDisplayMode.Full); + SetDefault(OsuSetting.BeatmapSelectionBlurLevel, 1f, 0, 1f, 0.01f); + // Online settings SetDefault(OsuSetting.Username, string.Empty); SetDefault(OsuSetting.Token, string.Empty); @@ -339,6 +341,7 @@ namespace osu.Game.Configuration ChatDisplayHeight, BeatmapListingCardSize, ToolbarClockDisplayMode, + BeatmapSelectionBlurLevel, Version, ShowFirstRunSetup, ShowConvertedBeatmaps, diff --git a/osu.Game/Localisation/UserInterfaceStrings.cs b/osu.Game/Localisation/UserInterfaceStrings.cs index ea664d7b50..0bf7dae403 100644 --- a/osu.Game/Localisation/UserInterfaceStrings.cs +++ b/osu.Game/Localisation/UserInterfaceStrings.cs @@ -109,6 +109,12 @@ namespace osu.Game.Localisation /// public static LocalisableString ModSelectHotkeyStyle => new TranslatableString(getKey(@"mod_select_hotkey_style"), @"Mod select hotkey style"); + /// + /// "Beatmap selection blur level" + /// + public static LocalisableString BeatmapSelectionBlurLevel => new TranslatableString(getKey(@"beatmap_selection_blur_level"), @"Beatmap selection blur level"); + + /// /// "no limit" /// diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs index 8b5e0b75b7..828119981f 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs @@ -42,6 +42,11 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface LabelText = UserInterfaceStrings.ModSelectHotkeyStyle, Current = config.GetBindable(OsuSetting.ModSelectHotkeyStyle), ClassicDefault = ModSelectHotkeyStyle.Classic + }, + new SettingsSlider + { + LabelText = UserInterfaceStrings.BeatmapSelectionBlurLevel, + Current = config.GetBindable(OsuSetting.BeatmapSelectionBlurLevel) } }; } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 4b3222c14a..bd1498f0dd 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -35,6 +35,7 @@ using osu.Game.Collections; using osu.Game.Graphics.UserInterface; using System.Diagnostics; using osu.Framework.Extensions.ObjectExtensions; +using osu.Game.Configuration; using osu.Game.Screens.Play; using osu.Game.Skinning; @@ -124,9 +125,26 @@ namespace osu.Game.Screens.Select [Resolved] internal IOverlayManager? OverlayManager { get; private set; } - [BackgroundDependencyLoader(true)] - private void load(AudioManager audio, OsuColour colours, ManageCollectionsDialog? manageCollectionsDialog, DifficultyRecommender? recommender) + private Bindable backgroundBlurLevel { get; set; } = new BindableFloat(); + + private void applyBackgroundBlur(float v) { + ApplyToBackground(background => + { + background.IgnoreUserSettings.Value = true; + background.BlurAmount.Value = v * BACKGROUND_BLUR; + }); + } + private void applyBackgroundBlur(ValueChangedEvent v) + { + applyBackgroundBlur(v.NewValue); + } + + [BackgroundDependencyLoader(true)] + private void load(AudioManager audio, OsuColour colours, ManageCollectionsDialog? manageCollectionsDialog, DifficultyRecommender? recommender, OsuConfigManager config) + { + backgroundBlurLevel = config.GetBindable(OsuSetting.BeatmapSelectionBlurLevel); + LoadComponentAsync(Carousel = new BeatmapCarousel { AllowSelection = false, // delay any selection until our bindables are ready to make a good choice. @@ -549,6 +567,9 @@ namespace osu.Game.Screens.Select { base.OnEntering(e); + backgroundBlurLevel.ValueChanged += applyBackgroundBlur; + applyBackgroundBlur(backgroundBlurLevel.Value); + this.FadeInFromZero(250); FilterControl.Activate(); @@ -596,6 +617,8 @@ namespace osu.Game.Screens.Select public override void OnResuming(ScreenTransitionEvent e) { base.OnResuming(e); + backgroundBlurLevel.ValueChanged += applyBackgroundBlur; + applyBackgroundBlur(backgroundBlurLevel.Value); // required due to https://github.com/ppy/osu-framework/issues/3218 ModSelect.SelectedMods.Disabled = false; @@ -641,6 +664,8 @@ namespace osu.Game.Screens.Select // Without this, it's possible for a transfer to happen while we are not the current screen. transferRulesetValue(); + backgroundBlurLevel.ValueChanged -= applyBackgroundBlur; + ModSelect.SelectedMods.UnbindFrom(selectedMods); playExitingTransition(); @@ -649,6 +674,8 @@ namespace osu.Game.Screens.Select public override bool OnExiting(ScreenExitEvent e) { + backgroundBlurLevel.ValueChanged -= applyBackgroundBlur; + if (base.OnExiting(e)) return true; @@ -742,7 +769,7 @@ namespace osu.Game.Screens.Select ApplyToBackground(backgroundModeBeatmap => { backgroundModeBeatmap.Beatmap = beatmap; - backgroundModeBeatmap.BlurAmount.Value = BACKGROUND_BLUR; + backgroundModeBeatmap.BlurAmount.Value = backgroundBlurLevel.Value * BACKGROUND_BLUR; backgroundModeBeatmap.FadeColour(Color4.White, 250); }); From 1f40b2daf6a585278b148c5e91ce4ac314ab3e01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 23 Jan 2023 21:22:18 +0100 Subject: [PATCH 065/142] Clean up xmldocs --- osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 2 +- .../Screens/Edit/Compose/Components/BlueprintContainer.cs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index d36ed02210..3c878ffd33 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -136,7 +136,7 @@ namespace osu.Game.Rulesets.Edit protected virtual Vector2[] ScreenSpaceAdditionalNodes => Array.Empty(); /// - /// The screen-space collection of base points that cause this to be selected via a drag. + /// The screen-space collection of base points on this that other objects can be snapped to. /// The first element of this collection is /// public Vector2[] ScreenSpaceSnapPoints => ScreenSpaceAdditionalNodes.Prepend(ScreenSpaceSelectionPoint).ToArray(); diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 8cb5a1face..e4e67d10d7 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -513,10 +513,10 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// Check for positional snap for given blueprint. /// - /// The blueprint to check for snapping - /// Distance travelled since start of dragging action. - /// The selection positions of blueprint before start of dragging action. - /// Whether found object to snap to. + /// The blueprint to check for snapping. + /// Distance travelled since start of dragging action. + /// The snap positions of blueprint before start of dragging action. + /// Whether an object to snap to was found. private bool checkSnappingBlueprintToNearbyObjects(SelectionBlueprint blueprint, Vector2 distanceTravelled, Vector2[] originalPositions) { var currentPositions = blueprint.ScreenSpaceSnapPoints; From a992682276cd49d1e2f71b730ddedf30bf618ece Mon Sep 17 00:00:00 2001 From: Susko3 Date: Mon, 23 Jan 2023 21:46:01 +0100 Subject: [PATCH 066/142] Fix `OsuTouchInputMapper` not handling all touches when using screen scaling --- osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs b/osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs index c75e179443..6c91d78e23 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs @@ -10,6 +10,7 @@ using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Framework.Input.StateChanges; using osu.Game.Configuration; +using osuTK; namespace osu.Game.Rulesets.Osu.UI { @@ -38,6 +39,8 @@ namespace osu.Game.Rulesets.Osu.UI mouseDisabled = config.GetBindable(OsuSetting.MouseDisableButtons); } + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; + protected override void OnTouchMove(TouchMoveEvent e) { base.OnTouchMove(e); From f13a5465ba38e91be20e4cdb3d811398e01b8430 Mon Sep 17 00:00:00 2001 From: Jeremiah DECOMBE Date: Mon, 23 Jan 2023 23:07:50 +0100 Subject: [PATCH 067/142] variable naming and loc modifications --- osu.Game/Configuration/OsuConfigManager.cs | 4 ++-- osu.Game/Localisation/UserInterfaceStrings.cs | 5 ++--- .../Settings/Sections/UserInterface/SongSelectSettings.cs | 4 ++-- osu.Game/Screens/Select/SongSelect.cs | 2 +- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 2b7c2102d4..f7b84527b5 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -60,7 +60,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.ToolbarClockDisplayMode, ToolbarClockDisplayMode.Full); - SetDefault(OsuSetting.BeatmapSelectionBlurLevel, 1f, 0, 1f, 0.01f); + SetDefault(OsuSetting.BeatmapSelectionBackgoundBlurLevel, 1f, 0, 1f, 0.01f); // Online settings SetDefault(OsuSetting.Username, string.Empty); @@ -341,7 +341,7 @@ namespace osu.Game.Configuration ChatDisplayHeight, BeatmapListingCardSize, ToolbarClockDisplayMode, - BeatmapSelectionBlurLevel, + BeatmapSelectionBackgoundBlurLevel, Version, ShowFirstRunSetup, ShowConvertedBeatmaps, diff --git a/osu.Game/Localisation/UserInterfaceStrings.cs b/osu.Game/Localisation/UserInterfaceStrings.cs index 0bf7dae403..9abb3aaddc 100644 --- a/osu.Game/Localisation/UserInterfaceStrings.cs +++ b/osu.Game/Localisation/UserInterfaceStrings.cs @@ -110,10 +110,9 @@ namespace osu.Game.Localisation public static LocalisableString ModSelectHotkeyStyle => new TranslatableString(getKey(@"mod_select_hotkey_style"), @"Mod select hotkey style"); /// - /// "Beatmap selection blur level" + /// "Song select background blur" /// - public static LocalisableString BeatmapSelectionBlurLevel => new TranslatableString(getKey(@"beatmap_selection_blur_level"), @"Beatmap selection blur level"); - + public static LocalisableString BeatmapSelectionBackgroundBlurLevel => new TranslatableString(getKey(@"beatmap_selection_background_blur_level"), @"Song select background blur"); /// /// "no limit" diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs index 828119981f..f0b2721153 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs @@ -45,8 +45,8 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface }, new SettingsSlider { - LabelText = UserInterfaceStrings.BeatmapSelectionBlurLevel, - Current = config.GetBindable(OsuSetting.BeatmapSelectionBlurLevel) + LabelText = UserInterfaceStrings.BeatmapSelectionBackgroundBlurLevel, + Current = config.GetBindable(OsuSetting.BeatmapSelectionBackgoundBlurLevel) } }; } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index bd1498f0dd..bf6e433a8e 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -143,7 +143,7 @@ namespace osu.Game.Screens.Select [BackgroundDependencyLoader(true)] private void load(AudioManager audio, OsuColour colours, ManageCollectionsDialog? manageCollectionsDialog, DifficultyRecommender? recommender, OsuConfigManager config) { - backgroundBlurLevel = config.GetBindable(OsuSetting.BeatmapSelectionBlurLevel); + backgroundBlurLevel = config.GetBindable(OsuSetting.BeatmapSelectionBackgoundBlurLevel); LoadComponentAsync(Carousel = new BeatmapCarousel { From 26adc28943a802ab924e21f3da02b7a46db083e9 Mon Sep 17 00:00:00 2001 From: Jeremiah DECOMBE Date: Mon, 23 Jan 2023 23:15:37 +0100 Subject: [PATCH 068/142] missing blank line between methods --- osu.Game/Screens/Select/SongSelect.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index bf6e433a8e..c43344d963 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -135,6 +135,7 @@ namespace osu.Game.Screens.Select background.BlurAmount.Value = v * BACKGROUND_BLUR; }); } + private void applyBackgroundBlur(ValueChangedEvent v) { applyBackgroundBlur(v.NewValue); From e6de167adb1c549d060314786730bfef06a243f5 Mon Sep 17 00:00:00 2001 From: naoey Date: Tue, 24 Jan 2023 07:25:13 +0900 Subject: [PATCH 069/142] Revert split and make collections boolean internal to `BeatmapManager` --- .../Beatmaps/WorkingBeatmapManagerTest.cs | 2 +- osu.Game/Beatmaps/BeatmapManager.cs | 26 ++++++------------- osu.Game/Screens/Edit/Editor.cs | 5 +--- osu.Game/Tests/Visual/EditorTestScene.cs | 7 +---- 4 files changed, 11 insertions(+), 29 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/WorkingBeatmapManagerTest.cs b/osu.Game.Tests/Beatmaps/WorkingBeatmapManagerTest.cs index 4cd866392b..89b8c8927d 100644 --- a/osu.Game.Tests/Beatmaps/WorkingBeatmapManagerTest.cs +++ b/osu.Game.Tests/Beatmaps/WorkingBeatmapManagerTest.cs @@ -124,7 +124,7 @@ namespace osu.Game.Tests.Beatmaps Assert.That(preserveCollection.BeatmapMD5Hashes, Does.Contain(initialHash)); Assert.That(noNewCollection.BeatmapMD5Hashes, Does.Not.Contain(initialHash)); - beatmaps.SaveExistingBeatmap(working.BeatmapInfo, working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo)); + beatmaps.Save(working.BeatmapInfo, working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo)); string finalHash = working.BeatmapInfo.MD5Hash; diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index d644031fe1..fc6d4081e7 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -186,7 +186,7 @@ namespace osu.Game.Beatmaps targetBeatmapSet.Beatmaps.Add(newBeatmap.BeatmapInfo); newBeatmap.BeatmapInfo.BeatmapSet = targetBeatmapSet; - SaveNewBeatmap(newBeatmap.BeatmapInfo, newBeatmap, beatmapSkin); + save(newBeatmap.BeatmapInfo, newBeatmap, beatmapSkin, false); workingBeatmapCache.Invalidate(targetBeatmapSet); return GetWorkingBeatmap(newBeatmap.BeatmapInfo); @@ -286,22 +286,7 @@ namespace osu.Game.Beatmaps /// The to save the content against. The file referenced by will be replaced. /// The content to write. /// The beatmap content to write, null if to be omitted. - public virtual void SaveExistingBeatmap(BeatmapInfo beatmapInfo, IBeatmap beatmapContent, ISkin? beatmapSkin = null) - { - string oldMd5Hash = beatmapInfo.MD5Hash; - - save(beatmapInfo, beatmapContent, beatmapSkin); - - Realm.Write(r => beatmapInfo.TransferCollectionReferences(r, oldMd5Hash)); - } - - /// - /// Saves a new file against a given . - /// - /// The to save the content against. The file referenced by will be replaced. - /// The content to write. - /// The beatmap content to write, null if to be omitted. - public virtual void SaveNewBeatmap(BeatmapInfo beatmapInfo, IBeatmap beatmapContent, ISkin? beatmapSkin = null) + public virtual void Save(BeatmapInfo beatmapInfo, IBeatmap beatmapContent, ISkin? beatmapSkin = null) { save(beatmapInfo, beatmapContent, beatmapSkin); } @@ -414,7 +399,7 @@ namespace osu.Game.Beatmaps setInfo.Status = BeatmapOnlineStatus.LocallyModified; } - private void save(BeatmapInfo beatmapInfo, IBeatmap beatmapContent, ISkin? beatmapSkin) + private void save(BeatmapInfo beatmapInfo, IBeatmap beatmapContent, ISkin? beatmapSkin, bool transferCollections = true) { var setInfo = beatmapInfo.BeatmapSet; Debug.Assert(setInfo != null); @@ -448,6 +433,8 @@ namespace osu.Game.Beatmaps if (existingFileInfo != null) DeleteFile(setInfo, existingFileInfo); + string oldMd5Hash = beatmapInfo.MD5Hash; + beatmapInfo.MD5Hash = stream.ComputeMD5Hash(); beatmapInfo.Hash = stream.ComputeSHA2Hash(); @@ -464,6 +451,9 @@ namespace osu.Game.Beatmaps setInfo.CopyChangesToRealm(liveBeatmapSet); + if (transferCollections) + beatmapInfo.TransferCollectionReferences(r, oldMd5Hash); + ProcessBeatmap?.Invoke((liveBeatmapSet, false)); }); } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 915f3d4a2a..74ea933255 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -429,10 +429,7 @@ namespace osu.Game.Screens.Edit try { // save the loaded beatmap's data stream. - if (isNewBeatmap) - beatmapManager.SaveNewBeatmap(editorBeatmap.BeatmapInfo, editorBeatmap.PlayableBeatmap, editorBeatmap.BeatmapSkin); - else - beatmapManager.SaveExistingBeatmap(editorBeatmap.BeatmapInfo, editorBeatmap.PlayableBeatmap, editorBeatmap.BeatmapSkin); + beatmapManager.Save(editorBeatmap.BeatmapInfo, editorBeatmap.PlayableBeatmap, editorBeatmap.BeatmapSkin); } catch (Exception ex) { diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index 8834911ff5..6e2f1e99cd 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -178,12 +178,7 @@ namespace osu.Game.Tests.Visual => testBeatmapManager.TestBeatmap; } - public override void SaveExistingBeatmap(BeatmapInfo info, IBeatmap beatmapContent, ISkin beatmapSkin = null) - { - // don't actually care about saving for this context. - } - - public override void SaveNewBeatmap(BeatmapInfo info, IBeatmap beatmapContent, ISkin beatmapSkin = null) + public override void Save(BeatmapInfo info, IBeatmap beatmapContent, ISkin beatmapSkin = null) { // don't actually care about saving for this context. } From b573e42cc25575588275eec428366fbffb1cc76b Mon Sep 17 00:00:00 2001 From: Jeremiah DECOMBE Date: Tue, 24 Jan 2023 00:08:11 +0100 Subject: [PATCH 070/142] BeatmapSelectionBackgroundBlurLevel renamed to SongSelectBackgroundBlurLevel --- osu.Game/Configuration/OsuConfigManager.cs | 4 ++-- osu.Game/Localisation/UserInterfaceStrings.cs | 2 +- .../Settings/Sections/UserInterface/SongSelectSettings.cs | 4 ++-- osu.Game/Screens/Select/SongSelect.cs | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index f7b84527b5..d26fc2ec43 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -60,7 +60,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.ToolbarClockDisplayMode, ToolbarClockDisplayMode.Full); - SetDefault(OsuSetting.BeatmapSelectionBackgoundBlurLevel, 1f, 0, 1f, 0.01f); + SetDefault(OsuSetting.SongSelectBackgoundBlurLevel, 1f, 0, 1f, 0.01f); // Online settings SetDefault(OsuSetting.Username, string.Empty); @@ -341,7 +341,7 @@ namespace osu.Game.Configuration ChatDisplayHeight, BeatmapListingCardSize, ToolbarClockDisplayMode, - BeatmapSelectionBackgoundBlurLevel, + SongSelectBackgoundBlurLevel, Version, ShowFirstRunSetup, ShowConvertedBeatmaps, diff --git a/osu.Game/Localisation/UserInterfaceStrings.cs b/osu.Game/Localisation/UserInterfaceStrings.cs index 9abb3aaddc..5e4c4dc8ed 100644 --- a/osu.Game/Localisation/UserInterfaceStrings.cs +++ b/osu.Game/Localisation/UserInterfaceStrings.cs @@ -112,7 +112,7 @@ namespace osu.Game.Localisation /// /// "Song select background blur" /// - public static LocalisableString BeatmapSelectionBackgroundBlurLevel => new TranslatableString(getKey(@"beatmap_selection_background_blur_level"), @"Song select background blur"); + public static LocalisableString SongSelectBackgroundBlurLevel => new TranslatableString(getKey(@"song_select_background_blur_level"), @"Song select background blur"); /// /// "no limit" diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs index f0b2721153..3228ebfe44 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs @@ -45,8 +45,8 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface }, new SettingsSlider { - LabelText = UserInterfaceStrings.BeatmapSelectionBackgroundBlurLevel, - Current = config.GetBindable(OsuSetting.BeatmapSelectionBackgoundBlurLevel) + LabelText = UserInterfaceStrings.SongSelectBackgroundBlurLevel, + Current = config.GetBindable(OsuSetting.SongSelectBackgoundBlurLevel) } }; } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index c43344d963..b756ac2a98 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -144,7 +144,7 @@ namespace osu.Game.Screens.Select [BackgroundDependencyLoader(true)] private void load(AudioManager audio, OsuColour colours, ManageCollectionsDialog? manageCollectionsDialog, DifficultyRecommender? recommender, OsuConfigManager config) { - backgroundBlurLevel = config.GetBindable(OsuSetting.BeatmapSelectionBackgoundBlurLevel); + backgroundBlurLevel = config.GetBindable(OsuSetting.SongSelectBackgoundBlurLevel); LoadComponentAsync(Carousel = new BeatmapCarousel { From c1876aac887a238d477892c806314f6b50d33adc Mon Sep 17 00:00:00 2001 From: Jeremiah DECOMBE Date: Tue, 24 Jan 2023 00:36:38 +0100 Subject: [PATCH 071/142] removing parameter name abbreviations --- osu.Game/Screens/Select/SongSelect.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index b756ac2a98..eab0620255 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -127,18 +127,18 @@ namespace osu.Game.Screens.Select private Bindable backgroundBlurLevel { get; set; } = new BindableFloat(); - private void applyBackgroundBlur(float v) + private void applyBackgroundBlur(float blurLevel) { ApplyToBackground(background => { background.IgnoreUserSettings.Value = true; - background.BlurAmount.Value = v * BACKGROUND_BLUR; + background.BlurAmount.Value = blurLevel * BACKGROUND_BLUR; }); } - private void applyBackgroundBlur(ValueChangedEvent v) + private void applyBackgroundBlur(ValueChangedEvent blurLevelValueChange) { - applyBackgroundBlur(v.NewValue); + applyBackgroundBlur(blurLevelValueChange.NewValue); } [BackgroundDependencyLoader(true)] From f067a8463150f391437107406fd51cd1ed3db474 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Tue, 24 Jan 2023 00:51:07 +0000 Subject: [PATCH 072/142] TestSceneWikiMarkdowContainer: Update image paths --- .../Online/TestSceneWikiMarkdownContainer.cs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs index b353123649..c01ce56d55 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs @@ -122,8 +122,8 @@ needs_cleanup: true { AddStep("Add absolute image", () => { - markdownContainer.CurrentPath = "https://dev.ppy.sh"; - markdownContainer.Text = "![intro](/wiki/Interface/img/intro-screen.jpg)"; + markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/images/Client/Interface/"; + markdownContainer.Text = "![intro](img/intro-screen.jpg)"; }); } @@ -132,7 +132,7 @@ needs_cleanup: true { AddStep("Add relative image", () => { - markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/Interface/"; + markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/images/Client/Interface/"; markdownContainer.Text = "![intro](img/intro-screen.jpg)"; }); } @@ -142,7 +142,7 @@ needs_cleanup: true { AddStep("Add paragraph with block image", () => { - markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/Interface/"; + markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/images/Client/Interface/"; markdownContainer.Text = @"Line before image ![play menu](img/play-menu.jpg ""Main Menu in osu!"") @@ -156,8 +156,8 @@ Line after image"; { AddStep("Add inline image", () => { - markdownContainer.CurrentPath = "https://dev.ppy.sh"; - markdownContainer.Text = "![osu! mode icon](/wiki/shared/mode/osu.png) osu!"; + markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/shared/"; + markdownContainer.Text = "![osu! mode icon](mode/osu.png) osu!"; }); } @@ -166,16 +166,16 @@ Line after image"; { AddStep("Add Table", () => { - markdownContainer.CurrentPath = "https://dev.ppy.sh"; + markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/images/shared/judgement/"; markdownContainer.Text = @" | Image | Name | Effect | | :-: | :-: | :-- | -| ![](/wiki/Skinning/Interface/img/hit300.png ""300"") | 300 | A possible score when tapping a hit circle precisely on time, completing a Slider and keeping the cursor over every tick, or completing a Spinner with the Spinner Metre full. A score of 300 appears in an blue score by default. Scoring nothing except 300s in a beatmap will award the player with the SS or SSH grade. | -| ![](/wiki/Skinning/Interface/img/hit300g.png ""Geki"") | (激) Geki | A term from Ouendan, called Elite Beat! in EBA. Appears when playing the last element in a combo in which the player has scored only 300s. Getting a Geki will give a sizable boost to the Life Bar. By default, it is blue. | -| ![](/wiki/Skinning/Interface/img/hit100.png ""100"") | 100 | A possible score one can get when tapping a Hit Object slightly late or early, completing a Slider and missing a number of ticks, or completing a Spinner with the Spinner Meter almost full. A score of 100 appears in a green score by default. When very skilled players test a beatmap and they get a lot of 100s, this may mean that the beatmap does not have correct timing. | -| ![](/wiki/Skinning/Interface/img/hit300k.png ""300 Katu"") ![](/wiki/Skinning/Interface/img/hit100k.png ""100 Katu"") | (喝) Katu or Katsu | A term from Ouendan, called Beat! in EBA. Appears when playing the last element in a combo in which the player has scored at least one 100, but no 50s or misses. Getting a Katu will give a small boost to the Life Bar. By default, it is coloured green or blue depending on whether the Katu itself is a 100 or a 300. | -| ![](/wiki/Skinning/Interface/img/hit50.png ""50"") | 50 | A possible score one can get when tapping a hit circle rather early or late but not early or late enough to cause a miss, completing a Slider and missing a lot of ticks, or completing a Spinner with the Spinner Metre close to full. A score of 50 appears in a orange score by default. Scoring a 50 in a combo will prevent the appearance of a Katu or a Geki at the combo's end. | -| ![](/wiki/Skinning/Interface/img/hit0.png ""Miss"") | Miss | A possible score one can get when not tapping a hit circle or too early (based on OD and AR, it may *shake* instead), not tapping or holding the Slider at least once, or completing a Spinner with low Spinner Metre fill. Scoring a Miss will reset the current combo to 0 and will prevent the appearance of a Katu or a Geki at the combo's end. | +| ![](osu!/hit300.png ""300"") | 300 | A possible score when tapping a hit circle precisely on time, completing a Slider and keeping the cursor over every tick, or completing a Spinner with the Spinner Metre full. A score of 300 appears in an blue score by default. Scoring nothing except 300s in a beatmap will award the player with the SS or SSH grade. | +| ![](osu!/hit300g.png ""Geki"") | (激) Geki | A term from Ouendan, called Elite Beat! in EBA. Appears when playing the last element in a combo in which the player has scored only 300s. Getting a Geki will give a sizable boost to the Life Bar. By default, it is blue. | +| ![](osu!/hit100.png ""100"") | 100 | A possible score one can get when tapping a Hit Object slightly late or early, completing a Slider and missing a number of ticks, or completing a Spinner with the Spinner Meter almost full. A score of 100 appears in a green score by default. When very skilled players test a beatmap and they get a lot of 100s, this may mean that the beatmap does not have correct timing. | +| ![](osu!/hit300k.png ""300 Katu"") ![](/wiki/Skinning/Interface/img/hit100k.png ""100 Katu"") | (喝) Katu or Katsu | A term from Ouendan, called Beat! in EBA. Appears when playing the last element in a combo in which the player has scored at least one 100, but no 50s or misses. Getting a Katu will give a small boost to the Life Bar. By default, it is coloured green or blue depending on whether the Katu itself is a 100 or a 300. | +| ![](osu!/hit50.png ""50"") | 50 | A possible score one can get when tapping a hit circle rather early or late but not early or late enough to cause a miss, completing a Slider and missing a lot of ticks, or completing a Spinner with the Spinner Metre close to full. A score of 50 appears in a orange score by default. Scoring a 50 in a combo will prevent the appearance of a Katu or a Geki at the combo's end. | +| ![](osu!/hit0.png ""Miss"") | Miss | A possible score one can get when not tapping a hit circle or too early (based on OD and AR, it may *shake* instead), not tapping or holding the Slider at least once, or completing a Spinner with low Spinner Metre fill. Scoring a Miss will reset the current combo to 0 and will prevent the appearance of a Katu or a Geki at the combo's end. | "; }); } @@ -185,7 +185,7 @@ Line after image"; { AddStep("Add image", () => { - markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/osu!_Program_Files/"; + markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/images/Client/Program_files/"; markdownContainer.Text = "![](img/file_structure.jpg \"The file structure of osu!'s installation folder, on Windows and macOS\")"; }); From 2454eb8cd9d56f42587e1577ac05824e016ac8f8 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Tue, 24 Jan 2023 00:51:40 +0000 Subject: [PATCH 073/142] TestSceneWikiMarkdownContainer: Add visual test for inline and fenced code syntax --- .../Online/TestSceneWikiMarkdownContainer.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs index c01ce56d55..7b8d04d25f 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs @@ -270,6 +270,30 @@ Phasellus eu nunc nec ligula semper fringilla. Aliquam magna neque, placerat sed }); } + [Test] + public void TestCodeSyntax() + { + AddStep("set content", () => + { + markdownContainer.Text = @" +This is a paragraph containing `inline code` synatax. +Oh wow I do love the `WikiMarkdownContainer`, it is very cool! + +This is a line before the fenced code block: +```csharp +public class WikiMarkdownContainer : MarkdownContainer +{ + public WikiMarkdownContainer() + { + this.foo = bar; + } +} +``` +This is a line after the fenced code block! +"; + }); + } + private partial class TestMarkdownContainer : WikiMarkdownContainer { public LinkInline Link; From 949610c8a4312feece4ddb74b36d6f2dd8cd8595 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Jan 2023 11:06:54 +0900 Subject: [PATCH 074/142] Add commentary as to why `ReceivePositionalInputAt` override is required --- osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs b/osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs index 6c91d78e23..ffb8c802c7 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs @@ -39,6 +39,7 @@ namespace osu.Game.Rulesets.Osu.UI mouseDisabled = config.GetBindable(OsuSetting.MouseDisableButtons); } + // Required to handle touches outside of the playfield when screen scaling is enabled. public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; protected override void OnTouchMove(TouchMoveEvent e) From 72cfe2ba5a4452ac134060ab90f2dc71ce02cd15 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Jan 2023 12:30:11 +0900 Subject: [PATCH 075/142] Move `private` field up with others --- osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs b/osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs index 9842e24d05..2e4f983fc4 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs @@ -21,6 +21,8 @@ namespace osu.Game.Rulesets.Osu.UI /// private readonly List trackedTouches = new List(); + private TrackedTouch? positionTrackingTouch; + private readonly OsuInputManager osuInputManager; private Bindable mouseDisabled = null!; @@ -44,8 +46,6 @@ namespace osu.Game.Rulesets.Osu.UI handleTouchMovement(e); } - private TrackedTouch? positionTrackingTouch; - protected override bool OnTouchDown(TouchDownEvent e) { OsuAction action = trackedTouches.Any(t => t.Action == OsuAction.LeftButton) From 04995b66da5f6abb71ae51eef0628915c1b7df41 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 24 Jan 2023 07:09:25 +0300 Subject: [PATCH 076/142] Add seed slider to Triangles test sceen --- osu.Game.Tests/Visual/Background/TestSceneTrianglesBackground.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Background/TestSceneTrianglesBackground.cs b/osu.Game.Tests/Visual/Background/TestSceneTrianglesBackground.cs index d3cdf928e8..8a5489f476 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneTrianglesBackground.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneTrianglesBackground.cs @@ -35,6 +35,7 @@ namespace osu.Game.Tests.Visual.Background base.LoadComplete(); AddSliderStep("Triangle scale", 0f, 10f, 1f, s => triangles.TriangleScale = s); + AddSliderStep("Seed", 0, 1000, 0, s => triangles.Reset(s)); } } } From fc558278ccc164d54dcd4d8adf5989e1399f705a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Jan 2023 13:37:12 +0900 Subject: [PATCH 077/142] Fix touch input handler settings not matching search for "touchscreen" --- osu.Game/OsuGameBase.cs | 4 +- .../Settings/Sections/Input/TouchSettings.cs | 40 +++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 osu.Game/Overlays/Settings/Sections/Input/TouchSettings.cs diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index a38aa19cef..cf58d07b9e 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -556,8 +556,8 @@ namespace osu.Game case JoystickHandler jh: return new JoystickSettings(jh); - case TouchHandler: - return new InputSection.HandlerSection(handler); + case TouchHandler th: + return new TouchSettings(th); } } diff --git a/osu.Game/Overlays/Settings/Sections/Input/TouchSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TouchSettings.cs new file mode 100644 index 0000000000..8d1b12d5b2 --- /dev/null +++ b/osu.Game/Overlays/Settings/Sections/Input/TouchSettings.cs @@ -0,0 +1,40 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Input.Handlers.Touch; +using osu.Framework.Localisation; +using osu.Game.Localisation; + +namespace osu.Game.Overlays.Settings.Sections.Input +{ + public partial class TouchSettings : SettingsSubsection + { + private readonly TouchHandler handler; + + public TouchSettings(TouchHandler handler) + { + this.handler = handler; + } + + [BackgroundDependencyLoader] + private void load() + { + Children = new Drawable[] + { + new SettingsCheckbox + { + LabelText = CommonStrings.Enabled, + Current = handler.Enabled + }, + }; + } + + public override IEnumerable FilterTerms => base.FilterTerms.Concat(new LocalisableString[] { @"touchscreen" }); + + protected override LocalisableString Header => handler.Description; + } +} From 04c96355cb682610e14be0b9731b4fba3489f685 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 24 Jan 2023 07:38:42 +0300 Subject: [PATCH 078/142] Use TriangleBorder shader to draw triangles --- osu.Game/Graphics/Backgrounds/Triangles.cs | 40 ++++++++++------------ 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs index 94397f7ffb..d9dff1574f 100644 --- a/osu.Game/Graphics/Backgrounds/Triangles.cs +++ b/osu.Game/Graphics/Backgrounds/Triangles.cs @@ -31,12 +31,6 @@ namespace osu.Game.Graphics.Backgrounds /// private const float equilateral_triangle_ratio = 0.866f; - /// - /// How many screen-space pixels are smoothed over. - /// Same behavior as Sprite's EdgeSmoothness. - /// - private const float edge_smoothness = 1; - private Color4 colourLight = Color4.White; public Color4 ColourLight @@ -115,7 +109,7 @@ namespace osu.Game.Graphics.Backgrounds private void load(IRenderer renderer, ShaderManager shaders) { texture = renderer.WhitePixel; - shader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE); + shader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, "TriangleBorder"); } protected override void LoadComplete() @@ -252,14 +246,17 @@ namespace osu.Game.Graphics.Backgrounds private class TrianglesDrawNode : DrawNode { + private float fill = 1f; + protected new Triangles Source => (Triangles)base.Source; private IShader shader; private Texture texture; private readonly List parts = new List(); - private Vector2 size; + private readonly Vector2 triangleSize = new Vector2(1f, equilateral_triangle_ratio) * triangle_size; + private Vector2 size; private IVertexBatch vertexBatch; public TrianglesDrawNode(Triangles source) @@ -290,29 +287,28 @@ namespace osu.Game.Graphics.Backgrounds } shader.Bind(); - - Vector2 localInflationAmount = edge_smoothness * DrawInfo.MatrixInverse.ExtractScale().Xy; + shader.GetUniform("thickness").UpdateValue(ref fill); foreach (TriangleParticle particle in parts) { - var offset = triangle_size * new Vector2(particle.Scale * 0.5f, particle.Scale * equilateral_triangle_ratio); + Vector2 relativeSize = Vector2.Divide(triangleSize * particle.Scale, size); - var triangle = new Triangle( - Vector2Extensions.Transform(particle.Position * size, DrawInfo.Matrix), - Vector2Extensions.Transform(particle.Position * size + offset, DrawInfo.Matrix), - Vector2Extensions.Transform(particle.Position * size + new Vector2(-offset.X, offset.Y), DrawInfo.Matrix) + Vector2 topLeft = particle.Position - new Vector2(relativeSize.X * 0.5f, 0f); + Vector2 topRight = topLeft + new Vector2(relativeSize.X, 0f); + Vector2 bottomLeft = topLeft + new Vector2(0f, relativeSize.Y); + Vector2 bottomRight = bottomLeft + new Vector2(relativeSize.X, 0f); + + var drawQuad = new Quad( + Vector2Extensions.Transform(topLeft * size, DrawInfo.Matrix), + Vector2Extensions.Transform(topRight * size, DrawInfo.Matrix), + Vector2Extensions.Transform(bottomLeft * size, DrawInfo.Matrix), + Vector2Extensions.Transform(bottomRight * size, DrawInfo.Matrix) ); ColourInfo colourInfo = DrawColourInfo.Colour; colourInfo.ApplyChild(particle.Colour); - renderer.DrawTriangle( - texture, - triangle, - colourInfo, - null, - vertexBatch.AddAction, - Vector2.Divide(localInflationAmount, new Vector2(2 * offset.X, offset.Y))); + renderer.DrawQuad(texture, drawQuad, colourInfo, vertexAction: vertexBatch.AddAction); } shader.Unbind(); From bb15ee50e066b161437c88bf5ecbb0bc18587141 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Jan 2023 13:52:59 +0900 Subject: [PATCH 079/142] Fix beatmap leaderboard potentially showing incorrect leaderboard --- osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index c419501add..5c720c8491 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -104,6 +104,9 @@ namespace osu.Game.Screens.Select.Leaderboards protected override APIRequest? FetchScores(CancellationToken cancellationToken) { + scoreRetrievalRequest?.Cancel(); + scoreRetrievalRequest = null; + var fetchBeatmapInfo = BeatmapInfo; if (fetchBeatmapInfo == null) @@ -152,8 +155,6 @@ namespace osu.Game.Screens.Select.Leaderboards else if (filterMods) requestMods = mods.Value; - scoreRetrievalRequest?.Cancel(); - var newRequest = new GetScoresRequest(fetchBeatmapInfo, fetchRuleset, Scope, requestMods); newRequest.Success += response => Schedule(() => { From b46ef67a14c68a48d210e206db542dc90cff20ee Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Tue, 24 Jan 2023 00:31:46 -0500 Subject: [PATCH 080/142] increase min minimum accuracy to 60% --- osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs b/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs index e18164db89..59fbfe49b6 100644 --- a/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs +++ b/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Mods [SettingSource("Minimum accuracy", "Trigger a failure if your accuracy goes below this value.", SettingControlType = typeof(SettingsSlider))] public BindableNumber MinimumAccuracy { get; } = new BindableDouble { - MinValue = 0.01, + MinValue = 0.60, MaxValue = 0.99, Precision = 0.01, Default = 0.9, From d783998c81f58bd1548e4f6569aca886868845e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9miah=20D=C3=89COMBE?= Date: Tue, 24 Jan 2023 09:09:05 +0100 Subject: [PATCH 081/142] using BindValueChanged and IsCurrentScreen for setting binding --- osu.Game/Screens/Select/SongSelect.cs | 34 +++++++++------------------ 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index eab0620255..dbd948ff86 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -127,24 +127,21 @@ namespace osu.Game.Screens.Select private Bindable backgroundBlurLevel { get; set; } = new BindableFloat(); - private void applyBackgroundBlur(float blurLevel) - { - ApplyToBackground(background => - { - background.IgnoreUserSettings.Value = true; - background.BlurAmount.Value = blurLevel * BACKGROUND_BLUR; - }); - } - - private void applyBackgroundBlur(ValueChangedEvent blurLevelValueChange) - { - applyBackgroundBlur(blurLevelValueChange.NewValue); - } - [BackgroundDependencyLoader(true)] private void load(AudioManager audio, OsuColour colours, ManageCollectionsDialog? manageCollectionsDialog, DifficultyRecommender? recommender, OsuConfigManager config) { backgroundBlurLevel = config.GetBindable(OsuSetting.SongSelectBackgoundBlurLevel); + backgroundBlurLevel.BindValueChanged(e => + { + if (this.IsCurrentScreen()) + { + ApplyToBackground(background => + { + background.IgnoreUserSettings.Value = true; + background.BlurAmount.Value = e.NewValue * BACKGROUND_BLUR; + }); + } + }, true); LoadComponentAsync(Carousel = new BeatmapCarousel { @@ -568,9 +565,6 @@ namespace osu.Game.Screens.Select { base.OnEntering(e); - backgroundBlurLevel.ValueChanged += applyBackgroundBlur; - applyBackgroundBlur(backgroundBlurLevel.Value); - this.FadeInFromZero(250); FilterControl.Activate(); @@ -618,8 +612,6 @@ namespace osu.Game.Screens.Select public override void OnResuming(ScreenTransitionEvent e) { base.OnResuming(e); - backgroundBlurLevel.ValueChanged += applyBackgroundBlur; - applyBackgroundBlur(backgroundBlurLevel.Value); // required due to https://github.com/ppy/osu-framework/issues/3218 ModSelect.SelectedMods.Disabled = false; @@ -665,8 +657,6 @@ namespace osu.Game.Screens.Select // Without this, it's possible for a transfer to happen while we are not the current screen. transferRulesetValue(); - backgroundBlurLevel.ValueChanged -= applyBackgroundBlur; - ModSelect.SelectedMods.UnbindFrom(selectedMods); playExitingTransition(); @@ -675,8 +665,6 @@ namespace osu.Game.Screens.Select public override bool OnExiting(ScreenExitEvent e) { - backgroundBlurLevel.ValueChanged -= applyBackgroundBlur; - if (base.OnExiting(e)) return true; From 7ca2a431e655bea849184e937819b384eb9cdfa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9miah=20D=C3=89COMBE?= Date: Tue, 24 Jan 2023 09:19:53 +0100 Subject: [PATCH 082/142] changing song select background blur setting to boolean --- osu.Game/Configuration/OsuConfigManager.cs | 4 ++-- .../Settings/Sections/UserInterface/SongSelectSettings.cs | 4 ++-- osu.Game/Screens/Select/SongSelect.cs | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index d26fc2ec43..fff5dc62c6 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -60,7 +60,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.ToolbarClockDisplayMode, ToolbarClockDisplayMode.Full); - SetDefault(OsuSetting.SongSelectBackgoundBlurLevel, 1f, 0, 1f, 0.01f); + SetDefault(OsuSetting.SongSelectBackgoundBlur, true); // Online settings SetDefault(OsuSetting.Username, string.Empty); @@ -341,7 +341,7 @@ namespace osu.Game.Configuration ChatDisplayHeight, BeatmapListingCardSize, ToolbarClockDisplayMode, - SongSelectBackgoundBlurLevel, + SongSelectBackgoundBlur, Version, ShowFirstRunSetup, ShowConvertedBeatmaps, diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs index 3228ebfe44..ab5a3a1280 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs @@ -43,10 +43,10 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface Current = config.GetBindable(OsuSetting.ModSelectHotkeyStyle), ClassicDefault = ModSelectHotkeyStyle.Classic }, - new SettingsSlider + new SettingsCheckbox { LabelText = UserInterfaceStrings.SongSelectBackgroundBlurLevel, - Current = config.GetBindable(OsuSetting.SongSelectBackgoundBlurLevel) + Current = config.GetBindable(OsuSetting.SongSelectBackgoundBlur) } }; } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index dbd948ff86..bbb88052aa 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -125,12 +125,12 @@ namespace osu.Game.Screens.Select [Resolved] internal IOverlayManager? OverlayManager { get; private set; } - private Bindable backgroundBlurLevel { get; set; } = new BindableFloat(); + private Bindable backgroundBlurLevel { get; set; } = new BindableBool(); [BackgroundDependencyLoader(true)] private void load(AudioManager audio, OsuColour colours, ManageCollectionsDialog? manageCollectionsDialog, DifficultyRecommender? recommender, OsuConfigManager config) { - backgroundBlurLevel = config.GetBindable(OsuSetting.SongSelectBackgoundBlurLevel); + backgroundBlurLevel = config.GetBindable(OsuSetting.SongSelectBackgoundBlur); backgroundBlurLevel.BindValueChanged(e => { if (this.IsCurrentScreen()) @@ -138,7 +138,7 @@ namespace osu.Game.Screens.Select ApplyToBackground(background => { background.IgnoreUserSettings.Value = true; - background.BlurAmount.Value = e.NewValue * BACKGROUND_BLUR; + background.BlurAmount.Value = e.NewValue ? BACKGROUND_BLUR : 0; }); } }, true); @@ -758,7 +758,7 @@ namespace osu.Game.Screens.Select ApplyToBackground(backgroundModeBeatmap => { backgroundModeBeatmap.Beatmap = beatmap; - backgroundModeBeatmap.BlurAmount.Value = backgroundBlurLevel.Value * BACKGROUND_BLUR; + backgroundModeBeatmap.BlurAmount.Value = backgroundBlurLevel.Value ? BACKGROUND_BLUR : 0f; backgroundModeBeatmap.FadeColour(Color4.White, 250); }); From df895c4fd685d3c2d61ddfae3edfb17282b785db Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 24 Jan 2023 00:21:39 -0800 Subject: [PATCH 083/142] Always make settings footer build display clickable --- osu.Game/Overlays/Settings/SettingsFooter.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Settings/SettingsFooter.cs b/osu.Game/Overlays/Settings/SettingsFooter.cs index 62933ed556..9ab0fa7ad8 100644 --- a/osu.Game/Overlays/Settings/SettingsFooter.cs +++ b/osu.Game/Overlays/Settings/SettingsFooter.cs @@ -49,7 +49,7 @@ namespace osu.Game.Overlays.Settings Text = game.Name, Font = OsuFont.GetFont(size: 18, weight: FontWeight.Bold), }, - new BuildDisplay(game.Version, DebugUtils.IsDebugBuild) + new BuildDisplay(game.Version) { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, @@ -81,15 +81,13 @@ namespace osu.Game.Overlays.Settings private partial class BuildDisplay : OsuAnimatedButton { private readonly string version; - private readonly bool isDebug; [Resolved] private OsuColour colours { get; set; } - public BuildDisplay(string version, bool isDebug) + public BuildDisplay(string version) { this.version = version; - this.isDebug = isDebug; Content.RelativeSizeAxes = Axes.Y; Content.AutoSizeAxes = AutoSizeAxes = Axes.X; @@ -99,8 +97,7 @@ namespace osu.Game.Overlays.Settings [BackgroundDependencyLoader(true)] private void load(ChangelogOverlay changelog) { - if (!isDebug) - Action = () => changelog?.ShowBuild(OsuGameBase.CLIENT_STREAM_NAME, version); + Action = () => changelog?.ShowBuild(OsuGameBase.CLIENT_STREAM_NAME, version); Add(new OsuSpriteText { @@ -110,7 +107,7 @@ namespace osu.Game.Overlays.Settings Anchor = Anchor.Centre, Origin = Anchor.Centre, Padding = new MarginPadding(5), - Colour = isDebug ? colours.Red : Color4.White, + Colour = DebugUtils.IsDebugBuild ? colours.Red : Color4.White, }); } } From c6bf755e688e149f6406b0fc57dcfb56df1297e7 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 24 Jan 2023 00:22:29 -0800 Subject: [PATCH 084/142] Remove `IsPresent` override from `ChangelogOverlay` --- osu.Game/Overlays/ChangelogOverlay.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Overlays/ChangelogOverlay.cs b/osu.Game/Overlays/ChangelogOverlay.cs index 671d649dcf..34472cb7cd 100644 --- a/osu.Game/Overlays/ChangelogOverlay.cs +++ b/osu.Game/Overlays/ChangelogOverlay.cs @@ -22,8 +22,6 @@ namespace osu.Game.Overlays { public partial class ChangelogOverlay : OnlineOverlay { - public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks; - public readonly Bindable Current = new Bindable(); private List builds; From 3a47be6e002eecbea52c443741fad75041261f93 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Jan 2023 17:43:14 +0900 Subject: [PATCH 085/142] Fix argon hit circles occasionally going missing during editor seeking --- .../Skinning/Argon/ArgonMainCirclePiece.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs index db458ec48a..a62efa96bf 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs @@ -64,21 +64,18 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon outerGradient = new Circle // renders the outer bright gradient { Size = new Vector2(OUTER_GRADIENT_SIZE), - Alpha = 1, Anchor = Anchor.Centre, Origin = Anchor.Centre, }, innerGradient = new Circle // renders the inner bright gradient { Size = new Vector2(INNER_GRADIENT_SIZE), - Alpha = 1, Anchor = Anchor.Centre, Origin = Anchor.Centre, }, innerFill = new Circle // renders the inner dark fill { Size = new Vector2(INNER_FILL_SIZE), - Alpha = 1, Anchor = Anchor.Centre, Origin = Anchor.Centre, }, @@ -123,14 +120,18 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon // Accent colour may be changed many times during a paused gameplay state. // Schedule the change to avoid transforms piling up. - Scheduler.AddOnce(updateStateTransforms); + Scheduler.AddOnce(() => + { + updateStateTransforms(drawableObject, drawableObject.State.Value); + + ApplyTransformsAt(double.MinValue, true); + ClearTransformsAfter(double.MinValue, true); + }); }, true); drawableObject.ApplyCustomUpdateState += updateStateTransforms; } - private void updateStateTransforms() => updateStateTransforms(drawableObject, drawableObject.State.Value); - private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state) { using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) From e0a7559d851fca88f92d8866cb54a08f8cd0f6b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9miah=20D=C3=89COMBE?= Date: Tue, 24 Jan 2023 09:55:08 +0100 Subject: [PATCH 086/142] variable naming + loc --- osu.Game/Localisation/UserInterfaceStrings.cs | 4 ++-- .../Settings/Sections/UserInterface/SongSelectSettings.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Localisation/UserInterfaceStrings.cs b/osu.Game/Localisation/UserInterfaceStrings.cs index 5e4c4dc8ed..fb9eeeb3de 100644 --- a/osu.Game/Localisation/UserInterfaceStrings.cs +++ b/osu.Game/Localisation/UserInterfaceStrings.cs @@ -110,9 +110,9 @@ namespace osu.Game.Localisation public static LocalisableString ModSelectHotkeyStyle => new TranslatableString(getKey(@"mod_select_hotkey_style"), @"Mod select hotkey style"); /// - /// "Song select background blur" + /// "Background blur" /// - public static LocalisableString SongSelectBackgroundBlurLevel => new TranslatableString(getKey(@"song_select_background_blur_level"), @"Song select background blur"); + public static LocalisableString BackgroundBlurLevel => new TranslatableString(getKey(@"background_blur_level"), @"Background blur"); /// /// "no limit" diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs index ab5a3a1280..11f7976842 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs @@ -45,7 +45,7 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface }, new SettingsCheckbox { - LabelText = UserInterfaceStrings.SongSelectBackgroundBlurLevel, + LabelText = UserInterfaceStrings.BackgroundBlurLevel, Current = config.GetBindable(OsuSetting.SongSelectBackgoundBlur) } }; diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index bbb88052aa..505f932bff 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -125,13 +125,13 @@ namespace osu.Game.Screens.Select [Resolved] internal IOverlayManager? OverlayManager { get; private set; } - private Bindable backgroundBlurLevel { get; set; } = new BindableBool(); + private Bindable configBackgroundBlur { get; set; } = new BindableBool(); [BackgroundDependencyLoader(true)] private void load(AudioManager audio, OsuColour colours, ManageCollectionsDialog? manageCollectionsDialog, DifficultyRecommender? recommender, OsuConfigManager config) { - backgroundBlurLevel = config.GetBindable(OsuSetting.SongSelectBackgoundBlur); - backgroundBlurLevel.BindValueChanged(e => + configBackgroundBlur = config.GetBindable(OsuSetting.SongSelectBackgoundBlur); + configBackgroundBlur.BindValueChanged(e => { if (this.IsCurrentScreen()) { @@ -758,7 +758,7 @@ namespace osu.Game.Screens.Select ApplyToBackground(backgroundModeBeatmap => { backgroundModeBeatmap.Beatmap = beatmap; - backgroundModeBeatmap.BlurAmount.Value = backgroundBlurLevel.Value ? BACKGROUND_BLUR : 0f; + backgroundModeBeatmap.BlurAmount.Value = configBackgroundBlur.Value ? BACKGROUND_BLUR : 0f; backgroundModeBeatmap.FadeColour(Color4.White, 250); }); From b9291cb116577b4549aa8b6177e4ec3da5018ffb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Jan 2023 17:59:25 +0900 Subject: [PATCH 087/142] Change some order and assert for positive visibility before scheduling an operation in changelog overlay --- osu.Game/Overlays/ChangelogOverlay.cs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/ChangelogOverlay.cs b/osu.Game/Overlays/ChangelogOverlay.cs index 34472cb7cd..4cc38c41e4 100644 --- a/osu.Game/Overlays/ChangelogOverlay.cs +++ b/osu.Game/Overlays/ChangelogOverlay.cs @@ -5,12 +5,14 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Game.Input.Bindings; using osu.Game.Online.API.Requests; @@ -79,6 +81,8 @@ namespace osu.Game.Overlays ArgumentNullException.ThrowIfNull(updateStream); ArgumentNullException.ThrowIfNull(version); + Show(); + performAfterFetch(() => { var build = builds.Find(b => b.Version == version && b.UpdateStream.Name == updateStream) @@ -87,8 +91,6 @@ namespace osu.Game.Overlays if (build != null) ShowBuild(build); }); - - Show(); } public override bool OnPressed(KeyBindingPressEvent e) @@ -125,11 +127,16 @@ namespace osu.Game.Overlays private Task initialFetchTask; - private void performAfterFetch(Action action) => Schedule(() => + private void performAfterFetch(Action action) { - fetchListing()?.ContinueWith(_ => - Schedule(action), TaskContinuationOptions.OnlyOnRanToCompletion); - }); + Debug.Assert(State.Value == Visibility.Visible); + + Schedule(() => + { + fetchListing()?.ContinueWith(_ => + Schedule(action), TaskContinuationOptions.OnlyOnRanToCompletion); + }); + } private Task fetchListing() { From acb42f7d1265fe8ab7a72d302a4af6d7e12d7bb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9miah=20D=C3=89COMBE?= Date: Tue, 24 Jan 2023 10:18:00 +0100 Subject: [PATCH 088/142] reusing gameplay background blur loc song select background blur --- .../Settings/Sections/UserInterface/SongSelectSettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs index 11f7976842..d40d24a528 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs @@ -45,7 +45,7 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface }, new SettingsCheckbox { - LabelText = UserInterfaceStrings.BackgroundBlurLevel, + LabelText = GameplaySettingsStrings.BackgroundBlur, Current = config.GetBindable(OsuSetting.SongSelectBackgoundBlur) } }; From c991aa5ca4f1a947c92e637228ee28c2b865e87b Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Tue, 24 Jan 2023 22:01:53 +0000 Subject: [PATCH 089/142] TestSceneWikiMarkdownContainer: Revert CurrentPath changes and update markdown img links only --- .../Online/TestSceneWikiMarkdownContainer.cs | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs index 7b8d04d25f..ab9fae5d2c 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs @@ -122,8 +122,8 @@ needs_cleanup: true { AddStep("Add absolute image", () => { - markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/images/Client/Interface/"; - markdownContainer.Text = "![intro](img/intro-screen.jpg)"; + markdownContainer.CurrentPath = "https://dev.ppy.sh"; + markdownContainer.Text = "![intro](wiki/images/Client/Interface/img/intro-screen.jpg)"; }); } @@ -132,8 +132,8 @@ needs_cleanup: true { AddStep("Add relative image", () => { - markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/images/Client/Interface/"; - markdownContainer.Text = "![intro](img/intro-screen.jpg)"; + markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/Interface/"; + markdownContainer.Text = "![intro](../images/Client/Interface/img/intro-screen.jpg)"; }); } @@ -142,10 +142,10 @@ needs_cleanup: true { AddStep("Add paragraph with block image", () => { - markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/images/Client/Interface/"; + markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/Interface/"; markdownContainer.Text = @"Line before image -![play menu](img/play-menu.jpg ""Main Menu in osu!"") +![play menu](../images/Client/Interface/img/play-menu.jpg ""Main Menu in osu!"") Line after image"; }); @@ -156,8 +156,8 @@ Line after image"; { AddStep("Add inline image", () => { - markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/shared/"; - markdownContainer.Text = "![osu! mode icon](mode/osu.png) osu!"; + markdownContainer.CurrentPath = "https://dev.ppy.sh"; + markdownContainer.Text = "![osu! mode icon](wiki/shared/mode/osu.png) osu!"; }); } @@ -166,16 +166,16 @@ Line after image"; { AddStep("Add Table", () => { - markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/images/shared/judgement/"; + markdownContainer.CurrentPath = "https://dev.ppy.sh"; markdownContainer.Text = @" | Image | Name | Effect | | :-: | :-: | :-- | -| ![](osu!/hit300.png ""300"") | 300 | A possible score when tapping a hit circle precisely on time, completing a Slider and keeping the cursor over every tick, or completing a Spinner with the Spinner Metre full. A score of 300 appears in an blue score by default. Scoring nothing except 300s in a beatmap will award the player with the SS or SSH grade. | -| ![](osu!/hit300g.png ""Geki"") | (激) Geki | A term from Ouendan, called Elite Beat! in EBA. Appears when playing the last element in a combo in which the player has scored only 300s. Getting a Geki will give a sizable boost to the Life Bar. By default, it is blue. | -| ![](osu!/hit100.png ""100"") | 100 | A possible score one can get when tapping a Hit Object slightly late or early, completing a Slider and missing a number of ticks, or completing a Spinner with the Spinner Meter almost full. A score of 100 appears in a green score by default. When very skilled players test a beatmap and they get a lot of 100s, this may mean that the beatmap does not have correct timing. | -| ![](osu!/hit300k.png ""300 Katu"") ![](/wiki/Skinning/Interface/img/hit100k.png ""100 Katu"") | (喝) Katu or Katsu | A term from Ouendan, called Beat! in EBA. Appears when playing the last element in a combo in which the player has scored at least one 100, but no 50s or misses. Getting a Katu will give a small boost to the Life Bar. By default, it is coloured green or blue depending on whether the Katu itself is a 100 or a 300. | -| ![](osu!/hit50.png ""50"") | 50 | A possible score one can get when tapping a hit circle rather early or late but not early or late enough to cause a miss, completing a Slider and missing a lot of ticks, or completing a Spinner with the Spinner Metre close to full. A score of 50 appears in a orange score by default. Scoring a 50 in a combo will prevent the appearance of a Katu or a Geki at the combo's end. | -| ![](osu!/hit0.png ""Miss"") | Miss | A possible score one can get when not tapping a hit circle or too early (based on OD and AR, it may *shake* instead), not tapping or holding the Slider at least once, or completing a Spinner with low Spinner Metre fill. Scoring a Miss will reset the current combo to 0 and will prevent the appearance of a Katu or a Geki at the combo's end. | +| ![](wiki/images/shared/judgement/osu!/hit300.png ""300"") | 300 | A possible score when tapping a hit circle precisely on time, completing a Slider and keeping the cursor over every tick, or completing a Spinner with the Spinner Metre full. A score of 300 appears in an blue score by default. Scoring nothing except 300s in a beatmap will award the player with the SS or SSH grade. | +| ![](wiki/images/shared/judgement/osu!/hit300g.png ""Geki"") | (激) Geki | A term from Ouendan, called Elite Beat! in EBA. Appears when playing the last element in a combo in which the player has scored only 300s. Getting a Geki will give a sizable boost to the Life Bar. By default, it is blue. | +| ![](wiki/images/shared/judgement/osu!/hit100.png ""100"") | 100 | A possible score one can get when tapping a Hit Object slightly late or early, completing a Slider and missing a number of ticks, or completing a Spinner with the Spinner Meter almost full. A score of 100 appears in a green score by default. When very skilled players test a beatmap and they get a lot of 100s, this may mean that the beatmap does not have correct timing. | +| ![](wiki/images/shared/judgement/osu!/hit300k.png ""300 Katu"") ![](/wiki/Skinning/Interface/img/hit100k.png ""100 Katu"") | (喝) Katu or Katsu | A term from Ouendan, called Beat! in EBA. Appears when playing the last element in a combo in which the player has scored at least one 100, but no 50s or misses. Getting a Katu will give a small boost to the Life Bar. By default, it is coloured green or blue depending on whether the Katu itself is a 100 or a 300. | +| ![](wiki/images/shared/judgement/osu!/hit50.png ""50"") | 50 | A possible score one can get when tapping a hit circle rather early or late but not early or late enough to cause a miss, completing a Slider and missing a lot of ticks, or completing a Spinner with the Spinner Metre close to full. A score of 50 appears in a orange score by default. Scoring a 50 in a combo will prevent the appearance of a Katu or a Geki at the combo's end. | +| ![](wiki/images/shared/judgement/osu!/hit0.png ""Miss"") | Miss | A possible score one can get when not tapping a hit circle or too early (based on OD and AR, it may *shake* instead), not tapping or holding the Slider at least once, or completing a Spinner with low Spinner Metre fill. Scoring a Miss will reset the current combo to 0 and will prevent the appearance of a Katu or a Geki at the combo's end. | "; }); } @@ -185,8 +185,8 @@ Line after image"; { AddStep("Add image", () => { - markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/images/Client/Program_files/"; - markdownContainer.Text = "![](img/file_structure.jpg \"The file structure of osu!'s installation folder, on Windows and macOS\")"; + markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/osu!_Program_Files/"; + markdownContainer.Text = "![](../images/Client/Program_files/img/file_structure.jpg \"The file structure of osu!'s installation folder, on Windows and macOS\")"; }); AddUntilStep("Wait image to load", () => markdownContainer.ChildrenOfType().First().DelayedLoadCompleted); From ff22a91d5287aaf19b7a8f5f80dfba2e7b75b92f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 31 Dec 2022 19:36:17 +0100 Subject: [PATCH 090/142] Move user cover lower down --- .../Profile/Header/TopHeaderContainer.cs | 165 +++++++++++------- osu.Game/Overlays/Profile/ProfileHeader.cs | 35 +--- 2 files changed, 99 insertions(+), 101 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs index 1fe39cb570..270be7cdf4 100644 --- a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs @@ -14,6 +14,7 @@ using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Overlays.Profile.Header.Components; +using osu.Game.Users; using osu.Game.Users.Drawables; using osuTK; @@ -28,6 +29,7 @@ namespace osu.Game.Overlays.Profile.Header [Resolved] private IAPIProvider api { get; set; } = null!; + private UserCoverBackground cover = null!; private SupporterIcon supporterTag = null!; private UpdateableAvatar avatar = null!; private OsuSpriteText usernameText = null!; @@ -40,7 +42,8 @@ namespace osu.Game.Overlays.Profile.Header [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) { - Height = 150; + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; InternalChildren = new Drawable[] { @@ -51,52 +54,73 @@ namespace osu.Game.Overlays.Profile.Header }, new FillFlowContainer { - Direction = FillDirection.Horizontal, - Margin = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN }, - Height = avatar_size, - AutoSizeAxes = Axes.X, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, Children = new Drawable[] { - avatar = new UpdateableAvatar(isInteractive: false, showGuestOnNull: false) + cover = new ProfileCoverBackground { - Size = new Vector2(avatar_size), - Masking = true, - CornerRadius = avatar_size * 0.25f, + RelativeSizeAxes = Axes.X, + Height = 250, }, - new OsuContextMenuContainer + new FillFlowContainer { - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - Child = new Container + Direction = FillDirection.Horizontal, + Margin = new MarginPadding { - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - Padding = new MarginPadding { Left = 10 }, - Children = new Drawable[] + Left = UserProfileOverlay.CONTENT_X_MARGIN, + Vertical = 10 + }, + Height = avatar_size, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + avatar = new UpdateableAvatar(isInteractive: false, showGuestOnNull: false) { - new FillFlowContainer + Size = new Vector2(avatar_size), + Masking = true, + CornerRadius = avatar_size * 0.25f, + }, + new OsuContextMenuContainer + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Child = new Container { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Padding = new MarginPadding { Left = 10 }, Children = new Drawable[] { new FillFlowContainer { AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(5), + Direction = FillDirection.Vertical, Children = new Drawable[] { - usernameText = new OsuSpriteText + new FillFlowContainer { - Font = OsuFont.GetFont(size: 24, weight: FontWeight.Regular) + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + usernameText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 24, weight: FontWeight.Regular) + }, + openUserExternally = new ExternalLinkButton + { + Margin = new MarginPadding { Left = 5 }, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + } }, - openUserExternally = new ExternalLinkButton + titleText = new OsuSpriteText { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, + Font = OsuFont.GetFont(size: 18, weight: FontWeight.Regular) }, groupBadgeFlow = new GroupBadgeFlow { @@ -105,54 +129,50 @@ namespace osu.Game.Overlays.Profile.Header } } }, - titleText = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 18, weight: FontWeight.Regular) - }, - } - }, - new FillFlowContainer - { - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, - Direction = FillDirection.Vertical, - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - supporterTag = new SupporterIcon - { - Height = 20, - Margin = new MarginPadding { Top = 5 } - }, - new Box - { - RelativeSizeAxes = Axes.X, - Height = 1.5f, - Margin = new MarginPadding { Top = 10 }, - Colour = colourProvider.Light1, - }, new FillFlowContainer { + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + Direction = FillDirection.Vertical, AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Top = 5 }, - Direction = FillDirection.Horizontal, Children = new Drawable[] { - userFlag = new UpdateableFlag + supporterTag = new SupporterIcon { - Size = new Vector2(28, 20), - ShowPlaceholderOnUnknown = false, + Height = 20, + Margin = new MarginPadding { Top = 5 } }, - userCountryText = new OsuSpriteText + new Box { - Font = OsuFont.GetFont(size: 17.5f, weight: FontWeight.Regular), - Margin = new MarginPadding { Left = 10 }, - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + Height = 1.5f, + Margin = new MarginPadding { Top = 10 }, Colour = colourProvider.Light1, - } + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Top = 5 }, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + userFlag = new UpdateableFlag + { + Size = new Vector2(28, 20), + ShowPlaceholderOnUnknown = false, + }, + userCountryText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 17.5f, weight: FontWeight.Regular), + Margin = new MarginPadding { Left = 10 }, + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Colour = colourProvider.Light1, + } + } + }, } - }, + } } } } @@ -169,6 +189,7 @@ namespace osu.Game.Overlays.Profile.Header { var user = data?.User; + cover.User = user; avatar.User = user; usernameText.Text = user?.Username ?? string.Empty; openUserExternally.Link = $@"{api.WebsiteRootUrl}/users/{user?.Id ?? 0}"; @@ -179,5 +200,15 @@ namespace osu.Game.Overlays.Profile.Header titleText.Colour = Color4Extensions.FromHex(user?.Colour ?? "fff"); groupBadgeFlow.User.Value = user; } + + private partial class ProfileCoverBackground : UserCoverBackground + { + protected override double LoadDelay => 0; + + public ProfileCoverBackground() + { + Masking = true; + } + } } } diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index c559a1c102..67b723b7e6 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -3,23 +3,17 @@ using System.Diagnostics; using osu.Framework.Bindables; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Framework.Localisation; using osu.Game.Overlays.Profile.Header; using osu.Game.Overlays.Profile.Header.Components; using osu.Game.Resources.Localisation.Web; -using osu.Game.Users; namespace osu.Game.Overlays.Profile { public partial class ProfileHeader : TabControlOverlayHeader { - private UserCoverBackground coverContainer = null!; - public Bindable User = new Bindable(); private CentreHeaderContainer centreHeaderContainer; @@ -29,8 +23,6 @@ namespace osu.Game.Overlays.Profile { ContentSidePadding = UserProfileOverlay.CONTENT_X_MARGIN; - User.ValueChanged += e => updateDisplay(e.NewValue); - TabControl.AddItem(LayoutStrings.HeaderUsersShow); // todo: pending implementation. @@ -41,25 +33,7 @@ namespace osu.Game.Overlays.Profile Debug.Assert(detailHeaderContainer != null); } - protected override Drawable CreateBackground() => - new Container - { - RelativeSizeAxes = Axes.X, - Height = 150, - Masking = true, - Children = new Drawable[] - { - coverContainer = new ProfileCoverBackground - { - RelativeSizeAxes = Axes.Both, - }, - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientVertical(Color4Extensions.FromHex("222").Opacity(0.8f), Color4Extensions.FromHex("222").Opacity(0.2f)) - }, - } - }; + protected override Drawable CreateBackground() => Empty(); protected override Drawable CreateContent() => new FillFlowContainer { @@ -103,8 +77,6 @@ namespace osu.Game.Overlays.Profile User = { BindTarget = User } }; - private void updateDisplay(UserProfileData? user) => coverContainer.User = user?.User; - private partial class ProfileHeaderTitle : OverlayTitle { public ProfileHeaderTitle() @@ -113,10 +85,5 @@ namespace osu.Game.Overlays.Profile IconTexture = "Icons/Hexacons/profile"; } } - - private partial class ProfileCoverBackground : UserCoverBackground - { - protected override double LoadDelay => 0; - } } } From ef7812412b5eadf301de7aa2d7e090f450d76d65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 31 Dec 2022 19:58:58 +0100 Subject: [PATCH 091/142] Update top header container appearance --- .../Online/TestSceneUserProfileOverlay.cs | 5 +- .../Profile/Header/TopHeaderContainer.cs | 114 ++++++++---------- 2 files changed, 54 insertions(+), 65 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs index fc8c2c0b6e..9aaa616c04 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs @@ -82,7 +82,7 @@ namespace osu.Game.Tests.Visual.Online { Username = @"Somebody", Id = 1, - CountryCode = CountryCode.Unknown, + CountryCode = CountryCode.JP, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c1.jpg", JoinDate = DateTimeOffset.Now.AddDays(-1), LastVisit = DateTimeOffset.Now, @@ -143,7 +143,8 @@ namespace osu.Game.Tests.Visual.Online { Available = 10, Total = 50 - } + }, + SupportLevel = 2, }; } } diff --git a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs index 270be7cdf4..a71db23989 100644 --- a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs @@ -7,6 +7,7 @@ using osu.Framework.Extensions; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -22,7 +23,7 @@ namespace osu.Game.Overlays.Profile.Header { public partial class TopHeaderContainer : CompositeDrawable { - private const float avatar_size = 110; + private const float avatar_size = 120; public readonly Bindable User = new Bindable(); @@ -67,60 +68,66 @@ namespace osu.Game.Overlays.Profile.Header new FillFlowContainer { Direction = FillDirection.Horizontal, - Margin = new MarginPadding + Padding = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN, Vertical = 10 }, - Height = avatar_size, + Spacing = new Vector2(20, 0), + Height = 85, RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, Children = new Drawable[] { avatar = new UpdateableAvatar(isInteractive: false, showGuestOnNull: false) { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, Size = new Vector2(avatar_size), Masking = true, CornerRadius = avatar_size * 0.25f, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Offset = new Vector2(0, 1), + Radius = 3, + Colour = Colour4.Black.Opacity(0.25f), + } }, new OsuContextMenuContainer { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, - Child = new Container + Child = new FillFlowContainer { - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - Padding = new MarginPadding { Left = 10 }, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, Children = new Drawable[] { new FillFlowContainer { AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5, 0), Children = new Drawable[] { - new FillFlowContainer + usernameText = new OsuSpriteText { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Children = new Drawable[] - { - usernameText = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 24, weight: FontWeight.Regular) - }, - openUserExternally = new ExternalLinkButton - { - Margin = new MarginPadding { Left = 5 }, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - }, - } + Font = OsuFont.GetFont(size: 24, weight: FontWeight.Regular) }, - titleText = new OsuSpriteText + supporterTag = new SupporterIcon { - Font = OsuFont.GetFont(size: 18, weight: FontWeight.Regular) + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Height = 15, + }, + openUserExternally = new ExternalLinkButton + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, }, groupBadgeFlow = new GroupBadgeFlow { @@ -129,52 +136,33 @@ namespace osu.Game.Overlays.Profile.Header } } }, + titleText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 16, weight: FontWeight.Regular), + Margin = new MarginPadding { Bottom = 5 } + }, new FillFlowContainer { - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, - Direction = FillDirection.Vertical, AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, Children = new Drawable[] { - supporterTag = new SupporterIcon + userFlag = new UpdateableFlag { - Height = 20, - Margin = new MarginPadding { Top = 5 } + Size = new Vector2(28, 20), + ShowPlaceholderOnUnknown = false, }, - new Box + userCountryText = new OsuSpriteText { - RelativeSizeAxes = Axes.X, - Height = 1.5f, - Margin = new MarginPadding { Top = 10 }, - Colour = colourProvider.Light1, - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Top = 5 }, - Direction = FillDirection.Horizontal, - Children = new Drawable[] - { - userFlag = new UpdateableFlag - { - Size = new Vector2(28, 20), - ShowPlaceholderOnUnknown = false, - }, - userCountryText = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 17.5f, weight: FontWeight.Regular), - Margin = new MarginPadding { Left = 10 }, - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Colour = colourProvider.Light1, - } - } - }, + Font = OsuFont.GetFont(size: 14f, weight: FontWeight.Regular), + Margin = new MarginPadding { Left = 5 }, + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + } } - } + }, } - } + }, } } } From e74176e5bd49fdf2462bf93fbf81225c5b7bb5e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 31 Dec 2022 20:09:49 +0100 Subject: [PATCH 092/142] Add cover toggle button --- ...dDetailsButton.cs => ToggleCoverButton.cs} | 21 +- .../Profile/Header/TopHeaderContainer.cs | 198 ++++++++++-------- 2 files changed, 117 insertions(+), 102 deletions(-) rename osu.Game/Overlays/Profile/Header/Components/{ExpandDetailsButton.cs => ToggleCoverButton.cs} (68%) diff --git a/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs b/osu.Game/Overlays/Profile/Header/Components/ToggleCoverButton.cs similarity index 68% rename from osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs rename to osu.Game/Overlays/Profile/Header/Components/ToggleCoverButton.cs index e7a83c95d8..9ae529f3ae 100644 --- a/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/ToggleCoverButton.cs @@ -5,7 +5,6 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; @@ -15,11 +14,11 @@ using osuTK; namespace osu.Game.Overlays.Profile.Header.Components { - public partial class ExpandDetailsButton : ProfileHeaderButton + public partial class ToggleCoverButton : ProfileHeaderButton { - public readonly BindableBool DetailsVisible = new BindableBool(); + public readonly BindableBool CoverVisible = new BindableBool(); - public override LocalisableString TooltipText => DetailsVisible.Value ? CommonStrings.ButtonsCollapse : CommonStrings.ButtonsExpand; + public override LocalisableString TooltipText => CoverVisible.Value ? UsersStrings.ShowCoverTo0 : UsersStrings.ShowCoverTo1; private SpriteIcon icon = null!; private Sample? sampleOpen; @@ -27,12 +26,12 @@ namespace osu.Game.Overlays.Profile.Header.Components protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverClickSounds(); - public ExpandDetailsButton() + public ToggleCoverButton() { Action = () => { - DetailsVisible.Toggle(); - (DetailsVisible.Value ? sampleOpen : sampleClose)?.Play(); + CoverVisible.Toggle(); + (CoverVisible.Value ? sampleOpen : sampleClose)?.Play(); }; } @@ -40,19 +39,21 @@ namespace osu.Game.Overlays.Profile.Header.Components private void load(OverlayColourProvider colourProvider, AudioManager audio) { IdleColour = colourProvider.Background2; - HoverColour = colourProvider.Background2.Lighten(0.2f); + HoverColour = colourProvider.Background1; sampleOpen = audio.Samples.Get(@"UI/dropdown-open"); sampleClose = audio.Samples.Get(@"UI/dropdown-close"); + AutoSizeAxes = Axes.None; + Size = new Vector2(30); Child = icon = new SpriteIcon { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(20, 12) + Size = new Vector2(10.5f, 12) }; - DetailsVisible.BindValueChanged(visible => updateState(visible.NewValue), true); + CoverVisible.BindValueChanged(visible => updateState(visible.NewValue), true); } private void updateState(bool detailsVisible) => icon.Icon = detailsVisible ? FontAwesome.Solid.ChevronUp : FontAwesome.Solid.ChevronDown; diff --git a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs index a71db23989..0204800326 100644 --- a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs @@ -65,108 +65,122 @@ namespace osu.Game.Overlays.Profile.Header RelativeSizeAxes = Axes.X, Height = 250, }, - new FillFlowContainer + new Container { - Direction = FillDirection.Horizontal, - Padding = new MarginPadding - { - Left = UserProfileOverlay.CONTENT_X_MARGIN, - Vertical = 10 - }, - Spacing = new Vector2(20, 0), - Height = 85, RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, Children = new Drawable[] { - avatar = new UpdateableAvatar(isInteractive: false, showGuestOnNull: false) + new FillFlowContainer { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Size = new Vector2(avatar_size), - Masking = true, - CornerRadius = avatar_size * 0.25f, - EdgeEffect = new EdgeEffectParameters + Direction = FillDirection.Horizontal, + Padding = new MarginPadding { - Type = EdgeEffectType.Shadow, - Offset = new Vector2(0, 1), - Radius = 3, - Colour = Colour4.Black.Opacity(0.25f), + Left = UserProfileOverlay.CONTENT_X_MARGIN, + Vertical = 10 + }, + Spacing = new Vector2(20, 0), + Height = 85, + RelativeSizeAxes = Axes.X, + Children = new Drawable[] + { + avatar = new UpdateableAvatar(isInteractive: false, showGuestOnNull: false) + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Size = new Vector2(avatar_size), + Masking = true, + CornerRadius = avatar_size * 0.25f, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Offset = new Vector2(0, 1), + Radius = 3, + Colour = Colour4.Black.Opacity(0.25f), + } + }, + new OsuContextMenuContainer + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Child = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Children = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5, 0), + Children = new Drawable[] + { + usernameText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 24, weight: FontWeight.Regular) + }, + supporterTag = new SupporterIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Height = 15, + }, + openUserExternally = new ExternalLinkButton + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + groupBadgeFlow = new GroupBadgeFlow + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + } + }, + titleText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 16, weight: FontWeight.Regular), + Margin = new MarginPadding { Bottom = 5 } + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + userFlag = new UpdateableFlag + { + Size = new Vector2(28, 20), + ShowPlaceholderOnUnknown = false, + }, + userCountryText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 14f, weight: FontWeight.Regular), + Margin = new MarginPadding { Left = 5 }, + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + } + } + }, + } + }, + }, } }, - new OsuContextMenuContainer + new ToggleCoverButton { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - Child = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Children = new Drawable[] - { - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(5, 0), - Children = new Drawable[] - { - usernameText = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 24, weight: FontWeight.Regular) - }, - supporterTag = new SupporterIcon - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Height = 15, - }, - openUserExternally = new ExternalLinkButton - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - }, - groupBadgeFlow = new GroupBadgeFlow - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - } - } - }, - titleText = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 16, weight: FontWeight.Regular), - Margin = new MarginPadding { Bottom = 5 } - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Children = new Drawable[] - { - userFlag = new UpdateableFlag - { - Size = new Vector2(28, 20), - ShowPlaceholderOnUnknown = false, - }, - userCountryText = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 14f, weight: FontWeight.Regular), - Margin = new MarginPadding { Left = 5 }, - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - } - } - }, - } - }, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Margin = new MarginPadding { Right = 10 } } - } - } - } + }, + }, + }, }, }; From 33e91cf51227294bec0ccf74904ef18813aafd2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 31 Dec 2022 20:28:28 +0100 Subject: [PATCH 093/142] Implement cover toggling --- .../Header/Components/ToggleCoverButton.cs | 2 +- .../Profile/Header/TopHeaderContainer.cs | 31 ++++++++++++++----- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/ToggleCoverButton.cs b/osu.Game/Overlays/Profile/Header/Components/ToggleCoverButton.cs index 9ae529f3ae..ef13f089c3 100644 --- a/osu.Game/Overlays/Profile/Header/Components/ToggleCoverButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/ToggleCoverButton.cs @@ -16,7 +16,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { public partial class ToggleCoverButton : ProfileHeaderButton { - public readonly BindableBool CoverVisible = new BindableBool(); + public readonly BindableBool CoverVisible = new BindableBool(true); public override LocalisableString TooltipText => CoverVisible.Value ? UsersStrings.ShowCoverTo0 : UsersStrings.ShowCoverTo1; diff --git a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs index 0204800326..ce0c56b52b 100644 --- a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs @@ -23,7 +23,8 @@ namespace osu.Game.Overlays.Profile.Header { public partial class TopHeaderContainer : CompositeDrawable { - private const float avatar_size = 120; + private const float content_height = 65; + private const float vertical_padding = 10; public readonly Bindable User = new Bindable(); @@ -39,6 +40,7 @@ namespace osu.Game.Overlays.Profile.Header private UpdateableFlag userFlag = null!; private OsuSpriteText userCountryText = null!; private GroupBadgeFlow groupBadgeFlow = null!; + private ToggleCoverButton coverToggle = null!; [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) @@ -63,7 +65,6 @@ namespace osu.Game.Overlays.Profile.Header cover = new ProfileCoverBackground { RelativeSizeAxes = Axes.X, - Height = 250, }, new Container { @@ -77,10 +78,10 @@ namespace osu.Game.Overlays.Profile.Header Padding = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN, - Vertical = 10 + Vertical = vertical_padding }, Spacing = new Vector2(20, 0), - Height = 85, + Height = content_height + 2 * vertical_padding, RelativeSizeAxes = Axes.X, Children = new Drawable[] { @@ -88,9 +89,7 @@ namespace osu.Game.Overlays.Profile.Header { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, - Size = new Vector2(avatar_size), Masking = true, - CornerRadius = avatar_size * 0.25f, EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Shadow, @@ -172,7 +171,7 @@ namespace osu.Game.Overlays.Profile.Header }, } }, - new ToggleCoverButton + coverToggle = new ToggleCoverButton { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, @@ -183,8 +182,15 @@ namespace osu.Game.Overlays.Profile.Header }, }, }; + } - User.BindValueChanged(user => updateUser(user.NewValue)); + protected override void LoadComplete() + { + base.LoadComplete(); + + User.BindValueChanged(user => updateUser(user.NewValue), true); + coverToggle.CoverVisible.BindValueChanged(_ => updateCoverState(), true); + FinishTransforms(true); } private void updateUser(UserProfileData? data) @@ -203,6 +209,15 @@ namespace osu.Game.Overlays.Profile.Header groupBadgeFlow.User.Value = user; } + private void updateCoverState() + { + const float transition_duration = 250; + + cover.ResizeHeightTo(coverToggle.CoverVisible.Value ? 250 : 0, transition_duration, Easing.OutQuint); + avatar.ResizeTo(new Vector2(coverToggle.CoverVisible.Value ? 120 : content_height), transition_duration, Easing.OutQuint); + avatar.TransformTo(nameof(avatar.CornerRadius), coverToggle.CoverVisible.Value ? 40f : 20f, transition_duration, Easing.OutQuint); + } + private partial class ProfileCoverBackground : UserCoverBackground { protected override double LoadDelay => 0; From f2df36e6a556be58a73df652d778aea35b0e780b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 24 Jan 2023 23:30:07 +0100 Subject: [PATCH 094/142] Persist cover visibility to user settings Follows web precedent, wherein the setting is saved to local storage. --- .../Online/TestSceneUserProfileHeader.cs | 20 +++++++++++++++++++ osu.Game/Configuration/OsuConfigManager.cs | 3 +++ .../Header/Components/ToggleCoverButton.cs | 10 +++++----- .../Profile/Header/TopHeaderContainer.cs | 18 +++++++++++------ 4 files changed, 40 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs index 513c473830..640e895b6c 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs @@ -6,6 +6,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Testing; +using osu.Game.Configuration; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Overlays.Profile; @@ -19,6 +20,9 @@ namespace osu.Game.Tests.Visual.Online [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green); + [Resolved] + private OsuConfigManager configManager { get; set; } = null!; + private ProfileHeader header = null!; [SetUpSteps] @@ -33,6 +37,22 @@ namespace osu.Game.Tests.Visual.Online AddStep("Show example user", () => header.User.Value = new UserProfileData(TestSceneUserProfileOverlay.TEST_USER, new OsuRuleset().RulesetInfo)); } + [Test] + public void TestProfileCoverExpanded() + { + AddStep("Set cover to expanded", () => configManager.SetValue(OsuSetting.ProfileCoverExpanded, true)); + AddStep("Show example user", () => header.User.Value = new UserProfileData(TestSceneUserProfileOverlay.TEST_USER, new OsuRuleset().RulesetInfo)); + AddUntilStep("Cover is expanded", () => header.ChildrenOfType().Single().Height, () => Is.GreaterThan(0)); + } + + [Test] + public void TestProfileCoverCollapsed() + { + AddStep("Set cover to collapsed", () => configManager.SetValue(OsuSetting.ProfileCoverExpanded, false)); + AddStep("Show example user", () => header.User.Value = new UserProfileData(TestSceneUserProfileOverlay.TEST_USER, new OsuRuleset().RulesetInfo)); + AddUntilStep("Cover is collapsed", () => header.ChildrenOfType().Single().Height, () => Is.EqualTo(0)); + } + [Test] public void TestOnlineState() { diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 6cbb677a64..507d5e611c 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -58,6 +58,8 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.BeatmapListingCardSize, BeatmapCardSize.Normal); + SetDefault(OsuSetting.ProfileCoverExpanded, true); + SetDefault(OsuSetting.ToolbarClockDisplayMode, ToolbarClockDisplayMode.Full); // Online settings @@ -375,5 +377,6 @@ namespace osu.Game.Configuration LastProcessedMetadataId, SafeAreaConsiderations, ComboColourNormalisationAmount, + ProfileCoverExpanded, } } diff --git a/osu.Game/Overlays/Profile/Header/Components/ToggleCoverButton.cs b/osu.Game/Overlays/Profile/Header/Components/ToggleCoverButton.cs index ef13f089c3..9171d5de7d 100644 --- a/osu.Game/Overlays/Profile/Header/Components/ToggleCoverButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/ToggleCoverButton.cs @@ -16,9 +16,9 @@ namespace osu.Game.Overlays.Profile.Header.Components { public partial class ToggleCoverButton : ProfileHeaderButton { - public readonly BindableBool CoverVisible = new BindableBool(true); + public readonly BindableBool CoverExpanded = new BindableBool(true); - public override LocalisableString TooltipText => CoverVisible.Value ? UsersStrings.ShowCoverTo0 : UsersStrings.ShowCoverTo1; + public override LocalisableString TooltipText => CoverExpanded.Value ? UsersStrings.ShowCoverTo0 : UsersStrings.ShowCoverTo1; private SpriteIcon icon = null!; private Sample? sampleOpen; @@ -30,8 +30,8 @@ namespace osu.Game.Overlays.Profile.Header.Components { Action = () => { - CoverVisible.Toggle(); - (CoverVisible.Value ? sampleOpen : sampleClose)?.Play(); + CoverExpanded.Toggle(); + (CoverExpanded.Value ? sampleOpen : sampleClose)?.Play(); }; } @@ -53,7 +53,7 @@ namespace osu.Game.Overlays.Profile.Header.Components Size = new Vector2(10.5f, 12) }; - CoverVisible.BindValueChanged(visible => updateState(visible.NewValue), true); + CoverExpanded.BindValueChanged(visible => updateState(visible.NewValue), true); } private void updateState(bool detailsVisible) => icon.Icon = detailsVisible ? FontAwesome.Solid.ChevronUp : FontAwesome.Solid.ChevronDown; diff --git a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs index ce0c56b52b..389f2301c4 100644 --- a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Cursor; @@ -42,12 +43,16 @@ namespace osu.Game.Overlays.Profile.Header private GroupBadgeFlow groupBadgeFlow = null!; private ToggleCoverButton coverToggle = null!; + private Bindable coverExpanded = null!; + [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) + private void load(OverlayColourProvider colourProvider, OsuConfigManager configManager) { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; + coverExpanded = configManager.GetBindable(OsuSetting.ProfileCoverExpanded); + InternalChildren = new Drawable[] { new Box @@ -175,7 +180,8 @@ namespace osu.Game.Overlays.Profile.Header { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, - Margin = new MarginPadding { Right = 10 } + Margin = new MarginPadding { Right = 10 }, + CoverExpanded = { BindTarget = coverExpanded } } }, }, @@ -189,7 +195,7 @@ namespace osu.Game.Overlays.Profile.Header base.LoadComplete(); User.BindValueChanged(user => updateUser(user.NewValue), true); - coverToggle.CoverVisible.BindValueChanged(_ => updateCoverState(), true); + coverExpanded.BindValueChanged(_ => updateCoverState(), true); FinishTransforms(true); } @@ -213,9 +219,9 @@ namespace osu.Game.Overlays.Profile.Header { const float transition_duration = 250; - cover.ResizeHeightTo(coverToggle.CoverVisible.Value ? 250 : 0, transition_duration, Easing.OutQuint); - avatar.ResizeTo(new Vector2(coverToggle.CoverVisible.Value ? 120 : content_height), transition_duration, Easing.OutQuint); - avatar.TransformTo(nameof(avatar.CornerRadius), coverToggle.CoverVisible.Value ? 40f : 20f, transition_duration, Easing.OutQuint); + cover.ResizeHeightTo(coverToggle.CoverExpanded.Value ? 250 : 0, transition_duration, Easing.OutQuint); + avatar.ResizeTo(new Vector2(coverToggle.CoverExpanded.Value ? 120 : content_height), transition_duration, Easing.OutQuint); + avatar.TransformTo(nameof(avatar.CornerRadius), coverToggle.CoverExpanded.Value ? 40f : 20f, transition_duration, Easing.OutQuint); } private partial class ProfileCoverBackground : UserCoverBackground From 4d7cd59df39faa42f462f20e39ea4db45ebd5af2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 24 Jan 2023 23:58:39 +0100 Subject: [PATCH 095/142] Fix `TestAbsoluteImage()` no longer actually testing absolute image URL --- osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs index ab9fae5d2c..ba9830181e 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs @@ -123,7 +123,7 @@ needs_cleanup: true AddStep("Add absolute image", () => { markdownContainer.CurrentPath = "https://dev.ppy.sh"; - markdownContainer.Text = "![intro](wiki/images/Client/Interface/img/intro-screen.jpg)"; + markdownContainer.Text = "![intro](/wiki/images/Client/Interface/img/intro-screen.jpg)"; }); } From a3bb6d1fab3a658d1b3566fa602db763c741814f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 25 Jan 2023 00:00:16 +0100 Subject: [PATCH 096/142] Revert a few more relative to absolute changes Because not sure why they were changed in the first place. --- .../Online/TestSceneWikiMarkdownContainer.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs index ba9830181e..0aa0295f7d 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs @@ -157,7 +157,7 @@ Line after image"; AddStep("Add inline image", () => { markdownContainer.CurrentPath = "https://dev.ppy.sh"; - markdownContainer.Text = "![osu! mode icon](wiki/shared/mode/osu.png) osu!"; + markdownContainer.Text = "![osu! mode icon](/wiki/shared/mode/osu.png) osu!"; }); } @@ -170,12 +170,12 @@ Line after image"; markdownContainer.Text = @" | Image | Name | Effect | | :-: | :-: | :-- | -| ![](wiki/images/shared/judgement/osu!/hit300.png ""300"") | 300 | A possible score when tapping a hit circle precisely on time, completing a Slider and keeping the cursor over every tick, or completing a Spinner with the Spinner Metre full. A score of 300 appears in an blue score by default. Scoring nothing except 300s in a beatmap will award the player with the SS or SSH grade. | -| ![](wiki/images/shared/judgement/osu!/hit300g.png ""Geki"") | (激) Geki | A term from Ouendan, called Elite Beat! in EBA. Appears when playing the last element in a combo in which the player has scored only 300s. Getting a Geki will give a sizable boost to the Life Bar. By default, it is blue. | -| ![](wiki/images/shared/judgement/osu!/hit100.png ""100"") | 100 | A possible score one can get when tapping a Hit Object slightly late or early, completing a Slider and missing a number of ticks, or completing a Spinner with the Spinner Meter almost full. A score of 100 appears in a green score by default. When very skilled players test a beatmap and they get a lot of 100s, this may mean that the beatmap does not have correct timing. | -| ![](wiki/images/shared/judgement/osu!/hit300k.png ""300 Katu"") ![](/wiki/Skinning/Interface/img/hit100k.png ""100 Katu"") | (喝) Katu or Katsu | A term from Ouendan, called Beat! in EBA. Appears when playing the last element in a combo in which the player has scored at least one 100, but no 50s or misses. Getting a Katu will give a small boost to the Life Bar. By default, it is coloured green or blue depending on whether the Katu itself is a 100 or a 300. | -| ![](wiki/images/shared/judgement/osu!/hit50.png ""50"") | 50 | A possible score one can get when tapping a hit circle rather early or late but not early or late enough to cause a miss, completing a Slider and missing a lot of ticks, or completing a Spinner with the Spinner Metre close to full. A score of 50 appears in a orange score by default. Scoring a 50 in a combo will prevent the appearance of a Katu or a Geki at the combo's end. | -| ![](wiki/images/shared/judgement/osu!/hit0.png ""Miss"") | Miss | A possible score one can get when not tapping a hit circle or too early (based on OD and AR, it may *shake* instead), not tapping or holding the Slider at least once, or completing a Spinner with low Spinner Metre fill. Scoring a Miss will reset the current combo to 0 and will prevent the appearance of a Katu or a Geki at the combo's end. | +| ![](/wiki/images/shared/judgement/osu!/hit300.png ""300"") | 300 | A possible score when tapping a hit circle precisely on time, completing a Slider and keeping the cursor over every tick, or completing a Spinner with the Spinner Metre full. A score of 300 appears in an blue score by default. Scoring nothing except 300s in a beatmap will award the player with the SS or SSH grade. | +| ![](/wiki/images/shared/judgement/osu!/hit300g.png ""Geki"") | (激) Geki | A term from Ouendan, called Elite Beat! in EBA. Appears when playing the last element in a combo in which the player has scored only 300s. Getting a Geki will give a sizable boost to the Life Bar. By default, it is blue. | +| ![](/wiki/images/shared/judgement/osu!/hit100.png ""100"") | 100 | A possible score one can get when tapping a Hit Object slightly late or early, completing a Slider and missing a number of ticks, or completing a Spinner with the Spinner Meter almost full. A score of 100 appears in a green score by default. When very skilled players test a beatmap and they get a lot of 100s, this may mean that the beatmap does not have correct timing. | +| ![](/wiki/images/shared/judgement/osu!/hit300k.png ""300 Katu"") ![](/wiki/Skinning/Interface/img/hit100k.png ""100 Katu"") | (喝) Katu or Katsu | A term from Ouendan, called Beat! in EBA. Appears when playing the last element in a combo in which the player has scored at least one 100, but no 50s or misses. Getting a Katu will give a small boost to the Life Bar. By default, it is coloured green or blue depending on whether the Katu itself is a 100 or a 300. | +| ![](/wiki/images/shared/judgement/osu!/hit50.png ""50"") | 50 | A possible score one can get when tapping a hit circle rather early or late but not early or late enough to cause a miss, completing a Slider and missing a lot of ticks, or completing a Spinner with the Spinner Metre close to full. A score of 50 appears in a orange score by default. Scoring a 50 in a combo will prevent the appearance of a Katu or a Geki at the combo's end. | +| ![](/wiki/images/shared/judgement/osu!/hit0.png ""Miss"") | Miss | A possible score one can get when not tapping a hit circle or too early (based on OD and AR, it may *shake* instead), not tapping or holding the Slider at least once, or completing a Spinner with low Spinner Metre fill. Scoring a Miss will reset the current combo to 0 and will prevent the appearance of a Katu or a Geki at the combo's end. | "; }); } From 4d4dfd9e8b7b263734edec6cb692e9174df8d7bc Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 25 Jan 2023 02:15:01 +0300 Subject: [PATCH 097/142] Fix iOS workflow still targeting old Xcode version --- .github/workflows/ci.yml | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 213c5082ab..5c11f91994 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -121,21 +121,12 @@ jobs: build-only-ios: name: Build only (iOS) - # change to macos-latest once GitHub finishes migrating all repositories to macOS 12. - runs-on: macos-12 + runs-on: macos-latest timeout-minutes: 60 steps: - name: Checkout uses: actions/checkout@v2 - # see https://github.com/actions/runner-images/issues/6771#issuecomment-1354713617 - # remove once all workflow VMs use Xcode 14.1 - - name: Set Xcode Version - shell: bash - run: | - sudo xcode-select -s "/Applications/Xcode_14.1.app" - echo "MD_APPLE_SDK_ROOT=/Applications/Xcode_14.1.app" >> $GITHUB_ENV - - name: Install .NET 6.0.x uses: actions/setup-dotnet@v1 with: From d8365f4fca4bfc0c52f261eaf79e67eafa7a8eb5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Jan 2023 11:47:15 +0900 Subject: [PATCH 098/142] Reverse order of application to match `DrawableHitObject` --- osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs index a62efa96bf..23c18d9207 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs @@ -122,10 +122,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon // Schedule the change to avoid transforms piling up. Scheduler.AddOnce(() => { - updateStateTransforms(drawableObject, drawableObject.State.Value); - ApplyTransformsAt(double.MinValue, true); ClearTransformsAfter(double.MinValue, true); + + updateStateTransforms(drawableObject, drawableObject.State.Value); }); }, true); From 739ec8d81d7c680aca106e7b49a4d7d43de67ca2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Jan 2023 14:04:30 +0900 Subject: [PATCH 099/142] Add argument hint for nondescript `bool` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Beatmaps/BeatmapManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index fc6d4081e7..89fe498f89 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -186,7 +186,7 @@ namespace osu.Game.Beatmaps targetBeatmapSet.Beatmaps.Add(newBeatmap.BeatmapInfo); newBeatmap.BeatmapInfo.BeatmapSet = targetBeatmapSet; - save(newBeatmap.BeatmapInfo, newBeatmap, beatmapSkin, false); + save(newBeatmap.BeatmapInfo, newBeatmap, beatmapSkin, transferCollections: false); workingBeatmapCache.Invalidate(targetBeatmapSet); return GetWorkingBeatmap(newBeatmap.BeatmapInfo); From 741ca968531f33dbd2f099a3d32d00d23ac4cb39 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Jan 2023 14:09:13 +0900 Subject: [PATCH 100/142] Make `transferCollections` argument to private method explicitly required --- osu.Game/Beatmaps/BeatmapManager.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 89fe498f89..f39523cedf 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -286,10 +286,8 @@ namespace osu.Game.Beatmaps /// The to save the content against. The file referenced by will be replaced. /// The content to write. /// The beatmap content to write, null if to be omitted. - public virtual void Save(BeatmapInfo beatmapInfo, IBeatmap beatmapContent, ISkin? beatmapSkin = null) - { - save(beatmapInfo, beatmapContent, beatmapSkin); - } + public virtual void Save(BeatmapInfo beatmapInfo, IBeatmap beatmapContent, ISkin? beatmapSkin = null) => + save(beatmapInfo, beatmapContent, beatmapSkin, transferCollections: true); public void DeleteAllVideos() { @@ -399,7 +397,7 @@ namespace osu.Game.Beatmaps setInfo.Status = BeatmapOnlineStatus.LocallyModified; } - private void save(BeatmapInfo beatmapInfo, IBeatmap beatmapContent, ISkin? beatmapSkin, bool transferCollections = true) + private void save(BeatmapInfo beatmapInfo, IBeatmap beatmapContent, ISkin? beatmapSkin, bool transferCollections) { var setInfo = beatmapInfo.BeatmapSet; Debug.Assert(setInfo != null); From 392ff2ffea6273063402d97580df9123b4874dd4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Jan 2023 14:11:02 +0900 Subject: [PATCH 101/142] Reword comment regarding hash transfer to make more sense --- osu.Game/Beatmaps/BeatmapManager.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index f39523cedf..ad56bbbc3a 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -280,9 +280,11 @@ namespace osu.Game.Beatmaps public IWorkingBeatmap DefaultBeatmap => workingBeatmapCache.DefaultBeatmap; /// - /// Saves an existing file against a given , also transferring the beatmap - /// hashes in any collections referencing it. + /// Saves an existing file against a given . /// + /// + /// This method will also update any user beatmap collection hash references to the new post-saved hash. + /// /// The to save the content against. The file referenced by will be replaced. /// The content to write. /// The beatmap content to write, null if to be omitted. From 3e91dd2a16094a84f6fdbeeadfbafe06a14999db Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Jan 2023 14:27:44 +0900 Subject: [PATCH 102/142] Update spacing along with expanded state --- .../Overlays/Profile/Header/TopHeaderContainer.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs index 389f2301c4..57197ab0de 100644 --- a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs @@ -45,6 +45,8 @@ namespace osu.Game.Overlays.Profile.Header private Bindable coverExpanded = null!; + private FillFlowContainer flow = null!; + [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider, OsuConfigManager configManager) { @@ -77,7 +79,7 @@ namespace osu.Game.Overlays.Profile.Header AutoSizeAxes = Axes.Y, Children = new Drawable[] { - new FillFlowContainer + flow = new FillFlowContainer { Direction = FillDirection.Horizontal, Padding = new MarginPadding @@ -85,7 +87,6 @@ namespace osu.Game.Overlays.Profile.Header Left = UserProfileOverlay.CONTENT_X_MARGIN, Vertical = vertical_padding }, - Spacing = new Vector2(20, 0), Height = content_height + 2 * vertical_padding, RelativeSizeAxes = Axes.X, Children = new Drawable[] @@ -219,9 +220,12 @@ namespace osu.Game.Overlays.Profile.Header { const float transition_duration = 250; - cover.ResizeHeightTo(coverToggle.CoverExpanded.Value ? 250 : 0, transition_duration, Easing.OutQuint); - avatar.ResizeTo(new Vector2(coverToggle.CoverExpanded.Value ? 120 : content_height), transition_duration, Easing.OutQuint); - avatar.TransformTo(nameof(avatar.CornerRadius), coverToggle.CoverExpanded.Value ? 40f : 20f, transition_duration, Easing.OutQuint); + bool expanded = coverToggle.CoverExpanded.Value; + + cover.ResizeHeightTo(expanded ? 250 : 0, transition_duration, Easing.OutQuint); + avatar.ResizeTo(new Vector2(expanded ? 120 : content_height), transition_duration, Easing.OutQuint); + avatar.TransformTo(nameof(avatar.CornerRadius), expanded ? 40f : 20f, transition_duration, Easing.OutQuint); + flow.TransformTo(nameof(flow.Spacing), new Vector2(expanded ? 20f : 10f), transition_duration, Easing.OutQuint); } private partial class ProfileCoverBackground : UserCoverBackground From 6bf7773532c117791c67451bae8280b84f2748cb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Jan 2023 14:27:54 +0900 Subject: [PATCH 103/142] Increase duration of expansion transition --- osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs index 57197ab0de..652ebcec26 100644 --- a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs @@ -218,7 +218,7 @@ namespace osu.Game.Overlays.Profile.Header private void updateCoverState() { - const float transition_duration = 250; + const float transition_duration = 500; bool expanded = coverToggle.CoverExpanded.Value; From 598c6fcbad58333eee5f0ab6e515989ba53ae308 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Jan 2023 15:48:57 +0900 Subject: [PATCH 104/142] Add basic support for stupidly long hold note skin textures --- .../special-skin/LongNoteTailWang.png | Bin 0 -> 101181 bytes .../Resources/special-skin/skin.ini | 4 +- .../Skinning/Legacy/LegacyBodyPiece.cs | 6 +- osu.Game/Skinning/Skin.cs | 56 +++++++++++++++++- 4 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 osu.Game.Rulesets.Mania.Tests/Resources/special-skin/LongNoteTailWang.png diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/LongNoteTailWang.png b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/LongNoteTailWang.png new file mode 100644 index 0000000000000000000000000000000000000000..982cc1d259efcd670f0e7baf338a11059c7b098c GIT binary patch literal 101181 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
  • m@nicEnXm?)|Fu4vV*{ZH((wm)g|Fp## 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 literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini index 9c987efc60..7c51036d69 100644 --- a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini +++ b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini @@ -14,4 +14,6 @@ Hit200: mania/hit200@2x Hit300: mania/hit300@2x Hit300g: mania/hit300g@2x StageLeft: mania/stage-left -StageRight: mania/stage-right \ No newline at end of file +StageRight: mania/stage-right +NoteImage0L: LongNoteTailWang +NoteImage1L: LongNoteTailWang diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs index c928ebb3e0..6887674a1f 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs @@ -54,6 +54,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy float lightScale = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.HoldNoteLightScale)?.Value ?? 1; + float minimumColumnWidth = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.MinimumColumnWidth)?.Value + ?? 1; + // Create a temporary animation to retrieve the number of frames, in an effort to calculate the intended frame length. // This animation is discarded and re-queried with the appropriate frame length afterwards. var tmp = skin.GetAnimation(lightImage, true, false); @@ -92,7 +95,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy d.RelativeSizeAxes = Axes.Both; d.Size = Vector2.One; d.FillMode = FillMode.Stretch; - // Todo: Wrap + d.Height = minimumColumnWidth / d.DrawWidth * 1.6f; + // Todo: Wrap? }); if (bodySprite != null) diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 19e8bc7092..2761a93290 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -7,9 +7,12 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Text; +using System.Threading; +using System.Threading.Tasks; using Newtonsoft.Json; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; @@ -18,6 +21,8 @@ using osu.Game.Audio; using osu.Game.Database; using osu.Game.IO; using osu.Game.Screens.Play.HUD; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Processing; namespace osu.Game.Skinning { @@ -77,7 +82,7 @@ namespace osu.Game.Skinning (storage as ResourceStore)?.AddExtension("ogg"); Samples = samples; - Textures = new TextureStore(resources.Renderer, resources.CreateTextureLoaderStore(storage)); + Textures = new TextureStore(resources.Renderer, new SquishingTextureLoaderStore(resources.CreateTextureLoaderStore(storage))); } else { @@ -210,5 +215,54 @@ namespace osu.Game.Skinning } #endregion + + public class SquishingTextureLoaderStore : IResourceStore + { + private readonly IResourceStore textureStore; + + public SquishingTextureLoaderStore(IResourceStore textureStore) + { + this.textureStore = textureStore; + } + + public void Dispose() + { + textureStore.Dispose(); + } + + public TextureUpload Get(string name) + { + var textureUpload = textureStore.Get(name); + + // NRT not enabled on framework side classes (IResourceStore / TextureLoaderStore), welp. + if (textureUpload.IsNull()) + return null!; + + // So there's a thing where some users have taken it upon themselves to create skin elements of insane dimensions. + // To the point where GPUs cannot load the textures (along with most image editor apps). + // To work around this, let's look out for any stupid images and shrink them down into a usable size. + const int max_supported_texture_size = 16384; + + if (textureUpload.Height > max_supported_texture_size || textureUpload.Width > max_supported_texture_size) + { + var image = Image.LoadPixelData(textureUpload.Data.ToArray(), textureUpload.Width, textureUpload.Height); + + image.Mutate(i => i.Resize(new Size( + Math.Min(textureUpload.Width, max_supported_texture_size), + Math.Min(textureUpload.Height, max_supported_texture_size) + ))); + + return new TextureUpload(image); + } + + return textureUpload; + } + + public Task GetAsync(string name, CancellationToken cancellationToken = new CancellationToken()) => textureStore.GetAsync(name, cancellationToken); + + public Stream GetStream(string name) => textureStore.GetStream(name); + + public IEnumerable GetAvailableResources() => textureStore.GetAvailableResources(); + } } } From d000a4ed28967ef7a40c541acb26df6969c82373 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Jan 2023 15:56:10 +0900 Subject: [PATCH 105/142] Make sure to dispose of the original texture upload as we are replacing it --- osu.Game/Skinning/Skin.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 2761a93290..298a94920f 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -247,6 +247,9 @@ namespace osu.Game.Skinning { var image = Image.LoadPixelData(textureUpload.Data.ToArray(), textureUpload.Width, textureUpload.Height); + // The original texture upload will no longer be returned or used. + textureUpload.Dispose(); + image.Mutate(i => i.Resize(new Size( Math.Min(textureUpload.Width, max_supported_texture_size), Math.Min(textureUpload.Height, max_supported_texture_size) From c1a5c16973d2a0a62b7393e5d647e2035793fa0a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Jan 2023 16:39:03 +0900 Subject: [PATCH 106/142] Reduce maximum texture size to a more commonly-supported `8192` --- osu.Game/Skinning/Skin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 298a94920f..c862a51870 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -241,7 +241,7 @@ namespace osu.Game.Skinning // So there's a thing where some users have taken it upon themselves to create skin elements of insane dimensions. // To the point where GPUs cannot load the textures (along with most image editor apps). // To work around this, let's look out for any stupid images and shrink them down into a usable size. - const int max_supported_texture_size = 16384; + const int max_supported_texture_size = 8192; if (textureUpload.Height > max_supported_texture_size || textureUpload.Width > max_supported_texture_size) { From 0a9b20d5d5f3a734e8b8f25dcefe8d6d08a41c22 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Jan 2023 20:01:00 +0900 Subject: [PATCH 107/142] Split lookup store into own file / class --- .../MaxDimensionLimitedTextureLoaderStore.cs | 69 ++++++++++++++++ osu.Game/Skinning/Skin.cs | 80 +++---------------- 2 files changed, 82 insertions(+), 67 deletions(-) create mode 100644 osu.Game/Skinning/MaxDimensionLimitedTextureLoaderStore.cs diff --git a/osu.Game/Skinning/MaxDimensionLimitedTextureLoaderStore.cs b/osu.Game/Skinning/MaxDimensionLimitedTextureLoaderStore.cs new file mode 100644 index 0000000000..94d7afaf7e --- /dev/null +++ b/osu.Game/Skinning/MaxDimensionLimitedTextureLoaderStore.cs @@ -0,0 +1,69 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using osu.Framework.Graphics.Textures; +using osu.Framework.IO.Stores; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Processing; + +namespace osu.Game.Skinning +{ + public class MaxDimensionLimitedTextureLoaderStore : IResourceStore + { + private readonly IResourceStore textureStore; + + public MaxDimensionLimitedTextureLoaderStore(IResourceStore textureStore) + { + this.textureStore = textureStore; + } + + public void Dispose() + { + textureStore?.Dispose(); + } + + public TextureUpload Get(string name) + { + var textureUpload = textureStore?.Get(name); + + // NRT not enabled on framework side classes (IResourceStore / TextureLoaderStore), welp. + if (textureUpload == null) + return null!; + + // So there's a thing where some users have taken it upon themselves to create skin elements of insane dimensions. + // To the point where GPUs cannot load the textures (along with most image editor apps). + // To work around this, let's look out for any stupid images and shrink them down into a usable size. + const int max_supported_texture_size = 8192; + + if (textureUpload.Height > max_supported_texture_size || textureUpload.Width > max_supported_texture_size) + { + var image = Image.LoadPixelData(textureUpload.Data.ToArray(), textureUpload.Width, textureUpload.Height); + + // The original texture upload will no longer be returned or used. + textureUpload.Dispose(); + + image.Mutate(i => i.Resize(new Size( + Math.Min(textureUpload.Width, max_supported_texture_size), + Math.Min(textureUpload.Height, max_supported_texture_size) + ))); + + return new TextureUpload(image); + } + + return textureUpload; + } + + public Task GetAsync(string name, CancellationToken cancellationToken = new CancellationToken()) => textureStore?.GetAsync(name, cancellationToken); + + public Stream GetStream(string name) => textureStore?.GetStream(name) ?? default; + + public IEnumerable GetAvailableResources() => textureStore?.GetAvailableResources(); + } +} diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index c862a51870..cc302a8b1e 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -1,18 +1,17 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable disable + using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; -using System.Threading; -using System.Threading.Tasks; using Newtonsoft.Json; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; -using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; @@ -21,8 +20,6 @@ using osu.Game.Audio; using osu.Game.Database; using osu.Game.IO; using osu.Game.Screens.Play.HUD; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Processing; namespace osu.Game.Skinning { @@ -31,12 +28,12 @@ namespace osu.Game.Skinning /// /// A texture store which can be used to perform user file lookups for this skin. /// - protected TextureStore? Textures { get; } + protected TextureStore Textures { get; } /// /// A sample store which can be used to perform user file lookups for this skin. /// - protected ISampleStore? Samples { get; } + protected ISampleStore Samples { get; } public readonly Live SkinInfo; @@ -46,17 +43,17 @@ namespace osu.Game.Skinning private readonly Dictionary drawableComponentInfo = new Dictionary(); - public abstract ISample? GetSample(ISampleInfo sampleInfo); + public abstract ISample GetSample(ISampleInfo sampleInfo); - public Texture? GetTexture(string componentName) => GetTexture(componentName, default, default); + public Texture GetTexture(string componentName) => GetTexture(componentName, default, default); - public abstract Texture? GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT); + public abstract Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT); - public abstract IBindable? GetConfig(TLookup lookup) + public abstract IBindable GetConfig(TLookup lookup) where TLookup : notnull where TValue : notnull; - private readonly RealmBackedResourceStore? realmBackedStorage; + private readonly RealmBackedResourceStore realmBackedStorage; /// /// Construct a new skin. @@ -65,7 +62,7 @@ namespace osu.Game.Skinning /// Access to game-wide resources. /// An optional store which will *replace* all file lookups that are usually sourced from . /// An optional filename to read the skin configuration from. If not provided, the configuration will be retrieved from the storage using "skin.ini". - protected Skin(SkinInfo skin, IStorageResourceProvider? resources, IResourceStore? storage = null, string configurationFilename = @"skin.ini") + protected Skin(SkinInfo skin, IStorageResourceProvider resources, IResourceStore storage = null, string configurationFilename = @"skin.ini") { if (resources != null) { @@ -82,7 +79,7 @@ namespace osu.Game.Skinning (storage as ResourceStore)?.AddExtension("ogg"); Samples = samples; - Textures = new TextureStore(resources.Renderer, new SquishingTextureLoaderStore(resources.CreateTextureLoaderStore(storage))); + Textures = new TextureStore(resources.Renderer, new MaxDimensionLimitedTextureLoaderStore(resources.CreateTextureLoaderStore(storage))); } else { @@ -106,7 +103,7 @@ namespace osu.Game.Skinning { string filename = $"{skinnableTarget}.json"; - byte[]? bytes = storage?.Get(filename); + byte[] bytes = storage?.Get(filename); if (bytes == null) continue; @@ -159,7 +156,7 @@ namespace osu.Game.Skinning DrawableComponentInfo[targetContainer.Target] = targetContainer.CreateSkinnableInfo().ToArray(); } - public virtual Drawable? GetDrawableComponent(ISkinComponentLookup lookup) + public virtual Drawable GetDrawableComponent(ISkinComponentLookup lookup) { switch (lookup) { @@ -216,56 +213,5 @@ namespace osu.Game.Skinning #endregion - public class SquishingTextureLoaderStore : IResourceStore - { - private readonly IResourceStore textureStore; - - public SquishingTextureLoaderStore(IResourceStore textureStore) - { - this.textureStore = textureStore; - } - - public void Dispose() - { - textureStore.Dispose(); - } - - public TextureUpload Get(string name) - { - var textureUpload = textureStore.Get(name); - - // NRT not enabled on framework side classes (IResourceStore / TextureLoaderStore), welp. - if (textureUpload.IsNull()) - return null!; - - // So there's a thing where some users have taken it upon themselves to create skin elements of insane dimensions. - // To the point where GPUs cannot load the textures (along with most image editor apps). - // To work around this, let's look out for any stupid images and shrink them down into a usable size. - const int max_supported_texture_size = 8192; - - if (textureUpload.Height > max_supported_texture_size || textureUpload.Width > max_supported_texture_size) - { - var image = Image.LoadPixelData(textureUpload.Data.ToArray(), textureUpload.Width, textureUpload.Height); - - // The original texture upload will no longer be returned or used. - textureUpload.Dispose(); - - image.Mutate(i => i.Resize(new Size( - Math.Min(textureUpload.Width, max_supported_texture_size), - Math.Min(textureUpload.Height, max_supported_texture_size) - ))); - - return new TextureUpload(image); - } - - return textureUpload; - } - - public Task GetAsync(string name, CancellationToken cancellationToken = new CancellationToken()) => textureStore.GetAsync(name, cancellationToken); - - public Stream GetStream(string name) => textureStore.GetStream(name); - - public IEnumerable GetAvailableResources() => textureStore.GetAvailableResources(); - } } } From 8caf960f9ad5e4d1e92cbdef6dd75ad5dd520b63 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Jan 2023 13:21:44 +0900 Subject: [PATCH 108/142] Revert weird nullable changes to `Skin.cs` --- osu.Game/Skinning/Skin.cs | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index cc302a8b1e..419dacadfd 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Diagnostics; @@ -28,12 +26,12 @@ namespace osu.Game.Skinning /// /// A texture store which can be used to perform user file lookups for this skin. /// - protected TextureStore Textures { get; } + protected TextureStore? Textures { get; } /// /// A sample store which can be used to perform user file lookups for this skin. /// - protected ISampleStore Samples { get; } + protected ISampleStore? Samples { get; } public readonly Live SkinInfo; @@ -43,17 +41,17 @@ namespace osu.Game.Skinning private readonly Dictionary drawableComponentInfo = new Dictionary(); - public abstract ISample GetSample(ISampleInfo sampleInfo); + public abstract ISample? GetSample(ISampleInfo sampleInfo); - public Texture GetTexture(string componentName) => GetTexture(componentName, default, default); + public Texture? GetTexture(string componentName) => GetTexture(componentName, default, default); - public abstract Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT); + public abstract Texture? GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT); - public abstract IBindable GetConfig(TLookup lookup) + public abstract IBindable? GetConfig(TLookup lookup) where TLookup : notnull where TValue : notnull; - private readonly RealmBackedResourceStore realmBackedStorage; + private readonly RealmBackedResourceStore? realmBackedStorage; /// /// Construct a new skin. @@ -62,7 +60,7 @@ namespace osu.Game.Skinning /// Access to game-wide resources. /// An optional store which will *replace* all file lookups that are usually sourced from . /// An optional filename to read the skin configuration from. If not provided, the configuration will be retrieved from the storage using "skin.ini". - protected Skin(SkinInfo skin, IStorageResourceProvider resources, IResourceStore storage = null, string configurationFilename = @"skin.ini") + protected Skin(SkinInfo skin, IStorageResourceProvider? resources, IResourceStore? storage = null, string configurationFilename = @"skin.ini") { if (resources != null) { @@ -103,7 +101,7 @@ namespace osu.Game.Skinning { string filename = $"{skinnableTarget}.json"; - byte[] bytes = storage?.Get(filename); + byte[]? bytes = storage?.Get(filename); if (bytes == null) continue; @@ -156,7 +154,7 @@ namespace osu.Game.Skinning DrawableComponentInfo[targetContainer.Target] = targetContainer.CreateSkinnableInfo().ToArray(); } - public virtual Drawable GetDrawableComponent(ISkinComponentLookup lookup) + public virtual Drawable? GetDrawableComponent(ISkinComponentLookup lookup) { switch (lookup) { @@ -212,6 +210,5 @@ namespace osu.Game.Skinning } #endregion - } } From 7bb6337d2ef8111784c7d7f4c6697748d5f6d53f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Jan 2023 13:24:45 +0900 Subject: [PATCH 109/142] Fix nullability (thanks bdach for patch) --- .../MaxDimensionLimitedTextureLoaderStore.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Skinning/MaxDimensionLimitedTextureLoaderStore.cs b/osu.Game/Skinning/MaxDimensionLimitedTextureLoaderStore.cs index 94d7afaf7e..503add45ed 100644 --- a/osu.Game/Skinning/MaxDimensionLimitedTextureLoaderStore.cs +++ b/osu.Game/Skinning/MaxDimensionLimitedTextureLoaderStore.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.IO; @@ -17,9 +15,9 @@ namespace osu.Game.Skinning { public class MaxDimensionLimitedTextureLoaderStore : IResourceStore { - private readonly IResourceStore textureStore; + private readonly IResourceStore? textureStore; - public MaxDimensionLimitedTextureLoaderStore(IResourceStore textureStore) + public MaxDimensionLimitedTextureLoaderStore(IResourceStore? textureStore) { this.textureStore = textureStore; } @@ -60,10 +58,12 @@ namespace osu.Game.Skinning return textureUpload; } - public Task GetAsync(string name, CancellationToken cancellationToken = new CancellationToken()) => textureStore?.GetAsync(name, cancellationToken); + // TODO: remove null-forgiving operator below after texture stores are NRT-annotated framework-side + public Task GetAsync(string name, CancellationToken cancellationToken = new CancellationToken()) + => textureStore?.GetAsync(name, cancellationToken) ?? Task.FromResult(null!); - public Stream GetStream(string name) => textureStore?.GetStream(name) ?? default; + public Stream? GetStream(string name) => textureStore?.GetStream(name); - public IEnumerable GetAvailableResources() => textureStore?.GetAvailableResources(); + public IEnumerable GetAvailableResources() => textureStore?.GetAvailableResources() ?? Array.Empty(); } } From 5bfd4e47a1b548626bee48e1a0b6edf6c148dd12 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Jan 2023 14:59:54 +0900 Subject: [PATCH 110/142] Refactor position tracking touch handling (and comments) to read better --- .../UI/OsuTouchInputMapper.cs | 59 +++++++++++++------ 1 file changed, 40 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs b/osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs index 2e4f983fc4..7ff85f46a5 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs @@ -59,27 +59,11 @@ namespace osu.Game.Rulesets.Osu.UI // This case gets special handling to allow for empty-space stream tapping. bool isDirectCircleTouch = osuInputManager.CheckScreenSpaceActionPressJudgeable(e.ScreenSpaceTouchDownPosition); - var trackedTouch = new TrackedTouch(e.Touch.Source, shouldResultInAction ? action : null, isDirectCircleTouch); + var newTouch = new TrackedTouch(e.Touch.Source, shouldResultInAction ? action : null, isDirectCircleTouch); - if (isDirectCircleTouch) - positionTrackingTouch = trackedTouch; - else - { - // If no direct touch is registered, we always use the new (latest) touch for positional tracking. - if (positionTrackingTouch?.DirectTouch != true) - positionTrackingTouch = trackedTouch; - else - { - // If not a direct circle touch, consider whether to release the an original direct touch. - if (positionTrackingTouch.DirectTouch && positionTrackingTouch.Action is OsuAction directTouchAction) - { - osuInputManager.KeyBindingContainer.TriggerReleased(directTouchAction); - positionTrackingTouch.Action = null; - } - } - } + updatePositionTracking(newTouch); - trackedTouches.Add(trackedTouch); + trackedTouches.Add(newTouch); // Important to update position before triggering the pressed action. handleTouchMovement(e); @@ -90,6 +74,43 @@ namespace osu.Game.Rulesets.Osu.UI return true; } + /// + /// Given a new touch, update the positional tracking state and any related operations. + /// + private void updatePositionTracking(TrackedTouch newTouch) + { + // If the new touch directly interacted with a circle's receptor, it always becomes the current touch for positional tracking. + if (newTouch.DirectTouch) + { + positionTrackingTouch = newTouch; + return; + } + + // Otherwise, we only want to use the new touch for position tracking if no other touch is tracking position yet.. + if (positionTrackingTouch == null) + { + positionTrackingTouch = newTouch; + return; + } + + // ..or if the current position tracking touch was not a direct touch (this one is debatable and may be change in the future, but it's the simplest way to handle) + if (!positionTrackingTouch.DirectTouch) + { + positionTrackingTouch = newTouch; + return; + } + + // In the case the new touch was not used for position tracking, we should also check the previous position tracking touch. + // If it was a direct touch and still has its action pressed, that action should be released. + // + // This is done to allow tracking with the initial touch while still having both Left/Right actions available for alternating with two more touches. + if (positionTrackingTouch.DirectTouch && positionTrackingTouch.Action is OsuAction directTouchAction) + { + osuInputManager.KeyBindingContainer.TriggerReleased(directTouchAction); + positionTrackingTouch.Action = null; + } + } + private void handleTouchMovement(TouchEvent touchEvent) { // Movement should only be tracked for the most recent touch. From e4a79d8581cb52b9e813170d2a68cc205b7cc200 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Jan 2023 15:01:05 +0900 Subject: [PATCH 111/142] Rename test scene to differentiate from other ruleset touch tests --- .../{TestSceneTouchInput.cs => TestSceneOsuTouchInput.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename osu.Game.Rulesets.Osu.Tests/{TestSceneTouchInput.cs => TestSceneOsuTouchInput.cs} (99%) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneTouchInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs similarity index 99% rename from osu.Game.Rulesets.Osu.Tests/TestSceneTouchInput.cs rename to osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs index 4589eb88c0..72bcec6045 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneTouchInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs @@ -29,7 +29,7 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Tests { [TestFixture] - public partial class TestSceneTouchInput : OsuManualInputManagerTestScene + public partial class TestSceneOsuTouchInput : OsuManualInputManagerTestScene { [Resolved] private OsuConfigManager config { get; set; } = null!; From 1cde90d55d8c8a22152dcd0f1cfe291b8b334b58 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Jan 2023 15:03:47 +0900 Subject: [PATCH 112/142] Add note about `CheckScreenSpaceActionPresJudgeable` being naive --- osu.Game.Rulesets.Osu/OsuInputManager.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Osu/OsuInputManager.cs b/osu.Game.Rulesets.Osu/OsuInputManager.cs index 465dd13362..ccd388192e 100644 --- a/osu.Game.Rulesets.Osu/OsuInputManager.cs +++ b/osu.Game.Rulesets.Osu/OsuInputManager.cs @@ -43,6 +43,10 @@ namespace osu.Game.Rulesets.Osu => new OsuKeyBindingContainer(ruleset, variant, unique); public bool CheckScreenSpaceActionPressJudgeable(Vector2 screenSpacePosition) => + // This is a very naive but simple approach. + // + // Based on user feedback of more nuanced scenarios (where touch doesn't behave as expected), + // this can be expanded to a more complex implementation, but I'd still want to keep it as simple as we can. NonPositionalInputQueue.OfType().Any(c => c.ReceivePositionalInputAt(screenSpacePosition)); public OsuInputManager(RulesetInfo ruleset) From 9499d3a20a9b5fefff77672410107a9c44ac2392 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Jan 2023 15:50:49 +0900 Subject: [PATCH 113/142] Add support for disabling "hit lighting" with osu! argon skin --- .../TestSceneHitCircle.cs | 12 +++++++ .../Skinning/Argon/ArgonMainCirclePiece.cs | 36 ++++++++++++++----- 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs index a418df605f..50f9c5e775 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs @@ -5,9 +5,11 @@ using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Configuration; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; @@ -22,6 +24,9 @@ namespace osu.Game.Rulesets.Osu.Tests { private int depthIndex; + [Resolved] + private OsuConfigManager config { get; set; } + [Test] public void TestHits() { @@ -56,6 +61,13 @@ namespace osu.Game.Rulesets.Osu.Tests AddStep("Hit stream late", () => SetContents(_ => testStream(5, true, 150))); } + [Test] + public void TestHitLighting() + { + AddToggleStep("toggle hit lighting", v => config.SetValue(OsuSetting.HitLighting, v)); + AddStep("Hit Big Single", () => SetContents(_ => testSingle(2, true))); + } + private Drawable testSingle(float circleSize, bool auto = false, double timeOffset = 0, Vector2? positionOffset = null) { var playfield = new TestOsuPlayfield(); diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs index db458ec48a..ab24f9402a 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Objects.Drawables; @@ -44,6 +45,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon private readonly IBindable indexInCurrentCombo = new Bindable(); private readonly FlashPiece flash; + private Bindable configHitLighting = null!; + [Resolved] private DrawableHitObject drawableObject { get; set; } = null!; @@ -96,12 +99,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon } [BackgroundDependencyLoader] - private void load() + private void load(OsuConfigManager config) { var drawableOsuObject = (DrawableOsuHitObject)drawableObject; accentColour.BindTo(drawableObject.AccentColour); indexInCurrentCombo.BindTo(drawableOsuObject.IndexInCurrentComboBindable); + + configHitLighting = config.GetBindable(OsuSetting.HitLighting); } protected override void LoadComplete() @@ -140,12 +145,15 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon case ArmedState.Hit: // Fade out time is at a maximum of 800. Must match `DrawableHitCircle`'s arbitrary lifetime spec. const double fade_out_time = 800; - const double flash_in_duration = 150; const double resize_duration = 400; const float shrink_size = 0.8f; + // When the user has hit lighting disabled, we won't be showing the bright white flash. + // To make things look good, the surrounding animations are also slightly adjusted. + bool showFlash = configHitLighting.Value; + // Animating with the number present is distracting. // The number disappearing is hidden by the bright flash. number.FadeOut(flash_in_duration / 2); @@ -176,15 +184,27 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon using (BeginDelayedSequence(flash_in_duration / 12)) { outerGradient.ResizeTo(OUTER_GRADIENT_SIZE * shrink_size, resize_duration, Easing.OutElasticHalf); - outerGradient - .FadeColour(Color4.White, 80) - .Then() - .FadeOut(flash_in_duration); + + if (showFlash) + { + outerGradient + .FadeColour(Color4.White, 80) + .Then() + .FadeOut(flash_in_duration); + } + else + { + outerGradient + .FadeColour(Color4.White, flash_in_duration * 8) + .FadeOut(flash_in_duration * 2); + } } - flash.FadeTo(1, flash_in_duration, Easing.OutQuint); + if (showFlash) + flash.FadeTo(1, flash_in_duration, Easing.OutQuint); + + this.FadeOut(showFlash ? fade_out_time : fade_out_time / 2, Easing.OutQuad); - this.FadeOut(fade_out_time, Easing.OutQuad); break; } } From f81dea4166baf3bc7d0816d82df62c2efac8e075 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Jan 2023 16:24:52 +0900 Subject: [PATCH 114/142] Remove now unused localisation string --- osu.Game/Localisation/UserInterfaceStrings.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game/Localisation/UserInterfaceStrings.cs b/osu.Game/Localisation/UserInterfaceStrings.cs index fb9eeeb3de..ea664d7b50 100644 --- a/osu.Game/Localisation/UserInterfaceStrings.cs +++ b/osu.Game/Localisation/UserInterfaceStrings.cs @@ -109,11 +109,6 @@ namespace osu.Game.Localisation /// public static LocalisableString ModSelectHotkeyStyle => new TranslatableString(getKey(@"mod_select_hotkey_style"), @"Mod select hotkey style"); - /// - /// "Background blur" - /// - public static LocalisableString BackgroundBlurLevel => new TranslatableString(getKey(@"background_blur_level"), @"Background blur"); - /// /// "no limit" /// From 1a9ed1ac3fa56473f5b2aaa586ba7f747de291ef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Jan 2023 16:28:06 +0900 Subject: [PATCH 115/142] Remove unnecessary `IgnoreUserSettings` value change --- osu.Game/Screens/Select/SongSelect.cs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 505f932bff..680c740b01 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -133,15 +133,11 @@ namespace osu.Game.Screens.Select configBackgroundBlur = config.GetBindable(OsuSetting.SongSelectBackgoundBlur); configBackgroundBlur.BindValueChanged(e => { - if (this.IsCurrentScreen()) - { - ApplyToBackground(background => - { - background.IgnoreUserSettings.Value = true; - background.BlurAmount.Value = e.NewValue ? BACKGROUND_BLUR : 0; - }); - } - }, true); + if (!this.IsCurrentScreen()) + return; + + ApplyToBackground(b => b.BlurAmount.Value = e.NewValue ? BACKGROUND_BLUR : 0); + }); LoadComponentAsync(Carousel = new BeatmapCarousel { From e333e12b2ec1e0d5985c57d6ad94b858627690fc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Jan 2023 16:28:38 +0900 Subject: [PATCH 116/142] Fix typo in settings enum (seriously) --- osu.Game/Configuration/OsuConfigManager.cs | 4 ++-- .../Settings/Sections/UserInterface/SongSelectSettings.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index fff5dc62c6..1c1745b55d 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -60,7 +60,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.ToolbarClockDisplayMode, ToolbarClockDisplayMode.Full); - SetDefault(OsuSetting.SongSelectBackgoundBlur, true); + SetDefault(OsuSetting.SongSelectBackgroundBlur, true); // Online settings SetDefault(OsuSetting.Username, string.Empty); @@ -341,7 +341,7 @@ namespace osu.Game.Configuration ChatDisplayHeight, BeatmapListingCardSize, ToolbarClockDisplayMode, - SongSelectBackgoundBlur, + SongSelectBackgroundBlur, Version, ShowFirstRunSetup, ShowConvertedBeatmaps, diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs index d40d24a528..6f30fcd100 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs @@ -46,7 +46,7 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface new SettingsCheckbox { LabelText = GameplaySettingsStrings.BackgroundBlur, - Current = config.GetBindable(OsuSetting.SongSelectBackgoundBlur) + Current = config.GetBindable(OsuSetting.SongSelectBackgroundBlur) } }; } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 680c740b01..41e722ae88 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -130,7 +130,7 @@ namespace osu.Game.Screens.Select [BackgroundDependencyLoader(true)] private void load(AudioManager audio, OsuColour colours, ManageCollectionsDialog? manageCollectionsDialog, DifficultyRecommender? recommender, OsuConfigManager config) { - configBackgroundBlur = config.GetBindable(OsuSetting.SongSelectBackgoundBlur); + configBackgroundBlur = config.GetBindable(OsuSetting.SongSelectBackgroundBlur); configBackgroundBlur.BindValueChanged(e => { if (!this.IsCurrentScreen()) From 01e280eb6bba20f85b49e98379550ad20906822c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Jan 2023 16:31:41 +0900 Subject: [PATCH 117/142] Add classic default for song select blur setting --- .../Settings/Sections/UserInterface/SongSelectSettings.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs index 6f30fcd100..d3303e409c 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs @@ -46,7 +46,8 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface new SettingsCheckbox { LabelText = GameplaySettingsStrings.BackgroundBlur, - Current = config.GetBindable(OsuSetting.SongSelectBackgroundBlur) + Current = config.GetBindable(OsuSetting.SongSelectBackgroundBlur), + ClassicDefault = false, } }; } From d15f8c2f3a1ffb79fb1fcfd2e0ee2717b2c0cbb0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Jan 2023 17:11:14 +0900 Subject: [PATCH 118/142] Fix beatmaps with multiple `osb` files potentially reading the storyboard from the wrong one In stable, the storyboard filename is fixed. In lazer, we were always looking for the first `.osb` in the database. In the case a beatmap archive housed more than one `.osb` files, this may load the incorrect one. Using `GetDisplayString` here feels like it could potentially go wrong in the future, so I'm open to hard-coding this locally (or using string manipulation to remove the ` [creator_name]` portion of the beatmap's filename). Open to opinions on that. Fixes storyboard playback in https://osu.ppy.sh/beatmapsets/1913687#osu/3947758 --- osu.Game/Beatmaps/WorkingBeatmapCache.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmapCache.cs b/osu.Game/Beatmaps/WorkingBeatmapCache.cs index e6f96330e7..546f0e6c3a 100644 --- a/osu.Game/Beatmaps/WorkingBeatmapCache.cs +++ b/osu.Game/Beatmaps/WorkingBeatmapCache.cs @@ -20,6 +20,7 @@ using osu.Framework.Statistics; using osu.Framework.Testing; using osu.Game.Beatmaps.Formats; using osu.Game.Database; +using osu.Game.Extensions; using osu.Game.IO; using osu.Game.Skinning; using osu.Game.Storyboards; @@ -268,7 +269,7 @@ namespace osu.Game.Beatmaps Stream storyboardFileStream = null; - if (BeatmapSetInfo?.Files.FirstOrDefault(f => f.Filename.EndsWith(".osb", StringComparison.OrdinalIgnoreCase))?.Filename is string storyboardFilename) + if (BeatmapSetInfo?.Files.FirstOrDefault(f => f.Filename.Equals($"{BeatmapSetInfo.GetDisplayString()}.osb", StringComparison.OrdinalIgnoreCase))?.Filename is string storyboardFilename) { string storyboardFileStorePath = BeatmapSetInfo?.GetPathForFile(storyboardFilename); storyboardFileStream = GetStream(storyboardFileStorePath); From d09d6f31d704c2b69ecdef00579f80261dc7a2e0 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 25 Jan 2023 12:20:51 +0300 Subject: [PATCH 119/142] Implement Masking property for TrianglesBackground --- .../Skinning/Default/TrianglesPiece.cs | 1 + .../TestSceneTrianglesBackground.cs | 7 ++- osu.Game/Graphics/Backgrounds/Triangles.cs | 43 +++++++++++++++---- 3 files changed, 42 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/TrianglesPiece.cs b/osu.Game.Rulesets.Osu/Skinning/Default/TrianglesPiece.cs index f1143cf14d..16cd302b88 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/TrianglesPiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/TrianglesPiece.cs @@ -15,6 +15,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default { TriangleScale = 1.2f; HideAlphaDiscrepancies = false; + Masking = false; } protected override void Update() diff --git a/osu.Game.Tests/Visual/Background/TestSceneTrianglesBackground.cs b/osu.Game.Tests/Visual/Background/TestSceneTrianglesBackground.cs index 8a5489f476..378dd99664 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneTrianglesBackground.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneTrianglesBackground.cs @@ -5,6 +5,7 @@ using osu.Game.Graphics.Backgrounds; using osu.Framework.Graphics; using osuTK.Graphics; using osu.Framework.Graphics.Shapes; +using osuTK; namespace osu.Game.Tests.Visual.Background { @@ -25,7 +26,10 @@ namespace osu.Game.Tests.Visual.Background { RelativeSizeAxes = Axes.Both, ColourLight = Color4.White, - ColourDark = Color4.Gray + ColourDark = Color4.Gray, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(0.9f) } }; } @@ -36,6 +40,7 @@ namespace osu.Game.Tests.Visual.Background AddSliderStep("Triangle scale", 0f, 10f, 1f, s => triangles.TriangleScale = s); AddSliderStep("Seed", 0, 1000, 0, s => triangles.Reset(s)); + AddToggleStep("Masking", m => triangles.Masking = m); } } } diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs index d9dff1574f..1fbf9b4dfa 100644 --- a/osu.Game/Graphics/Backgrounds/Triangles.cs +++ b/osu.Game/Graphics/Backgrounds/Triangles.cs @@ -77,6 +77,12 @@ namespace osu.Game.Graphics.Backgrounds set => triangleScale.Value = value; } + /// + /// If enabled, only the portion of triangles that falls within this 's + /// shape is drawn to the screen. + /// + public bool Masking { get; set; } = true; + /// /// Whether we should drop-off alpha values of triangles more quickly to improve /// the visual appearance of fading. This defaults to on as it is generally more @@ -252,6 +258,7 @@ namespace osu.Game.Graphics.Backgrounds private IShader shader; private Texture texture; + private bool masking; private readonly List parts = new List(); private readonly Vector2 triangleSize = new Vector2(1f, equilateral_triangle_ratio) * triangle_size; @@ -271,6 +278,7 @@ namespace osu.Game.Graphics.Backgrounds shader = Source.shader; texture = Source.texture; size = Source.DrawSize; + masking = Source.Masking; parts.Clear(); parts.AddRange(Source.parts); @@ -294,26 +302,45 @@ namespace osu.Game.Graphics.Backgrounds Vector2 relativeSize = Vector2.Divide(triangleSize * particle.Scale, size); Vector2 topLeft = particle.Position - new Vector2(relativeSize.X * 0.5f, 0f); - Vector2 topRight = topLeft + new Vector2(relativeSize.X, 0f); - Vector2 bottomLeft = topLeft + new Vector2(0f, relativeSize.Y); - Vector2 bottomRight = bottomLeft + new Vector2(relativeSize.X, 0f); + + Quad triangleQuad = masking ? clampToDrawable(topLeft, relativeSize) : new Quad(topLeft.X, topLeft.Y, relativeSize.X, relativeSize.Y); var drawQuad = new Quad( - Vector2Extensions.Transform(topLeft * size, DrawInfo.Matrix), - Vector2Extensions.Transform(topRight * size, DrawInfo.Matrix), - Vector2Extensions.Transform(bottomLeft * size, DrawInfo.Matrix), - Vector2Extensions.Transform(bottomRight * size, DrawInfo.Matrix) + Vector2Extensions.Transform(triangleQuad.TopLeft * size, DrawInfo.Matrix), + Vector2Extensions.Transform(triangleQuad.TopRight * size, DrawInfo.Matrix), + Vector2Extensions.Transform(triangleQuad.BottomLeft * size, DrawInfo.Matrix), + Vector2Extensions.Transform(triangleQuad.BottomRight * size, DrawInfo.Matrix) ); ColourInfo colourInfo = DrawColourInfo.Colour; colourInfo.ApplyChild(particle.Colour); - renderer.DrawQuad(texture, drawQuad, colourInfo, vertexAction: vertexBatch.AddAction); + RectangleF textureCoords = new RectangleF( + triangleQuad.TopLeft.X - topLeft.X, + triangleQuad.TopLeft.Y - topLeft.Y, + triangleQuad.Width, + triangleQuad.Height + ) / relativeSize; + + renderer.DrawQuad(texture, drawQuad, colourInfo, new RectangleF(0, 0, 1, 1), vertexBatch.AddAction, textureCoords: textureCoords); } shader.Unbind(); } + private static Quad clampToDrawable(Vector2 topLeft, Vector2 size) + { + float leftClamped = Math.Clamp(topLeft.X, 0f, 1f); + float topClamped = Math.Clamp(topLeft.Y, 0f, 1f); + + return new Quad( + leftClamped, + topClamped, + Math.Clamp(topLeft.X + size.X, 0f, 1f) - leftClamped, + Math.Clamp(topLeft.Y + size.Y, 0f, 1f) - topClamped + ); + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From 78cfe2f5476ec361f46f93c6376ba46188273093 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Jan 2023 18:46:52 +0900 Subject: [PATCH 120/142] Fix hit circle kiai test scene not always working as expected --- .../TestSceneHitCircleKiai.cs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleKiai.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleKiai.cs index 2c9f1acd2c..718664d649 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleKiai.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleKiai.cs @@ -4,29 +4,38 @@ #nullable disable using NUnit.Framework; +using osu.Framework.Audio; +using osu.Framework.Audio.Track; +using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; namespace osu.Game.Rulesets.Osu.Tests { [TestFixture] - public partial class TestSceneHitCircleKiai : TestSceneHitCircle + public partial class TestSceneHitCircleKiai : TestSceneHitCircle, IBeatSyncProvider { + private ControlPointInfo controlPoints { get; set; } + [SetUp] public void SetUp() => Schedule(() => { - var controlPointInfo = new ControlPointInfo(); + controlPoints = new ControlPointInfo(); - controlPointInfo.Add(0, new TimingControlPoint { BeatLength = 500 }); - controlPointInfo.Add(0, new EffectControlPoint { KiaiMode = true }); + controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 }); + controlPoints.Add(0, new EffectControlPoint { KiaiMode = true }); Beatmap.Value = CreateWorkingBeatmap(new Beatmap { - ControlPointInfo = controlPointInfo + ControlPointInfo = controlPoints }); // track needs to be playing for BeatSyncedContainer to work. Beatmap.Value.Track.Start(); }); + + ChannelAmplitudes IHasAmplitudes.CurrentAmplitudes => new ChannelAmplitudes(); + ControlPointInfo IBeatSyncProvider.ControlPoints => controlPoints; + IClock IBeatSyncProvider.Clock => Clock; } } From 1485a6e00670118ff29dd79912b3ddc29f22a773 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 25 Jan 2023 12:54:30 +0300 Subject: [PATCH 121/142] Make masking false by default --- osu.Game.Rulesets.Osu/Skinning/Default/TrianglesPiece.cs | 1 - osu.Game/Graphics/Backgrounds/Triangles.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/TrianglesPiece.cs b/osu.Game.Rulesets.Osu/Skinning/Default/TrianglesPiece.cs index 16cd302b88..f1143cf14d 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/TrianglesPiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/TrianglesPiece.cs @@ -15,7 +15,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default { TriangleScale = 1.2f; HideAlphaDiscrepancies = false; - Masking = false; } protected override void Update() diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs index 1fbf9b4dfa..6750d74a08 100644 --- a/osu.Game/Graphics/Backgrounds/Triangles.cs +++ b/osu.Game/Graphics/Backgrounds/Triangles.cs @@ -81,7 +81,7 @@ namespace osu.Game.Graphics.Backgrounds /// If enabled, only the portion of triangles that falls within this 's /// shape is drawn to the screen. /// - public bool Masking { get; set; } = true; + public bool Masking { get; set; } /// /// Whether we should drop-off alpha values of triangles more quickly to improve From 48d68b0f4f385ca862db266011cb9fffcb5efee7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 Jan 2023 18:59:26 +0900 Subject: [PATCH 122/142] Add very basic kiai flash to argon hit circles --- .../Skinning/Argon/ArgonMainCirclePiece.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs index db458ec48a..d50a3eba13 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs @@ -43,6 +43,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon private readonly IBindable accentColour = new Bindable(); private readonly IBindable indexInCurrentCombo = new Bindable(); private readonly FlashPiece flash; + private readonly KiaiFlash kiaiFlash; [Resolved] private DrawableHitObject drawableObject { get; set; } = null!; @@ -82,6 +83,15 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon Anchor = Anchor.Centre, Origin = Anchor.Centre, }, + new CircularContainer + { + Masking = true, + Size = Size, + Child = kiaiFlash = new KiaiFlash + { + RelativeSizeAxes = Axes.Both, + } + }, number = new OsuSpriteText { Font = OsuFont.Default.With(size: 52, weight: FontWeight.Bold), @@ -117,6 +127,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon outerGradient.ClearTransforms(targetMember: nameof(Colour)); outerGradient.Colour = ColourInfo.GradientVertical(colour.NewValue, colour.NewValue.Darken(0.1f)); + kiaiFlash.Colour = colour.NewValue; outerFill.Colour = innerFill.Colour = colour.NewValue.Darken(4); innerGradient.Colour = ColourInfo.GradientVertical(colour.NewValue.Darken(0.5f), colour.NewValue.Darken(0.6f)); flash.Colour = colour.NewValue; From fd495e87f74d5e6b76d67b9fa2865378b95c562b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 25 Jan 2023 18:37:58 +0100 Subject: [PATCH 123/142] Fix `GetAsync()` not limiting texture dimensions --- .../MaxDimensionLimitedTextureLoaderStore.cs | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/MaxDimensionLimitedTextureLoaderStore.cs b/osu.Game/Skinning/MaxDimensionLimitedTextureLoaderStore.cs index 503add45ed..f15097a169 100644 --- a/osu.Game/Skinning/MaxDimensionLimitedTextureLoaderStore.cs +++ b/osu.Game/Skinning/MaxDimensionLimitedTextureLoaderStore.cs @@ -35,6 +35,25 @@ namespace osu.Game.Skinning if (textureUpload == null) return null!; + return limitTextureUploadSize(textureUpload); + } + + public async Task GetAsync(string name, CancellationToken cancellationToken = new CancellationToken()) + { + // NRT not enabled on framework side classes (IResourceStore / TextureLoaderStore), welp. + if (textureStore == null) + return null!; + + var textureUpload = await textureStore.GetAsync(name, cancellationToken).ConfigureAwait(false); + + if (textureUpload == null) + return null!; + + return await Task.Run(() => limitTextureUploadSize(textureUpload), cancellationToken).ConfigureAwait(false); + } + + private TextureUpload limitTextureUploadSize(TextureUpload textureUpload) + { // So there's a thing where some users have taken it upon themselves to create skin elements of insane dimensions. // To the point where GPUs cannot load the textures (along with most image editor apps). // To work around this, let's look out for any stupid images and shrink them down into a usable size. @@ -58,10 +77,6 @@ namespace osu.Game.Skinning return textureUpload; } - // TODO: remove null-forgiving operator below after texture stores are NRT-annotated framework-side - public Task GetAsync(string name, CancellationToken cancellationToken = new CancellationToken()) - => textureStore?.GetAsync(name, cancellationToken) ?? Task.FromResult(null!); - public Stream? GetStream(string name) => textureStore?.GetStream(name); public IEnumerable GetAvailableResources() => textureStore?.GetAvailableResources() ?? Array.Empty(); From 34aa8b872eb38a460b2a726d2f689a5da96c80c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 25 Jan 2023 18:39:26 +0100 Subject: [PATCH 124/142] Mention stable weirdness next to constant --- osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs index 6887674a1f..05ba2b8f22 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs @@ -95,7 +95,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy d.RelativeSizeAxes = Axes.Both; d.Size = Vector2.One; d.FillMode = FillMode.Stretch; - d.Height = minimumColumnWidth / d.DrawWidth * 1.6f; + d.Height = minimumColumnWidth / d.DrawWidth * 1.6f; // constant matching stable. // Todo: Wrap? }); From 7cc4fd4efc9fce06a9705152234bd12c7657c3c2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Jan 2023 15:09:36 +0900 Subject: [PATCH 125/142] Use the exact method stable uses for generating storyboard filenames --- osu.Game/Beatmaps/WorkingBeatmapCache.cs | 33 ++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmapCache.cs b/osu.Game/Beatmaps/WorkingBeatmapCache.cs index 546f0e6c3a..76a31a6f78 100644 --- a/osu.Game/Beatmaps/WorkingBeatmapCache.cs +++ b/osu.Game/Beatmaps/WorkingBeatmapCache.cs @@ -20,7 +20,6 @@ using osu.Framework.Statistics; using osu.Framework.Testing; using osu.Game.Beatmaps.Formats; using osu.Game.Database; -using osu.Game.Extensions; using osu.Game.IO; using osu.Game.Skinning; using osu.Game.Storyboards; @@ -269,7 +268,10 @@ namespace osu.Game.Beatmaps Stream storyboardFileStream = null; - if (BeatmapSetInfo?.Files.FirstOrDefault(f => f.Filename.Equals($"{BeatmapSetInfo.GetDisplayString()}.osb", StringComparison.OrdinalIgnoreCase))?.Filename is string storyboardFilename) + string mainStoryboardFilename = getMainStoryboardFilename(BeatmapSetInfo.Metadata); + + if (BeatmapSetInfo?.Files.FirstOrDefault(f => f.Filename.Equals(mainStoryboardFilename, StringComparison.OrdinalIgnoreCase))?.Filename is string + storyboardFilename) { string storyboardFileStorePath = BeatmapSetInfo?.GetPathForFile(storyboardFilename); storyboardFileStream = GetStream(storyboardFileStorePath); @@ -313,6 +315,33 @@ namespace osu.Game.Beatmaps } public override Stream GetStream(string storagePath) => resources.Files.GetStream(storagePath); + + private string getMainStoryboardFilename(IBeatmapMetadataInfo metadata) + { + // Matches stable implementation, because it's probably simpler than trying to do anything else. + // This may need to be reconsidered after we begin storing storyboards in the new editor. + return windowsFilenameStrip( + (metadata.Artist.Length > 0 ? metadata.Artist + @" - " + metadata.Title : Path.GetFileNameWithoutExtension(metadata.AudioFile)) + + (metadata.Author.Username.Length > 0 ? @" (" + metadata.Author.Username + @")" : string.Empty) + + @".osb"); + + string windowsFilenameStrip(string entry) + { + // Inlined from Path.GetInvalidFilenameChars() to ensure the windows characters are used (to match stable). + char[] invalidCharacters = + { + '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', + '\x08', '\x09', '\x0A', '\x0B', '\x0C', '\x0D', '\x0E', '\x0F', '\x10', '\x11', '\x12', + '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1A', '\x1B', '\x1C', '\x1D', + '\x1E', '\x1F', '\x22', '\x3C', '\x3E', '\x7C', ':', '*', '?', '\\', '/' + }; + + foreach (char c in invalidCharacters) + entry = entry.Replace(c.ToString(), string.Empty); + + return entry; + } + } } } } From de1d473d2908527a903f12fed42713b359aac81a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Jan 2023 15:24:10 +0900 Subject: [PATCH 126/142] Fix kiai flash being visible and incorrectly sized during hit animation --- .../Skinning/Argon/ArgonMainCirclePiece.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs index 04a4d4bede..e7e6e26b5e 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon private readonly IBindable accentColour = new Bindable(); private readonly IBindable indexInCurrentCombo = new Bindable(); private readonly FlashPiece flash; - private readonly KiaiFlash kiaiFlash; + private readonly Container kiaiContainer; private Bindable configHitLighting = null!; @@ -83,11 +83,13 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon Anchor = Anchor.Centre, Origin = Anchor.Centre, }, - new CircularContainer + kiaiContainer = new CircularContainer { Masking = true, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, Size = Size, - Child = kiaiFlash = new KiaiFlash + Child = new KiaiFlash { RelativeSizeAxes = Axes.Both, } @@ -129,7 +131,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon outerGradient.ClearTransforms(targetMember: nameof(Colour)); outerGradient.Colour = ColourInfo.GradientVertical(colour.NewValue, colour.NewValue.Darken(0.1f)); - kiaiFlash.Colour = colour.NewValue; + kiaiContainer.Colour = colour.NewValue; outerFill.Colour = innerFill.Colour = colour.NewValue.Darken(4); innerGradient.Colour = ColourInfo.GradientVertical(colour.NewValue.Darken(0.5f), colour.NewValue.Darken(0.6f)); flash.Colour = colour.NewValue; @@ -191,6 +193,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon // gradient layers. border.ResizeTo(Size * shrink_size + new Vector2(border.BorderThickness), resize_duration, Easing.OutElasticHalf); + // Kiai flash should track the overall size but also be cleaned up quite fast, so we don't get additional + // flashes after the hit animation is already in a mostly-completed state. + kiaiContainer.ResizeTo(Size * shrink_size, resize_duration, Easing.OutElasticHalf); + kiaiContainer.FadeOut(flash_in_duration, Easing.OutQuint); + // The outer gradient is resize with a slight delay from the border. // This is to give it a bomb-like effect, with the border "triggering" its animation when getting close. using (BeginDelayedSequence(flash_in_duration / 12)) From d63719a602b3c5ef397d6cf6017cfc31a34a1476 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Jan 2023 16:04:56 +0900 Subject: [PATCH 127/142] Move and rename the base component class --- osu.Game/Skinning/Components/BeatmapAttributeText.cs | 2 +- osu.Game/Skinning/Components/TextElement.cs | 2 +- ...ultTextSkinComponent.cs => FontAdjustableSkinComponent.cs} | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) rename osu.Game/Skinning/{Components/DefaultTextSkinComponent.cs => FontAdjustableSkinComponent.cs} (88%) diff --git a/osu.Game/Skinning/Components/BeatmapAttributeText.cs b/osu.Game/Skinning/Components/BeatmapAttributeText.cs index 5696158d49..68bb1e7ddc 100644 --- a/osu.Game/Skinning/Components/BeatmapAttributeText.cs +++ b/osu.Game/Skinning/Components/BeatmapAttributeText.cs @@ -23,7 +23,7 @@ using osu.Game.Resources.Localisation.Web; namespace osu.Game.Skinning.Components { [UsedImplicitly] - public partial class BeatmapAttributeText : DefaultTextSkinComponent + public partial class BeatmapAttributeText : FontAdjustableSkinComponent { [SettingSource("Attribute", "The attribute to be displayed.")] public Bindable Attribute { get; } = new Bindable(BeatmapAttribute.StarRating); diff --git a/osu.Game/Skinning/Components/TextElement.cs b/osu.Game/Skinning/Components/TextElement.cs index fb779fdb83..d87fb125bb 100644 --- a/osu.Game/Skinning/Components/TextElement.cs +++ b/osu.Game/Skinning/Components/TextElement.cs @@ -12,7 +12,7 @@ using osu.Game.Graphics.Sprites; namespace osu.Game.Skinning.Components { [UsedImplicitly] - public partial class TextElement : DefaultTextSkinComponent + public partial class TextElement : FontAdjustableSkinComponent { [SettingSource("Text", "The text to be displayed.")] public Bindable Text { get; } = new Bindable("Circles!"); diff --git a/osu.Game/Skinning/Components/DefaultTextSkinComponent.cs b/osu.Game/Skinning/FontAdjustableSkinComponent.cs similarity index 88% rename from osu.Game/Skinning/Components/DefaultTextSkinComponent.cs rename to osu.Game/Skinning/FontAdjustableSkinComponent.cs index abe16918c5..716b472ba5 100644 --- a/osu.Game/Skinning/Components/DefaultTextSkinComponent.cs +++ b/osu.Game/Skinning/FontAdjustableSkinComponent.cs @@ -7,12 +7,12 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Configuration; using osu.Game.Graphics; -namespace osu.Game.Skinning.Components +namespace osu.Game.Skinning { /// /// Skin element that contains text and have ability to control its font. /// - public abstract partial class DefaultTextSkinComponent : Container, ISkinnableDrawable + public abstract partial class FontAdjustableSkinComponent : Container, ISkinnableDrawable { public bool UsesFixedAnchor { get; set; } From 64e7f6f138a5ae087f785b736248aba009b6bfe7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Jan 2023 16:05:07 +0900 Subject: [PATCH 128/142] Add more documentation around the implementation of `FontAdjustableSkinComponent` --- osu.Game/Skinning/FontAdjustableSkinComponent.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game/Skinning/FontAdjustableSkinComponent.cs b/osu.Game/Skinning/FontAdjustableSkinComponent.cs index 716b472ba5..ee73417bfe 100644 --- a/osu.Game/Skinning/FontAdjustableSkinComponent.cs +++ b/osu.Game/Skinning/FontAdjustableSkinComponent.cs @@ -10,23 +10,30 @@ using osu.Game.Graphics; namespace osu.Game.Skinning { /// - /// Skin element that contains text and have ability to control its font. + /// A skin component that contains text and allows the user to choose its font. /// public abstract partial class FontAdjustableSkinComponent : Container, ISkinnableDrawable { public bool UsesFixedAnchor { get; set; } - [SettingSource("Font", "Font to use.")] + [SettingSource("Font", "The font to use.")] public Bindable Font { get; } = new Bindable(Typeface.Torus); + /// + /// Implement to apply the user font selection to one or more components. + /// protected abstract void SetFont(FontUsage font); protected override void LoadComplete() { base.LoadComplete(); + Font.BindValueChanged(e => { - FontUsage f = OsuFont.GetFont(e.NewValue, weight: e.NewValue == Typeface.Venera ? FontWeight.Bold : FontWeight.Regular); + // We only have bold weight for venera, so let's force that. + FontWeight fontWeight = e.NewValue == Typeface.Venera ? FontWeight.Bold : FontWeight.Regular; + + FontUsage f = OsuFont.GetFont(e.NewValue, weight: fontWeight); SetFont(f); }, true); } From e5d4979bc3942aa564a758bf5e09331dcc3ebdcb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Jan 2023 16:56:37 +0900 Subject: [PATCH 129/142] Fix default health bar having a very weird anchor point in the skin editor --- .../Screens/Play/HUD/DefaultHealthDisplay.cs | 45 +++++++++++-------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs b/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs index 76027f9e5d..62d66efb33 100644 --- a/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/DefaultHealthDisplay.cs @@ -78,30 +78,39 @@ namespace osu.Game.Screens.Play.HUD public DefaultHealthDisplay() { - Size = new Vector2(1, 5); - RelativeSizeAxes = Axes.X; - Margin = new MarginPadding { Top = 20 }; + const float padding = 20; + const float bar_height = 5; - InternalChildren = new Drawable[] + Size = new Vector2(1, bar_height + padding * 2); + RelativeSizeAxes = Axes.X; + + InternalChild = new Container { - new Box + Padding = new MarginPadding { Vertical = padding }, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - }, - fill = new Container - { - RelativeSizeAxes = Axes.Both, - Size = new Vector2(0, 1), - Masking = true, - Children = new[] + new Box { - new Box + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + }, + fill = new Container + { + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0, 1), + Masking = true, + Children = new[] { - RelativeSizeAxes = Axes.Both, + new Box + { + RelativeSizeAxes = Axes.Both, + } } - } - }, + }, + } }; } From 7344d34d5b59ef6c9517478df3903482a77ad106 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Jan 2023 17:12:41 +0900 Subject: [PATCH 130/142] Move `where` class specs to next line --- .../Blueprints/Sliders/Components/PathControlPointPiece.cs | 3 ++- .../Sliders/Components/PathControlPointVisualiser.cs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index a4d5c08b8a..12e5ca0236 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -32,7 +32,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components /// A visualisation of a single in an osu hit object with a path. /// /// The type of which this visualises. - public partial class PathControlPointPiece : BlueprintPiece, IHasTooltip where T : OsuHitObject, IHasPath + public partial class PathControlPointPiece : BlueprintPiece, IHasTooltip + where T : OsuHitObject, IHasPath { public Action, MouseButtonEvent> RequestSelection; 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 0310239052..17d0fc457a 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -29,7 +29,8 @@ using osuTK.Input; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { - public partial class PathControlPointVisualiser : CompositeDrawable, IKeyBindingHandler, IHasContextMenu where T : OsuHitObject, IHasPath + public partial class PathControlPointVisualiser : CompositeDrawable, IKeyBindingHandler, IHasContextMenu + where T : OsuHitObject, IHasPath { public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; // allow context menu to appear outside of the playfield. From 2b55e05b10857a09c3162fec5ef965da3034e775 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 27 Jan 2023 13:31:21 +0900 Subject: [PATCH 131/142] Adjust argon hit lighting further --- .../Skinning/Argon/ArgonMainCirclePiece.cs | 41 ++++++++++--------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs index 2a53122cc1..9e86e1e411 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs @@ -151,10 +151,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon const float shrink_size = 0.8f; - // When the user has hit lighting disabled, we won't be showing the bright white flash. - // To make things look good, the surrounding animations are also slightly adjusted. - bool showFlash = configHitLighting.Value; - // Animating with the number present is distracting. // The number disappearing is hidden by the bright flash. number.FadeOut(flash_in_duration / 2); @@ -186,25 +182,28 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon { outerGradient.ResizeTo(OUTER_GRADIENT_SIZE * shrink_size, resize_duration, Easing.OutElasticHalf); - if (showFlash) - { - outerGradient - .FadeColour(Color4.White, 80) - .Then() - .FadeOut(flash_in_duration); - } - else - { - outerGradient - .FadeColour(Color4.White, flash_in_duration * 8) - .FadeOut(flash_in_duration * 2); - } + outerGradient + .FadeColour(Color4.White, 80) + .Then() + .FadeOut(flash_in_duration); } - if (showFlash) + if (configHitLighting.Value) + { + flash.HitLighting = true; flash.FadeTo(1, flash_in_duration, Easing.OutQuint); - this.FadeOut(showFlash ? fade_out_time : fade_out_time / 2, Easing.OutQuad); + this.FadeOut(fade_out_time, Easing.OutQuad); + } + else + { + flash.HitLighting = false; + flash.FadeTo(1, flash_in_duration, Easing.OutQuint) + .Then() + .FadeOut(flash_in_duration, Easing.OutQuint); + + this.FadeOut(fade_out_time * 0.8f, Easing.OutQuad); + } break; } @@ -236,6 +235,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon Child.AlwaysPresent = true; } + public bool HitLighting { get; set; } + protected override void Update() { base.Update(); @@ -244,7 +245,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon { Type = EdgeEffectType.Glow, Colour = Colour, - Radius = OsuHitObject.OBJECT_RADIUS * 1.2f, + Radius = OsuHitObject.OBJECT_RADIUS * (HitLighting ? 1.2f : 0.6f), }; } } From a126c72a4fbf150c59fba9e65bafe77ff02c5ad6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 28 Jan 2023 15:17:35 +0100 Subject: [PATCH 132/142] Adjust skin resources test to facilitate loading many --- .../Skins/TestSceneSkinResources.cs | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Skins/TestSceneSkinResources.cs b/osu.Game.Tests/Skins/TestSceneSkinResources.cs index 748668b6ca..b29211f442 100644 --- a/osu.Game.Tests/Skins/TestSceneSkinResources.cs +++ b/osu.Game.Tests/Skins/TestSceneSkinResources.cs @@ -31,17 +31,14 @@ namespace osu.Game.Tests.Skins [Resolved] private SkinManager skins { get; set; } = null!; - private ISkin skin = null!; - - [BackgroundDependencyLoader] - private void load() - { - var imported = skins.Import(new ImportTask(TestResources.OpenResource("Archives/ogg-skin.osk"), "ogg-skin.osk")).GetResultSafely(); - skin = imported.PerformRead(skinInfo => skins.GetSkin(skinInfo)); - } - [Test] - public void TestRetrieveOggSample() => AddAssert("sample is non-null", () => skin.GetSample(new SampleInfo("sample")) != null); + public void TestRetrieveOggSample() + { + ISkin skin = null!; + + AddStep("import skin", () => skin = importSkinFromArchives(@"ogg-skin.osk")); + AddAssert("sample is non-null", () => skin.GetSample(new SampleInfo(@"sample")) != null); + } [Test] public void TestSampleRetrievalOrder() @@ -78,6 +75,12 @@ namespace osu.Game.Tests.Skins }); } + private Skin importSkinFromArchives(string filename) + { + var imported = skins.Import(new ImportTask(TestResources.OpenResource($@"Archives/{filename}"), filename)).GetResultSafely(); + return imported.PerformRead(skinInfo => skins.GetSkin(skinInfo)); + } + private class TestSkin : Skin { public const string SAMPLE_NAME = "test-sample"; From 9bdb78791f0bab12c70c7f3ef6159a6720b33338 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 28 Jan 2023 15:23:15 +0100 Subject: [PATCH 133/142] Add failing test case --- .../Archives/conflicting-filenames-skin.osk | Bin 0 -> 3665 bytes osu.Game.Tests/Skins/TestSceneSkinResources.cs | 10 ++++++++++ 2 files changed, 10 insertions(+) create mode 100644 osu.Game.Tests/Resources/Archives/conflicting-filenames-skin.osk diff --git a/osu.Game.Tests/Resources/Archives/conflicting-filenames-skin.osk b/osu.Game.Tests/Resources/Archives/conflicting-filenames-skin.osk new file mode 100644 index 0000000000000000000000000000000000000000..73576f3e22dd29c4c6a99ca62cf5e842f1760f33 GIT binary patch literal 3665 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 Q^%a4Fbl;F-BK_6tU*i>nr2qf` literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Skins/TestSceneSkinResources.cs b/osu.Game.Tests/Skins/TestSceneSkinResources.cs index b29211f442..aaec319b57 100644 --- a/osu.Game.Tests/Skins/TestSceneSkinResources.cs +++ b/osu.Game.Tests/Skins/TestSceneSkinResources.cs @@ -40,6 +40,16 @@ namespace osu.Game.Tests.Skins AddAssert("sample is non-null", () => skin.GetSample(new SampleInfo(@"sample")) != null); } + [Test] + public void TestRetrievalWithConflictingFilenames() + { + ISkin skin = null!; + + AddStep("import skin", () => skin = importSkinFromArchives(@"conflicting-filenames-skin.osk")); + AddAssert("texture is non-null", () => skin.GetTexture(@"spinner-osu") != null); + AddAssert("sample is non-null", () => skin.GetSample(new SampleInfo(@"spinner-osu")) != null); + } + [Test] public void TestSampleRetrievalOrder() { From 20f4061e303774455802bfbae69313645e19fc8c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 29 Jan 2023 00:24:05 +0300 Subject: [PATCH 134/142] Move ongoing operation wait step to proper place --- .../Visual/Multiplayer/QueueModeTestScene.cs | 2 ++ .../Multiplayer/TestSceneAllPlayersQueueMode.cs | 15 --------------- .../Multiplayer/TestSceneHostOnlyQueueMode.cs | 13 ------------- 3 files changed, 2 insertions(+), 28 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs index 0e9863a9f5..0f1ba9ba75 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs @@ -16,6 +16,7 @@ using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; +using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Lounge; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; @@ -85,6 +86,7 @@ namespace osu.Game.Tests.Visual.Multiplayer ClickButtonWhenEnabled(); AddUntilStep("wait for join", () => MultiplayerClient.RoomJoined); + AddUntilStep("wait for ongoing operation to complete", () => !(CurrentScreen as OnlinePlayScreen).ChildrenOfType().Single().InProgress.Value); } [Test] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs index 6da9d37648..869b8bb328 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs @@ -15,7 +15,6 @@ using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; using osu.Game.Screens.Play; @@ -44,14 +43,6 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] - /* - * TearDown : System.TimeoutException : "wait for ongoing operation to complete" timed out - * --TearDown - * at osu.Framework.Testing.Drawables.Steps.UntilStepButton.<>c__DisplayClass11_0.<.ctor>b__0() - * at osu.Framework.Testing.Drawables.Steps.StepButton.PerformStep(Boolean userTriggered) - * at osu.Framework.Testing.TestScene.runNextStep(Action onCompletion, Action`1 onError, Func`2 stopCondition) - */ public void TestItemAddedToTheEndOfQueue() { addItem(() => OtherBeatmap); @@ -64,7 +55,6 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] // See above public void TestNextItemSelectedAfterGameplayFinish() { addItem(() => OtherBeatmap); @@ -82,7 +72,6 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] // See above public void TestItemsNotClearedWhenSwitchToHostOnlyMode() { addItem(() => OtherBeatmap); @@ -98,7 +87,6 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] // See above public void TestCorrectItemSelectedAfterNewItemAdded() { addItem(() => OtherBeatmap); @@ -106,7 +94,6 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] // See above public void TestCorrectRulesetSelectedAfterNewItemAdded() { addItem(() => OtherBeatmap, new CatchRuleset().RulesetInfo); @@ -124,7 +111,6 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] // See above public void TestCorrectModsSelectedAfterNewItemAdded() { addItem(() => OtherBeatmap, mods: new Mod[] { new OsuModDoubleTime() }); @@ -153,7 +139,6 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for song select", () => (songSelect = CurrentSubScreen as Screens.Select.SongSelect) != null); AddUntilStep("wait for loaded", () => songSelect.AsNonNull().BeatmapSetsLoaded); - AddUntilStep("wait for ongoing operation to complete", () => !(CurrentScreen as OnlinePlayScreen).ChildrenOfType().Single().InProgress.Value); if (ruleset != null) AddStep($"set {ruleset.Name} ruleset", () => songSelect.AsNonNull().Ruleset.Value = ruleset); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs index dc36f539e3..78baa4a39b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs @@ -50,14 +50,6 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] - /* - * TearDown : System.TimeoutException : "wait for ongoing operation to complete" timed out - * --TearDown - * at osu.Framework.Testing.Drawables.Steps.UntilStepButton.<>c__DisplayClass11_0.<.ctor>b__0() - * at osu.Framework.Testing.Drawables.Steps.StepButton.PerformStep(Boolean userTriggered) - * at osu.Framework.Testing.TestScene.runNextStep(Action onCompletion, Action`1 onError, Func`2 stopCondition) - */ public void TestItemStillSelectedAfterChangeToSameBeatmap() { selectNewItem(() => InitialBeatmap); @@ -66,7 +58,6 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] // See above public void TestItemStillSelectedAfterChangeToOtherBeatmap() { selectNewItem(() => OtherBeatmap); @@ -75,7 +66,6 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] // See above public void TestOnlyLastItemChangedAfterGameplayFinished() { RunGameplay(); @@ -90,7 +80,6 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - [FlakyTest] // See above public void TestAddItemsAsHost() { addItem(() => OtherBeatmap); @@ -115,7 +104,6 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for song select", () => CurrentSubScreen is Screens.Select.SongSelect select && select.BeatmapSetsLoaded); BeatmapInfo otherBeatmap = null; - AddUntilStep("wait for ongoing operation to complete", () => !(CurrentScreen as OnlinePlayScreen).ChildrenOfType().Single().InProgress.Value); AddStep("select other beatmap", () => ((Screens.Select.SongSelect)CurrentSubScreen).FinaliseSelection(otherBeatmap = beatmap())); AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen); @@ -131,7 +119,6 @@ namespace osu.Game.Tests.Visual.Multiplayer }); AddUntilStep("wait for song select", () => CurrentSubScreen is Screens.Select.SongSelect select && select.BeatmapSetsLoaded); - AddUntilStep("wait for ongoing operation to complete", () => !(CurrentScreen as OnlinePlayScreen).ChildrenOfType().Single().InProgress.Value); AddStep("select other beatmap", () => ((Screens.Select.SongSelect)CurrentSubScreen).FinaliseSelection(beatmap())); AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen); } From 55a045b2b24443b4fef1e0e343dfe85d37ee91b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 28 Jan 2023 15:59:50 +0100 Subject: [PATCH 135/142] Adjust beatmap skin resources test to facilitate loading many --- .../Skins/TestSceneBeatmapSkinResources.cs | 32 ++++++++----------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs index 5256bcb3af..0bcaf217ab 100644 --- a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs +++ b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs @@ -1,12 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio.Track; using osu.Framework.Extensions; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Testing; using osu.Game.Audio; using osu.Game.Beatmaps; @@ -20,29 +19,26 @@ namespace osu.Game.Tests.Skins public partial class TestSceneBeatmapSkinResources : OsuTestScene { [Resolved] - private BeatmapManager beatmaps { get; set; } + private BeatmapManager beatmaps { get; set; } = null!; - private IWorkingBeatmap beatmap; - - [BackgroundDependencyLoader] - private void load() + [Test] + public void TestRetrieveOggAudio() { - var imported = beatmaps.Import(new ImportTask(TestResources.OpenResource("Archives/ogg-beatmap.osz"), "ogg-beatmap.osz")).GetResultSafely(); + IWorkingBeatmap beatmap = null!; - imported?.PerformRead(s => + AddStep("import beatmap", () => beatmap = importBeatmapFromArchives(@"ogg-beatmap.osz")); + AddAssert("sample is non-null", () => beatmap.Skin.GetSample(new SampleInfo(@"sample")) != null); + AddAssert("track is non-null", () => { - beatmap = beatmaps.GetWorkingBeatmap(s.Beatmaps[0]); + using (var track = beatmap.LoadTrack()) + return track is not TrackVirtual; }); } - [Test] - public void TestRetrieveOggSample() => AddAssert("sample is non-null", () => beatmap.Skin.GetSample(new SampleInfo("sample")) != null); - - [Test] - public void TestRetrieveOggTrack() => AddAssert("track is non-null", () => + private IWorkingBeatmap importBeatmapFromArchives(string filename) { - using (var track = beatmap.LoadTrack()) - return track is not TrackVirtual; - }); + var imported = beatmaps.Import(new ImportTask(TestResources.OpenResource($@"Archives/{filename}"), filename)).GetResultSafely(); + return imported.AsNonNull().PerformRead(s => beatmaps.GetWorkingBeatmap(s.Beatmaps[0])); + } } } From a8f828d203f8efab41d9ce23e2e34cc76825db3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 28 Jan 2023 16:02:11 +0100 Subject: [PATCH 136/142] Add another failing test case --- .../Archives/conflicting-filenames-beatmap.osz | Bin 0 -> 6604 bytes .../Skins/TestSceneBeatmapSkinResources.cs | 10 ++++++++++ 2 files changed, 10 insertions(+) create mode 100644 osu.Game.Tests/Resources/Archives/conflicting-filenames-beatmap.osz diff --git a/osu.Game.Tests/Resources/Archives/conflicting-filenames-beatmap.osz b/osu.Game.Tests/Resources/Archives/conflicting-filenames-beatmap.osz new file mode 100644 index 0000000000000000000000000000000000000000..c6b5f083ff6e5f17cb632ae21a29d2ebe58eb963 GIT binary patch literal 6604 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 literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs index 0bcaf217ab..d9212386c3 100644 --- a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs +++ b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs @@ -35,6 +35,16 @@ namespace osu.Game.Tests.Skins }); } + [Test] + public void TestRetrievalWithConflictingFilenames() + { + IWorkingBeatmap beatmap = null!; + + AddStep("import beatmap", () => beatmap = importBeatmapFromArchives(@"conflicting-filenames-beatmap.osz")); + AddAssert("texture is non-null", () => beatmap.Skin.GetTexture(@"spinner-osu") != null); + AddAssert("sample is non-null", () => beatmap.Skin.GetSample(new SampleInfo(@"spinner-osu")) != null); + } + private IWorkingBeatmap importBeatmapFromArchives(string filename) { var imported = beatmaps.Import(new ImportTask(TestResources.OpenResource($@"Archives/{filename}"), filename)).GetResultSafely(); From c5e1f54185b6ad3391f61b409035d06f79aaca46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 28 Jan 2023 21:16:22 +0100 Subject: [PATCH 137/142] Fix sample store creation mutating shared resource store --- .../Rulesets/TestSceneDrawableRulesetDependencies.cs | 2 ++ osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs | 2 ++ osu.Game/Skinning/Skin.cs | 9 ++++++--- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs b/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs index 3fba21050e..5cfcca303f 100644 --- a/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs +++ b/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs @@ -150,6 +150,8 @@ namespace osu.Game.Tests.Rulesets public IBindable AggregateTempo => throw new NotImplementedException(); public int PlaybackConcurrency { get; set; } + + public void AddExtension(string extension) => throw new NotImplementedException(); } private class TestShaderManager : ShaderManager diff --git a/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs index c50f63c3b2..96b02ee4dc 100644 --- a/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs +++ b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs @@ -157,6 +157,8 @@ namespace osu.Game.Rulesets.UI set => throw new NotSupportedException(); } + public void AddExtension(string extension) => throw new NotSupportedException(); + public void Dispose() { if (primary.IsNotNull()) primary.Dispose(); diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 419dacadfd..37e98946c9 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -69,12 +69,15 @@ namespace osu.Game.Skinning storage ??= realmBackedStorage = new RealmBackedResourceStore(SkinInfo, resources.Files, resources.RealmAccess); var samples = resources.AudioManager?.GetSampleStore(storage); + if (samples != null) + { samples.PlaybackConcurrency = OsuGameBase.SAMPLE_CONCURRENCY; - // osu-stable performs audio lookups in order of wav -> mp3 -> ogg. - // The GetSampleStore() call above internally adds wav and mp3, so ogg is added at the end to ensure expected ordering. - (storage as ResourceStore)?.AddExtension("ogg"); + // osu-stable performs audio lookups in order of wav -> mp3 -> ogg. + // The GetSampleStore() call above internally adds wav and mp3, so ogg is added at the end to ensure expected ordering. + samples.AddExtension(@"ogg"); + } Samples = samples; Textures = new TextureStore(resources.Renderer, new MaxDimensionLimitedTextureLoaderStore(resources.CreateTextureLoaderStore(storage))); From b1cbe20cd82e93533b73ccfeeac17986129824a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 28 Jan 2023 21:16:37 +0100 Subject: [PATCH 138/142] Adjust markdown text flow to framework-side changes --- .../Containers/Markdown/OsuMarkdownTextFlowContainer.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs index 5f5b9acf56..dbc358882c 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs @@ -42,8 +42,12 @@ namespace osu.Game.Graphics.Containers.Markdown protected override void AddFootnoteBacklink(FootnoteLink footnoteBacklink) => AddDrawable(new OsuMarkdownFootnoteBacklink(footnoteBacklink)); - protected override SpriteText CreateEmphasisedSpriteText(bool bold, bool italic) - => CreateSpriteText().With(t => t.Font = t.Font.With(weight: bold ? FontWeight.Bold : FontWeight.Regular, italics: italic)); + protected override void ApplyEmphasisedCreationParameters(SpriteText spriteText, bool bold, bool italic) + { + base.ApplyEmphasisedCreationParameters(spriteText, bold, italic); + + spriteText.Font = spriteText.Font.With(weight: bold ? FontWeight.Bold : FontWeight.Regular, italics: italic); + } protected override void AddCustomComponent(CustomContainerInline inline) { From 54d5d4e7c6a96fb4cd1d48d8b89ad6d508e181b0 Mon Sep 17 00:00:00 2001 From: Cootz Date: Tue, 31 Jan 2023 07:06:26 +0300 Subject: [PATCH 139/142] Fix for the issue --- .../Settings/Sections/Maintenance/MigrationSelectScreen.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs index 80bf292057..57eb5ce60e 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs @@ -47,6 +47,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance var directoryInfos = target.GetDirectories(); var fileInfos = target.GetFiles(); + //With an empty disk (mb flash drive), this if could be false if (directoryInfos.Length > 0 || fileInfos.Length > 0) { // Quick test for whether there's already an osu! install at the target path. @@ -65,7 +66,11 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance return; } - target = target.CreateSubdirectory("osu-lazer"); + //Check for a root directory + if (target.Parent == null) + target = Directory.CreateDirectory(Path.Combine(target.FullName, "osu-lazer")); + else + target = target.CreateSubdirectory("osu-lazer"); } } catch (Exception e) From b18652b25fa366ea9191db55af53b43fc02fcb30 Mon Sep 17 00:00:00 2001 From: Cootz Date: Tue, 31 Jan 2023 10:14:21 +0300 Subject: [PATCH 140/142] CreateSubDirectory removed. Fixes the empty root issue --- .../Sections/Maintenance/MigrationSelectScreen.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs index 57eb5ce60e..159a99809d 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationSelectScreen.cs @@ -47,8 +47,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance var directoryInfos = target.GetDirectories(); var fileInfos = target.GetFiles(); - //With an empty disk (mb flash drive), this if could be false - if (directoryInfos.Length > 0 || fileInfos.Length > 0) + if (directoryInfos.Length > 0 || fileInfos.Length > 0 || target.Parent == null) { // Quick test for whether there's already an osu! install at the target path. if (fileInfos.Any(f => f.Name == OsuGameBase.CLIENT_DATABASE_FILENAME)) @@ -66,11 +65,8 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance return; } - //Check for a root directory - if (target.Parent == null) - target = Directory.CreateDirectory(Path.Combine(target.FullName, "osu-lazer")); - else - target = target.CreateSubdirectory("osu-lazer"); + //We aren't using CreateSubDirectory due to the flaw with a root of a drive + target = Directory.CreateDirectory(Path.Combine(target.FullName, "osu-lazer")); } } catch (Exception e) From 3d8b35184fe2edf3dd8a4d0a8ad52a9b6f833999 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 Jan 2023 16:24:06 +0900 Subject: [PATCH 141/142] Update framework --- 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 71944065bf..4bdf5d68c5 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - +