From b51c41a8043ae04fdc57aad461beb9f74b2320b5 Mon Sep 17 00:00:00 2001 From: Terochi Date: Thu, 9 Mar 2023 20:14:58 +0100 Subject: [PATCH] Addressed changes --- osu.Game.Tests/Mods/ModSettingsTest.cs | 92 ++++++++++++++++++-------- osu.Game/Rulesets/Mods/Mod.cs | 69 ++++++++++++------- 2 files changed, 109 insertions(+), 52 deletions(-) diff --git a/osu.Game.Tests/Mods/ModSettingsTest.cs b/osu.Game.Tests/Mods/ModSettingsTest.cs index 45e162df1a..e2e050870e 100644 --- a/osu.Game.Tests/Mods/ModSettingsTest.cs +++ b/osu.Game.Tests/Mods/ModSettingsTest.cs @@ -14,7 +14,7 @@ namespace osu.Game.Tests.Mods public class ModSettingsTest { [Test] - public void TestModSettingsUnboundWhenCopied() + public void TestModSettingsUnboundWhenCloned() { var original = new OsuModDoubleTime(); var copy = (OsuModDoubleTime)original.DeepClone(); @@ -26,7 +26,7 @@ namespace osu.Game.Tests.Mods } [Test] - public void TestMultiModSettingsUnboundWhenCopied() + public void TestMultiModSettingsUnboundWhenCloned() { var original = new MultiMod(new OsuModDoubleTime()); var copy = (MultiMod)original.DeepClone(); @@ -54,6 +54,42 @@ namespace osu.Game.Tests.Mods Assert.That(modBool.TestSetting.Value, Is.EqualTo(!modBool.TestSetting.Default)); } + [Test] + public void TestDefaultValueKeptWhenCopied() + { + var modBoolTrue = new TestNonMatchingSettingTypeModBool { TestSetting = { Default = true, Value = false } }; + var modBoolFalse = new TestNonMatchingSettingTypeModBool { TestSetting = { Default = false, Value = true } }; + + modBoolFalse.CopySharedSettings(modBoolTrue); + + Assert.That(modBoolFalse.TestSetting.Default, Is.EqualTo(false)); + Assert.That(modBoolFalse.TestSetting.Value, Is.EqualTo(modBoolTrue.TestSetting.Value)); + } + + [Test] + public void TestValueResetsToDefaultWhenCopied() + { + var modDouble = new TestNonMatchingSettingTypeModDouble(); + var modInt = new TestNonMatchingSettingTypeModInt { TestSetting = { Value = 1 } }; + + modInt.CopySharedSettings(modDouble); + + Assert.That(modInt.TestSetting.Value, Is.EqualTo(modInt.TestSetting.Default)); + } + + [Test] + public void TestRelativelyScaleWithClampedRangeWhenCopied() + { + const double setting_change = 50.4; + + var modDouble100 = new TestNonMatchingSettingTypeModDouble { TestSetting = { MaxValue = 100, MinValue = 0, Value = setting_change } }; + var modDouble200 = new TestNonMatchingSettingTypeModDouble { TestSetting = { MaxValue = 200, MinValue = 0 } }; + + modDouble200.CopySharedSettings(modDouble100); + + Assert.That(modDouble200.TestSetting.Value, Is.EqualTo(setting_change * 2)); + } + [Test] public void TestCopyDoubleToIntWithDefaultRange() { @@ -64,7 +100,32 @@ namespace osu.Game.Tests.Mods modInt.CopySharedSettings(modDouble); - Assert.That(modInt.TestSetting.Value, Is.EqualTo((int)setting_change)); + Assert.That(modInt.TestSetting.Value, Is.EqualTo(Convert.ToInt32(setting_change))); + } + + [Test] + public void TestCopyDoubleToIntWithOutOfBoundsRange() + { + const double setting_change = 50.4; + + var modDouble = new TestNonMatchingSettingTypeModDouble { TestSetting = { MinValue = int.MinValue - 1d, Value = setting_change } }; + // make RangeConstrainedBindable.HasDefinedRange return true + var modInt = new TestNonMatchingSettingTypeModInt { TestSetting = { MinValue = int.MinValue + 1 } }; + + modInt.CopySharedSettings(modDouble); + + Assert.That(modInt.TestSetting.Value, Is.EqualTo(Convert.ToInt32(setting_change))); + } + + [Test] + public void TestCopyDoubleToIntWithOutOfBoundsValue() + { + var modDouble = new TestNonMatchingSettingTypeModDouble { TestSetting = { MinValue = int.MinValue + 1, Value = int.MaxValue + 1d } }; + var modInt = new TestNonMatchingSettingTypeModInt { TestSetting = { MinValue = int.MinValue + 1 } }; + + modInt.CopySharedSettings(modDouble); + + Assert.That(modInt.TestSetting.Value, Is.EqualTo(int.MaxValue)); } [Test] @@ -80,31 +141,6 @@ namespace osu.Game.Tests.Mods Assert.That(modDouble.TestSetting.Value, Is.EqualTo(setting_change)); } - [Test] - public void TestCopyDoubleToIntWithClampedRange() - { - const double setting_change = 50.4; - - var modDouble = new TestNonMatchingSettingTypeModDouble { TestSetting = { MaxValue = 100, MinValue = 0, Value = setting_change } }; - var modInt = new TestNonMatchingSettingTypeModInt { TestSetting = { MaxValue = 200, MinValue = 0 } }; - - modInt.CopySharedSettings(modDouble); - - Assert.That(modInt.TestSetting.Value, Is.EqualTo(Convert.ToInt32(setting_change * 2))); - } - - [Test] - public void TestDefaultValueKeptWhenCopied() - { - var modBoolTrue = new TestNonMatchingSettingTypeModBool { TestSetting = { Default = true, Value = false } }; - var modBoolFalse = new TestNonMatchingSettingTypeModBool { TestSetting = { Default = false, Value = true } }; - - modBoolFalse.CopySharedSettings(modBoolTrue); - - Assert.That(modBoolFalse.TestSetting.Default, Is.EqualTo(false)); - Assert.That(modBoolFalse.TestSetting.Value, Is.EqualTo(modBoolTrue.TestSetting.Value)); - } - private class TestNonMatchingSettingTypeModDouble : TestNonMatchingSettingTypeMod { public override string Acronym => "NMD"; diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index e630a53045..85014d555d 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -169,28 +169,31 @@ namespace osu.Game.Rulesets.Mods const string max_value = nameof(BindableNumber.MaxValue); const string value = nameof(Bindable.Value); - Dictionary oldSettings = new Dictionary(); + Dictionary sourceSettings = new Dictionary(); - foreach (var (_, property) in source.GetSettingsSourceProperties()) + foreach (var (_, sourceProperty) in source.GetSettingsSourceProperties()) { - oldSettings.Add(property.Name.ToSnakeCase(), property.GetValue(source)!); + sourceSettings.Add(sourceProperty.Name.ToSnakeCase(), sourceProperty.GetValue(source)!); } - foreach (var (_, property) in this.GetSettingsSourceProperties()) + foreach (var (_, targetProperty) in this.GetSettingsSourceProperties()) { - object targetSetting = property.GetValue(this)!; + object targetSetting = targetProperty.GetValue(this)!; - if (!oldSettings.TryGetValue(property.Name.ToSnakeCase(), out object? sourceSetting)) + if (!sourceSettings.TryGetValue(targetProperty.Name.ToSnakeCase(), out object? sourceSetting)) continue; if (((IBindable)sourceSetting).IsDefault) - // keep at default value if the source is default + { + // reset to default value if the source is default + targetSetting.GetType().GetMethod(nameof(Bindable.SetDefault))!.Invoke(targetSetting, null); continue; + } - Type? targetType = getGenericBaseType(targetSetting, typeof(BindableNumber<>)); - Type? sourceType = getGenericBaseType(sourceSetting, typeof(BindableNumber<>)); + Type? targetBindableNumberType = getGenericBaseType(targetSetting, typeof(BindableNumber<>)); + Type? sourceBindableNumberType = getGenericBaseType(sourceSetting, typeof(BindableNumber<>)); - if (targetType == null || sourceType == null) + if (targetBindableNumberType == null || sourceBindableNumberType == null) { if (getGenericBaseType(targetSetting, typeof(Bindable<>))!.GenericTypeArguments.Single() == getGenericBaseType(sourceSetting, typeof(Bindable<>))!.GenericTypeArguments.Single()) @@ -202,8 +205,16 @@ namespace osu.Game.Rulesets.Mods continue; } - Type targetGenericType = targetType.GenericTypeArguments.Single(); - Type sourceGenericType = sourceType.GenericTypeArguments.Single(); + bool rangeOutOfBounds = false; + + Type targetGenericType = targetBindableNumberType.GenericTypeArguments.Single(); + Type sourceGenericType = sourceBindableNumberType.GenericTypeArguments.Single(); + + if (!Convert.ToBoolean(getValue(targetSetting, nameof(RangeConstrainedBindable.HasDefinedRange))) || + !Convert.ToBoolean(getValue(sourceSetting, nameof(RangeConstrainedBindable.HasDefinedRange)))) + // check if we have a range to rescale from and a range to rescale to + // if not, copy the raw value + rangeOutOfBounds = true; double allowedMin = Math.Max( Convert.ToDouble(targetGenericType.GetField("MinValue")!.GetValue(null)), @@ -219,25 +230,35 @@ namespace osu.Game.Rulesets.Mods double targetMax = getValueDouble(targetSetting, max_value); double sourceMin = getValueDouble(sourceSetting, min_value); double sourceMax = getValueDouble(sourceSetting, max_value); - double sourceValue = getValueDouble(sourceSetting, value); + double sourceValue = Math.Clamp(getValueDouble(sourceSetting, value), allowedMin, allowedMax); - // convert value to same ratio - double targetValue = (sourceValue - sourceMin) / (sourceMax - sourceMin) * (targetMax - targetMin) + targetMin; + double targetValue = rangeOutOfBounds + // keep raw value + ? sourceValue + // convert value to same ratio + : (sourceValue - sourceMin) / (sourceMax - sourceMin) * (targetMax - targetMin) + targetMin; - setValue(targetSetting, value, Convert.ChangeType(targetValue, targetType.GenericTypeArguments.Single())); + setValue(targetSetting, value, Convert.ChangeType(targetValue, targetBindableNumberType.GenericTypeArguments.Single())); - double getValueDouble(object target, string name) => - Math.Clamp(Convert.ToDouble(getValue(target, name)!), allowedMin, allowedMax); + double getValueDouble(object setting, string name) + { + double settingValue = Convert.ToDouble(getValue(setting, name)!); + + if (settingValue < allowedMin || settingValue > allowedMax) + rangeOutOfBounds = true; + + return settingValue; + } } - object? getValue(object target, string name) => - target.GetType().GetProperty(name)!.GetValue(target); + object? getValue(object setting, string name) => + setting.GetType().GetProperty(name)!.GetValue(setting); - void setValue(object target, string name, object? newValue) => - target.GetType().GetProperty(name)!.SetValue(target, newValue); + void setValue(object setting, string name, object? newValue) => + setting.GetType().GetProperty(name)!.SetValue(setting, newValue); - Type? getGenericBaseType(object target, Type genericType) => - target.GetType().GetTypeInheritance().FirstOrDefault(t => t.IsGenericType && t.GetGenericTypeDefinition() == genericType); + Type? getGenericBaseType(object setting, Type genericType) => + setting.GetType().GetTypeInheritance().FirstOrDefault(t => t.IsGenericType && t.GetGenericTypeDefinition() == genericType); } ///