diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs index 68d0038b24..b61c13a2df 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceAttributes.cs @@ -17,6 +17,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty [JsonProperty("accuracy")] public double Accuracy { get; set; } + [JsonProperty("effective_miss_count")] + public double EffectiveMissCount { get; set; } + public override IEnumerable GetAttributesForDisplay() { foreach (var attribute in base.GetAttributesForDisplay()) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index 2c2dbddf13..95a1e8bc66 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -21,6 +21,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty private int countMeh; private int countMiss; + private double effectiveMissCount; + public TaikoPerformanceCalculator() : base(new TaikoRuleset()) { @@ -35,7 +37,11 @@ namespace osu.Game.Rulesets.Taiko.Difficulty countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh); countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss); - double multiplier = 1.12; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things + // The effectiveMissCount is calculated by gaining a ratio for totalSuccessfulHits and increasing the miss penalty for shorter object counts lower than 1000. + if (totalSuccessfulHits > 0) + effectiveMissCount = Math.Max(1.0, 1000.0 / totalSuccessfulHits) * countMiss; + + double multiplier = 1.13; if (score.Mods.Any(m => m is ModHidden)) multiplier *= 1.075; @@ -55,6 +61,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty { Difficulty = difficultyValue, Accuracy = accuracyValue, + EffectiveMissCount = effectiveMissCount, Total = totalValue }; } @@ -66,18 +73,21 @@ namespace osu.Game.Rulesets.Taiko.Difficulty double lengthBonus = 1 + 0.1 * Math.Min(1.0, totalHits / 1500.0); difficultyValue *= lengthBonus; - difficultyValue *= Math.Pow(0.986, countMiss); + difficultyValue *= Math.Pow(0.986, effectiveMissCount); if (score.Mods.Any(m => m is ModEasy)) - difficultyValue *= 0.980; + difficultyValue *= 0.985; if (score.Mods.Any(m => m is ModHidden)) difficultyValue *= 1.025; - if (score.Mods.Any(m => m is ModFlashlight)) - difficultyValue *= 1.05 * lengthBonus; + if (score.Mods.Any(m => m is ModHardRock)) + difficultyValue *= 1.050; - return difficultyValue * Math.Pow(score.Accuracy, 1.5); + if (score.Mods.Any(m => m is ModFlashlight)) + difficultyValue *= 1.050 * lengthBonus; + + return difficultyValue * Math.Pow(score.Accuracy, 2.0); } private double computeAccuracyValue(ScoreInfo score, TaikoDifficultyAttributes attributes) @@ -85,18 +95,20 @@ namespace osu.Game.Rulesets.Taiko.Difficulty if (attributes.GreatHitWindow <= 0) return 0; - double accuracyValue = Math.Pow(140.0 / attributes.GreatHitWindow, 1.1) * Math.Pow(score.Accuracy, 12.0) * 27; + double accuracyValue = Math.Pow(60.0 / attributes.GreatHitWindow, 1.1) * Math.Pow(score.Accuracy, 8.0) * Math.Pow(attributes.StarRating, 0.4) * 27.0; double lengthBonus = Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3)); accuracyValue *= lengthBonus; - // Slight HDFL Bonus for accuracy. + // Slight HDFL Bonus for accuracy. A clamp is used to prevent against negative values if (score.Mods.Any(m => m is ModFlashlight) && score.Mods.Any(m => m is ModHidden)) - accuracyValue *= 1.10 * lengthBonus; + accuracyValue *= Math.Max(1.050, 1.075 * lengthBonus); return accuracyValue; } private int totalHits => countGreat + countOk + countMeh + countMiss; + + private int totalSuccessfulHits => countGreat + countOk + countMeh; } } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index bb9e83a21c..c3e485d56b 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -244,8 +244,12 @@ namespace osu.Game.Tests.Visual.SongSelect const int total_set_count = 200; - for (int i = 0; i < total_set_count; i++) - sets.Add(TestResources.CreateTestBeatmapSetInfo()); + AddStep("Populuate beatmap sets", () => + { + sets.Clear(); + for (int i = 0; i < total_set_count; i++) + sets.Add(TestResources.CreateTestBeatmapSetInfo()); + }); loadBeatmaps(sets); @@ -275,8 +279,12 @@ namespace osu.Game.Tests.Visual.SongSelect const int total_set_count = 20; - for (int i = 0; i < total_set_count; i++) - sets.Add(TestResources.CreateTestBeatmapSetInfo(3)); + AddStep("Populuate beatmap sets", () => + { + sets.Clear(); + for (int i = 0; i < total_set_count; i++) + sets.Add(TestResources.CreateTestBeatmapSetInfo(3)); + }); loadBeatmaps(sets); @@ -493,18 +501,23 @@ namespace osu.Game.Tests.Visual.SongSelect const string zzz_string = "zzzzz"; - for (int i = 0; i < 20; i++) + AddStep("Populuate beatmap sets", () => { - var set = TestResources.CreateTestBeatmapSetInfo(); + sets.Clear(); - if (i == 4) - set.Beatmaps.ForEach(b => b.Metadata.Artist = zzz_string); + for (int i = 0; i < 20; i++) + { + var set = TestResources.CreateTestBeatmapSetInfo(); - if (i == 16) - set.Beatmaps.ForEach(b => b.Metadata.Author.Username = zzz_string); + if (i == 4) + set.Beatmaps.ForEach(b => b.Metadata.Artist = zzz_string); - sets.Add(set); - } + if (i == 16) + set.Beatmaps.ForEach(b => b.Metadata.Author.Username = zzz_string); + + sets.Add(set); + } + }); loadBeatmaps(sets); @@ -521,21 +534,27 @@ namespace osu.Game.Tests.Visual.SongSelect public void TestSortingStability() { var sets = new List(); + int idOffset = 0; - for (int i = 0; i < 10; i++) + AddStep("Populuate beatmap sets", () => { - var set = TestResources.CreateTestBeatmapSetInfo(); + sets.Clear(); - // only need to set the first as they are a shared reference. - var beatmap = set.Beatmaps.First(); + for (int i = 0; i < 10; i++) + { + var set = TestResources.CreateTestBeatmapSetInfo(); - beatmap.Metadata.Artist = $"artist {i / 2}"; - beatmap.Metadata.Title = $"title {9 - i}"; + // only need to set the first as they are a shared reference. + var beatmap = set.Beatmaps.First(); - sets.Add(set); - } + beatmap.Metadata.Artist = $"artist {i / 2}"; + beatmap.Metadata.Title = $"title {9 - i}"; - int idOffset = sets.First().OnlineID; + sets.Add(set); + } + + idOffset = sets.First().OnlineID; + }); loadBeatmaps(sets); @@ -556,26 +575,32 @@ namespace osu.Game.Tests.Visual.SongSelect public void TestSortingStabilityWithNewItems() { List sets = new List(); + int idOffset = 0; - for (int i = 0; i < 3; i++) + AddStep("Populuate beatmap sets", () => { - var set = TestResources.CreateTestBeatmapSetInfo(3); + sets.Clear(); - // only need to set the first as they are a shared reference. - var beatmap = set.Beatmaps.First(); + for (int i = 0; i < 3; i++) + { + var set = TestResources.CreateTestBeatmapSetInfo(3); - beatmap.Metadata.Artist = "same artist"; - beatmap.Metadata.Title = "same title"; + // only need to set the first as they are a shared reference. + var beatmap = set.Beatmaps.First(); - sets.Add(set); - } + beatmap.Metadata.Artist = "same artist"; + beatmap.Metadata.Title = "same title"; - int idOffset = sets.First().OnlineID; + sets.Add(set); + } + + idOffset = sets.First().OnlineID; + }); loadBeatmaps(sets); AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false)); - AddAssert("Items remain in original order", () => carousel.BeatmapSets.Select((set, index) => set.OnlineID == idOffset + index).All(b => b)); + assertOriginalOrderMaintained(); AddStep("Add new item", () => { @@ -590,10 +615,16 @@ namespace osu.Game.Tests.Visual.SongSelect carousel.UpdateBeatmapSet(set); }); - AddAssert("Items remain in original order", () => carousel.BeatmapSets.Select((set, index) => set.OnlineID == idOffset + index).All(b => b)); + assertOriginalOrderMaintained(); AddStep("Sort by title", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Title }, false)); - AddAssert("Items remain in original order", () => carousel.BeatmapSets.Select((set, index) => set.OnlineID == idOffset + index).All(b => b)); + assertOriginalOrderMaintained(); + + void assertOriginalOrderMaintained() + { + AddAssert("Items remain in original order", + () => carousel.BeatmapSets.Select(s => s.OnlineID), () => Is.EqualTo(carousel.BeatmapSets.Select((set, index) => idOffset + index))); + } } [Test] @@ -601,13 +632,18 @@ namespace osu.Game.Tests.Visual.SongSelect { List sets = new List(); - for (int i = 0; i < 3; i++) + AddStep("Populuate beatmap sets", () => { - var set = TestResources.CreateTestBeatmapSetInfo(3); - set.Beatmaps[0].StarRating = 3 - i; - set.Beatmaps[2].StarRating = 6 + i; - sets.Add(set); - } + sets.Clear(); + + for (int i = 0; i < 3; i++) + { + var set = TestResources.CreateTestBeatmapSetInfo(3); + set.Beatmaps[0].StarRating = 3 - i; + set.Beatmaps[2].StarRating = 6 + i; + sets.Add(set); + } + }); loadBeatmaps(sets); @@ -759,8 +795,13 @@ namespace osu.Game.Tests.Visual.SongSelect { List manySets = new List(); - for (int i = 1; i <= 50; i++) - manySets.Add(TestResources.CreateTestBeatmapSetInfo(3)); + AddStep("Populuate beatmap sets", () => + { + manySets.Clear(); + + for (int i = 1; i <= 50; i++) + manySets.Add(TestResources.CreateTestBeatmapSetInfo(3)); + }); loadBeatmaps(manySets); @@ -791,6 +832,8 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("populate maps", () => { + manySets.Clear(); + for (int i = 0; i < 10; i++) { manySets.Add(TestResources.CreateTestBeatmapSetInfo(3, new[]