From a838a08dd46307cb82c6dfe79895bd2d7d494e74 Mon Sep 17 00:00:00 2001 From: Samuel Cattini-Schultz Date: Sat, 23 Nov 2024 13:41:20 +1100 Subject: [PATCH 1/6] Correct test values --- .../OsuCalculatorServiceTest.cs | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/Difficalcy.Osu.Tests/OsuCalculatorServiceTest.cs b/Difficalcy.Osu.Tests/OsuCalculatorServiceTest.cs index 7327400..fd59b79 100644 --- a/Difficalcy.Osu.Tests/OsuCalculatorServiceTest.cs +++ b/Difficalcy.Osu.Tests/OsuCalculatorServiceTest.cs @@ -21,8 +21,8 @@ protected override CalculatorService< ); [Theory] - [InlineData(6.7171144000821119d, 291.34799376682508d, "diffcalc-test", new string[] { })] - [InlineData(8.9825709931204205d, 717.13844713272601d, "diffcalc-test", new string[] { "DT" })] + [InlineData(6.7171144000821119d, 291.6916492167043, "diffcalc-test", new string[] { })] + [InlineData(8.9825709931204205d, 718.5552449511403, "diffcalc-test", new string[] { "DT" })] public void Test( double expectedDifficultyTotal, double expectedPerformanceTotal, @@ -61,17 +61,32 @@ public void TestAllParameters() Mehs = 4, Oks = 3, }; - TestGetCalculationReturnsCorrectValues(12.418442356371395, 1415.202990027042, score); + TestGetCalculationReturnsCorrectValues(12.418442356371395, 1441.8141993457837, score); } [Fact] - public void TestClassicMod() + public void TestAllParametersClassicMod() { var score = new OsuScore { BeatmapId = "diffcalc-test", - Mods = [new Mod() { Acronym = "CL" }], + Mods = + [ + new Mod() { Acronym = "HD" }, + new Mod() { Acronym = "HR" }, + new Mod() + { + Acronym = "DT", + Settings = new Dictionary { { "speed_change", "2" } }, + }, + new Mod() { Acronym = "FL" }, + new Mod() { Acronym = "CL" }, + ], + Combo = 200, + Misses = 5, + Mehs = 4, + Oks = 3, }; - TestGetCalculationReturnsCorrectValues(6.7171144000821119d, 289.16416504218972, score); + TestGetCalculationReturnsCorrectValues(12.418442356371395, 1405.0910286547635, score); } } From 46512c095ebc6e22c3bfbfc221de9d3fad4f8968 Mon Sep 17 00:00:00 2001 From: Samuel Cattini-Schultz Date: Sat, 23 Nov 2024 14:31:14 +1100 Subject: [PATCH 2/6] Change docs port to 9000 --- compose.override.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compose.override.yaml b/compose.override.yaml index 6105afb..1d1b3ca 100644 --- a/compose.override.yaml +++ b/compose.override.yaml @@ -25,4 +25,4 @@ services: docs: ports: - - 8000:8000 + - 9000:8000 From 28896329481e7bb939c2b3072bdb88992e8801c1 Mon Sep 17 00:00:00 2001 From: Samuel Cattini-Schultz Date: Sat, 23 Nov 2024 14:31:17 +1100 Subject: [PATCH 3/6] Implement lazer acc SliderTails and SliderTicks --- Difficalcy.Osu/Models/OsuScore.cs | 6 + .../Services/OsuCalculatorService.cs | 114 ++++++++++++++++-- 2 files changed, 108 insertions(+), 12 deletions(-) diff --git a/Difficalcy.Osu/Models/OsuScore.cs b/Difficalcy.Osu/Models/OsuScore.cs index fd694d5..93749a3 100644 --- a/Difficalcy.Osu/Models/OsuScore.cs +++ b/Difficalcy.Osu/Models/OsuScore.cs @@ -18,6 +18,12 @@ public record OsuScore : Score, IValidatableObject [Range(0, int.MaxValue)] public int Oks { get; init; } = 0; + [Range(0, int.MaxValue)] + public int? SliderTails { get; init; } + + [Range(0, int.MaxValue)] + public int? SliderTicks { get; init; } // LargeTickHits + public IEnumerable Validate(ValidationContext validationContext) { if (Misses > 0 && Combo is null) diff --git a/Difficalcy.Osu/Services/OsuCalculatorService.cs b/Difficalcy.Osu/Services/OsuCalculatorService.cs index 01baf60..474376d 100644 --- a/Difficalcy.Osu/Services/OsuCalculatorService.cs +++ b/Difficalcy.Osu/Services/OsuCalculatorService.cs @@ -6,9 +6,11 @@ using Difficalcy.Models; using Difficalcy.Osu.Models; using Difficalcy.Services; +using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Difficulty; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; @@ -105,13 +107,41 @@ object difficultyAttributes score.Combo ?? beatmap.HitObjects.Count + beatmap.HitObjects.OfType().Sum(s => s.NestedHitObjects.Count - 1); - var statistics = GetHitResults( - beatmap.HitObjects.Count, - score.Misses, - score.Mehs, - score.Oks - ); - var accuracy = CalculateAccuracy(statistics); + + Dictionary statistics; + double accuracy; + + if (mods.OfType().Any(m => m.NoSliderHeadAccuracy.Value)) + { + statistics = GetClassicHitResults( + beatmap.HitObjects.Count, + score.Misses, + score.Mehs, + score.Oks + ); + accuracy = CalculateClassicAccuracy(statistics); + } + else + { + var maxSliderTails = beatmap.HitObjects.OfType().Count(); + + var maxSliderTicks = beatmap + .HitObjects.OfType() + .Sum(s => s.NestedHitObjects.Count(x => x is SliderTick or SliderRepeat)); + + statistics = GetLazerHitResults( + beatmap.HitObjects.Count, + maxSliderTails, + maxSliderTicks, + score.Misses, + score.Mehs, + score.Oks, + score.SliderTails, + score.SliderTicks + ); + + accuracy = CalculateLazerAccuracy(statistics, maxSliderTails, maxSliderTicks); + } var scoreInfo = new ScoreInfo(beatmap.BeatmapInfo, OsuRuleset.RulesetInfo) { @@ -150,7 +180,7 @@ private LazerMod ModToLazerMod(Mod mod) return apiMod.ToMod(OsuRuleset); } - private static Dictionary GetHitResults( + private static Dictionary GetClassicHitResults( int hitResultCount, int countMiss, int countMeh, @@ -168,18 +198,78 @@ int countOk }; } - private static double CalculateAccuracy(Dictionary statistics) + private static Dictionary GetLazerHitResults( + int hitResultCount, + int sliderTailCount, + int sliderTickCount, + int countMiss, + int countMeh, + int countOk, + int? countSliderTails, + int? countSliderTicks + ) + { + var countGreat = hitResultCount - countOk - countMeh - countMiss; + + return new Dictionary + { + { HitResult.Great, countGreat }, + { HitResult.Ok, countOk }, + { HitResult.Meh, countMeh }, + { HitResult.Miss, countMiss }, + { HitResult.SliderTailHit, countSliderTails ?? sliderTailCount }, + { HitResult.LargeTickHit, countSliderTicks ?? sliderTickCount }, + { + HitResult.LargeTickMiss, + sliderTickCount - (countSliderTicks ?? sliderTickCount) + }, + }; + } + + private static double CalculateClassicAccuracy(Dictionary statistics) + { + var countGreat = statistics[HitResult.Great]; + var countOk = statistics[HitResult.Ok]; + var countMeh = statistics[HitResult.Meh]; + var countMiss = statistics[HitResult.Miss]; + var countObjects = countGreat + countOk + countMeh + countMiss; + + if (countObjects == 0) + return 1; + + var max = countObjects * 6; + var total = (countGreat * 6) + (countOk * 2) + countMeh; + + return (double)total / max; + } + + private static double CalculateLazerAccuracy( + Dictionary statistics, + int sliderTailCount, + int sliderTickCount + ) { var countGreat = statistics[HitResult.Great]; var countOk = statistics[HitResult.Ok]; var countMeh = statistics[HitResult.Meh]; var countMiss = statistics[HitResult.Miss]; - var total = countGreat + countOk + countMeh + countMiss; + var countSliderTails = statistics[HitResult.SliderTailHit]; + var countSliderTicks = statistics[HitResult.LargeTickHit]; - if (total == 0) + var countObject = countGreat + countOk + countMeh + countMiss; + + if (countObject == 0) return 1; - return (double)((6 * countGreat) + (2 * countOk) + countMeh) / (6 * total); + var max = (countObject * 6) + (sliderTailCount * 3) + (sliderTickCount * 0.6); + var total = + (countGreat * 6) + + (countOk * 2) + + countMeh + + (countSliderTails * 3) + + (countSliderTicks * 0.6); + + return (double)total / max; } private static OsuDifficulty GetDifficultyFromDifficultyAttributes( From 0f12054d3b55874da178a6933cb793e0407559e7 Mon Sep 17 00:00:00 2001 From: Samuel Cattini-Schultz Date: Sat, 23 Nov 2024 14:47:51 +1100 Subject: [PATCH 4/6] Update all params tests for slider ticks and tails --- Difficalcy.Osu.Tests/OsuCalculatorServiceTest.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Difficalcy.Osu.Tests/OsuCalculatorServiceTest.cs b/Difficalcy.Osu.Tests/OsuCalculatorServiceTest.cs index fd59b79..8c34d6e 100644 --- a/Difficalcy.Osu.Tests/OsuCalculatorServiceTest.cs +++ b/Difficalcy.Osu.Tests/OsuCalculatorServiceTest.cs @@ -60,8 +60,10 @@ public void TestAllParameters() Misses = 5, Mehs = 4, Oks = 3, + SliderTails = 2, + SliderTicks = 1, }; - TestGetCalculationReturnsCorrectValues(12.418442356371395, 1441.8141993457837, score); + TestGetCalculationReturnsCorrectValues(12.418442356371395, 1208.1384749524739, score); } [Fact] @@ -86,6 +88,8 @@ public void TestAllParametersClassicMod() Misses = 5, Mehs = 4, Oks = 3, + SliderTails = 2, + SliderTicks = 1, }; TestGetCalculationReturnsCorrectValues(12.418442356371395, 1405.0910286547635, score); } From 28a586ce9e68afdbd0a2dbe20957d5a7b4756d88 Mon Sep 17 00:00:00 2001 From: Samuel Cattini-Schultz Date: Sat, 23 Nov 2024 14:58:06 +1100 Subject: [PATCH 5/6] Refactor to clean up calculation services a tad --- .../Services/CatchCalculatorService.cs | 36 ++++++++++--------- .../Services/ManiaCalculatorService.cs | 33 +++++++++-------- .../Services/OsuCalculatorService.cs | 34 ++++++++++-------- .../Services/TaikoCalculatorService.cs | 34 ++++++++++-------- 4 files changed, 77 insertions(+), 60 deletions(-) diff --git a/Difficalcy.Catch/Services/CatchCalculatorService.cs b/Difficalcy.Catch/Services/CatchCalculatorService.cs index 0bb008c..2b11f50 100644 --- a/Difficalcy.Catch/Services/CatchCalculatorService.cs +++ b/Difficalcy.Catch/Services/CatchCalculatorService.cs @@ -8,7 +8,6 @@ using Difficalcy.Models; using Difficalcy.Services; using osu.Game.Beatmaps; -using osu.Game.Beatmaps.Legacy; using osu.Game.Online.API; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Catch.Difficulty; @@ -88,6 +87,24 @@ object difficultyAttributes { var catchDifficultyAttributes = (CatchDifficultyAttributes)difficultyAttributes; + var scoreInfo = GetScoreInfo(score); + + var performanceCalculator = CatchRuleset.CreatePerformanceCalculator(); + var performanceAttributes = + performanceCalculator.Calculate(scoreInfo, catchDifficultyAttributes) + as CatchPerformanceAttributes; + + return new CatchCalculation() + { + Difficulty = GetDifficultyFromDifficultyAttributes(catchDifficultyAttributes), + Performance = GetPerformanceFromPerformanceAttributes(performanceAttributes), + Accuracy = scoreInfo.Accuracy, + Combo = scoreInfo.MaxCombo, + }; + } + + private ScoreInfo GetScoreInfo(CatchScore score) + { var workingBeatmap = GetWorkingBeatmap(score.BeatmapId); var mods = score.Mods.Select(ModToLazerMod).ToArray(); var beatmap = workingBeatmap.GetPlayableBeatmap(CatchRuleset.RulesetInfo, mods); @@ -98,7 +115,7 @@ object difficultyAttributes + beatmap .HitObjects.OfType() .SelectMany(j => j.NestedHitObjects) - .Count(h => !(h is TinyDroplet)); + .Count(h => h is not TinyDroplet); var statistics = GetHitResults( beatmap, score.Misses, @@ -107,26 +124,13 @@ object difficultyAttributes ); var accuracy = CalculateAccuracy(statistics); - var scoreInfo = new ScoreInfo(beatmap.BeatmapInfo, CatchRuleset.RulesetInfo) + return new ScoreInfo(beatmap.BeatmapInfo, CatchRuleset.RulesetInfo) { Accuracy = accuracy, MaxCombo = combo, Statistics = statistics, Mods = mods, }; - - var performanceCalculator = CatchRuleset.CreatePerformanceCalculator(); - var performanceAttributes = - performanceCalculator.Calculate(scoreInfo, catchDifficultyAttributes) - as CatchPerformanceAttributes; - - return new CatchCalculation() - { - Difficulty = GetDifficultyFromDifficultyAttributes(catchDifficultyAttributes), - Performance = GetPerformanceFromPerformanceAttributes(performanceAttributes), - Accuracy = accuracy, - Combo = combo, - }; } private CalculatorWorkingBeatmap GetWorkingBeatmap(string beatmapId) diff --git a/Difficalcy.Mania/Services/ManiaCalculatorService.cs b/Difficalcy.Mania/Services/ManiaCalculatorService.cs index 0a30b93..956276c 100644 --- a/Difficalcy.Mania/Services/ManiaCalculatorService.cs +++ b/Difficalcy.Mania/Services/ManiaCalculatorService.cs @@ -7,7 +7,6 @@ using Difficalcy.Mania.Models; using Difficalcy.Models; using Difficalcy.Services; -using osu.Game.Beatmaps.Legacy; using osu.Game.Online.API; using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Mania.Difficulty; @@ -87,6 +86,24 @@ object difficultyAttributes ) { var maniaDifficultyAttributes = (ManiaDifficultyAttributes)difficultyAttributes; + + var scoreInfo = GetScoreInfo(score); + + var performanceCalculator = ManiaRuleset.CreatePerformanceCalculator(); + var performanceAttributes = + performanceCalculator.Calculate(scoreInfo, maniaDifficultyAttributes) + as ManiaPerformanceAttributes; + + return new ManiaCalculation() + { + Difficulty = GetDifficultyFromDifficultyAttributes(maniaDifficultyAttributes), + Performance = GetPerformanceFromPerformanceAttributes(performanceAttributes), + Accuracy = scoreInfo.Accuracy, + }; + } + + private ScoreInfo GetScoreInfo(ManiaScore score) + { var workingBeatmap = GetWorkingBeatmap(score.BeatmapId); var mods = score.Mods.Select(ModToLazerMod).ToArray(); var beatmap = workingBeatmap.GetPlayableBeatmap(ManiaRuleset.RulesetInfo, mods); @@ -103,25 +120,13 @@ object difficultyAttributes ); var accuracy = CalculateAccuracy(statistics); - var scoreInfo = new ScoreInfo(beatmap.BeatmapInfo, ManiaRuleset.RulesetInfo) + return new ScoreInfo(beatmap.BeatmapInfo, ManiaRuleset.RulesetInfo) { Accuracy = accuracy, MaxCombo = 0, Statistics = statistics, Mods = mods, }; - - var performanceCalculator = ManiaRuleset.CreatePerformanceCalculator(); - var performanceAttributes = - performanceCalculator.Calculate(scoreInfo, maniaDifficultyAttributes) - as ManiaPerformanceAttributes; - - return new ManiaCalculation() - { - Difficulty = GetDifficultyFromDifficultyAttributes(maniaDifficultyAttributes), - Performance = GetPerformanceFromPerformanceAttributes(performanceAttributes), - Accuracy = accuracy, - }; } private CalculatorWorkingBeatmap GetWorkingBeatmap(string beatmapId) diff --git a/Difficalcy.Osu/Services/OsuCalculatorService.cs b/Difficalcy.Osu/Services/OsuCalculatorService.cs index 474376d..39fde0a 100644 --- a/Difficalcy.Osu/Services/OsuCalculatorService.cs +++ b/Difficalcy.Osu/Services/OsuCalculatorService.cs @@ -6,7 +6,6 @@ using Difficalcy.Models; using Difficalcy.Osu.Models; using Difficalcy.Services; -using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Difficulty; @@ -99,6 +98,24 @@ object difficultyAttributes { var osuDifficultyAttributes = (OsuDifficultyAttributes)difficultyAttributes; + var scoreInfo = GetScoreInfo(score); + + var performanceCalculator = OsuRuleset.CreatePerformanceCalculator(); + var performanceAttributes = + performanceCalculator.Calculate(scoreInfo, osuDifficultyAttributes) + as OsuPerformanceAttributes; + + return new OsuCalculation() + { + Difficulty = GetDifficultyFromDifficultyAttributes(osuDifficultyAttributes), + Performance = GetPerformanceFromPerformanceAttributes(performanceAttributes), + Accuracy = scoreInfo.Accuracy, + Combo = scoreInfo.MaxCombo, + }; + } + + private ScoreInfo GetScoreInfo(OsuScore score) + { var workingBeatmap = GetWorkingBeatmap(score.BeatmapId); var mods = score.Mods.Select(ModToLazerMod).ToArray(); var beatmap = workingBeatmap.GetPlayableBeatmap(OsuRuleset.RulesetInfo, mods); @@ -143,26 +160,13 @@ object difficultyAttributes accuracy = CalculateLazerAccuracy(statistics, maxSliderTails, maxSliderTicks); } - var scoreInfo = new ScoreInfo(beatmap.BeatmapInfo, OsuRuleset.RulesetInfo) + return new ScoreInfo(beatmap.BeatmapInfo, OsuRuleset.RulesetInfo) { Accuracy = accuracy, MaxCombo = combo, Statistics = statistics, Mods = mods, }; - - var performanceCalculator = OsuRuleset.CreatePerformanceCalculator(); - var performanceAttributes = - performanceCalculator.Calculate(scoreInfo, osuDifficultyAttributes) - as OsuPerformanceAttributes; - - return new OsuCalculation() - { - Difficulty = GetDifficultyFromDifficultyAttributes(osuDifficultyAttributes), - Performance = GetPerformanceFromPerformanceAttributes(performanceAttributes), - Accuracy = accuracy, - Combo = combo, - }; } private CalculatorWorkingBeatmap GetWorkingBeatmap(string beatmapId) diff --git a/Difficalcy.Taiko/Services/TaikoCalculatorService.cs b/Difficalcy.Taiko/Services/TaikoCalculatorService.cs index dd9959e..6d5f20f 100644 --- a/Difficalcy.Taiko/Services/TaikoCalculatorService.cs +++ b/Difficalcy.Taiko/Services/TaikoCalculatorService.cs @@ -7,7 +7,6 @@ using Difficalcy.Models; using Difficalcy.Services; using Difficalcy.Taiko.Models; -using osu.Game.Beatmaps.Legacy; using osu.Game.Online.API; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko; @@ -94,6 +93,24 @@ object difficultyAttributes { var taikoDifficultyAttributes = (TaikoDifficultyAttributes)difficultyAttributes; + var scoreInfo = GetScoreInfo(score); + + var performanceCalculator = TaikoRuleset.CreatePerformanceCalculator(); + var performanceAttributes = + performanceCalculator.Calculate(scoreInfo, taikoDifficultyAttributes) + as TaikoPerformanceAttributes; + + return new TaikoCalculation() + { + Difficulty = GetDifficultyFromDifficultyAttributes(taikoDifficultyAttributes), + Performance = GetPerformanceFromPerformanceAttributes(performanceAttributes), + Accuracy = scoreInfo.Accuracy, + Combo = scoreInfo.MaxCombo, + }; + } + + private ScoreInfo GetScoreInfo(TaikoScore score) + { var workingBeatmap = GetWorkingBeatmap(score.BeatmapId); var mods = score.Mods.Select(ModToLazerMod).ToArray(); var beatmap = workingBeatmap.GetPlayableBeatmap(TaikoRuleset.RulesetInfo, mods); @@ -103,26 +120,13 @@ object difficultyAttributes var statistics = GetHitResults(hitResultCount, score.Misses, score.Oks); var accuracy = CalculateAccuracy(statistics); - var scoreInfo = new ScoreInfo(beatmap.BeatmapInfo, TaikoRuleset.RulesetInfo) + return new ScoreInfo(beatmap.BeatmapInfo, TaikoRuleset.RulesetInfo) { Accuracy = accuracy, MaxCombo = combo, Statistics = statistics, Mods = mods, }; - - var performanceCalculator = TaikoRuleset.CreatePerformanceCalculator(); - var performanceAttributes = - performanceCalculator.Calculate(scoreInfo, taikoDifficultyAttributes) - as TaikoPerformanceAttributes; - - return new TaikoCalculation() - { - Difficulty = GetDifficultyFromDifficultyAttributes(taikoDifficultyAttributes), - Performance = GetPerformanceFromPerformanceAttributes(performanceAttributes), - Accuracy = accuracy, - Combo = combo, - }; } private CalculatorWorkingBeatmap GetWorkingBeatmap(string beatmapId) From 612d9549a2deec757fca78b85a62f7368f363d1a Mon Sep 17 00:00:00 2001 From: Samuel Cattini-Schultz Date: Sat, 23 Nov 2024 15:02:12 +1100 Subject: [PATCH 6/6] Update api reference --- docs/docs/api-reference/difficalcy-osu.json | 34 +++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/docs/docs/api-reference/difficalcy-osu.json b/docs/docs/api-reference/difficalcy-osu.json index 6f1daef..abdda3c 100644 --- a/docs/docs/api-reference/difficalcy-osu.json +++ b/docs/docs/api-reference/difficalcy-osu.json @@ -70,6 +70,26 @@ "format": "int32" } }, + { + "name": "SliderTails", + "in": "query", + "schema": { + "maximum": 2147483647, + "minimum": 0, + "type": "integer", + "format": "int32" + } + }, + { + "name": "SliderTicks", + "in": "query", + "schema": { + "maximum": 2147483647, + "minimum": 0, + "type": "integer", + "format": "int32" + } + }, { "name": "BeatmapId", "in": "query", @@ -296,6 +316,20 @@ "minimum": 0, "type": "integer", "format": "int32" + }, + "sliderTails": { + "maximum": 2147483647, + "minimum": 0, + "type": "integer", + "format": "int32", + "nullable": true + }, + "sliderTicks": { + "maximum": 2147483647, + "minimum": 0, + "type": "integer", + "format": "int32", + "nullable": true } }, "additionalProperties": false