Skip to content

Commit

Permalink
Merge pull request #110 from Syriiin/fix/lazer-acc
Browse files Browse the repository at this point in the history
Add support for slider ticks and tails for lazer accuracy
  • Loading branch information
Syriiin authored Nov 23, 2024
2 parents a98cec7 + 612d954 commit f304515
Show file tree
Hide file tree
Showing 8 changed files with 244 additions and 78 deletions.
36 changes: 20 additions & 16 deletions Difficalcy.Catch/Services/CatchCalculatorService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -98,7 +115,7 @@ object difficultyAttributes
+ beatmap
.HitObjects.OfType<JuiceStream>()
.SelectMany(j => j.NestedHitObjects)
.Count(h => !(h is TinyDroplet));
.Count(h => h is not TinyDroplet);
var statistics = GetHitResults(
beatmap,
score.Misses,
Expand All @@ -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)
Expand Down
33 changes: 19 additions & 14 deletions Difficalcy.Mania/Services/ManiaCalculatorService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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)
Expand Down
31 changes: 25 additions & 6 deletions Difficalcy.Osu.Tests/OsuCalculatorServiceTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -60,18 +60,37 @@ public void TestAllParameters()
Misses = 5,
Mehs = 4,
Oks = 3,
SliderTails = 2,
SliderTicks = 1,
};
TestGetCalculationReturnsCorrectValues(12.418442356371395, 1415.202990027042, score);
TestGetCalculationReturnsCorrectValues(12.418442356371395, 1208.1384749524739, 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<string, string> { { "speed_change", "2" } },
},
new Mod() { Acronym = "FL" },
new Mod() { Acronym = "CL" },
],
Combo = 200,
Misses = 5,
Mehs = 4,
Oks = 3,
SliderTails = 2,
SliderTicks = 1,
};
TestGetCalculationReturnsCorrectValues(6.7171144000821119d, 289.16416504218972, score);
TestGetCalculationReturnsCorrectValues(12.418442356371395, 1405.0910286547635, score);
}
}
6 changes: 6 additions & 0 deletions Difficalcy.Osu/Models/OsuScore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ValidationResult> Validate(ValidationContext validationContext)
{
if (Misses > 0 && Combo is null)
Expand Down
146 changes: 120 additions & 26 deletions Difficalcy.Osu/Services/OsuCalculatorService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
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;
Expand Down Expand Up @@ -97,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);
Expand All @@ -105,33 +124,48 @@ object difficultyAttributes
score.Combo
?? beatmap.HitObjects.Count
+ beatmap.HitObjects.OfType<Slider>().Sum(s => s.NestedHitObjects.Count - 1);
var statistics = GetHitResults(
beatmap.HitObjects.Count,
score.Misses,
score.Mehs,
score.Oks
);
var accuracy = CalculateAccuracy(statistics);

var scoreInfo = new ScoreInfo(beatmap.BeatmapInfo, OsuRuleset.RulesetInfo)
Dictionary<HitResult, int> statistics;
double accuracy;

if (mods.OfType<OsuModClassic>().Any(m => m.NoSliderHeadAccuracy.Value))
{
Accuracy = accuracy,
MaxCombo = combo,
Statistics = statistics,
Mods = mods,
};
statistics = GetClassicHitResults(
beatmap.HitObjects.Count,
score.Misses,
score.Mehs,
score.Oks
);
accuracy = CalculateClassicAccuracy(statistics);
}
else
{
var maxSliderTails = beatmap.HitObjects.OfType<Slider>().Count();

var performanceCalculator = OsuRuleset.CreatePerformanceCalculator();
var performanceAttributes =
performanceCalculator.Calculate(scoreInfo, osuDifficultyAttributes)
as OsuPerformanceAttributes;
var maxSliderTicks = beatmap
.HitObjects.OfType<Slider>()
.Sum(s => s.NestedHitObjects.Count(x => x is SliderTick or SliderRepeat));

return new OsuCalculation()
statistics = GetLazerHitResults(
beatmap.HitObjects.Count,
maxSliderTails,
maxSliderTicks,
score.Misses,
score.Mehs,
score.Oks,
score.SliderTails,
score.SliderTicks
);

accuracy = CalculateLazerAccuracy(statistics, maxSliderTails, maxSliderTicks);
}

return new ScoreInfo(beatmap.BeatmapInfo, OsuRuleset.RulesetInfo)
{
Difficulty = GetDifficultyFromDifficultyAttributes(osuDifficultyAttributes),
Performance = GetPerformanceFromPerformanceAttributes(performanceAttributes),
Accuracy = accuracy,
Combo = combo,
MaxCombo = combo,
Statistics = statistics,
Mods = mods,
};
}

Expand All @@ -150,7 +184,7 @@ private LazerMod ModToLazerMod(Mod mod)
return apiMod.ToMod(OsuRuleset);
}

private static Dictionary<HitResult, int> GetHitResults(
private static Dictionary<HitResult, int> GetClassicHitResults(
int hitResultCount,
int countMiss,
int countMeh,
Expand All @@ -168,18 +202,78 @@ int countOk
};
}

private static double CalculateAccuracy(Dictionary<HitResult, int> statistics)
private static Dictionary<HitResult, int> 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, int>
{
{ 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<HitResult, int> 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<HitResult, int> 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(
Expand Down
Loading

0 comments on commit f304515

Please sign in to comment.