-
Notifications
You must be signed in to change notification settings - Fork 91
Fix non-CL acc generation #275
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -50,32 +50,63 @@ protected override Dictionary<HitResult, int> GenerateHitResults(IBeatmap beatma | |
// Use lazer info only if score has sliderhead accuracy | ||
if (mods.OfType<OsuModClassic>().Any(m => m.NoSliderHeadAccuracy.Value)) | ||
{ | ||
return generateHitResults(beatmap, Accuracy / 100, Misses, Mehs, Goods, null, null); | ||
return generateHitResults(beatmap, Accuracy / 100, mods, Misses, Mehs, Goods, null, null); | ||
} | ||
else | ||
{ | ||
return generateHitResults(beatmap, Accuracy / 100, Misses, Mehs, Goods, largeTickMisses, sliderTailMisses); | ||
return generateHitResults(beatmap, Accuracy / 100, mods, Misses, Mehs, Goods, largeTickMisses, sliderTailMisses); | ||
} | ||
} | ||
|
||
private static Dictionary<HitResult, int> generateHitResults(IBeatmap beatmap, double accuracy, int countMiss, int? countMeh, int? countGood, int? countLargeTickMisses, int? countSliderTailMisses) | ||
private static Dictionary<HitResult, int> generateHitResults(IBeatmap beatmap, double accuracy, Mod[] mods, int countMiss, int? countMeh, int? countGood, int? countLargeTickMisses, int? countSliderTailMisses) | ||
{ | ||
bool usingClassicSliderAccuracy = mods.OfType<OsuModClassic>().Any(m => m.NoSliderHeadAccuracy.Value); | ||
|
||
int countGreat; | ||
|
||
int totalResultCount = beatmap.HitObjects.Count; | ||
|
||
int countLargeTicks = beatmap.HitObjects.Sum(obj => obj.NestedHitObjects.Count(x => x is SliderTick or SliderRepeat)); | ||
int countSmallTicks = beatmap.HitObjects.Count(x => x is Slider); | ||
|
||
// Sliderheads are large ticks too if slideracc is disabled | ||
if (usingClassicSliderAccuracy) | ||
countLargeTicks += countSmallTicks; | ||
|
||
countLargeTickMisses = Math.Min(countLargeTickMisses ?? 0, countLargeTicks); | ||
countSliderTailMisses = Math.Min(countSliderTailMisses ?? 0, countSmallTicks); | ||
|
||
if (countMeh != null || countGood != null) | ||
{ | ||
countGreat = totalResultCount - (countGood ?? 0) - (countMeh ?? 0) - countMiss; | ||
} | ||
else | ||
{ | ||
// Total result count excluding countMiss | ||
int relevantResultCount = totalResultCount - countMiss; | ||
// Relevant result count without misses (normal misses and slider-related misses) | ||
// We need to exclude them from judgement count so total value will be equal to desired after misses are accounted for | ||
double countSuccessfulHits; | ||
|
||
// If there's no classic slider accuracy - we need to weight normal judgements accordingly. | ||
// Normal judgements in this context are 300s, 100s, 50s and misses. | ||
// Slider-related judgements are large tick hits/misses and slider tail hits/misses. | ||
double normalJudgementWeight = 1.0; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd also appreciate a comment as to why this exist and why we need to use it There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For example let's assume that map has enough sliders so 10% of the score belongs to sliderends and sliderpoints. In this case we need to compensate for the fact that "circles" (includes sliderheads and spinners) only hold 90% of the influence they would've had in the CL scenario where they're the only type of judgments. circleJudgementWeight is technically inaccurate (I probably need to fix comment above). This refers to 300s/100s/50s/misses. When "not normal" judgements are slider end hits and big tick hits. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
if (usingClassicSliderAccuracy) | ||
{ | ||
countSuccessfulHits = totalResultCount - countMiss; | ||
} | ||
else | ||
{ | ||
double maxSliderPortion = countSmallTicks * 0.5 + countLargeTicks * 0.1; | ||
normalJudgementWeight = (totalResultCount + maxSliderPortion) / totalResultCount; | ||
|
||
double missedSliderPortion = (double)countSliderTailMisses * 0.5 + (double)countLargeTickMisses * 0.1; | ||
countSuccessfulHits = totalResultCount - (countMiss + missedSliderPortion) / normalJudgementWeight; | ||
} | ||
|
||
// Accuracy excluding countMiss. We need that because we're trying to achieve target accuracy without touching countMiss | ||
// So it's better to pretened that there were 0 misses in the 1st place | ||
double relevantAccuracy = accuracy * totalResultCount / relevantResultCount; | ||
double relevantAccuracy = accuracy * totalResultCount / countSuccessfulHits; | ||
|
||
// Clamp accuracy to account for user trying to break the algorithm by inputting impossible values | ||
relevantAccuracy = Math.Clamp(relevantAccuracy, 0, 1); | ||
|
@@ -87,7 +118,7 @@ private static Dictionary<HitResult, int> generateHitResults(IBeatmap beatmap, d | |
double ratio50To100 = Math.Pow(1 - (relevantAccuracy - 0.25) / 0.75, 2); | ||
|
||
// Derived from the formula: Accuracy = (6 * c300 + 2 * c100 + c50) / (6 * totalHits), assuming that c50 = c100 * ratio50to100 | ||
double count100Estimate = 6 * relevantResultCount * (1 - relevantAccuracy) / (5 * ratio50To100 + 4); | ||
double count100Estimate = 6 * countSuccessfulHits * (1 - relevantAccuracy) / (5 * ratio50To100 + 4) * normalJudgementWeight; | ||
|
||
// Get count50 according to c50 = c100 * ratio50to100 | ||
double count50Estimate = count100Estimate * ratio50To100; | ||
|
@@ -102,23 +133,23 @@ private static Dictionary<HitResult, int> generateHitResults(IBeatmap beatmap, d | |
else if (relevantAccuracy >= 1.0 / 6) | ||
{ | ||
// Derived from the formula: Accuracy = (6 * c300 + 2 * c100 + c50) / (6 * totalHits), assuming that c300 = 0 | ||
double count100Estimate = 6 * relevantResultCount * relevantAccuracy - relevantResultCount; | ||
double count100Estimate = 6 * countSuccessfulHits * relevantAccuracy - countSuccessfulHits; | ||
|
||
// We only had 100s and 50s in that scenario so rest of the hits are 50s | ||
double count50Estimate = relevantResultCount - count100Estimate; | ||
double count50Estimate = countSuccessfulHits - count100Estimate; | ||
|
||
// Round it to get int number of 100s | ||
countGood = (int?)Math.Round(count100Estimate); | ||
countGood = (int?)Math.Round(count100Estimate * normalJudgementWeight); | ||
|
||
// Get number of 50s as difference between total mistimed hits and count100 | ||
countMeh = (int?)(Math.Round(count100Estimate + count50Estimate) - countGood); | ||
countMeh = (int?)(Math.Round((count100Estimate + count50Estimate) * normalJudgementWeight) - countGood); | ||
} | ||
// If accuracy is less than 16.67% - it means that we have only 50s or misses | ||
// Assuming that we removed misses in the 1st place - that means that we need to add additional misses to achieve target accuracy | ||
else | ||
{ | ||
// Derived from the formula: Accuracy = (6 * c300 + 2 * c100 + c50) / (6 * totalHits), assuming that c300 = c100 = 0 | ||
double count50Estimate = 6 * relevantResultCount * relevantAccuracy; | ||
double count50Estimate = 6 * (totalResultCount - countMiss) * relevantAccuracy; | ||
|
||
// We have 0 100s, because we can't start adding 100s again after reaching "only 50s" point | ||
countGood = 0; | ||
|
@@ -130,6 +161,10 @@ private static Dictionary<HitResult, int> generateHitResults(IBeatmap beatmap, d | |
countMiss = (int)(totalResultCount - countMeh); | ||
} | ||
|
||
// Clamp goods if total amount is bigger than possible | ||
countGood -= Math.Clamp((int)(countGood + countMeh + countMiss - totalResultCount), 0, (int)countGood); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't this be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, this is correct. Thanks to increased weight (we add more 100s than in CL case) we can add more 100s than possible. This line is removing those extra impossible 100s. |
||
countMeh -= Math.Clamp((int)(countGood + countMeh + countMiss - totalResultCount), 0, (int)countMeh); | ||
|
||
// Rest of the hits are 300s | ||
countGreat = (int)(totalResultCount - countGood - countMeh - countMiss); | ||
} | ||
|
@@ -142,17 +177,18 @@ private static Dictionary<HitResult, int> generateHitResults(IBeatmap beatmap, d | |
{ HitResult.Miss, countMiss } | ||
}; | ||
|
||
if (countLargeTickMisses != null) | ||
result[HitResult.LargeTickMiss] = countLargeTickMisses.Value; | ||
|
||
if (countSliderTailMisses != null) | ||
result[HitResult.SliderTailHit] = beatmap.HitObjects.Count(x => x is Slider) - countSliderTailMisses.Value; | ||
result[HitResult.LargeTickHit] = countLargeTicks - (int)countLargeTickMisses; | ||
result[HitResult.LargeTickMiss] = (int)countLargeTickMisses; | ||
result[usingClassicSliderAccuracy ? HitResult.SmallTickHit : HitResult.SliderTailHit] = countSmallTicks - (int)countSliderTailMisses; | ||
if (usingClassicSliderAccuracy) result[HitResult.SmallTickMiss] = (int)countSliderTailMisses; | ||
|
||
return result; | ||
} | ||
|
||
protected override double GetAccuracy(IBeatmap beatmap, Dictionary<HitResult, int> statistics, Mod[] mods) | ||
{ | ||
bool usingClassicSliderAccuracy = mods.OfType<OsuModClassic>().Any(m => m.NoSliderHeadAccuracy.Value); | ||
|
||
int countGreat = statistics[HitResult.Great]; | ||
int countGood = statistics[HitResult.Ok]; | ||
int countMeh = statistics[HitResult.Meh]; | ||
|
@@ -161,18 +197,18 @@ protected override double GetAccuracy(IBeatmap beatmap, Dictionary<HitResult, in | |
double total = 6 * countGreat + 2 * countGood + countMeh; | ||
double max = 6 * (countGreat + countGood + countMeh + countMiss); | ||
|
||
if (statistics.TryGetValue(HitResult.SliderTailHit, out int countSliderTailHit)) | ||
if (!usingClassicSliderAccuracy && statistics.TryGetValue(HitResult.SliderTailHit, out int countSliderTailHit)) | ||
{ | ||
int countSliders = beatmap.HitObjects.Count(x => x is Slider); | ||
|
||
total += 3 * countSliderTailHit; | ||
max += 3 * countSliders; | ||
} | ||
|
||
if (statistics.TryGetValue(HitResult.LargeTickMiss, out int countLargeTickMiss)) | ||
if (!usingClassicSliderAccuracy && statistics.TryGetValue(HitResult.LargeTickMiss, out int countLargeTicksMiss)) | ||
{ | ||
int countLargeTicks = beatmap.HitObjects.Sum(obj => obj.NestedHitObjects.Count(x => x is SliderTick or SliderRepeat)); | ||
int countLargeTickHit = countLargeTicks - countLargeTickMiss; | ||
int countLargeTickHit = countLargeTicks - countLargeTicksMiss; | ||
|
||
total += 0.6 * countLargeTickHit; | ||
max += 0.6 * countLargeTicks; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
count...
is used for hitresults here, so maybetotal...
instead? preferably for the other usages in this file that already exist as well, to reduce the confusionThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
totalLargeTicks doesn't sounds good to me
totalResultCount
has "count" in it, and "totalResult" is a descriptor of what kind of count it ismaybe rename to
largeTickCount
would be better?