diff --git a/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs b/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs index da457c9e8f..dcfeea5db8 100644 --- a/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs +++ b/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs @@ -58,6 +58,12 @@ namespace osu.Game.Tests.Beatmaps { OsuModDoubleTime dt = null; + AddStep("set computation function", () => difficultyCache.ComputeDifficulty = lookup => + { + var modRateAdjust = (ModRateAdjust)lookup.OrderedMods.SingleOrDefault(mod => mod is ModRateAdjust); + return new StarDifficulty(BASE_STARS + modRateAdjust?.SpeedChange.Value ?? 0, 0); + }); + AddStep("change selected mod to DT", () => SelectedMods.Value = new[] { dt = new OsuModDoubleTime { SpeedChange = { Value = 1.5 } } }); AddUntilStep($"star difficulty -> {BASE_STARS + 1.5}", () => starDifficultyBindable.Value?.Stars == BASE_STARS + 1.5); @@ -68,6 +74,29 @@ namespace osu.Game.Tests.Beatmaps AddUntilStep($"star difficulty -> {BASE_STARS + 1.75}", () => starDifficultyBindable.Value?.Stars == BASE_STARS + 1.75); } + [Test] + public void TestStarDifficultyAdjustHashCodeConflict() + { + OsuModDifficultyAdjust difficultyAdjust = null; + + AddStep("set computation function", () => difficultyCache.ComputeDifficulty = lookup => + { + var modDifficultyAdjust = (ModDifficultyAdjust)lookup.OrderedMods.SingleOrDefault(mod => mod is ModDifficultyAdjust); + return new StarDifficulty(BASE_STARS * (modDifficultyAdjust?.OverallDifficulty.Value ?? 1), 0); + }); + + AddStep("change selected mod to DA", () => SelectedMods.Value = new[] { difficultyAdjust = new OsuModDifficultyAdjust() }); + AddUntilStep($"star difficulty -> {BASE_STARS}", () => starDifficultyBindable.Value?.Stars == BASE_STARS); + + AddStep("change DA difficulty to 0.5", () => difficultyAdjust.OverallDifficulty.Value = 0.5f); + AddUntilStep($"star difficulty -> {BASE_STARS * 0.5f}", () => starDifficultyBindable.Value?.Stars == BASE_STARS / 2); + + // hash code of 0 (the value) conflicts with the hash code of null (the initial/default value). + // it's important that the mod reference and its underlying bindable references stay the same to demonstrate this failure. + AddStep("change DA difficulty to 0", () => difficultyAdjust.OverallDifficulty.Value = 0); + AddUntilStep("star difficulty -> 0", () => starDifficultyBindable.Value?.Stars == 0); + } + [Test] public void TestKeyEqualsWithDifferentModInstances() { @@ -133,13 +162,11 @@ namespace osu.Game.Tests.Beatmaps private class TestBeatmapDifficultyCache : BeatmapDifficultyCache { + public Func ComputeDifficulty { get; set; } + protected override Task ComputeValueAsync(DifficultyCacheLookup lookup, CancellationToken token = default) { - var rateAdjust = lookup.OrderedMods.OfType().SingleOrDefault(); - if (rateAdjust != null) - return Task.FromResult(new StarDifficulty(BASE_STARS + rateAdjust.SpeedChange.Value, 0)); - - return Task.FromResult(new StarDifficulty(BASE_STARS, 0)); + return Task.FromResult(ComputeDifficulty?.Invoke(lookup) ?? new StarDifficulty(BASE_STARS, 0)); } } } diff --git a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs index 5025e4ad4b..0aa6a6dd0b 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs @@ -310,7 +310,7 @@ namespace osu.Game.Beatmaps Beatmap = beatmap; // In the case that the user hasn't given us a ruleset, use the beatmap's default ruleset. Ruleset = ruleset ?? Beatmap.Ruleset; - OrderedMods = mods?.OrderBy(m => m.Acronym).ToArray() ?? Array.Empty(); + OrderedMods = mods?.OrderBy(m => m.Acronym).Select(mod => mod.DeepClone()).ToArray() ?? Array.Empty(); } public bool Equals(DifficultyCacheLookup other)