From bc0ab7dd4f1313e448c63661f3252beb18595e1c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 29 Jun 2021 23:39:32 +0300 Subject: [PATCH 01/58] Fix `RestoreDefaultValueButton` not behaving correctly on number types --- osu.Game/Overlays/RestoreDefaultValueButton.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/RestoreDefaultValueButton.cs b/osu.Game/Overlays/RestoreDefaultValueButton.cs index fe36f6ba6d..2f0c43dedb 100644 --- a/osu.Game/Overlays/RestoreDefaultValueButton.cs +++ b/osu.Game/Overlays/RestoreDefaultValueButton.cs @@ -20,7 +20,7 @@ namespace osu.Game.Overlays { public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks; - private readonly BindableWithCurrent current = new BindableWithCurrent(); + private readonly IBindableWithCurrent current = IBindableWithCurrent.Create(); // this is done to ensure a click on this button doesn't trigger focus on a parent element which contains the button. public override bool AcceptsFocus => true; @@ -62,7 +62,8 @@ namespace osu.Game.Overlays Action += () => { - if (!current.Disabled) current.SetDefault(); + if (!Current.Disabled) + Current.SetDefault(); }; } @@ -96,12 +97,12 @@ namespace osu.Game.Overlays private void updateState() { - if (current == null) + if (Current == null) return; - this.FadeTo(current.IsDefault ? 0f : - hovering && !current.Disabled ? 1f : 0.65f, 200, Easing.OutQuint); - this.FadeColour(current.Disabled ? Color4.Gray : buttonColour, 200, Easing.OutQuint); + this.FadeTo(Current.IsDefault ? 0f : + hovering && !Current.Disabled ? 1f : 0.65f, 200, Easing.OutQuint); + this.FadeColour(Current.Disabled ? Color4.Gray : buttonColour, 200, Easing.OutQuint); } } } From 2b366e04fd1fed1506a8e356de951466e040793a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 2 Jul 2021 21:06:57 +0300 Subject: [PATCH 02/58] Revert "Fix `RestoreDefaultValueButton` not behaving correctly on number types" This reverts commit bc0ab7dd4f1313e448c63661f3252beb18595e1c. --- osu.Game/Overlays/RestoreDefaultValueButton.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/RestoreDefaultValueButton.cs b/osu.Game/Overlays/RestoreDefaultValueButton.cs index 2f0c43dedb..fe36f6ba6d 100644 --- a/osu.Game/Overlays/RestoreDefaultValueButton.cs +++ b/osu.Game/Overlays/RestoreDefaultValueButton.cs @@ -20,7 +20,7 @@ namespace osu.Game.Overlays { public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks; - private readonly IBindableWithCurrent current = IBindableWithCurrent.Create(); + private readonly BindableWithCurrent current = new BindableWithCurrent(); // this is done to ensure a click on this button doesn't trigger focus on a parent element which contains the button. public override bool AcceptsFocus => true; @@ -62,8 +62,7 @@ namespace osu.Game.Overlays Action += () => { - if (!Current.Disabled) - Current.SetDefault(); + if (!current.Disabled) current.SetDefault(); }; } @@ -97,12 +96,12 @@ namespace osu.Game.Overlays private void updateState() { - if (Current == null) + if (current == null) return; - this.FadeTo(Current.IsDefault ? 0f : - hovering && !Current.Disabled ? 1f : 0.65f, 200, Easing.OutQuint); - this.FadeColour(Current.Disabled ? Color4.Gray : buttonColour, 200, Easing.OutQuint); + this.FadeTo(current.IsDefault ? 0f : + hovering && !current.Disabled ? 1f : 0.65f, 200, Easing.OutQuint); + this.FadeColour(current.Disabled ? Color4.Gray : buttonColour, 200, Easing.OutQuint); } } } From 612ed6353c909da3d1099222795d8a3abf49a2d0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 2 Jul 2021 22:30:26 +0300 Subject: [PATCH 03/58] Resolve `RestoreDefaultValueButton` issue by internal management --- .../Overlays/RestoreDefaultValueButton.cs | 41 +++++++++++-------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/osu.Game/Overlays/RestoreDefaultValueButton.cs b/osu.Game/Overlays/RestoreDefaultValueButton.cs index fe36f6ba6d..cd4490d452 100644 --- a/osu.Game/Overlays/RestoreDefaultValueButton.cs +++ b/osu.Game/Overlays/RestoreDefaultValueButton.cs @@ -20,15 +20,30 @@ namespace osu.Game.Overlays { public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks; - private readonly BindableWithCurrent current = new BindableWithCurrent(); - // this is done to ensure a click on this button doesn't trigger focus on a parent element which contains the button. public override bool AcceptsFocus => true; + private Bindable current; + public Bindable Current { - get => current.Current; - set => current.Current = value; + get => current; + set + { + if (current != null) + { + current.ValueChanged -= onValueChanged; + current.DefaultChanged -= onDefaultChanged; + current.DisabledChanged -= onDisabledChanged; + } + + current = value; + + current.ValueChanged += onValueChanged; + current.DefaultChanged += onDefaultChanged; + current.DisabledChanged += onDisabledChanged; + UpdateState(); + } } private Color4 buttonColour; @@ -62,21 +77,11 @@ namespace osu.Game.Overlays Action += () => { - if (!current.Disabled) current.SetDefault(); + if (!current.Disabled) + current.SetDefault(); }; } - protected override void LoadComplete() - { - base.LoadComplete(); - - Current.ValueChanged += _ => UpdateState(); - Current.DisabledChanged += _ => UpdateState(); - Current.DefaultChanged += _ => UpdateState(); - - UpdateState(); - } - public LocalisableString TooltipText => "revert to default"; protected override bool OnHover(HoverEvent e) @@ -92,6 +97,10 @@ namespace osu.Game.Overlays UpdateState(); } + private void onValueChanged(ValueChangedEvent _) => UpdateState(); + private void onDefaultChanged(ValueChangedEvent _) => UpdateState(); + private void onDisabledChanged(bool _) => UpdateState(); + public void UpdateState() => Scheduler.AddOnce(updateState); private void updateState() From 83578e7c9d878530bf68a5ae2099916ff619824f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 2 Jul 2021 23:24:51 +0300 Subject: [PATCH 04/58] Hold a bound copy reference instead --- osu.Game/Overlays/RestoreDefaultValueButton.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/RestoreDefaultValueButton.cs b/osu.Game/Overlays/RestoreDefaultValueButton.cs index cd4490d452..0790929ab2 100644 --- a/osu.Game/Overlays/RestoreDefaultValueButton.cs +++ b/osu.Game/Overlays/RestoreDefaultValueButton.cs @@ -30,14 +30,8 @@ namespace osu.Game.Overlays get => current; set { - if (current != null) - { - current.ValueChanged -= onValueChanged; - current.DefaultChanged -= onDefaultChanged; - current.DisabledChanged -= onDisabledChanged; - } - - current = value; + current?.UnbindAll(); + current = value.GetBoundCopy(); current.ValueChanged += onValueChanged; current.DefaultChanged += onDefaultChanged; From 28adb43a4a6151cc669d8512830e472cf51dc369 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 7 Jul 2021 09:26:17 +0300 Subject: [PATCH 05/58] Add detailed explaination for the reason of using old binding method --- osu.Game/Overlays/RestoreDefaultValueButton.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Overlays/RestoreDefaultValueButton.cs b/osu.Game/Overlays/RestoreDefaultValueButton.cs index 0790929ab2..44adb70108 100644 --- a/osu.Game/Overlays/RestoreDefaultValueButton.cs +++ b/osu.Game/Overlays/RestoreDefaultValueButton.cs @@ -23,6 +23,12 @@ namespace osu.Game.Overlays // this is done to ensure a click on this button doesn't trigger focus on a parent element which contains the button. public override bool AcceptsFocus => true; + // this is intentionally not using BindableWithCurrent as this needs a BindableNumber instance for an accurate IsDefault value. + // + // this also cannot use IBindableWithCurrent.Create() due to BindableNumberWithCurrent + // directly casting given bindables to BindableNumber, which is not necessarily the case. + // + // therefore rely on the old method of taking each current bindable instance for now, until things are settled framework-side. private Bindable current; public Bindable Current From ddca132ab594c7c325e4f609656be2bc73352338 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 7 Jul 2021 21:38:38 +0900 Subject: [PATCH 06/58] Add difficulty adjustment mod tests --- .../Mods/ModDifficultyAdjustTest.cs | 168 ++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs diff --git a/osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs b/osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs new file mode 100644 index 0000000000..fcbdcbe724 --- /dev/null +++ b/osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs @@ -0,0 +1,168 @@ +// 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 NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Online.API; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Tests.Mods +{ + [TestFixture] + [Ignore("Currently broken, pending fixes/reworking of ModDifficultyAdjust.")] + public class ModDifficultyAdjustTest + { + // Todo: This shouldn't exist, but is currently required for the mod to accept a new BeatmapDifficulty object... + private int currentId; + + private TestModDifficultyAdjust testMod; + + [SetUp] + public void Setup() + { + currentId = 0; + testMod = new TestModDifficultyAdjust(); + + // Todo: This shouldn't be a thing, but is currently required because this causes the mod to keep track of the bindables internally... + applyDifficulty(new BeatmapDifficulty + { + DrainRate = -1, + OverallDifficulty = -1 + }); + } + + [Test] + public void TestUnchangedSettingsFollowAppliedDifficulty() + { + var result = applyDifficulty(new BeatmapDifficulty + { + DrainRate = 10, + OverallDifficulty = 10 + }); + + Assert.That(result.DrainRate, Is.EqualTo(10)); + Assert.That(result.OverallDifficulty, Is.EqualTo(10)); + + result = applyDifficulty(new BeatmapDifficulty + { + DrainRate = 1, + OverallDifficulty = 1 + }); + + Assert.That(result.DrainRate, Is.EqualTo(1)); + Assert.That(result.OverallDifficulty, Is.EqualTo(1)); + } + + [Test] + public void TestChangedSettingsOverrideAppliedDifficulty() + { + testMod.OverallDifficulty.Value = 4; + + var result = applyDifficulty(new BeatmapDifficulty + { + DrainRate = 10, + OverallDifficulty = 10 + }); + + Assert.That(result.DrainRate, Is.EqualTo(10)); + Assert.That(result.OverallDifficulty, Is.EqualTo(4)); + + result = applyDifficulty(new BeatmapDifficulty + { + DrainRate = 1, + OverallDifficulty = 1 + }); + + Assert.That(result.DrainRate, Is.EqualTo(1)); + Assert.That(result.OverallDifficulty, Is.EqualTo(4)); + } + + [Test] + public void TestChangedSettingsRetainedWhenSameValueIsApplied() + { + testMod.OverallDifficulty.Value = 4; + + // Apply and de-apply the same value as the mod. + applyDifficulty(new BeatmapDifficulty { OverallDifficulty = 4 }); + var result = applyDifficulty(new BeatmapDifficulty { OverallDifficulty = 10 }); + + Assert.That(result.OverallDifficulty, Is.EqualTo(4)); + } + + [Test] + public void TestChangedSettingSerialisedWhenSameValueIsApplied() + { + applyDifficulty(new BeatmapDifficulty { OverallDifficulty = 4 }); + testMod.OverallDifficulty.Value = 4; + + var result = (TestModDifficultyAdjust)new APIMod(testMod).ToMod(new TestRuleset()); + + Assert.That(result.OverallDifficulty.Value, Is.EqualTo(4)); + } + + [Test] + public void TestChangedSettingsRevertedToDefault() + { + applyDifficulty(new BeatmapDifficulty + { + DrainRate = 10, + OverallDifficulty = 10 + }); + + testMod.OverallDifficulty.Value = 4; + testMod.ResetSettingsToDefaults(); + + Assert.That(testMod.DrainRate.Value, Is.EqualTo(10)); + Assert.That(testMod.OverallDifficulty.Value, Is.EqualTo(10)); + } + + /// + /// Applies a to the mod and returns a new + /// representing the result if the mod were applied to a fresh instance. + /// + private BeatmapDifficulty applyDifficulty(BeatmapDifficulty difficulty) + { + difficulty.ID = ++currentId; + testMod.ReadFromDifficulty(difficulty); + + var newDifficulty = new BeatmapDifficulty(); + testMod.ApplyToDifficulty(newDifficulty); + return newDifficulty; + } + + private class TestModDifficultyAdjust : ModDifficultyAdjust + { + } + + private class TestRuleset : Ruleset + { + public override IEnumerable GetModsFor(ModType type) + { + if (type == ModType.DifficultyIncrease) + yield return new TestModDifficultyAdjust(); + } + + public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) + { + throw new System.NotImplementedException(); + } + + public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) + { + throw new System.NotImplementedException(); + } + + public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) + { + throw new System.NotImplementedException(); + } + + public override string Description => string.Empty; + public override string ShortName => string.Empty; + } + } +} From 663ffae42f9b8e97037e7f4c3ba06b8acb68d600 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Wed, 7 Jul 2021 21:02:11 +0900 Subject: [PATCH 07/58] Fix hit object selection blueprint potential null reference --- osu.Game/Rulesets/Edit/HitObjectSelectionBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectSelectionBlueprint.cs b/osu.Game/Rulesets/Edit/HitObjectSelectionBlueprint.cs index 56434b1d82..77dc55c6ef 100644 --- a/osu.Game/Rulesets/Edit/HitObjectSelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/HitObjectSelectionBlueprint.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Edit /// protected virtual bool AlwaysShowWhenSelected => false; - protected override bool ShouldBeAlive => (DrawableObject.IsAlive && DrawableObject.IsPresent) || (AlwaysShowWhenSelected && State == SelectionState.Selected); + protected override bool ShouldBeAlive => (DrawableObject?.IsAlive == true && DrawableObject.IsPresent) || (AlwaysShowWhenSelected && State == SelectionState.Selected); protected HitObjectSelectionBlueprint(HitObject hitObject) : base(hitObject) From 8d94e8f5346b7bf17ee8cf4936ce177533e041b8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Jul 2021 14:28:05 +0900 Subject: [PATCH 08/58] Enable tests and update expectations --- osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs b/osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs index fcbdcbe724..692f4ec5b7 100644 --- a/osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs +++ b/osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs @@ -13,7 +13,6 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Tests.Mods { [TestFixture] - [Ignore("Currently broken, pending fixes/reworking of ModDifficultyAdjust.")] public class ModDifficultyAdjustTest { // Todo: This shouldn't exist, but is currently required for the mod to accept a new BeatmapDifficulty object... @@ -116,8 +115,16 @@ namespace osu.Game.Tests.Mods testMod.OverallDifficulty.Value = 4; testMod.ResetSettingsToDefaults(); - Assert.That(testMod.DrainRate.Value, Is.EqualTo(10)); - Assert.That(testMod.OverallDifficulty.Value, Is.EqualTo(10)); + Assert.That(testMod.DrainRate.Value, Is.Null); + Assert.That(testMod.OverallDifficulty.Value, Is.Null); + + var applied = applyDifficulty(new BeatmapDifficulty + { + DrainRate = 10, + OverallDifficulty = 10 + }); + + Assert.That(applied.OverallDifficulty, Is.EqualTo(10)); } /// @@ -126,10 +133,12 @@ namespace osu.Game.Tests.Mods /// private BeatmapDifficulty applyDifficulty(BeatmapDifficulty difficulty) { + // ensure that ReadFromDifficulty doesn't pollute the values. + var newDifficulty = difficulty.Clone(); + difficulty.ID = ++currentId; testMod.ReadFromDifficulty(difficulty); - var newDifficulty = new BeatmapDifficulty(); testMod.ApplyToDifficulty(newDifficulty); return newDifficulty; } From 0e4f4a6fde3ca36abd4978b01e9806d0a3d0a6a7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Jul 2021 14:28:13 +0900 Subject: [PATCH 09/58] Initial storage changes --- .../Mods/CatchModDifficultyAdjust.cs | 29 +++--- .../Mods/OsuModDifficultyAdjust.cs | 29 +++--- .../Mods/TaikoModDifficultyAdjust.cs | 8 +- osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs | 97 +++---------------- 4 files changed, 40 insertions(+), 123 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs index bd7a1df2e4..aaa426cd03 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs @@ -13,23 +13,23 @@ namespace osu.Game.Rulesets.Catch.Mods public class CatchModDifficultyAdjust : ModDifficultyAdjust, IApplicableToBeatmapProcessor { [SettingSource("Circle Size", "Override a beatmap's set CS.", FIRST_SETTING_ORDER - 1)] - public BindableNumber CircleSize { get; } = new BindableFloatWithLimitExtension + public Bindable CircleSize { get; } = new Bindable { + /* Precision = 0.1f, MinValue = 1, MaxValue = 10, - Default = 5, - Value = 5, + */ }; [SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1)] - public BindableNumber ApproachRate { get; } = new BindableFloatWithLimitExtension + public Bindable ApproachRate { get; } = new Bindable { + /* Precision = 0.1f, MinValue = 1, MaxValue = 10, - Default = 5, - Value = 5, + */ }; [SettingSource("Spicy Patterns", "Adjust the patterns as if Hard Rock is enabled.")] @@ -39,8 +39,9 @@ namespace osu.Game.Rulesets.Catch.Mods { base.ApplyLimits(extended); - CircleSize.MaxValue = extended ? 11 : 10; - ApproachRate.MaxValue = extended ? 11 : 10; + // TODO: reimplement + // CircleSize.MaxValue = extended ? 11 : 10; + // ApproachRate.MaxValue = extended ? 11 : 10; } public override string SettingDescription @@ -61,20 +62,12 @@ namespace osu.Game.Rulesets.Catch.Mods } } - protected override void TransferSettings(BeatmapDifficulty difficulty) - { - base.TransferSettings(difficulty); - - TransferSetting(CircleSize, difficulty.CircleSize); - TransferSetting(ApproachRate, difficulty.ApproachRate); - } - protected override void ApplySettings(BeatmapDifficulty difficulty) { base.ApplySettings(difficulty); - ApplySetting(CircleSize, cs => difficulty.CircleSize = cs); - ApplySetting(ApproachRate, ar => difficulty.ApproachRate = ar); + if (CircleSize.Value != null) difficulty.CircleSize = CircleSize.Value.Value; + if (ApproachRate.Value != null) difficulty.ApproachRate = ApproachRate.Value.Value; } public void ApplyToBeatmapProcessor(IBeatmapProcessor beatmapProcessor) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs index 1cb25edecf..54e30c56a3 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs @@ -12,31 +12,32 @@ namespace osu.Game.Rulesets.Osu.Mods public class OsuModDifficultyAdjust : ModDifficultyAdjust { [SettingSource("Circle Size", "Override a beatmap's set CS.", FIRST_SETTING_ORDER - 1)] - public BindableNumber CircleSize { get; } = new BindableFloatWithLimitExtension + public Bindable CircleSize { get; } = new Bindable { + /* Precision = 0.1f, MinValue = 0, MaxValue = 10, - Default = 5, - Value = 5, + */ }; [SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1)] - public BindableNumber ApproachRate { get; } = new BindableFloatWithLimitExtension + public Bindable ApproachRate { get; } = new Bindable { + /* Precision = 0.1f, MinValue = 0, MaxValue = 10, - Default = 5, - Value = 5, + */ }; protected override void ApplyLimits(bool extended) { base.ApplyLimits(extended); - CircleSize.MaxValue = extended ? 11 : 10; - ApproachRate.MaxValue = extended ? 11 : 10; + // TODO: reimplement + // CircleSize.MaxValue = extended ? 11 : 10; + // ApproachRate.MaxValue = extended ? 11 : 10; } public override string SettingDescription @@ -55,20 +56,12 @@ namespace osu.Game.Rulesets.Osu.Mods } } - protected override void TransferSettings(BeatmapDifficulty difficulty) - { - base.TransferSettings(difficulty); - - TransferSetting(CircleSize, difficulty.CircleSize); - TransferSetting(ApproachRate, difficulty.ApproachRate); - } - protected override void ApplySettings(BeatmapDifficulty difficulty) { base.ApplySettings(difficulty); - ApplySetting(CircleSize, cs => difficulty.CircleSize = cs); - ApplySetting(ApproachRate, ar => difficulty.ApproachRate = ar); + if (CircleSize.Value != null) difficulty.CircleSize = CircleSize.Value.Value; + if (ApproachRate.Value != null) difficulty.ApproachRate = ApproachRate.Value.Value; } } } diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs index 4006652bd5..902ccdc14e 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs @@ -12,13 +12,13 @@ namespace osu.Game.Rulesets.Taiko.Mods public class TaikoModDifficultyAdjust : ModDifficultyAdjust { [SettingSource("Scroll Speed", "Adjust a beatmap's set scroll speed", LAST_SETTING_ORDER + 1)] - public BindableNumber ScrollSpeed { get; } = new BindableFloat + public Bindable ScrollSpeed { get; } = new Bindable { + /* Precision = 0.05f, MinValue = 0.25f, MaxValue = 4, - Default = 1, - Value = 1, + */ }; public override string SettingDescription @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Taiko.Mods { base.ApplySettings(difficulty); - ApplySetting(ScrollSpeed, scroll => difficulty.SliderMultiplier *= scroll); + if (ScrollSpeed.Value != null) difficulty.SliderMultiplier = ScrollSpeed.Value.Value; } } } diff --git a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs index b70eee4e1d..e98bd14720 100644 --- a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs @@ -1,13 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Game.Beatmaps; +using System; +using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; -using System; -using System.Collections.Generic; +using osu.Game.Beatmaps; using osu.Game.Configuration; -using System.Linq; namespace osu.Game.Rulesets.Mods { @@ -34,23 +33,23 @@ namespace osu.Game.Rulesets.Mods protected const int LAST_SETTING_ORDER = 2; [SettingSource("HP Drain", "Override a beatmap's set HP.", FIRST_SETTING_ORDER)] - public BindableNumber DrainRate { get; } = new BindableFloatWithLimitExtension + public Bindable DrainRate { get; } = new Bindable { + /* Precision = 0.1f, MinValue = 0, MaxValue = 10, - Default = 5, - Value = 5, + */ }; [SettingSource("Accuracy", "Override a beatmap's set OD.", LAST_SETTING_ORDER)] - public BindableNumber OverallDifficulty { get; } = new BindableFloatWithLimitExtension + public Bindable OverallDifficulty { get; } = new Bindable { + /* Precision = 0.1f, MinValue = 0, MaxValue = 10, - Default = 5, - Value = 5, + */ }; [SettingSource("Extended Limits", "Adjust difficulty beyond sane limits.")] @@ -67,8 +66,9 @@ namespace osu.Game.Rulesets.Mods /// Whether limits should extend beyond sane ranges. protected virtual void ApplyLimits(bool extended) { - DrainRate.MaxValue = extended ? 11 : 10; - OverallDifficulty.MaxValue = extended ? 11 : 10; + // TODO: reimplement + // DrainRate.MaxValue = extended ? 11 : 10; + // OverallDifficulty.MaxValue = extended ? 11 : 10; } public override string SettingDescription @@ -86,89 +86,20 @@ namespace osu.Game.Rulesets.Mods } } - private BeatmapDifficulty difficulty; - public void ReadFromDifficulty(BeatmapDifficulty difficulty) { - if (this.difficulty == null || this.difficulty.ID != difficulty.ID) - { - TransferSettings(difficulty); - this.difficulty = difficulty; - } } public void ApplyToDifficulty(BeatmapDifficulty difficulty) => ApplySettings(difficulty); - /// - /// Transfer initial settings from the beatmap to settings. - /// - /// The beatmap's initial values. - protected virtual void TransferSettings(BeatmapDifficulty difficulty) - { - TransferSetting(DrainRate, difficulty.DrainRate); - TransferSetting(OverallDifficulty, difficulty.OverallDifficulty); - } - - private readonly Dictionary userChangedSettings = new Dictionary(); - - /// - /// Transfer a setting from to a configuration bindable. - /// Only performs the transfer if the user is not currently overriding. - /// - protected void TransferSetting(BindableNumber bindable, T beatmapDefault) - where T : struct, IComparable, IConvertible, IEquatable - { - bindable.UnbindEvents(); - - userChangedSettings.TryAdd(bindable, false); - - bindable.Default = beatmapDefault; - - // users generally choose a difficulty setting and want it to stick across multiple beatmap changes. - // we only want to value transfer if the user hasn't changed the value previously. - if (!userChangedSettings[bindable]) - bindable.Value = beatmapDefault; - - bindable.ValueChanged += _ => userChangedSettings[bindable] = !bindable.IsDefault; - } - - internal override void CopyAdjustedSetting(IBindable target, object source) - { - // if the value is non-bindable, it's presumably coming from an external source (like the API) - therefore presume it is not default. - // if the value is bindable, defer to the source's IsDefault to be able to tell. - userChangedSettings[target] = !(source is IBindable bindableSource) || !bindableSource.IsDefault; - base.CopyAdjustedSetting(target, source); - } - - /// - /// Applies a setting from a configuration bindable using , if it has been changed by the user. - /// - protected void ApplySetting(BindableNumber setting, Action applyFunc) - where T : struct, IComparable, IConvertible, IEquatable - { - if (userChangedSettings.TryGetValue(setting, out bool userChangedSetting) && userChangedSetting) - applyFunc.Invoke(setting.Value); - } - /// /// Apply all custom settings to the provided beatmap. /// /// The beatmap to have settings applied. protected virtual void ApplySettings(BeatmapDifficulty difficulty) { - ApplySetting(DrainRate, dr => difficulty.DrainRate = dr); - ApplySetting(OverallDifficulty, od => difficulty.OverallDifficulty = od); - } - - public override void ResetSettingsToDefaults() - { - base.ResetSettingsToDefaults(); - - if (difficulty != null) - { - // base implementation potentially overwrite modified defaults that came from a beatmap selection. - TransferSettings(difficulty); - } + if (DrainRate.Value != null) difficulty.DrainRate = DrainRate.Value.Value; + if (OverallDifficulty.Value != null) difficulty.OverallDifficulty = OverallDifficulty.Value.Value; } /// From d540156e94c3254ee43a9aa28dfc1f13a01852ce Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Jul 2021 14:29:30 +0900 Subject: [PATCH 10/58] Remove now unnecessary `BeatmapDifficulty.ID` --- osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs b/osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs index 692f4ec5b7..84cf796835 100644 --- a/osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs +++ b/osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs @@ -15,23 +15,12 @@ namespace osu.Game.Tests.Mods [TestFixture] public class ModDifficultyAdjustTest { - // Todo: This shouldn't exist, but is currently required for the mod to accept a new BeatmapDifficulty object... - private int currentId; - private TestModDifficultyAdjust testMod; [SetUp] public void Setup() { - currentId = 0; testMod = new TestModDifficultyAdjust(); - - // Todo: This shouldn't be a thing, but is currently required because this causes the mod to keep track of the bindables internally... - applyDifficulty(new BeatmapDifficulty - { - DrainRate = -1, - OverallDifficulty = -1 - }); } [Test] @@ -136,7 +125,6 @@ namespace osu.Game.Tests.Mods // ensure that ReadFromDifficulty doesn't pollute the values. var newDifficulty = difficulty.Clone(); - difficulty.ID = ++currentId; testMod.ReadFromDifficulty(difficulty); testMod.ApplyToDifficulty(newDifficulty); From bd4b3f5268936c8a440073679cda9645c77c524c Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 8 Jul 2021 15:42:29 +0900 Subject: [PATCH 11/58] Add catch selection blueprint visual test scene (without tests) --- .../Editor/CatchEditorTestSceneContainer.cs | 66 +++++++++++++++++++ .../CatchSelectionBlueprintTestScene.cs | 24 +++++++ .../TestSceneJuiceStreamSelectionBlueprint.cs | 38 +++++++++++ .../Visual/SelectionBlueprintTestScene.cs | 3 +- 4 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Catch.Tests/Editor/CatchEditorTestSceneContainer.cs create mode 100644 osu.Game.Rulesets.Catch.Tests/Editor/CatchSelectionBlueprintTestScene.cs create mode 100644 osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/CatchEditorTestSceneContainer.cs b/osu.Game.Rulesets.Catch.Tests/Editor/CatchEditorTestSceneContainer.cs new file mode 100644 index 0000000000..158c8edba5 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Editor/CatchEditorTestSceneContainer.cs @@ -0,0 +1,66 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Catch.Edit; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Catch.Tests.Editor +{ + public class CatchEditorTestSceneContainer : Container + { + [Cached(typeof(Playfield))] + public readonly ScrollingPlayfield Playfield; + + protected override Container Content { get; } + + public CatchEditorTestSceneContainer() + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + Width = CatchPlayfield.WIDTH; + Height = 1000; + Padding = new MarginPadding + { + Bottom = 100 + }; + + InternalChildren = new Drawable[] + { + new ScrollingTestContainer(ScrollingDirection.Down) + { + TimeRange = 1000, + RelativeSizeAxes = Axes.Both, + Child = Playfield = new TestCatchPlayfield + { + RelativeSizeAxes = Axes.Both + } + }, + new PlayfieldBorder + { + PlayfieldBorderStyle = { Value = PlayfieldBorderStyle.Full }, + Clock = new FramedClock(new StopwatchClock(true)) + }, + Content = new Container + { + RelativeSizeAxes = Axes.Both + } + }; + } + + private class TestCatchPlayfield : CatchEditorPlayfield + { + public TestCatchPlayfield() + : base(new BeatmapDifficulty { CircleSize = 0 }) + { + } + } + } +} diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/CatchSelectionBlueprintTestScene.cs b/osu.Game.Rulesets.Catch.Tests/Editor/CatchSelectionBlueprintTestScene.cs new file mode 100644 index 0000000000..dcdc32145b --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Editor/CatchSelectionBlueprintTestScene.cs @@ -0,0 +1,24 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Catch.Tests.Editor +{ + public abstract class CatchSelectionBlueprintTestScene : SelectionBlueprintTestScene + { + protected ScrollingHitObjectContainer HitObjectContainer => contentContainer.Playfield.HitObjectContainer; + + protected override Container Content => contentContainer; + + private readonly CatchEditorTestSceneContainer contentContainer; + + protected CatchSelectionBlueprintTestScene() + { + base.Content.Add(contentContainer = new CatchEditorTestSceneContainer()); + } + } +} diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs new file mode 100644 index 0000000000..1b96175020 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs @@ -0,0 +1,38 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Catch.Edit.Blueprints; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osuTK; + +namespace osu.Game.Rulesets.Catch.Tests.Editor +{ + public class TestSceneJuiceStreamSelectionBlueprint : CatchSelectionBlueprintTestScene + { + public TestSceneJuiceStreamSelectionBlueprint() + { + var hitObject = new JuiceStream + { + OriginalX = 100, + StartTime = 100, + Path = new SliderPath(PathType.PerfectCurve, new[] + { + Vector2.Zero, + new Vector2(200, 100), + new Vector2(0, 200), + }), + }; + var controlPoint = new ControlPointInfo(); + controlPoint.Add(0, new TimingControlPoint + { + BeatLength = 100 + }); + hitObject.ApplyDefaults(controlPoint, new BeatmapDifficulty { CircleSize = 0 }); + AddBlueprint(new JuiceStreamSelectionBlueprint(hitObject)); + } + } +} diff --git a/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs b/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs index dc12a4999d..c3fb3bfc17 100644 --- a/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs +++ b/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using JetBrains.Annotations; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; @@ -23,7 +24,7 @@ namespace osu.Game.Tests.Visual }); } - protected void AddBlueprint(HitObjectSelectionBlueprint blueprint, DrawableHitObject drawableObject) + protected void AddBlueprint(HitObjectSelectionBlueprint blueprint, [CanBeNull] DrawableHitObject drawableObject = null) { Add(blueprint.With(d => { From 8da1335e5fe30a39fcc2b80ec5cc49eb51efed96 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 8 Jul 2021 15:51:46 +0900 Subject: [PATCH 12/58] Add catch placement blueprint visual test scenes (without tests) --- .../CatchPlacementBlueprintTestScene.cs | 45 +++++++++++++++++++ ...TestSceneBananaShowerPlacementBlueprint.cs | 28 ++++++++++++ .../TestSceneFruitPlacementBlueprint.cs | 19 ++++++++ .../Visual/PlacementBlueprintTestScene.cs | 2 +- 4 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Catch.Tests/Editor/CatchPlacementBlueprintTestScene.cs create mode 100644 osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs create mode 100644 osu.Game.Rulesets.Catch.Tests/Editor/TestSceneFruitPlacementBlueprint.cs diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/CatchPlacementBlueprintTestScene.cs b/osu.Game.Rulesets.Catch.Tests/Editor/CatchPlacementBlueprintTestScene.cs new file mode 100644 index 0000000000..12a8a97338 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Editor/CatchPlacementBlueprintTestScene.cs @@ -0,0 +1,45 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Timing; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Catch.Tests.Editor +{ + public abstract class CatchPlacementBlueprintTestScene : PlacementBlueprintTestScene + { + protected new ScrollingHitObjectContainer HitObjectContainer => contentContainer.Playfield.HitObjectContainer; + + protected override Container Content => contentContainer; + + private readonly CatchEditorTestSceneContainer contentContainer; + + protected CatchPlacementBlueprintTestScene() + { + base.Content.Add(contentContainer = new CatchEditorTestSceneContainer + { + Clock = new FramedClock(new ManualClock()) + }); + } + + // Unused because AddHitObject is overriden + protected override Container CreateHitObjectContainer() => new Container(); + + protected override void AddHitObject(DrawableHitObject hitObject) + { + contentContainer.Playfield.HitObjectContainer.Add(hitObject); + } + + protected override SnapResult SnapForBlueprint(PlacementBlueprint blueprint) + { + var result = base.SnapForBlueprint(blueprint); + result.Time = HitObjectContainer.TimeAtScreenSpacePosition(result.ScreenSpacePosition); + return result; + } + } +} diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs new file mode 100644 index 0000000000..dd8a7490b5 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs @@ -0,0 +1,28 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Catch.Edit.Blueprints; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.Objects.Drawables; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; + +namespace osu.Game.Rulesets.Catch.Tests.Editor +{ + public class TestSceneBananaShowerPlacementBlueprint : CatchPlacementBlueprintTestScene + { + protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableBananaShower((BananaShower)hitObject); + + protected override PlacementBlueprint CreateBlueprint() => new BananaShowerPlacementBlueprint(); + + protected override void AddHitObject(DrawableHitObject hitObject) + { + // Create nested bananas (but positions are not randomized because beatmap processing is not done). + hitObject.HitObject.ApplyDefaults(new ControlPointInfo(), Beatmap.Value.BeatmapInfo.BaseDifficulty); + + base.AddHitObject(hitObject); + } + } +} diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneFruitPlacementBlueprint.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneFruitPlacementBlueprint.cs new file mode 100644 index 0000000000..c1ce27f204 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneFruitPlacementBlueprint.cs @@ -0,0 +1,19 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Catch.Edit.Blueprints; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.Objects.Drawables; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; + +namespace osu.Game.Rulesets.Catch.Tests.Editor +{ + public class TestSceneFruitPlacementBlueprint : CatchPlacementBlueprintTestScene + { + protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableFruit((Fruit)hitObject); + + protected override PlacementBlueprint CreateBlueprint() => new FruitPlacementBlueprint(); + } +} diff --git a/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs b/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs index 2dc77fa72a..130a39c33a 100644 --- a/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs +++ b/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs @@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual protected PlacementBlueprintTestScene() { - Add(HitObjectContainer = CreateHitObjectContainer().With(c => c.Clock = new FramedClock(new StopwatchClock()))); + base.Content.Add(HitObjectContainer = CreateHitObjectContainer().With(c => c.Clock = new FramedClock(new StopwatchClock()))); } [BackgroundDependencyLoader] From ae67409f41aae01d8a8408c2e0763f0668a03e1f Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 8 Jul 2021 16:12:02 +0900 Subject: [PATCH 13/58] Add a test of fruit placement blueprint --- .../CatchPlacementBlueprintTestScene.cs | 37 ++++++++++++++++++- .../TestSceneFruitPlacementBlueprint.cs | 25 +++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/CatchPlacementBlueprintTestScene.cs b/osu.Game.Rulesets.Catch.Tests/Editor/CatchPlacementBlueprintTestScene.cs index 12a8a97338..c99e6626e7 100644 --- a/osu.Game.Rulesets.Catch.Tests/Editor/CatchPlacementBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Catch.Tests/Editor/CatchPlacementBlueprintTestScene.cs @@ -1,18 +1,30 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Collections.Generic; +using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; using osu.Framework.Timing; +using osu.Game.Rulesets.Catch.Edit.Blueprints.Components; +using osu.Game.Rulesets.Catch.Objects.Drawables; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Tests.Visual; +using osuTK; +using osuTK.Input; namespace osu.Game.Rulesets.Catch.Tests.Editor { public abstract class CatchPlacementBlueprintTestScene : PlacementBlueprintTestScene { + protected const double TIME_SNAP = 100; + + protected DrawableCatchHitObject LastObject; + protected new ScrollingHitObjectContainer HitObjectContainer => contentContainer.Playfield.HitObjectContainer; protected override Container Content => contentContainer; @@ -27,18 +39,41 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor }); } + [SetUp] + public void Setup() => Schedule(() => + { + HitObjectContainer.Clear(); + ResetPlacement(); + LastObject = null; + }); + + protected void AddMoveStep(double time, float x) => AddStep($"move to time={time}, x={x}", () => + { + float y = HitObjectContainer.PositionAtTime(time); + Vector2 pos = HitObjectContainer.ToScreenSpace(new Vector2(x, y + HitObjectContainer.DrawHeight)); + InputManager.MoveMouseTo(pos); + }); + + protected void AddClickStep(MouseButton button) => AddStep($"click {button}", () => + { + InputManager.Click(button); + }); + + protected IEnumerable FruitOutlines => Content.ChildrenOfType(); + // Unused because AddHitObject is overriden protected override Container CreateHitObjectContainer() => new Container(); protected override void AddHitObject(DrawableHitObject hitObject) { + LastObject = (DrawableCatchHitObject)hitObject; contentContainer.Playfield.HitObjectContainer.Add(hitObject); } protected override SnapResult SnapForBlueprint(PlacementBlueprint blueprint) { var result = base.SnapForBlueprint(blueprint); - result.Time = HitObjectContainer.TimeAtScreenSpacePosition(result.ScreenSpacePosition); + result.Time = Math.Round(HitObjectContainer.TimeAtScreenSpacePosition(result.ScreenSpacePosition) / TIME_SNAP) * TIME_SNAP; return result; } } diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneFruitPlacementBlueprint.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneFruitPlacementBlueprint.cs index c1ce27f204..4b1c45ae2f 100644 --- a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneFruitPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneFruitPlacementBlueprint.cs @@ -1,12 +1,17 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; +using NUnit.Framework; +using osu.Framework.Utils; using osu.Game.Rulesets.Catch.Edit.Blueprints; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawables; +using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; +using osuTK.Input; namespace osu.Game.Rulesets.Catch.Tests.Editor { @@ -15,5 +20,25 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableFruit((Fruit)hitObject); protected override PlacementBlueprint CreateBlueprint() => new FruitPlacementBlueprint(); + + [Test] + public void TestFruitPlacementPosition() + { + const double time = 300; + const float x = CatchPlayfield.CENTER_X; + + AddMoveStep(time, x); + AddClickStep(MouseButton.Left); + + AddAssert("outline position is correct", () => + { + var outline = FruitOutlines.Single(); + return Precision.AlmostEquals(outline.X, x) && + Precision.AlmostEquals(outline.Y, HitObjectContainer.PositionAtTime(time)); + }); + + AddAssert("fruit time is correct", () => Precision.AlmostEquals(LastObject.StartTimeBindable.Value, time)); + AddAssert("fruit position is correct", () => Precision.AlmostEquals(LastObject.X, x)); + } } } From 68116aa042a9062547b1fff4a8c9d81fd0e05464 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 8 Jul 2021 16:17:09 +0900 Subject: [PATCH 14/58] Fix placement blueprint animation is not running in test scene --- .../Editor/CatchPlacementBlueprintTestScene.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/CatchPlacementBlueprintTestScene.cs b/osu.Game.Rulesets.Catch.Tests/Editor/CatchPlacementBlueprintTestScene.cs index c99e6626e7..1d30ae34cd 100644 --- a/osu.Game.Rulesets.Catch.Tests/Editor/CatchPlacementBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Catch.Tests/Editor/CatchPlacementBlueprintTestScene.cs @@ -33,10 +33,9 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor protected CatchPlacementBlueprintTestScene() { - base.Content.Add(contentContainer = new CatchEditorTestSceneContainer - { - Clock = new FramedClock(new ManualClock()) - }); + base.Content.Add(contentContainer = new CatchEditorTestSceneContainer()); + + contentContainer.Playfield.Clock = new FramedClock(new ManualClock()); } [SetUp] From 4ac7d629d7fcd739e2bfac5175113e515ad33157 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 8 Jul 2021 16:36:41 +0900 Subject: [PATCH 15/58] Expose current placement blueprint --- osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs b/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs index 130a39c33a..42cf826bd4 100644 --- a/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs +++ b/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs @@ -17,7 +17,7 @@ namespace osu.Game.Tests.Visual public abstract class PlacementBlueprintTestScene : OsuManualInputManagerTestScene, IPlacementHandler { protected readonly Container HitObjectContainer; - private PlacementBlueprint currentBlueprint; + protected PlacementBlueprint CurrentBlueprint { get; private set; } protected PlacementBlueprintTestScene() { @@ -63,9 +63,9 @@ namespace osu.Game.Tests.Visual protected void ResetPlacement() { - if (currentBlueprint != null) - Remove(currentBlueprint); - Add(currentBlueprint = CreateBlueprint()); + if (CurrentBlueprint != null) + Remove(CurrentBlueprint); + Add(CurrentBlueprint = CreateBlueprint()); } public void Delete(HitObject hitObject) @@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual { base.Update(); - currentBlueprint.UpdateTimeAndPosition(SnapForBlueprint(currentBlueprint)); + CurrentBlueprint.UpdateTimeAndPosition(SnapForBlueprint(CurrentBlueprint)); } protected virtual SnapResult SnapForBlueprint(PlacementBlueprint blueprint) => From 8ac3015f14dcba91ddb413687e191ac3da7346f5 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 8 Jul 2021 16:36:44 +0900 Subject: [PATCH 16/58] Add tests of banana shower placement blueprint --- ...TestSceneBananaShowerPlacementBlueprint.cs | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs index dd8a7490b5..3f9db5cbf6 100644 --- a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs @@ -1,13 +1,19 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; +using NUnit.Framework; +using osu.Framework.Testing; +using osu.Framework.Utils; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Catch.Edit.Blueprints; +using osu.Game.Rulesets.Catch.Edit.Blueprints.Components; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawables; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; +using osuTK.Input; namespace osu.Game.Rulesets.Catch.Tests.Editor { @@ -24,5 +30,58 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor base.AddHitObject(hitObject); } + + [Test] + public void TestBasicPlacement() + { + const double start_time = 100; + const double end_time = 500; + + AddMoveStep(start_time, 0); + AddClickStep(MouseButton.Left); + AddMoveStep(end_time, 0); + AddClickStep(MouseButton.Right); + AddAssert("banana shower is placed", () => LastObject is DrawableBananaShower); + AddAssert("start time is correct", () => Precision.AlmostEquals(LastObject.HitObject.StartTime, start_time)); + AddAssert("end time is correct", () => Precision.AlmostEquals(LastObject.HitObject.GetEndTime(), end_time)); + } + + [Test] + public void TestReversePlacement() + { + const double start_time = 100; + const double end_time = 500; + + AddMoveStep(end_time, 0); + AddClickStep(MouseButton.Left); + AddMoveStep(start_time, 0); + AddClickStep(MouseButton.Right); + AddAssert("start time is correct", () => Precision.AlmostEquals(LastObject.HitObject.StartTime, start_time)); + AddAssert("end time is correct", () => Precision.AlmostEquals(LastObject.HitObject.GetEndTime(), end_time)); + } + + [Test] + public void TestFinishWithZeroDuration() + { + AddMoveStep(100, 0); + AddClickStep(MouseButton.Left); + AddClickStep(MouseButton.Right); + AddAssert("banana shower is not placed", () => LastObject == null); + AddAssert("state is waiting", () => CurrentBlueprint?.PlacementActive == PlacementBlueprint.PlacementState.Waiting); + } + + [Test] + public void TestOpacity() + { + AddMoveStep(100, 0); + AddClickStep(MouseButton.Left); + AddAssert("outline is semitransparent", () => timeSpanOutline.Alpha < 1); + AddMoveStep(200, 0); + AddAssert("outline is opaque", () => Precision.AlmostEquals(timeSpanOutline.Alpha, 1)); + AddMoveStep(100, 0); + AddAssert("outline is semitransparent", () => timeSpanOutline.Alpha < 1); + } + + private TimeSpanOutline timeSpanOutline => Content.ChildrenOfType().Single(); } } From 25b94061fd8b414deaefb1db961d0655e8b350dd Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 8 Jul 2021 16:40:18 +0900 Subject: [PATCH 17/58] Fix assert step not waiting for transformation --- .../Editor/TestSceneBananaShowerPlacementBlueprint.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs index 3f9db5cbf6..e3811b7669 100644 --- a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs @@ -75,11 +75,11 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor { AddMoveStep(100, 0); AddClickStep(MouseButton.Left); - AddAssert("outline is semitransparent", () => timeSpanOutline.Alpha < 1); + AddUntilStep("outline is semitransparent", () => Precision.DefinitelyBigger(1, timeSpanOutline.Alpha)); AddMoveStep(200, 0); - AddAssert("outline is opaque", () => Precision.AlmostEquals(timeSpanOutline.Alpha, 1)); + AddUntilStep("outline is opaque", () => Precision.AlmostEquals(timeSpanOutline.Alpha, 1)); AddMoveStep(100, 0); - AddAssert("outline is semitransparent", () => timeSpanOutline.Alpha < 1); + AddUntilStep("outline is semitransparent", () => Precision.DefinitelyBigger(1, timeSpanOutline.Alpha)); } private TimeSpanOutline timeSpanOutline => Content.ChildrenOfType().Single(); From fcee69ffe6c479f1ca226612b56cf4391d0f908b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Jul 2021 15:52:49 +0900 Subject: [PATCH 18/58] Fix `ShowsDefaultIndicator` not actually being consumed --- osu.Game/Overlays/Settings/SettingsItem.cs | 23 +++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index 15a0a42d31..c60ad020f0 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -101,10 +101,10 @@ namespace osu.Game.Overlays.Settings public event Action SettingChanged; + private readonly RestoreDefaultValueButton restoreDefaultButton; + protected SettingsItem() { - RestoreDefaultValueButton restoreDefaultButton; - RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; Padding = new MarginPadding { Right = SettingsPanel.CONTENT_MARGINS }; @@ -126,14 +126,19 @@ namespace osu.Game.Overlays.Settings // all bindable logic is in constructor intentionally to support "CreateSettingsControls" being used in a context it is // never loaded, but requires bindable storage. - if (controlWithCurrent != null) - { - controlWithCurrent.Current.ValueChanged += _ => SettingChanged?.Invoke(); - controlWithCurrent.Current.DisabledChanged += _ => updateDisabled(); + if (controlWithCurrent == null) + throw new ArgumentException(@$"Control created via {nameof(CreateControl)} must implement {nameof(IHasCurrentValue)}"); - if (ShowsDefaultIndicator) - restoreDefaultButton.Current = controlWithCurrent.Current; - } + controlWithCurrent.Current.ValueChanged += _ => SettingChanged?.Invoke(); + controlWithCurrent.Current.DisabledChanged += _ => updateDisabled(); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + if (ShowsDefaultIndicator) + restoreDefaultButton.Current = controlWithCurrent.Current; } private void updateDisabled() From c4313d6e96b687befbbc9462c4320c3185e24dbf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Jul 2021 15:53:49 +0900 Subject: [PATCH 19/58] Initial implementation of new flow (only working for approach rate) --- .../Mods/OsuModDifficultyAdjust.cs | 4 +- .../TestSceneModDifficultyAdjustSettings.cs | 74 ++++++++++++++ .../Mods/ApproachRateSettingsControl.cs | 20 ++++ .../Mods/DifficultyAdjustSettingsControl.cs | 97 +++++++++++++++++++ osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs | 4 +- 5 files changed, 195 insertions(+), 4 deletions(-) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs create mode 100644 osu.Game/Rulesets/Mods/ApproachRateSettingsControl.cs create mode 100644 osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs index 54e30c56a3..82c4a6fd56 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public class OsuModDifficultyAdjust : ModDifficultyAdjust { - [SettingSource("Circle Size", "Override a beatmap's set CS.", FIRST_SETTING_ORDER - 1)] + [SettingSource("Circle Size", "Override a beatmap's set CS.", FIRST_SETTING_ORDER - 1, SettingControlType = typeof(DifficultyAdjustSettingsControl))] public Bindable CircleSize { get; } = new Bindable { /* @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Mods */ }; - [SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1)] + [SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1, SettingControlType = typeof(ApproachRateSettingsControl))] public Bindable ApproachRate { get; } = new Bindable { /* diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs new file mode 100644 index 0000000000..b1ad92273c --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs @@ -0,0 +1,74 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.Rulesets.Osu.Mods; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneModDifficultyAdjustSettings : OsuManualInputManagerTestScene + { + [SetUpSteps] + public void SetUpSteps() + { + AddStep("create difficulty adjust", () => + { + var modDifficultyAdjust = new OsuModDifficultyAdjust(); + + Child = new Container + { + Size = new Vector2(300), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] + { + new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + ChildrenEnumerable = modDifficultyAdjust.CreateSettingsControls(), + }, + } + }; + }); + + setBeatmapWithDifficultyParameters(5); + setBeatmapWithDifficultyParameters(8); + } + + [Test] + public void TestBasic() + { + } + + private void setBeatmapWithDifficultyParameters(float value) + { + AddStep($"set beatmap with all {value}", () => Beatmap.Value = CreateWorkingBeatmap(new Beatmap() + { + BeatmapInfo = new BeatmapInfo + { + BaseDifficulty = new BeatmapDifficulty + { + OverallDifficulty = value, + CircleSize = value, + DrainRate = value, + ApproachRate = value, + } + } + })); + } + } +} diff --git a/osu.Game/Rulesets/Mods/ApproachRateSettingsControl.cs b/osu.Game/Rulesets/Mods/ApproachRateSettingsControl.cs new file mode 100644 index 0000000000..15a94cb271 --- /dev/null +++ b/osu.Game/Rulesets/Mods/ApproachRateSettingsControl.cs @@ -0,0 +1,20 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Beatmaps; + +namespace osu.Game.Rulesets.Mods +{ + public class ApproachRateSettingsControl : DifficultyAdjustSettingsControl + { + public ApproachRateSettingsControl() + { + CurrentNumber.Precision = 0.1f; + + CurrentNumber.MinValue = 0; + CurrentNumber.MaxValue = 10; + } + + protected override float UpdateFromDifficulty(BeatmapDifficulty difficulty) => difficulty.ApproachRate; + } +} diff --git a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs new file mode 100644 index 0000000000..2b437370ea --- /dev/null +++ b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs @@ -0,0 +1,97 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Beatmaps; +using osu.Game.Overlays.Settings; + +namespace osu.Game.Rulesets.Mods +{ + // TODO: make abstract once we finish making each implementation. + public class DifficultyAdjustSettingsControl : SettingsItem + { + [Resolved] + private IBindable beatmap { get; set; } + + protected readonly BindableNumber CurrentNumber = new BindableNumber + { + // TODO: these need to be pulled out of the main bindable. + MinValue = 0, + MaxValue = 10, + }; + + protected override Drawable CreateControl() => new ControlDrawable(CurrentNumber); + + private bool isInternalChange; + + protected override void LoadComplete() + { + base.LoadComplete(); + + beatmap.BindValueChanged(b => + { + updateFromDifficulty(); + }, true); + + Current.BindValueChanged(current => + { + if (current.NewValue == null) + updateFromDifficulty(); + }); + + CurrentNumber.BindValueChanged(number => + { + if (!isInternalChange) + Current.Value = number.NewValue; + }); + } + + private void updateFromDifficulty() + { + var difficulty = beatmap.Value.BeatmapInfo.BaseDifficulty; + + if (difficulty == null) + return; + + if (Current.Value == null) + { + isInternalChange = true; + CurrentNumber.Value = UpdateFromDifficulty(difficulty); + isInternalChange = false; + } + } + + // TODO: make abstract + protected virtual float UpdateFromDifficulty(BeatmapDifficulty difficulty) => 0; + + private class ControlDrawable : CompositeDrawable, IHasCurrentValue + { + private readonly BindableWithCurrent current = new BindableWithCurrent(); + + public Bindable Current + { + get => current.Current; + set => current.Current = value; + } + + public ControlDrawable(BindableNumber currentNumber) + { + InternalChildren = new Drawable[] + { + new SettingsSlider + { + ShowsDefaultIndicator = false, + Current = currentNumber, + } + }; + + AutoSizeAxes = Axes.Y; + RelativeSizeAxes = Axes.X; + } + } + } +} diff --git a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs index e98bd14720..4b68d9cc3f 100644 --- a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Mods protected const int LAST_SETTING_ORDER = 2; - [SettingSource("HP Drain", "Override a beatmap's set HP.", FIRST_SETTING_ORDER)] + [SettingSource("HP Drain", "Override a beatmap's set HP.", FIRST_SETTING_ORDER, SettingControlType = typeof(DifficultyAdjustSettingsControl))] public Bindable DrainRate { get; } = new Bindable { /* @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Mods */ }; - [SettingSource("Accuracy", "Override a beatmap's set OD.", LAST_SETTING_ORDER)] + [SettingSource("Accuracy", "Override a beatmap's set OD.", LAST_SETTING_ORDER, SettingControlType = typeof(DifficultyAdjustSettingsControl))] public Bindable OverallDifficulty { get; } = new Bindable { /* From a6e94dd4918e70e1e5040b5523ec0958e1fb0355 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Jul 2021 16:40:32 +0900 Subject: [PATCH 20/58] Add back extended limits support --- .../Mods/CatchModDifficultyAdjust.cs | 17 ++-- .../Mods/OsuModDifficultyAdjust.cs | 20 ++--- .../Mods/TaikoModDifficultyAdjust.cs | 9 +- .../TestSceneModDifficultyAdjustSettings.cs | 2 +- .../Mods/ApproachRateSettingsControl.cs | 8 -- .../Mods/DifficultyAdjustSettingsControl.cs | 21 +++-- osu.Game/Rulesets/Mods/DifficultyBindable.cs | 84 +++++++++++++++++++ osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs | 81 ++---------------- 8 files changed, 121 insertions(+), 121 deletions(-) create mode 100644 osu.Game/Rulesets/Mods/DifficultyBindable.cs diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs index aaa426cd03..947edb5dd9 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs @@ -13,35 +13,28 @@ namespace osu.Game.Rulesets.Catch.Mods public class CatchModDifficultyAdjust : ModDifficultyAdjust, IApplicableToBeatmapProcessor { [SettingSource("Circle Size", "Override a beatmap's set CS.", FIRST_SETTING_ORDER - 1)] - public Bindable CircleSize { get; } = new Bindable + public DifficultyBindable CircleSize { get; } = new DifficultyBindable { - /* Precision = 0.1f, MinValue = 1, MaxValue = 10, - */ }; [SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1)] - public Bindable ApproachRate { get; } = new Bindable + public DifficultyBindable ApproachRate { get; } = new DifficultyBindable { - /* Precision = 0.1f, MinValue = 1, MaxValue = 10, - */ }; [SettingSource("Spicy Patterns", "Adjust the patterns as if Hard Rock is enabled.")] public BindableBool HardRockOffsets { get; } = new BindableBool(); - protected override void ApplyLimits(bool extended) + public CatchModDifficultyAdjust() { - base.ApplyLimits(extended); - - // TODO: reimplement - // CircleSize.MaxValue = extended ? 11 : 10; - // ApproachRate.MaxValue = extended ? 11 : 10; + CircleSize.ExtendedLimits.BindTo(ExtendedLimits); + ApproachRate.ExtendedLimits.BindTo(ExtendedLimits); } public override string SettingDescription diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs index 82c4a6fd56..403ec2c33d 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; -using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Rulesets.Mods; @@ -12,32 +11,27 @@ namespace osu.Game.Rulesets.Osu.Mods public class OsuModDifficultyAdjust : ModDifficultyAdjust { [SettingSource("Circle Size", "Override a beatmap's set CS.", FIRST_SETTING_ORDER - 1, SettingControlType = typeof(DifficultyAdjustSettingsControl))] - public Bindable CircleSize { get; } = new Bindable + public DifficultyBindable CircleSize { get; } = new DifficultyBindable { - /* Precision = 0.1f, MinValue = 0, MaxValue = 10, - */ + ExtendedMaxValue = 11, }; [SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1, SettingControlType = typeof(ApproachRateSettingsControl))] - public Bindable ApproachRate { get; } = new Bindable + public DifficultyBindable ApproachRate { get; } = new DifficultyBindable { - /* Precision = 0.1f, MinValue = 0, MaxValue = 10, - */ + ExtendedMaxValue = 11, }; - protected override void ApplyLimits(bool extended) + public OsuModDifficultyAdjust() { - base.ApplyLimits(extended); - - // TODO: reimplement - // CircleSize.MaxValue = extended ? 11 : 10; - // ApproachRate.MaxValue = extended ? 11 : 10; + CircleSize.ExtendedLimits.BindTo(ExtendedLimits); + ApproachRate.ExtendedLimits.BindTo(ExtendedLimits); } public override string SettingDescription diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs index 902ccdc14e..110a7eebc8 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; -using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Rulesets.Mods; @@ -11,14 +10,12 @@ namespace osu.Game.Rulesets.Taiko.Mods { public class TaikoModDifficultyAdjust : ModDifficultyAdjust { - [SettingSource("Scroll Speed", "Adjust a beatmap's set scroll speed", LAST_SETTING_ORDER + 1)] - public Bindable ScrollSpeed { get; } = new Bindable + [SettingSource("Scroll Speed", "Adjust a beatmap's set scroll speed", LAST_SETTING_ORDER + 1, SettingControlType = typeof(DifficultyAdjustSettingsControl))] + public DifficultyBindable ScrollSpeed { get; } = new DifficultyBindable { - /* Precision = 0.05f, MinValue = 0.25f, MaxValue = 4, - */ }; public override string SettingDescription @@ -39,7 +36,7 @@ namespace osu.Game.Rulesets.Taiko.Mods { base.ApplySettings(difficulty); - if (ScrollSpeed.Value != null) difficulty.SliderMultiplier = ScrollSpeed.Value.Value; + if (ScrollSpeed.Value != null) difficulty.SliderMultiplier *= ScrollSpeed.Value.Value; } } } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs index b1ad92273c..7d982a55cb 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs @@ -56,7 +56,7 @@ namespace osu.Game.Tests.Visual.UserInterface private void setBeatmapWithDifficultyParameters(float value) { - AddStep($"set beatmap with all {value}", () => Beatmap.Value = CreateWorkingBeatmap(new Beatmap() + AddStep($"set beatmap with all {value}", () => Beatmap.Value = CreateWorkingBeatmap(new Beatmap { BeatmapInfo = new BeatmapInfo { diff --git a/osu.Game/Rulesets/Mods/ApproachRateSettingsControl.cs b/osu.Game/Rulesets/Mods/ApproachRateSettingsControl.cs index 15a94cb271..18773afb30 100644 --- a/osu.Game/Rulesets/Mods/ApproachRateSettingsControl.cs +++ b/osu.Game/Rulesets/Mods/ApproachRateSettingsControl.cs @@ -7,14 +7,6 @@ namespace osu.Game.Rulesets.Mods { public class ApproachRateSettingsControl : DifficultyAdjustSettingsControl { - public ApproachRateSettingsControl() - { - CurrentNumber.Precision = 0.1f; - - CurrentNumber.MinValue = 0; - CurrentNumber.MaxValue = 10; - } - protected override float UpdateFromDifficulty(BeatmapDifficulty difficulty) => difficulty.ApproachRate; } } diff --git a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs index 2b437370ea..1aede3425a 100644 --- a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs +++ b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs @@ -17,17 +17,26 @@ namespace osu.Game.Rulesets.Mods [Resolved] private IBindable beatmap { get; set; } - protected readonly BindableNumber CurrentNumber = new BindableNumber - { - // TODO: these need to be pulled out of the main bindable. - MinValue = 0, - MaxValue = 10, - }; + protected readonly BindableNumber CurrentNumber = new BindableNumber(); protected override Drawable CreateControl() => new ControlDrawable(CurrentNumber); private bool isInternalChange; + private DifficultyBindable difficultyBindable; + + public override Bindable Current + { + get => base.Current; + set + { + // intercept and extract the DifficultyBindable. + difficultyBindable = (DifficultyBindable)value; + CurrentNumber.BindTo(difficultyBindable.CurrentNumber); + base.Current = value; + } + } + protected override void LoadComplete() { base.LoadComplete(); diff --git a/osu.Game/Rulesets/Mods/DifficultyBindable.cs b/osu.Game/Rulesets/Mods/DifficultyBindable.cs new file mode 100644 index 0000000000..d721154392 --- /dev/null +++ b/osu.Game/Rulesets/Mods/DifficultyBindable.cs @@ -0,0 +1,84 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Bindables; + +namespace osu.Game.Rulesets.Mods +{ + public class DifficultyBindable : Bindable + { + /// + /// Whether the extended limits should be applied to this bindable. + /// + public BindableBool ExtendedLimits { get; } = new BindableBool(); + + /// + /// An internal numeric bindable to hold and propagate min/max/precision. + /// The value of this bindable should not be set. + /// + public readonly BindableFloat CurrentNumber = new BindableFloat + { + MinValue = 0, + MaxValue = 10, + }; + + public float Precision + { + set => CurrentNumber.Precision = value; + } + + public float MinValue + { + set => CurrentNumber.MinValue = value; + } + + private float maxValue; + + public float MaxValue + { + set + { + if (value == maxValue) + return; + + maxValue = value; + updateMaxValue(); + } + } + + private float? extendedMaxValue; + + /// + /// The maximum value to be used when extended limits are applied. + /// + public float? ExtendedMaxValue + { + set + { + if (value == extendedMaxValue) + return; + + extendedMaxValue = value; + updateMaxValue(); + } + } + + public DifficultyBindable() + { + ExtendedLimits.BindValueChanged(_ => updateMaxValue()); + + BindValueChanged(val => + { + // Ensure that in the case serialisation runs in the wrong order (and limit extensions aren't applied yet) the deserialised value is still propagated. + if (val.NewValue != null) + CurrentNumber.MaxValue = MathF.Max(CurrentNumber.MaxValue, val.NewValue.Value); + }); + } + + private void updateMaxValue() + { + CurrentNumber.MaxValue = ExtendedLimits.Value && extendedMaxValue != null ? extendedMaxValue.Value : maxValue; + } + } +} diff --git a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs index 4b68d9cc3f..d636f22dea 100644 --- a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs @@ -33,23 +33,21 @@ namespace osu.Game.Rulesets.Mods protected const int LAST_SETTING_ORDER = 2; [SettingSource("HP Drain", "Override a beatmap's set HP.", FIRST_SETTING_ORDER, SettingControlType = typeof(DifficultyAdjustSettingsControl))] - public Bindable DrainRate { get; } = new Bindable + public DifficultyBindable DrainRate { get; } = new DifficultyBindable { - /* Precision = 0.1f, MinValue = 0, MaxValue = 10, - */ + ExtendedMaxValue = 11, }; [SettingSource("Accuracy", "Override a beatmap's set OD.", LAST_SETTING_ORDER, SettingControlType = typeof(DifficultyAdjustSettingsControl))] - public Bindable OverallDifficulty { get; } = new Bindable + public DifficultyBindable OverallDifficulty { get; } = new DifficultyBindable { - /* Precision = 0.1f, MinValue = 0, MaxValue = 10, - */ + ExtendedMaxValue = 11, }; [SettingSource("Extended Limits", "Adjust difficulty beyond sane limits.")] @@ -57,18 +55,8 @@ namespace osu.Game.Rulesets.Mods protected ModDifficultyAdjust() { - ExtendedLimits.BindValueChanged(extend => ApplyLimits(extend.NewValue)); - } - - /// - /// Changes the difficulty adjustment limits. Occurs when the value of is changed. - /// - /// Whether limits should extend beyond sane ranges. - protected virtual void ApplyLimits(bool extended) - { - // TODO: reimplement - // DrainRate.MaxValue = extended ? 11 : 10; - // OverallDifficulty.MaxValue = extended ? 11 : 10; + OverallDifficulty.ExtendedLimits.BindTo(ExtendedLimits); + DrainRate.ExtendedLimits.BindTo(ExtendedLimits); } public override string SettingDescription @@ -101,62 +89,5 @@ namespace osu.Game.Rulesets.Mods if (DrainRate.Value != null) difficulty.DrainRate = DrainRate.Value.Value; if (OverallDifficulty.Value != null) difficulty.OverallDifficulty = OverallDifficulty.Value.Value; } - - /// - /// A that extends its min/max values to support any assigned value. - /// - protected class BindableDoubleWithLimitExtension : BindableDouble - { - public override double Value - { - get => base.Value; - set - { - if (value < MinValue) - MinValue = value; - if (value > MaxValue) - MaxValue = value; - base.Value = value; - } - } - } - - /// - /// A that extends its min/max values to support any assigned value. - /// - protected class BindableFloatWithLimitExtension : BindableFloat - { - public override float Value - { - get => base.Value; - set - { - if (value < MinValue) - MinValue = value; - if (value > MaxValue) - MaxValue = value; - base.Value = value; - } - } - } - - /// - /// A that extends its min/max values to support any assigned value. - /// - protected class BindableIntWithLimitExtension : BindableInt - { - public override int Value - { - get => base.Value; - set - { - if (value < MinValue) - MinValue = value; - if (value > MaxValue) - MaxValue = value; - base.Value = value; - } - } - } } } From bd7c3345881f312d07074c4676f73f8fc532affb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Jul 2021 16:56:16 +0900 Subject: [PATCH 21/58] Avoid the need for per-settings control classes --- .../Mods/CatchModDifficultyAdjust.cs | 4 ++++ osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs | 4 +++- .../Mods/TaikoModDifficultyAdjust.cs | 1 + .../Rulesets/Mods/ApproachRateSettingsControl.cs | 12 ------------ .../Rulesets/Mods/DifficultyAdjustSettingsControl.cs | 6 +----- osu.Game/Rulesets/Mods/DifficultyBindable.cs | 6 ++++++ osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs | 2 ++ 7 files changed, 17 insertions(+), 18 deletions(-) delete mode 100644 osu.Game/Rulesets/Mods/ApproachRateSettingsControl.cs diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs index 947edb5dd9..80b5244c34 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs @@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Catch.Mods Precision = 0.1f, MinValue = 1, MaxValue = 10, + ExtendedMaxValue = 11, + ReadFromDifficulty = diff => diff.CircleSize, }; [SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1)] @@ -26,6 +28,8 @@ namespace osu.Game.Rulesets.Catch.Mods Precision = 0.1f, MinValue = 1, MaxValue = 10, + ExtendedMaxValue = 11, + ReadFromDifficulty = diff => diff.ApproachRate, }; [SettingSource("Spicy Patterns", "Adjust the patterns as if Hard Rock is enabled.")] diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs index 403ec2c33d..d93b097663 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs @@ -17,15 +17,17 @@ namespace osu.Game.Rulesets.Osu.Mods MinValue = 0, MaxValue = 10, ExtendedMaxValue = 11, + ReadFromDifficulty = diff => diff.CircleSize, }; - [SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1, SettingControlType = typeof(ApproachRateSettingsControl))] + [SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1, SettingControlType = typeof(DifficultyAdjustSettingsControl))] public DifficultyBindable ApproachRate { get; } = new DifficultyBindable { Precision = 0.1f, MinValue = 0, MaxValue = 10, ExtendedMaxValue = 11, + ReadFromDifficulty = diff => diff.ApproachRate, }; public OsuModDifficultyAdjust() diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs index 110a7eebc8..ace105b21c 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs @@ -16,6 +16,7 @@ namespace osu.Game.Rulesets.Taiko.Mods Precision = 0.05f, MinValue = 0.25f, MaxValue = 4, + ReadFromDifficulty = _ => 1, }; public override string SettingDescription diff --git a/osu.Game/Rulesets/Mods/ApproachRateSettingsControl.cs b/osu.Game/Rulesets/Mods/ApproachRateSettingsControl.cs deleted file mode 100644 index 18773afb30..0000000000 --- a/osu.Game/Rulesets/Mods/ApproachRateSettingsControl.cs +++ /dev/null @@ -1,12 +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.Beatmaps; - -namespace osu.Game.Rulesets.Mods -{ - public class ApproachRateSettingsControl : DifficultyAdjustSettingsControl - { - protected override float UpdateFromDifficulty(BeatmapDifficulty difficulty) => difficulty.ApproachRate; - } -} diff --git a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs index 1aede3425a..ef2b88846a 100644 --- a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs +++ b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs @@ -11,7 +11,6 @@ using osu.Game.Overlays.Settings; namespace osu.Game.Rulesets.Mods { - // TODO: make abstract once we finish making each implementation. public class DifficultyAdjustSettingsControl : SettingsItem { [Resolved] @@ -69,14 +68,11 @@ namespace osu.Game.Rulesets.Mods if (Current.Value == null) { isInternalChange = true; - CurrentNumber.Value = UpdateFromDifficulty(difficulty); + CurrentNumber.Value = difficultyBindable.ReadFromDifficulty(difficulty); isInternalChange = false; } } - // TODO: make abstract - protected virtual float UpdateFromDifficulty(BeatmapDifficulty difficulty) => 0; - private class ControlDrawable : CompositeDrawable, IHasCurrentValue { private readonly BindableWithCurrent current = new BindableWithCurrent(); diff --git a/osu.Game/Rulesets/Mods/DifficultyBindable.cs b/osu.Game/Rulesets/Mods/DifficultyBindable.cs index d721154392..7b01b1e0c7 100644 --- a/osu.Game/Rulesets/Mods/DifficultyBindable.cs +++ b/osu.Game/Rulesets/Mods/DifficultyBindable.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Bindables; +using osu.Game.Beatmaps; namespace osu.Game.Rulesets.Mods { @@ -23,6 +24,11 @@ namespace osu.Game.Rulesets.Mods MaxValue = 10, }; + /// + /// A function that can extract the current value of this setting from a beatmap difficulty for display purposes. + /// + public Func ReadFromDifficulty; + public float Precision { set => CurrentNumber.Precision = value; diff --git a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs index d636f22dea..06bf7d9a6b 100644 --- a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs @@ -39,6 +39,7 @@ namespace osu.Game.Rulesets.Mods MinValue = 0, MaxValue = 10, ExtendedMaxValue = 11, + ReadFromDifficulty = diff => diff.DrainRate, }; [SettingSource("Accuracy", "Override a beatmap's set OD.", LAST_SETTING_ORDER, SettingControlType = typeof(DifficultyAdjustSettingsControl))] @@ -48,6 +49,7 @@ namespace osu.Game.Rulesets.Mods MinValue = 0, MaxValue = 10, ExtendedMaxValue = 11, + ReadFromDifficulty = diff => diff.OverallDifficulty, }; [SettingSource("Extended Limits", "Adjust difficulty beyond sane limits.")] From 88b00123f6efe28c876d954b3a6957ad6febf2b9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Jul 2021 17:00:55 +0900 Subject: [PATCH 22/58] Use existing reflection methods to avoid manual binding of `ExtendedLimits` --- osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs | 6 ------ osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs | 6 ------ osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs | 7 +++++-- 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs index 80b5244c34..8686627c2a 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs @@ -35,12 +35,6 @@ namespace osu.Game.Rulesets.Catch.Mods [SettingSource("Spicy Patterns", "Adjust the patterns as if Hard Rock is enabled.")] public BindableBool HardRockOffsets { get; } = new BindableBool(); - public CatchModDifficultyAdjust() - { - CircleSize.ExtendedLimits.BindTo(ExtendedLimits); - ApproachRate.ExtendedLimits.BindTo(ExtendedLimits); - } - public override string SettingDescription { get diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs index d93b097663..983216dfa1 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs @@ -30,12 +30,6 @@ namespace osu.Game.Rulesets.Osu.Mods ReadFromDifficulty = diff => diff.ApproachRate, }; - public OsuModDifficultyAdjust() - { - CircleSize.ExtendedLimits.BindTo(ExtendedLimits); - ApproachRate.ExtendedLimits.BindTo(ExtendedLimits); - } - public override string SettingDescription { get diff --git a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs index 06bf7d9a6b..d9d305d457 100644 --- a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs @@ -57,8 +57,11 @@ namespace osu.Game.Rulesets.Mods protected ModDifficultyAdjust() { - OverallDifficulty.ExtendedLimits.BindTo(ExtendedLimits); - DrainRate.ExtendedLimits.BindTo(ExtendedLimits); + foreach (var (_, property) in this.GetOrderedSettingsSourceProperties()) + { + if (property.GetValue(this) is DifficultyBindable diffAdjustBindable) + diffAdjustBindable.ExtendedLimits.BindTo(ExtendedLimits); + } } public override string SettingDescription From 533db01cc0782e3e31465e04efe1d19ce5bb189d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Jul 2021 17:14:51 +0900 Subject: [PATCH 23/58] Add comprehensive tests of difficulty adjust settings --- .../TestSceneModDifficultyAdjustSettings.cs | 143 +++++++++++++++++- 1 file changed, 137 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs index 7d982a55cb..d0a02a6c2d 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -8,6 +9,8 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Overlays.Settings; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; using osuTK; using osuTK.Graphics; @@ -16,12 +19,14 @@ namespace osu.Game.Tests.Visual.UserInterface { public class TestSceneModDifficultyAdjustSettings : OsuManualInputManagerTestScene { + private OsuModDifficultyAdjust modDifficultyAdjust; + [SetUpSteps] public void SetUpSteps() { - AddStep("create difficulty adjust", () => + AddStep("create control", () => { - var modDifficultyAdjust = new OsuModDifficultyAdjust(); + modDifficultyAdjust = new OsuModDifficultyAdjust(); Child = new Container { @@ -44,14 +49,140 @@ namespace osu.Game.Tests.Visual.UserInterface } }; }); - - setBeatmapWithDifficultyParameters(5); - setBeatmapWithDifficultyParameters(8); } [Test] - public void TestBasic() + public void TestFollowsBeatmapDefaultsVisually() { + setBeatmapWithDifficultyParameters(5); + + checkSliderAtValue("Circle Size", 5); + checkBindableAtValue("Circle Size", null); + + setBeatmapWithDifficultyParameters(8); + + checkSliderAtValue("Circle Size", 8); + checkBindableAtValue("Circle Size", null); + } + + [Test] + public void TestOutOfRangeValueStillApplied() + { + AddStep("set override cs to 11", () => modDifficultyAdjust.CircleSize.Value = 11); + + checkSliderAtValue("Circle Size", 11); + checkBindableAtValue("Circle Size", 11); + + // this is a no-op, just showing that it won't reset the value during deserialisation. + setExtendedLimits(false); + + checkSliderAtValue("Circle Size", 11); + checkBindableAtValue("Circle Size", 11); + + // setting extended limits will reset the serialisation exception. + // this should be fine as the goal is to allow, at most, the value of extended limits. + setExtendedLimits(true); + + checkSliderAtValue("Circle Size", 11); + checkBindableAtValue("Circle Size", 11); + } + + [Test] + public void TestExtendedLimits() + { + setSliderValue("Circle Size", 99); + + checkSliderAtValue("Circle Size", 10); + checkBindableAtValue("Circle Size", 10); + + setExtendedLimits(true); + + checkSliderAtValue("Circle Size", 10); + checkBindableAtValue("Circle Size", 10); + + setSliderValue("Circle Size", 99); + + checkSliderAtValue("Circle Size", 11); + checkBindableAtValue("Circle Size", 11); + + setExtendedLimits(false); + + checkSliderAtValue("Circle Size", 10); + checkBindableAtValue("Circle Size", 10); + } + + [Test] + public void TestUserOverrideMaintainedOnBeatmapChange() + { + setSliderValue("Circle Size", 9); + + setBeatmapWithDifficultyParameters(2); + + checkSliderAtValue("Circle Size", 9); + checkBindableAtValue("Circle Size", 9); + } + + [Test] + public void TestResetToDefault() + { + setBeatmapWithDifficultyParameters(2); + + setSliderValue("Circle Size", 9); + checkSliderAtValue("Circle Size", 9); + checkBindableAtValue("Circle Size", 9); + + resetToDefault("Circle Size"); + checkSliderAtValue("Circle Size", 2); + checkBindableAtValue("Circle Size", null); + } + + [Test] + public void TestUserOverrideMaintainedOnMatchingBeatmapValue() + { + setBeatmapWithDifficultyParameters(2); + + checkSliderAtValue("Circle Size", 2); + checkBindableAtValue("Circle Size", null); + + setSliderValue("Circle Size", 9); + checkSliderAtValue("Circle Size", 9); + checkBindableAtValue("Circle Size", 9); + + setBeatmapWithDifficultyParameters(4); + + checkSliderAtValue("Circle Size", 9); + checkBindableAtValue("Circle Size", 9); + } + + private void resetToDefault(string name) + { + AddStep($"Reset {name} to default", () => + this.ChildrenOfType().First(c => c.LabelText == name) + .Current.SetDefault()); + } + + private void setExtendedLimits(bool status) => + AddStep($"Set extended limits {status}", () => modDifficultyAdjust.ExtendedLimits.Value = status); + + private void setSliderValue(string name, float value) + { + AddStep($"Set {name} slider to {value}", () => + this.ChildrenOfType().First(c => c.LabelText == name) + .ChildrenOfType>().First().Current.Value = value); + } + + private void checkBindableAtValue(string name, float? expectedValue) + { + AddAssert($"Bindable {name} is {(expectedValue?.ToString() ?? "null")}", () => + this.ChildrenOfType().First(c => c.LabelText == name) + .Current.Value == expectedValue); + } + + private void checkSliderAtValue(string name, float expectedValue) + { + AddAssert($"Slider {name} at {expectedValue}", () => + this.ChildrenOfType().First(c => c.LabelText == name) + .ChildrenOfType>().First().Current.Value == expectedValue); } private void setBeatmapWithDifficultyParameters(float value) From 52ea62e3b2176d3da8fe83e689da62694e01fda5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Jul 2021 17:39:59 +0900 Subject: [PATCH 24/58] Add more comments and xmldoc --- .../Mods/DifficultyAdjustSettingsControl.cs | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs index ef2b88846a..05a36d3d31 100644 --- a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs +++ b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs @@ -16,9 +16,13 @@ namespace osu.Game.Rulesets.Mods [Resolved] private IBindable beatmap { get; set; } - protected readonly BindableNumber CurrentNumber = new BindableNumber(); + /// + /// Used to track the display value on the setting slider. + /// This can either be a user override or the beatmap default (when is null). + /// + private readonly BindableNumber displayNumber = new BindableNumber(); - protected override Drawable CreateControl() => new ControlDrawable(CurrentNumber); + protected override Drawable CreateControl() => new ControlDrawable(displayNumber); private bool isInternalChange; @@ -31,7 +35,10 @@ namespace osu.Game.Rulesets.Mods { // intercept and extract the DifficultyBindable. difficultyBindable = (DifficultyBindable)value; - CurrentNumber.BindTo(difficultyBindable.CurrentNumber); + + // this bind is used to transfer bounds/precision only. + displayNumber.BindTo(difficultyBindable.CurrentNumber); + base.Current = value; } } @@ -40,18 +47,18 @@ namespace osu.Game.Rulesets.Mods { base.LoadComplete(); - beatmap.BindValueChanged(b => - { - updateFromDifficulty(); - }, true); + beatmap.BindValueChanged(b => updateFromDifficulty(), true); Current.BindValueChanged(current => { + // the user override has changed; transfer the correct value to the visual display. if (current.NewValue == null) updateFromDifficulty(); + else + displayNumber.Value = current.NewValue.Value; }); - CurrentNumber.BindValueChanged(number => + displayNumber.BindValueChanged(number => { if (!isInternalChange) Current.Value = number.NewValue; @@ -67,8 +74,9 @@ namespace osu.Game.Rulesets.Mods if (Current.Value == null) { + // ensure the beatmap's value is not transferred as a user override. isInternalChange = true; - CurrentNumber.Value = difficultyBindable.ReadFromDifficulty(difficulty); + displayNumber.Value = difficultyBindable.ReadFromDifficulty(difficulty); isInternalChange = false; } } @@ -77,6 +85,8 @@ namespace osu.Game.Rulesets.Mods { private readonly BindableWithCurrent current = new BindableWithCurrent(); + // Mainly just for fulfilling the interface requirements. + // The actual update flow is done via the provided number. public Bindable Current { get => current.Current; From ba939c0b657aa8bd683635b100bac9d2335c2e7f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Jul 2021 17:40:09 +0900 Subject: [PATCH 25/58] Simplify serialisation edge case by moving to `Value` override --- osu.Game/Rulesets/Mods/DifficultyBindable.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Mods/DifficultyBindable.cs b/osu.Game/Rulesets/Mods/DifficultyBindable.cs index 7b01b1e0c7..22d8472922 100644 --- a/osu.Game/Rulesets/Mods/DifficultyBindable.cs +++ b/osu.Game/Rulesets/Mods/DifficultyBindable.cs @@ -73,13 +73,19 @@ namespace osu.Game.Rulesets.Mods public DifficultyBindable() { ExtendedLimits.BindValueChanged(_ => updateMaxValue()); + } - BindValueChanged(val => + public override float? Value + { + get => base.Value; + set { // Ensure that in the case serialisation runs in the wrong order (and limit extensions aren't applied yet) the deserialised value is still propagated. - if (val.NewValue != null) - CurrentNumber.MaxValue = MathF.Max(CurrentNumber.MaxValue, val.NewValue.Value); - }); + if (value != null) + CurrentNumber.MaxValue = MathF.Max(CurrentNumber.MaxValue, value.Value); + + base.Value = value; + } } private void updateMaxValue() From b7803b889e5560adbd11e325c9421322d3b39ee2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Jul 2021 20:37:38 +0900 Subject: [PATCH 26/58] Rename control class to be more descriptive --- osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs index 05a36d3d31..da5fb516f6 100644 --- a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs +++ b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Mods /// private readonly BindableNumber displayNumber = new BindableNumber(); - protected override Drawable CreateControl() => new ControlDrawable(displayNumber); + protected override Drawable CreateControl() => new SliderControl(displayNumber); private bool isInternalChange; @@ -81,19 +81,20 @@ namespace osu.Game.Rulesets.Mods } } - private class ControlDrawable : CompositeDrawable, IHasCurrentValue + private class SliderControl : CompositeDrawable, IHasCurrentValue { private readonly BindableWithCurrent current = new BindableWithCurrent(); // Mainly just for fulfilling the interface requirements. // The actual update flow is done via the provided number. + // Of note, this is used for the "reset to default" flow. public Bindable Current { get => current.Current; set => current.Current = value; } - public ControlDrawable(BindableNumber currentNumber) + public SliderControl(BindableNumber currentNumber) { InternalChildren = new Drawable[] { From a7be6327709a8e59561b257c21786c133860100a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 9 Jul 2021 00:39:09 +0300 Subject: [PATCH 27/58] Improve documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Overlays/RestoreDefaultValueButton.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/RestoreDefaultValueButton.cs b/osu.Game/Overlays/RestoreDefaultValueButton.cs index 44adb70108..7413837852 100644 --- a/osu.Game/Overlays/RestoreDefaultValueButton.cs +++ b/osu.Game/Overlays/RestoreDefaultValueButton.cs @@ -23,12 +23,8 @@ namespace osu.Game.Overlays // this is done to ensure a click on this button doesn't trigger focus on a parent element which contains the button. public override bool AcceptsFocus => true; - // this is intentionally not using BindableWithCurrent as this needs a BindableNumber instance for an accurate IsDefault value. - // - // this also cannot use IBindableWithCurrent.Create() due to BindableNumberWithCurrent - // directly casting given bindables to BindableNumber, which is not necessarily the case. - // - // therefore rely on the old method of taking each current bindable instance for now, until things are settled framework-side. + // this is intentionally not using BindableWithCurrent, as it can use the wrong IsDefault implementation when passed a BindableNumber. + // using GetBoundCopy() ensures that the received bindable is of the exact same type as the source bindable and uses the proper IsDefault implementation. private Bindable current; public Bindable Current From 0223c569df8395004aca92aa8923275b9ea22b61 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 9 Jul 2021 00:48:48 +0300 Subject: [PATCH 28/58] Remove no longer necessary method definitions --- osu.Game/Overlays/RestoreDefaultValueButton.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/RestoreDefaultValueButton.cs b/osu.Game/Overlays/RestoreDefaultValueButton.cs index 7413837852..469d48d82b 100644 --- a/osu.Game/Overlays/RestoreDefaultValueButton.cs +++ b/osu.Game/Overlays/RestoreDefaultValueButton.cs @@ -35,9 +35,9 @@ namespace osu.Game.Overlays current?.UnbindAll(); current = value.GetBoundCopy(); - current.ValueChanged += onValueChanged; - current.DefaultChanged += onDefaultChanged; - current.DisabledChanged += onDisabledChanged; + current.ValueChanged += _ => UpdateState(); + current.DefaultChanged += _ => UpdateState(); + current.DisabledChanged += _ => UpdateState(); UpdateState(); } } @@ -93,10 +93,6 @@ namespace osu.Game.Overlays UpdateState(); } - private void onValueChanged(ValueChangedEvent _) => UpdateState(); - private void onDefaultChanged(ValueChangedEvent _) => UpdateState(); - private void onDisabledChanged(bool _) => UpdateState(); - public void UpdateState() => Scheduler.AddOnce(updateState); private void updateState() From 9f7c6adb5849777f77de6ba48c129bf8b53fd130 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Jul 2021 12:15:28 +0900 Subject: [PATCH 29/58] Fix test failures due to logger pollution As seen at https://github.com/ppy/osu/pull/13831/checks?check_run_id=3025050307. I can't confirm that this will fix the issue but it looks like the only plausible reason. I have confirmed that the logging is not coming from the local (first logging is guaranteed to be after `SetupForRun`). --- osu.Game/Tests/CleanRunHeadlessGameHost.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Tests/CleanRunHeadlessGameHost.cs b/osu.Game/Tests/CleanRunHeadlessGameHost.cs index baa7b27d28..03ab94d1da 100644 --- a/osu.Game/Tests/CleanRunHeadlessGameHost.cs +++ b/osu.Game/Tests/CleanRunHeadlessGameHost.cs @@ -25,8 +25,11 @@ namespace osu.Game.Tests protected override void SetupForRun() { - base.SetupForRun(); Storage.DeleteDirectory(string.Empty); + + // base call needs to be run *after* storage is emptied, as it updates the (static) logger's storage and may start writing + // log entries from another source if a unit test host is shared over multiple tests, causing a file access denied exception. + base.SetupForRun(); } } } From 887035c12e85861704ed243ee2b3f5a9eb8ef2f2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Jul 2021 12:21:24 +0900 Subject: [PATCH 30/58] Fix migration target having left over files potentially causing test failures As seen at https://github.com/ppy/osu/pull/13831/checks?check_run_id=3025050324. --- osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index a540ad7247..4c44e2ec72 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -184,6 +184,9 @@ namespace osu.Game.Tests.NonVisual Assert.DoesNotThrow(() => osu.Migrate(customPath2)); Assert.That(File.Exists(Path.Combine(customPath2, database_filename))); + // some files may have been left behind for whatever reason, but that's not what we're testing here. + customPath = prepareCustomPath(); + Assert.DoesNotThrow(() => osu.Migrate(customPath)); Assert.That(File.Exists(Path.Combine(customPath, database_filename))); } From 9786e1a932426971cc09f11009785a842da70a3f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Jul 2021 12:36:54 +0900 Subject: [PATCH 31/58] Ensure run-from-screen song select reaches correct point in execution Fixes issues as seen at https://github.com/ppy/osu/runs/3023581865?check_suite_focus=true. Song select may take a few frames to perform initial selection as there is a bit of internal async logic. This ensures that the beatmap has been updated before continuing with test execution. --- .../Visual/Navigation/TestScenePerformFromScreen.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs b/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs index 92152bce18..4ec76e1e4b 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs @@ -58,8 +58,7 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestPerformAtSongSelectFromPlayerLoader() { - AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait()); - PushAndConfirm(() => new TestPlaySongSelect()); + importAndWaitForSongSelect(); AddStep("Press enter", () => InputManager.Key(Key.Enter)); AddUntilStep("Wait for new screen", () => Game.ScreenStack.CurrentScreen is PlayerLoader); @@ -72,8 +71,7 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestPerformAtMenuFromPlayerLoader() { - AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait()); - PushAndConfirm(() => new TestPlaySongSelect()); + importAndWaitForSongSelect(); AddStep("Press enter", () => InputManager.Key(Key.Enter)); AddUntilStep("Wait for new screen", () => Game.ScreenStack.CurrentScreen is PlayerLoader); @@ -172,6 +170,13 @@ namespace osu.Game.Tests.Visual.Navigation } } + private void importAndWaitForSongSelect() + { + AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait()); + PushAndConfirm(() => new TestPlaySongSelect()); + AddUntilStep("beatmap updated", () => Game.Beatmap.Value.BeatmapSetInfo.OnlineBeatmapSetID == 241526); + } + public class DialogBlockingScreen : OsuScreen { [Resolved] From df4bd86cfc946e8ca44a24edf8dd21b78b174ecf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Jul 2021 13:17:25 +0900 Subject: [PATCH 32/58] Fix storage wrapping logic setting logger too early in startup sequence --- osu.Game/IO/OsuStorage.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/IO/OsuStorage.cs b/osu.Game/IO/OsuStorage.cs index 75130b0f9b..802c71e363 100644 --- a/osu.Game/IO/OsuStorage.cs +++ b/osu.Game/IO/OsuStorage.cs @@ -102,8 +102,15 @@ namespace osu.Game.IO protected override void ChangeTargetStorage(Storage newStorage) { + var lastStorage = UnderlyingStorage; base.ChangeTargetStorage(newStorage); - Logger.Storage = UnderlyingStorage.GetStorageForDirectory("logs"); + + if (lastStorage != null) + { + // for now we assume that if there was a previous storage, this is a migration operation. + // the logger shouldn't be set during initialisation as it can cause cross-talk in tests (due to being static). + Logger.Storage = UnderlyingStorage.GetStorageForDirectory("logs"); + } } public override void Migrate(Storage newStorage) From 90326f8864723bc4427512705da4295a9f316300 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Jul 2021 13:24:26 +0900 Subject: [PATCH 33/58] Standardise variables --- osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs | 4 ++-- osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs | 4 ++-- osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs | 2 +- osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs | 2 +- osu.Game/Rulesets/Mods/DifficultyBindable.cs | 4 ++-- osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs | 4 ++-- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs index 8686627c2a..bd78d3b085 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Catch.Mods MinValue = 1, MaxValue = 10, ExtendedMaxValue = 11, - ReadFromDifficulty = diff => diff.CircleSize, + ReadCurrentFromDifficulty = diff => diff.CircleSize, }; [SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1)] @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Catch.Mods MinValue = 1, MaxValue = 10, ExtendedMaxValue = 11, - ReadFromDifficulty = diff => diff.ApproachRate, + ReadCurrentFromDifficulty = diff => diff.ApproachRate, }; [SettingSource("Spicy Patterns", "Adjust the patterns as if Hard Rock is enabled.")] diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs index 983216dfa1..3a6b232f9f 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Mods MinValue = 0, MaxValue = 10, ExtendedMaxValue = 11, - ReadFromDifficulty = diff => diff.CircleSize, + ReadCurrentFromDifficulty = diff => diff.CircleSize, }; [SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1, SettingControlType = typeof(DifficultyAdjustSettingsControl))] @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Mods MinValue = 0, MaxValue = 10, ExtendedMaxValue = 11, - ReadFromDifficulty = diff => diff.ApproachRate, + ReadCurrentFromDifficulty = diff => diff.ApproachRate, }; public override string SettingDescription diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs index ace105b21c..9540e35780 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Taiko.Mods Precision = 0.05f, MinValue = 0.25f, MaxValue = 4, - ReadFromDifficulty = _ => 1, + ReadCurrentFromDifficulty = _ => 1, }; public override string SettingDescription diff --git a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs index da5fb516f6..9ca44b25e8 100644 --- a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs +++ b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs @@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Mods { // ensure the beatmap's value is not transferred as a user override. isInternalChange = true; - displayNumber.Value = difficultyBindable.ReadFromDifficulty(difficulty); + displayNumber.Value = difficultyBindable.ReadCurrentFromDifficulty(difficulty); isInternalChange = false; } } diff --git a/osu.Game/Rulesets/Mods/DifficultyBindable.cs b/osu.Game/Rulesets/Mods/DifficultyBindable.cs index 22d8472922..26538fa4e3 100644 --- a/osu.Game/Rulesets/Mods/DifficultyBindable.cs +++ b/osu.Game/Rulesets/Mods/DifficultyBindable.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Mods /// /// Whether the extended limits should be applied to this bindable. /// - public BindableBool ExtendedLimits { get; } = new BindableBool(); + public readonly BindableBool ExtendedLimits = new BindableBool(); /// /// An internal numeric bindable to hold and propagate min/max/precision. @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Mods /// /// A function that can extract the current value of this setting from a beatmap difficulty for display purposes. /// - public Func ReadFromDifficulty; + public Func ReadCurrentFromDifficulty; public float Precision { diff --git a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs index d9d305d457..b78c30e8a5 100644 --- a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Mods MinValue = 0, MaxValue = 10, ExtendedMaxValue = 11, - ReadFromDifficulty = diff => diff.DrainRate, + ReadCurrentFromDifficulty = diff => diff.DrainRate, }; [SettingSource("Accuracy", "Override a beatmap's set OD.", LAST_SETTING_ORDER, SettingControlType = typeof(DifficultyAdjustSettingsControl))] @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Mods MinValue = 0, MaxValue = 10, ExtendedMaxValue = 11, - ReadFromDifficulty = diff => diff.OverallDifficulty, + ReadCurrentFromDifficulty = diff => diff.OverallDifficulty, }; [SettingSource("Extended Limits", "Adjust difficulty beyond sane limits.")] From f9cd7f10d827836be6080376b2adf5c7b0148ff4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Jul 2021 13:26:00 +0900 Subject: [PATCH 34/58] Allow null values for `ReadCurrentFromDifficulty` As long as this isn't a constructor parameter it feels best to gracefully handle omission. Realistically having it in the ctor is the best move, but it doesn't feel great in line with the other parameters passed in via object initalisers. --- osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs index 9ca44b25e8..aa25f20c32 100644 --- a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs +++ b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs @@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Mods if (difficulty == null) return; - if (Current.Value == null) + if (Current.Value == null && difficultyBindable.ReadCurrentFromDifficulty != null) { // ensure the beatmap's value is not transferred as a user override. isInternalChange = true; From 51bd83b3f4998915cab2a055ad1012c6259b2775 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Jul 2021 13:30:14 +0900 Subject: [PATCH 35/58] Update override matching test to match expectations --- .../TestSceneModDifficultyAdjustSettings.cs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs index d0a02a6c2d..bf494d4362 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs @@ -139,19 +139,22 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestUserOverrideMaintainedOnMatchingBeatmapValue() { - setBeatmapWithDifficultyParameters(2); + setBeatmapWithDifficultyParameters(3); - checkSliderAtValue("Circle Size", 2); + checkSliderAtValue("Circle Size", 3); checkBindableAtValue("Circle Size", null); - setSliderValue("Circle Size", 9); - checkSliderAtValue("Circle Size", 9); - checkBindableAtValue("Circle Size", 9); + // need to initially change it away from the current beatmap value to trigger an override. + setSliderValue("Circle Size", 4); + setSliderValue("Circle Size", 3); + + checkSliderAtValue("Circle Size", 3); + checkBindableAtValue("Circle Size", 3); setBeatmapWithDifficultyParameters(4); - checkSliderAtValue("Circle Size", 9); - checkBindableAtValue("Circle Size", 9); + checkSliderAtValue("Circle Size", 3); + checkBindableAtValue("Circle Size", 3); } private void resetToDefault(string name) From e0277763d0f7f504ba5e003cce10ba7ac2938632 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Jul 2021 13:50:07 +0900 Subject: [PATCH 36/58] Refactor `DifficultyAdjustSettingsControl` to help with readability --- .../Mods/DifficultyAdjustSettingsControl.cs | 48 ++++++++++--------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs index aa25f20c32..9286dd58a9 100644 --- a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs +++ b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs @@ -18,11 +18,14 @@ namespace osu.Game.Rulesets.Mods /// /// Used to track the display value on the setting slider. - /// This can either be a user override or the beatmap default (when is null). /// - private readonly BindableNumber displayNumber = new BindableNumber(); + /// + /// When the mod is overriding a default, this will match the value of . + /// When there is no override (ie. is null), this value will match the beatmap provided default via . + /// + private readonly BindableNumber sliderDisplayCurrent = new BindableNumber(); - protected override Drawable CreateControl() => new SliderControl(displayNumber); + protected override Drawable CreateControl() => new SliderControl(sliderDisplayCurrent); private bool isInternalChange; @@ -33,11 +36,10 @@ namespace osu.Game.Rulesets.Mods get => base.Current; set { - // intercept and extract the DifficultyBindable. + // Intercept and extract the internal number bindable from DifficultyBindable. + // This will provide bounds and precision specifications for the slider bar. difficultyBindable = (DifficultyBindable)value; - - // this bind is used to transfer bounds/precision only. - displayNumber.BindTo(difficultyBindable.CurrentNumber); + sliderDisplayCurrent.BindTo(difficultyBindable.CurrentNumber); base.Current = value; } @@ -51,15 +53,22 @@ namespace osu.Game.Rulesets.Mods Current.BindValueChanged(current => { - // the user override has changed; transfer the correct value to the visual display. - if (current.NewValue == null) - updateFromDifficulty(); + if (current.NewValue != null) + { + // a user override has been added or updated. + sliderDisplayCurrent.Value = current.NewValue.Value; + } else - displayNumber.Value = current.NewValue.Value; + { + // user override was removed, so restore the beatmap provided value. + updateFromDifficulty(); + } }); - displayNumber.BindValueChanged(number => + sliderDisplayCurrent.BindValueChanged(number => { + // this handles the transfer of the slider value to the main bindable. + // as such, should be skipped if the slider is being updated via updateFromDifficulty(). if (!isInternalChange) Current.Value = number.NewValue; }); @@ -76,23 +85,16 @@ namespace osu.Game.Rulesets.Mods { // ensure the beatmap's value is not transferred as a user override. isInternalChange = true; - displayNumber.Value = difficultyBindable.ReadCurrentFromDifficulty(difficulty); + sliderDisplayCurrent.Value = difficultyBindable.ReadCurrentFromDifficulty(difficulty); isInternalChange = false; } } private class SliderControl : CompositeDrawable, IHasCurrentValue { - private readonly BindableWithCurrent current = new BindableWithCurrent(); - - // Mainly just for fulfilling the interface requirements. - // The actual update flow is done via the provided number. - // Of note, this is used for the "reset to default" flow. - public Bindable Current - { - get => current.Current; - set => current.Current = value; - } + // This is required as SettingsItem relies heavily on this bindable for internal use. + // The actual update flow is done via the bindable provided in the constructor. + public Bindable Current { get; set; } = new Bindable(); public SliderControl(BindableNumber currentNumber) { From 741062a6da3a7ad7d3126210585511ac2abdc4fd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Jul 2021 13:58:44 +0900 Subject: [PATCH 37/58] Simplify bindable update methods --- .../Mods/DifficultyAdjustSettingsControl.cs | 45 +++++++++---------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs index 9286dd58a9..0f48ed4190 100644 --- a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs +++ b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs @@ -21,12 +21,15 @@ namespace osu.Game.Rulesets.Mods /// /// /// When the mod is overriding a default, this will match the value of . - /// When there is no override (ie. is null), this value will match the beatmap provided default via . + /// When there is no override (ie. is null), this value will match the beatmap provided default via . /// private readonly BindableNumber sliderDisplayCurrent = new BindableNumber(); protected override Drawable CreateControl() => new SliderControl(sliderDisplayCurrent); + /// + /// Guards against beatmap values displayed on slider bars being transferred to user override. + /// private bool isInternalChange; private DifficultyBindable difficultyBindable; @@ -49,21 +52,8 @@ namespace osu.Game.Rulesets.Mods { base.LoadComplete(); - beatmap.BindValueChanged(b => updateFromDifficulty(), true); - - Current.BindValueChanged(current => - { - if (current.NewValue != null) - { - // a user override has been added or updated. - sliderDisplayCurrent.Value = current.NewValue.Value; - } - else - { - // user override was removed, so restore the beatmap provided value. - updateFromDifficulty(); - } - }); + Current.BindValueChanged(current => updateCurrentFromSlider()); + beatmap.BindValueChanged(b => updateCurrentFromSlider(), true); sliderDisplayCurrent.BindValueChanged(number => { @@ -74,20 +64,27 @@ namespace osu.Game.Rulesets.Mods }); } - private void updateFromDifficulty() + private void updateCurrentFromSlider() { + if (Current.Value != null) + { + // a user override has been added or updated. + sliderDisplayCurrent.Value = Current.Value.Value; + return; + } + var difficulty = beatmap.Value.BeatmapInfo.BaseDifficulty; if (difficulty == null) return; - if (Current.Value == null && difficultyBindable.ReadCurrentFromDifficulty != null) - { - // ensure the beatmap's value is not transferred as a user override. - isInternalChange = true; - sliderDisplayCurrent.Value = difficultyBindable.ReadCurrentFromDifficulty(difficulty); - isInternalChange = false; - } + // generally should always be implemented, else the slider will have a zero default. + if (difficultyBindable.ReadCurrentFromDifficulty == null) + return; + + isInternalChange = true; + sliderDisplayCurrent.Value = difficultyBindable.ReadCurrentFromDifficulty(difficulty); + isInternalChange = false; } private class SliderControl : CompositeDrawable, IHasCurrentValue From a9250a0d984acdbcc6a92daebc1009cae3e2feaf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Jul 2021 18:23:27 +0900 Subject: [PATCH 38/58] Limit update notifications to once per startup This logic was intentionally designed to continue to prompt the user to update if they haven't, but that seems pretty anti-user. The change will stop the update prompts from showing more than once per game startup, unless manually invoked by the user a second time. Closes https://github.com/ppy/osu/issues/13821. --- osu.Desktop/Updater/SquirrelUpdateManager.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/SquirrelUpdateManager.cs index 58d67c11d9..73f53721d1 100644 --- a/osu.Desktop/Updater/SquirrelUpdateManager.cs +++ b/osu.Desktop/Updater/SquirrelUpdateManager.cs @@ -68,6 +68,8 @@ namespace osu.Desktop.Updater return false; } + scheduleRecheck = false; + if (notification == null) { notification = new UpdateProgressNotification(this) { State = ProgressNotificationState.Active }; @@ -98,7 +100,6 @@ namespace osu.Desktop.Updater // could fail if deltas are unavailable for full update path (https://github.com/Squirrel/Squirrel.Windows/issues/959) // try again without deltas. await checkForUpdateAsync(false, notification).ConfigureAwait(false); - scheduleRecheck = false; } else { From b705213ea930473e8f84f4cc92cb96fbc3dcb89f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 10 Jul 2021 11:44:32 +0200 Subject: [PATCH 39/58] Update test to match expectations after refactor --- .../Visual/UserInterface/TestSceneModSelectOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 91edc226c9..3485d7fbc3 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -80,7 +80,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("show", () => modSelect.Show()); AddAssert("ensure first is unchanged", () => SelectedMods.Value.OfType().Single().CircleSize.Value == 8); - AddAssert("ensure second is default", () => selectedMods2.Value.OfType().Single().CircleSize.Value == 5); + AddAssert("ensure second is default", () => selectedMods2.Value.OfType().Single().CircleSize.Value == null); } [Test] From c44558e3c880ab5bf1384449acbb575b2267d4a8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 10 Jul 2021 17:57:52 +0300 Subject: [PATCH 40/58] Add back `LoadComplete` override --- osu.Game/Overlays/RestoreDefaultValueButton.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Overlays/RestoreDefaultValueButton.cs b/osu.Game/Overlays/RestoreDefaultValueButton.cs index 469d48d82b..fd3ee16fe6 100644 --- a/osu.Game/Overlays/RestoreDefaultValueButton.cs +++ b/osu.Game/Overlays/RestoreDefaultValueButton.cs @@ -78,6 +78,12 @@ namespace osu.Game.Overlays }; } + protected override void LoadComplete() + { + base.LoadComplete(); + UpdateState(); + } + public LocalisableString TooltipText => "revert to default"; protected override bool OnHover(HoverEvent e) From a1f3adc32004200198b227e3047c391ef828b754 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 10 Jul 2021 19:56:44 +0300 Subject: [PATCH 41/58] Add simple test cases --- .../Visual/Settings/TestSceneSettingsItem.cs | 60 +++++++++++++++---- 1 file changed, 47 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs b/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs index f63145f534..ebcdc8c702 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs @@ -4,7 +4,6 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Bindables; -using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Overlays.Settings; using osu.Game.Overlays; @@ -17,28 +16,63 @@ namespace osu.Game.Tests.Visual.Settings [Test] public void TestRestoreDefaultValueButtonVisibility() { - TestSettingsTextBox textBox = null; + SettingsTextBox textBox = null; + RestoreDefaultValueButton restoreDefaultValueButton = null; - AddStep("create settings item", () => Child = textBox = new TestSettingsTextBox + AddStep("create settings item", () => { - Current = new Bindable + Child = textBox = new SettingsTextBox { - Default = "test", - Value = "test" - } + Current = new Bindable + { + Default = "test", + Value = "test" + } + }; + + restoreDefaultValueButton = textBox.ChildrenOfType>().Single(); }); - AddAssert("restore button hidden", () => textBox.RestoreDefaultValueButton.Alpha == 0); + AddAssert("restore button hidden", () => restoreDefaultValueButton.Alpha == 0); AddStep("change value from default", () => textBox.Current.Value = "non-default"); - AddUntilStep("restore button shown", () => textBox.RestoreDefaultValueButton.Alpha > 0); + AddUntilStep("restore button shown", () => restoreDefaultValueButton.Alpha > 0); AddStep("restore default", () => textBox.Current.SetDefault()); - AddUntilStep("restore button hidden", () => textBox.RestoreDefaultValueButton.Alpha == 0); + AddUntilStep("restore button hidden", () => restoreDefaultValueButton.Alpha == 0); } - private class TestSettingsTextBox : SettingsTextBox + /// + /// Tests precision of the restore default value button, with a couple of single floating-point numbers that could potentially underflow. + /// + [TestCase(4.2f)] + [TestCase(9.9f)] + public void TestRestoreDefaultValueButtonPrecision(float initialValue) { - public Drawable RestoreDefaultValueButton => this.ChildrenOfType>().Single(); + SettingsSlider sliderBar = null; + RestoreDefaultValueButton restoreDefaultValueButton = null; + + AddStep("create settings item", () => + { + Child = sliderBar = new SettingsSlider + { + Current = new BindableFloat(initialValue) + { + MinValue = 0f, + MaxValue = 10f, + Precision = 0.1f, + } + }; + + restoreDefaultValueButton = sliderBar.ChildrenOfType>().Single(); + }); + + AddAssert("restore button hidden", () => restoreDefaultValueButton.Alpha == 0); + + AddStep("change value to 5.1f", () => sliderBar.Current.Value = 5.0f); + AddUntilStep("restore button shown", () => restoreDefaultValueButton.Alpha > 0); + + AddStep("restore default", () => sliderBar.Current.SetDefault()); + AddUntilStep("restore button hidden", () => restoreDefaultValueButton.Alpha == 0); } } -} \ No newline at end of file +} From 07ede7a14745c3b1adf4f22e704f1a5aa5529d75 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 11 Jul 2021 03:34:57 +0300 Subject: [PATCH 42/58] Disallow custom rulesets from score submission --- osu.Game/Screens/Play/SoloPlayer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/SoloPlayer.cs b/osu.Game/Screens/Play/SoloPlayer.cs index ef1087dd62..d90e8e0168 100644 --- a/osu.Game/Screens/Play/SoloPlayer.cs +++ b/osu.Game/Screens/Play/SoloPlayer.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Online.Solo; +using osu.Game.Rulesets; using osu.Game.Scoring; namespace osu.Game.Screens.Play @@ -27,7 +28,7 @@ namespace osu.Game.Screens.Play if (!(Beatmap.Value.BeatmapInfo.OnlineBeatmapID is int beatmapId)) return null; - if (!(Ruleset.Value.ID is int rulesetId)) + if (!(Ruleset.Value.ID is int rulesetId) || Ruleset.Value.ID > ILegacyRuleset.MAX_LEGACY_RULESET_ID) return null; return new CreateSoloScoreRequest(beatmapId, rulesetId, Game.VersionHash); From 6b8de2a10b6fc3cdbea511a66a4673c824c60fe8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 11 Jul 2021 03:35:35 +0300 Subject: [PATCH 43/58] Add test coverage for excluded cases in score submission --- .../TestScenePlayerScoreSubmission.cs | 92 +++++++++++++++---- 1 file changed, 73 insertions(+), 19 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index f9ccb10778..5ff2e9c439 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Linq; using NUnit.Framework; using osu.Framework.Screens; @@ -10,6 +11,7 @@ using osu.Game.Online.Rooms; using osu.Game.Online.Solo; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Ranking; @@ -17,17 +19,26 @@ using osu.Game.Tests.Beatmaps; namespace osu.Game.Tests.Visual.Gameplay { - public class TestScenePlayerScoreSubmission : OsuPlayerTestScene + public class TestScenePlayerScoreSubmission : PlayerTestScene { protected override bool AllowFail => allowFail; private bool allowFail; + private Func createCustomBeatmap; + private Func createCustomRuleset; + private DummyAPIAccess dummyAPI => (DummyAPIAccess)API; protected override bool HasCustomSteps => true; - protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) + protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(false); + + protected override Ruleset CreatePlayerRuleset() => createCustomRuleset?.Invoke() ?? new OsuRuleset(); + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => createCustomBeatmap?.Invoke(ruleset) ?? createTestBeatmap(ruleset); + + private IBeatmap createTestBeatmap(RulesetInfo ruleset) { var beatmap = (TestBeatmap)base.CreateBeatmap(ruleset); @@ -36,14 +47,12 @@ namespace osu.Game.Tests.Visual.Gameplay return beatmap; } - protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(false); - [Test] public void TestNoSubmissionOnResultsWithNoToken() { prepareTokenResponse(false); - CreateTest(() => allowFail = false); + createPlayerTest(); AddUntilStep("wait for token request", () => Player.TokenCreationRequested); @@ -63,7 +72,7 @@ namespace osu.Game.Tests.Visual.Gameplay { prepareTokenResponse(true); - CreateTest(() => allowFail = false); + createPlayerTest(); AddUntilStep("wait for token request", () => Player.TokenCreationRequested); @@ -82,7 +91,7 @@ namespace osu.Game.Tests.Visual.Gameplay { prepareTokenResponse(false); - CreateTest(() => allowFail = false); + createPlayerTest(); AddUntilStep("wait for token request", () => Player.TokenCreationRequested); @@ -99,7 +108,7 @@ namespace osu.Game.Tests.Visual.Gameplay { prepareTokenResponse(true); - CreateTest(() => allowFail = true); + createPlayerTest(true); AddUntilStep("wait for token request", () => Player.TokenCreationRequested); @@ -114,7 +123,7 @@ namespace osu.Game.Tests.Visual.Gameplay { prepareTokenResponse(true); - CreateTest(() => allowFail = true); + createPlayerTest(true); AddUntilStep("wait for token request", () => Player.TokenCreationRequested); @@ -131,7 +140,7 @@ namespace osu.Game.Tests.Visual.Gameplay { prepareTokenResponse(true); - CreateTest(() => allowFail = false); + createPlayerTest(); AddUntilStep("wait for token request", () => Player.TokenCreationRequested); @@ -144,7 +153,7 @@ namespace osu.Game.Tests.Visual.Gameplay { prepareTokenResponse(true); - CreateTest(() => allowFail = false); + createPlayerTest(); AddUntilStep("wait for token request", () => Player.TokenCreationRequested); @@ -154,18 +163,49 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("ensure failing submission", () => Player.SubmittedScore?.ScoreInfo.Passed == false); } - private void addFakeHit() + [Test] + public void TestNoSubmissionOnLocalBeatmap() { - AddUntilStep("wait for first result", () => Player.Results.Count > 0); + prepareTokenResponse(true); - AddStep("force successfuly hit", () => + createPlayerTest(false, r => { - Player.ScoreProcessor.RevertResult(Player.Results.First()); - Player.ScoreProcessor.ApplyResult(new OsuJudgementResult(Beatmap.Value.Beatmap.HitObjects.First(), new OsuJudgement()) - { - Type = HitResult.Great, - }); + var beatmap = createTestBeatmap(r); + beatmap.BeatmapInfo.OnlineBeatmapID = null; + return beatmap; }); + + AddUntilStep("wait for token request", () => Player.TokenCreationRequested); + + addFakeHit(); + + AddStep("exit", () => Player.Exit()); + AddAssert("ensure no submission", () => Player.SubmittedScore == null); + } + + [Test] + public void TestNoSubmissionOnCustomRuleset() + { + prepareTokenResponse(true); + + createPlayerTest(false, createRuleset: () => new OsuRuleset { RulesetInfo = { ID = 10 } }); + + AddUntilStep("wait for token request", () => Player.TokenCreationRequested); + + addFakeHit(); + + AddStep("exit", () => Player.Exit()); + AddAssert("ensure no submission", () => Player.SubmittedScore == null); + } + + private void createPlayerTest(bool allowFail = false, Func createBeatmap = null, Func createRuleset = null) + { + CreateTest(() => AddStep("set up requirements", () => + { + this.allowFail = allowFail; + createCustomBeatmap = createBeatmap; + createCustomRuleset = createRuleset; + })); } private void prepareTokenResponse(bool validToken) @@ -188,5 +228,19 @@ namespace osu.Game.Tests.Visual.Gameplay }; }); } + + private void addFakeHit() + { + AddUntilStep("wait for first result", () => Player.Results.Count > 0); + + AddStep("force successfuly hit", () => + { + Player.ScoreProcessor.RevertResult(Player.Results.First()); + Player.ScoreProcessor.ApplyResult(new OsuJudgementResult(Beatmap.Value.Beatmap.HitObjects.First(), new OsuJudgement()) + { + Type = HitResult.Great, + }); + }); + } } } From f21ea3b790812bf48d108601191b11e1b462f087 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 11 Jul 2021 03:46:19 +0300 Subject: [PATCH 44/58] Update player test scene `Ruleset` bindable from creation method --- osu.Game/Tests/Visual/PlayerTestScene.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs index 088e997de9..93491c800f 100644 --- a/osu.Game/Tests/Visual/PlayerTestScene.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -57,7 +57,9 @@ namespace osu.Game.Tests.Visual protected void LoadPlayer() { - var ruleset = Ruleset.Value.CreateInstance(); + var ruleset = CreatePlayerRuleset(); + Ruleset.Value = ruleset.RulesetInfo; + var beatmap = CreateBeatmap(ruleset.RulesetInfo); Beatmap.Value = CreateWorkingBeatmap(beatmap); From 79d546afa2efef11dc7035567ebd9caefaa539e0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 11 Jul 2021 10:14:42 +0900 Subject: [PATCH 45/58] Add missing osu!catch difficulty adjust attributes --- osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs index bd78d3b085..e59a0a0431 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Catch.Mods { public class CatchModDifficultyAdjust : ModDifficultyAdjust, IApplicableToBeatmapProcessor { - [SettingSource("Circle Size", "Override a beatmap's set CS.", FIRST_SETTING_ORDER - 1)] + [SettingSource("Circle Size", "Override a beatmap's set CS.", FIRST_SETTING_ORDER - 1, SettingControlType = typeof(DifficultyAdjustSettingsControl))] public DifficultyBindable CircleSize { get; } = new DifficultyBindable { Precision = 0.1f, @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Catch.Mods ReadCurrentFromDifficulty = diff => diff.CircleSize, }; - [SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1)] + [SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1, SettingControlType = typeof(DifficultyAdjustSettingsControl))] public DifficultyBindable ApproachRate { get; } = new DifficultyBindable { Precision = 0.1f, From 32b4f5fbd60239274af90153d35e4db631986619 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 11 Jul 2021 15:12:46 +0200 Subject: [PATCH 46/58] Do not store direct references to original bindable `DifficultyAdjustSettingsControl` and its inner `SliderControl` were holding different references to `DifficultyBindable`s from the difficulty adjust mod, therefore leading to bindings being lost to the framework-side automatic unbind logic if the mod was toggled off and back on in rapid succession. Resolve by adding a shadowed implementation of `GetBoundCopy()` and using it to isolate the controls from the mod bindable. --- .../Rulesets/Mods/DifficultyAdjustSettingsControl.cs | 4 ++-- osu.Game/Rulesets/Mods/DifficultyBindable.cs | 11 +++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs index 0f48ed4190..067657159b 100644 --- a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs +++ b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs @@ -41,10 +41,10 @@ namespace osu.Game.Rulesets.Mods { // Intercept and extract the internal number bindable from DifficultyBindable. // This will provide bounds and precision specifications for the slider bar. - difficultyBindable = (DifficultyBindable)value; + difficultyBindable = ((DifficultyBindable)value).GetBoundCopy(); sliderDisplayCurrent.BindTo(difficultyBindable.CurrentNumber); - base.Current = value; + base.Current = difficultyBindable; } } diff --git a/osu.Game/Rulesets/Mods/DifficultyBindable.cs b/osu.Game/Rulesets/Mods/DifficultyBindable.cs index 26538fa4e3..0f16126464 100644 --- a/osu.Game/Rulesets/Mods/DifficultyBindable.cs +++ b/osu.Game/Rulesets/Mods/DifficultyBindable.cs @@ -92,5 +92,16 @@ namespace osu.Game.Rulesets.Mods { CurrentNumber.MaxValue = ExtendedLimits.Value && extendedMaxValue != null ? extendedMaxValue.Value : maxValue; } + + public new DifficultyBindable GetBoundCopy() => new DifficultyBindable + { + BindTarget = this, + CurrentNumber = { BindTarget = CurrentNumber }, + ExtendedLimits = { BindTarget = ExtendedLimits }, + ReadCurrentFromDifficulty = ReadCurrentFromDifficulty, + // the following is only safe as long as these values are effectively constants. + MaxValue = maxValue, + ExtendedMaxValue = extendedMaxValue + }; } } From 9e70136100c6550abd53ef07aa36adc0318b2c24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 11 Jul 2021 17:26:00 +0200 Subject: [PATCH 47/58] Adjust test case slightly --- osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs b/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs index ebcdc8c702..df59b9284b 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs @@ -42,12 +42,14 @@ namespace osu.Game.Tests.Visual.Settings } /// - /// Tests precision of the restore default value button, with a couple of single floating-point numbers that could potentially underflow. + /// Ensures that the reset to default button uses the correct implementation of IsDefault to determine whether it should be shown or not. + /// Values have been chosen so that after being set, Value != Default (but they are close enough that the difference is negligible compared to Precision). /// [TestCase(4.2f)] [TestCase(9.9f)] public void TestRestoreDefaultValueButtonPrecision(float initialValue) { + BindableFloat current = null; SettingsSlider sliderBar = null; RestoreDefaultValueButton restoreDefaultValueButton = null; @@ -55,7 +57,7 @@ namespace osu.Game.Tests.Visual.Settings { Child = sliderBar = new SettingsSlider { - Current = new BindableFloat(initialValue) + Current = current = new BindableFloat(initialValue) { MinValue = 0f, MaxValue = 10f, @@ -68,7 +70,7 @@ namespace osu.Game.Tests.Visual.Settings AddAssert("restore button hidden", () => restoreDefaultValueButton.Alpha == 0); - AddStep("change value to 5.1f", () => sliderBar.Current.Value = 5.0f); + AddStep("change value to next closest", () => sliderBar.Current.Value += current.Precision * 0.6f); AddUntilStep("restore button shown", () => restoreDefaultValueButton.Alpha > 0); AddStep("restore default", () => sliderBar.Current.SetDefault()); From a6258d705ed1f188061333ac7299e5c55ca81761 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Jul 2021 11:26:30 +0900 Subject: [PATCH 48/58] Make `CurrentNumber` internal --- osu.Game/Rulesets/Mods/DifficultyBindable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/DifficultyBindable.cs b/osu.Game/Rulesets/Mods/DifficultyBindable.cs index 0f16126464..1f76a3e366 100644 --- a/osu.Game/Rulesets/Mods/DifficultyBindable.cs +++ b/osu.Game/Rulesets/Mods/DifficultyBindable.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Mods /// An internal numeric bindable to hold and propagate min/max/precision. /// The value of this bindable should not be set. /// - public readonly BindableFloat CurrentNumber = new BindableFloat + internal readonly BindableFloat CurrentNumber = new BindableFloat { MinValue = 0, MaxValue = 10, From 3642febbb684180f937b6e9dc06d5b4a5695eade Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Jul 2021 12:35:40 +0900 Subject: [PATCH 49/58] Fix one new incorrect formatting inspection from EAP6 --- .../Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs index 271fbde5c3..8cff790e80 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs @@ -31,10 +31,7 @@ namespace osu.Game.Tests.Visual.SongSelect private readonly TestBeatmapDifficultyCache testDifficultyCache = new TestBeatmapDifficultyCache(); [Test] - public void TestLocal([Values("Beatmap", "Some long title and stuff")] - string title, - [Values("Trial", "Some1's very hardest difficulty")] - string version) + public void TestLocal([Values("Beatmap", "Some long title and stuff")] string title, [Values("Trial", "Some1's very hardest difficulty")] string version) { showMetadataForBeatmap(() => CreateWorkingBeatmap(new Beatmap { From f548ba4f6925058b182319d597a0968d2aff29bb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Jul 2021 14:07:17 +0900 Subject: [PATCH 50/58] Update realm libraries to fix windows 8.1 incompatibility --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 8a3c69e40c..f4cf01eb82 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -35,7 +35,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + From 37dac1a775ee22035b3cf9660883912d7985a0d8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Jul 2021 14:36:28 +0900 Subject: [PATCH 51/58] Update mobile projects' local references to older realm --- osu.Android.props | 2 +- osu.iOS.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 9280eaf97c..f8c446555d 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -56,6 +56,6 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 2eea646c61..79746be0b3 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -99,6 +99,6 @@ - + From 47a593ad7d9bb118eec72e484f7f3a7988139528 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Jul 2021 14:55:09 +0900 Subject: [PATCH 52/58] Force a re-check on any exception being thrown --- osu.Desktop/Updater/SquirrelUpdateManager.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/SquirrelUpdateManager.cs index 73f53721d1..910751a723 100644 --- a/osu.Desktop/Updater/SquirrelUpdateManager.cs +++ b/osu.Desktop/Updater/SquirrelUpdateManager.cs @@ -111,6 +111,7 @@ namespace osu.Desktop.Updater catch (Exception) { // we'll ignore this and retry later. can be triggered by no internet connection or thread abortion. + scheduleRecheck = true; } finally { From 242982730fb5fca4c029bb1ca3e23cf59e222121 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 12 Jul 2021 16:52:57 +0900 Subject: [PATCH 53/58] Fix incorrect DifficultyBindable binding implementation --- osu.Game/Rulesets/Mods/DifficultyBindable.cs | 29 ++++++++++++++------ 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/osu.Game/Rulesets/Mods/DifficultyBindable.cs b/osu.Game/Rulesets/Mods/DifficultyBindable.cs index 1f76a3e366..cf86e9ad19 100644 --- a/osu.Game/Rulesets/Mods/DifficultyBindable.cs +++ b/osu.Game/Rulesets/Mods/DifficultyBindable.cs @@ -71,6 +71,12 @@ namespace osu.Game.Rulesets.Mods } public DifficultyBindable() + : this(null) + { + } + + public DifficultyBindable(float? defaultValue = null) + : base(defaultValue) { ExtendedLimits.BindValueChanged(_ => updateMaxValue()); } @@ -93,15 +99,22 @@ namespace osu.Game.Rulesets.Mods CurrentNumber.MaxValue = ExtendedLimits.Value && extendedMaxValue != null ? extendedMaxValue.Value : maxValue; } - public new DifficultyBindable GetBoundCopy() => new DifficultyBindable + public override void BindTo(Bindable them) { - BindTarget = this, - CurrentNumber = { BindTarget = CurrentNumber }, - ExtendedLimits = { BindTarget = ExtendedLimits }, - ReadCurrentFromDifficulty = ReadCurrentFromDifficulty, + if (!(them is DifficultyBindable otherDifficultyBindable)) + throw new InvalidOperationException($"Cannot bind to a non-{nameof(DifficultyBindable)}."); + + base.BindTo(them); + + CurrentNumber.BindTarget = otherDifficultyBindable.CurrentNumber; + ExtendedLimits.BindTarget = otherDifficultyBindable.ExtendedLimits; + ReadCurrentFromDifficulty = otherDifficultyBindable.ReadCurrentFromDifficulty; + // the following is only safe as long as these values are effectively constants. - MaxValue = maxValue, - ExtendedMaxValue = extendedMaxValue - }; + MaxValue = otherDifficultyBindable.maxValue; + ExtendedMaxValue = otherDifficultyBindable.extendedMaxValue; + } + + public new DifficultyBindable GetBoundCopy() => new DifficultyBindable { BindTarget = this }; } } From 4b393209ec2715fb57ce3750e8fab11e572cb8d1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 12 Jul 2021 17:33:29 +0900 Subject: [PATCH 54/58] Implement UnbindFrom() --- osu.Game/Rulesets/Mods/DifficultyBindable.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game/Rulesets/Mods/DifficultyBindable.cs b/osu.Game/Rulesets/Mods/DifficultyBindable.cs index cf86e9ad19..ff859de30b 100644 --- a/osu.Game/Rulesets/Mods/DifficultyBindable.cs +++ b/osu.Game/Rulesets/Mods/DifficultyBindable.cs @@ -115,6 +115,17 @@ namespace osu.Game.Rulesets.Mods ExtendedMaxValue = otherDifficultyBindable.extendedMaxValue; } + public override void UnbindFrom(IUnbindable them) + { + if (!(them is DifficultyBindable otherDifficultyBindable)) + throw new InvalidOperationException($"Cannot unbind from a non-{nameof(DifficultyBindable)}."); + + base.UnbindFrom(them); + + CurrentNumber.UnbindFrom(otherDifficultyBindable.CurrentNumber); + ExtendedLimits.UnbindFrom(otherDifficultyBindable.ExtendedLimits); + } + public new DifficultyBindable GetBoundCopy() => new DifficultyBindable { BindTarget = this }; } } From 78c74e97d1d2e33735511caf475e0a3d9b34efc1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Jul 2021 18:08:19 +0900 Subject: [PATCH 55/58] Change to alternative formatting --- .../Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs index 8cff790e80..449401c0bf 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs @@ -31,7 +31,11 @@ namespace osu.Game.Tests.Visual.SongSelect private readonly TestBeatmapDifficultyCache testDifficultyCache = new TestBeatmapDifficultyCache(); [Test] - public void TestLocal([Values("Beatmap", "Some long title and stuff")] string title, [Values("Trial", "Some1's very hardest difficulty")] string version) + public void TestLocal( + [Values("Beatmap", "Some long title and stuff")] + string title, + [Values("Trial", "Some1's very hardest difficulty")] + string version) { showMetadataForBeatmap(() => CreateWorkingBeatmap(new Beatmap { From f3fe472a3313eb9f3406571d57d87cc7ed1559e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 12 Jul 2021 22:25:21 +0200 Subject: [PATCH 56/58] Add failing test case for reset to defaults --- .../TestSceneModDifficultyAdjustSettings.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs index bf494d4362..e0d76b3e4a 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs @@ -157,6 +157,23 @@ namespace osu.Game.Tests.Visual.UserInterface checkBindableAtValue("Circle Size", 3); } + [Test] + public void TestResetToDefaults() + { + setBeatmapWithDifficultyParameters(5); + + setSliderValue("Circle Size", 3); + setExtendedLimits(true); + + checkSliderAtValue("Circle Size", 3); + checkBindableAtValue("Circle Size", 3); + + AddStep("reset mod settings", () => modDifficultyAdjust.ResetSettingsToDefaults()); + + checkSliderAtValue("Circle Size", 5); + checkBindableAtValue("Circle Size", null); + } + private void resetToDefault(string name) { AddStep($"Reset {name} to default", () => From cce4a4dc31553b1a8ba2358529a4718fa71523f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 12 Jul 2021 22:25:33 +0200 Subject: [PATCH 57/58] Fix incorrect value copy order in `BindTo()` --- osu.Game/Rulesets/Mods/DifficultyBindable.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Mods/DifficultyBindable.cs b/osu.Game/Rulesets/Mods/DifficultyBindable.cs index ff859de30b..664b88eef4 100644 --- a/osu.Game/Rulesets/Mods/DifficultyBindable.cs +++ b/osu.Game/Rulesets/Mods/DifficultyBindable.cs @@ -104,15 +104,17 @@ namespace osu.Game.Rulesets.Mods if (!(them is DifficultyBindable otherDifficultyBindable)) throw new InvalidOperationException($"Cannot bind to a non-{nameof(DifficultyBindable)}."); - base.BindTo(them); - - CurrentNumber.BindTarget = otherDifficultyBindable.CurrentNumber; - ExtendedLimits.BindTarget = otherDifficultyBindable.ExtendedLimits; ReadCurrentFromDifficulty = otherDifficultyBindable.ReadCurrentFromDifficulty; - // the following is only safe as long as these values are effectively constants. + // the following max value copies are only safe as long as these values are effectively constants. MaxValue = otherDifficultyBindable.maxValue; ExtendedMaxValue = otherDifficultyBindable.extendedMaxValue; + + ExtendedLimits.BindTarget = otherDifficultyBindable.ExtendedLimits; + + // the actual values need to be copied after the max value constraints. + CurrentNumber.BindTarget = otherDifficultyBindable.CurrentNumber; + base.BindTo(them); } public override void UnbindFrom(IUnbindable them) From ac15dae93062b2cba9f934d9ae1ed5f7e822abe3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Jul 2021 12:35:25 +0900 Subject: [PATCH 58/58] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 9280eaf97c..d36ae50280 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 8a3c69e40c..cf4918346c 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 2eea646c61..e573ca97e3 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - +