mirror of
https://github.com/ppy/osu-tools.git
synced 2025-06-07 23:07:01 +09:00
Merge pull request #252 from MrHeliX/fix_simulate_mania
Fixes for mania simulate command
This commit is contained in:
commit
9272b2269f
7 changed files with 99 additions and 53 deletions
|
@ -68,7 +68,7 @@ namespace PerformanceCalculator.Simulate
|
|||
};
|
||||
}
|
||||
|
||||
protected override double GetAccuracy(IBeatmap beatmap, Dictionary<HitResult, int> statistics)
|
||||
protected override double GetAccuracy(IBeatmap beatmap, Dictionary<HitResult, int> statistics, Mod[] mods)
|
||||
{
|
||||
double hits = statistics[HitResult.Great] + statistics[HitResult.LargeTickHit] + statistics[HitResult.SmallTickHit];
|
||||
double total = hits + statistics[HitResult.Miss] + statistics[HitResult.SmallTickMiss];
|
||||
|
|
|
@ -36,12 +36,14 @@ namespace PerformanceCalculator.Simulate
|
|||
|
||||
public override Ruleset Ruleset => new ManiaRuleset();
|
||||
|
||||
protected override Dictionary<HitResult, int> GenerateHitResults(IBeatmap beatmap, Mod[] mods) => generateHitResults(beatmap, Accuracy / 100, Misses, Mehs, oks, Goods, greats);
|
||||
protected override Dictionary<HitResult, int> GenerateHitResults(IBeatmap beatmap, Mod[] mods) => generateHitResults(beatmap, mods, Accuracy / 100, Misses, Mehs, oks, Goods, greats);
|
||||
|
||||
private static Dictionary<HitResult, int> generateHitResults(IBeatmap beatmap, double accuracy, int countMiss, int? countMeh, int? countOk, int? countGood, int? countGreat)
|
||||
private static Dictionary<HitResult, int> generateHitResults(IBeatmap beatmap, Mod[] mods, double accuracy, int countMiss, int? countMeh, int? countOk, int? countGood, int? countGreat)
|
||||
{
|
||||
// One judgement per normal note. Two judgements per hold note (head + tail).
|
||||
int totalHits = beatmap.HitObjects.Count + beatmap.HitObjects.Count(ho => ho is HoldNote);
|
||||
int totalHits = beatmap.HitObjects.Count;
|
||||
if (!mods.Any(m => m is ModClassic))
|
||||
totalHits += beatmap.HitObjects.Count(ho => ho is HoldNote);
|
||||
|
||||
if (countMeh != null || countOk != null || countGood != null || countGreat != null)
|
||||
{
|
||||
|
@ -58,32 +60,36 @@ namespace PerformanceCalculator.Simulate
|
|||
};
|
||||
}
|
||||
|
||||
// Let Great=Perfect=6, Good=4, Ok=2, Meh=1, Miss=0. The total should be this.
|
||||
int targetTotal = (int)Math.Round(accuracy * totalHits * 6);
|
||||
int perfectValue = mods.Any(m => m is ModClassic) ? 60 : 61;
|
||||
|
||||
// Let Great = 60, Good = 40, Ok = 20, Meh = 10, Miss = 0, Perfect = 61 or 60 depending on CL. The total should be this.
|
||||
int targetTotal = (int)Math.Round(accuracy * totalHits * perfectValue);
|
||||
|
||||
// Start by assuming every non miss is a meh
|
||||
// This is how much increase is needed by the rest
|
||||
int remainingHits = totalHits - countMiss;
|
||||
int delta = targetTotal - remainingHits;
|
||||
int delta = Math.Max(targetTotal - (10 * remainingHits), 0);
|
||||
|
||||
// Each great and perfect increases total by 5 (great-meh=5)
|
||||
// There is no difference in accuracy between them, so just halve arbitrarily (favouring perfects for an odd number).
|
||||
int greatsAndPerfects = Math.Min(delta / 5, remainingHits);
|
||||
int greats = greatsAndPerfects / 2;
|
||||
int perfects = greatsAndPerfects - greats;
|
||||
delta -= (greats + perfects) * 5;
|
||||
remainingHits -= greats + perfects;
|
||||
// Each perfect increases total by 50 (CL) or 51 (no CL) (perfect - meh = 50 or 51)
|
||||
int perfects = Math.Min(delta / (perfectValue - 10), remainingHits);
|
||||
delta -= perfects * (perfectValue - 10);
|
||||
remainingHits -= perfects;
|
||||
|
||||
// Each good increases total by 3 (good-meh=3).
|
||||
countGood = Math.Min(delta / 3, remainingHits);
|
||||
delta -= countGood.Value * 3;
|
||||
// Each great increases total by 50 (great - meh = 50)
|
||||
int greats = Math.Min(delta / 50, remainingHits);
|
||||
delta -= greats * 50;
|
||||
remainingHits -= greats;
|
||||
|
||||
// Each good increases total by 30 (good - meh = 30)
|
||||
countGood = Math.Min(delta / 30, remainingHits);
|
||||
delta -= countGood.Value * 30;
|
||||
remainingHits -= countGood.Value;
|
||||
|
||||
// Each ok increases total by 1 (ok-meh=1).
|
||||
int oks = delta;
|
||||
// Each ok increases total by 10 (ok - meh = 10)
|
||||
int oks = Math.Min(delta / 10, remainingHits);
|
||||
remainingHits -= oks;
|
||||
|
||||
// Everything else is a meh, as initially assumed.
|
||||
// Everything else is a meh, as initially assumed
|
||||
countMeh = remainingHits;
|
||||
|
||||
return new Dictionary<HitResult, int>
|
||||
|
@ -96,5 +102,22 @@ namespace PerformanceCalculator.Simulate
|
|||
{ HitResult.Miss, countMiss }
|
||||
};
|
||||
}
|
||||
|
||||
protected override double GetAccuracy(IBeatmap beatmap, Dictionary<HitResult, int> statistics, Mod[] mods)
|
||||
{
|
||||
int countPerfect = statistics[HitResult.Perfect];
|
||||
int countGreat = statistics[HitResult.Great];
|
||||
int countGood = statistics[HitResult.Good];
|
||||
int countOk = statistics[HitResult.Ok];
|
||||
int countMeh = statistics[HitResult.Meh];
|
||||
int countMiss = statistics[HitResult.Miss];
|
||||
|
||||
int perfectWeight = mods.Any(m => m is ModClassic) ? 300 : 305;
|
||||
|
||||
double total = (perfectWeight * countPerfect) + (300 * countGreat) + (200 * countGood) + (100 * countOk) + (50 * countMeh);
|
||||
double max = perfectWeight * (countPerfect + countGreat + countGood + countOk + countMeh + countMiss);
|
||||
|
||||
return total / max;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -151,7 +151,7 @@ namespace PerformanceCalculator.Simulate
|
|||
return result;
|
||||
}
|
||||
|
||||
protected override double GetAccuracy(IBeatmap beatmap, Dictionary<HitResult, int> statistics)
|
||||
protected override double GetAccuracy(IBeatmap beatmap, Dictionary<HitResult, int> statistics, Mod[] mods)
|
||||
{
|
||||
int countGreat = statistics[HitResult.Great];
|
||||
int countGood = statistics[HitResult.Ok];
|
||||
|
|
|
@ -74,7 +74,7 @@ namespace PerformanceCalculator.Simulate
|
|||
var statistics = GenerateHitResults(beatmap, mods);
|
||||
var scoreInfo = new ScoreInfo(beatmap.BeatmapInfo, ruleset.RulesetInfo)
|
||||
{
|
||||
Accuracy = GetAccuracy(beatmap, statistics),
|
||||
Accuracy = GetAccuracy(beatmap, statistics, mods),
|
||||
MaxCombo = Combo ?? (int)Math.Round(PercentCombo / 100 * beatmapMaxCombo),
|
||||
Statistics = statistics,
|
||||
LegacyTotalScore = LegacyTotalScore,
|
||||
|
@ -91,6 +91,6 @@ namespace PerformanceCalculator.Simulate
|
|||
|
||||
protected abstract Dictionary<HitResult, int> GenerateHitResults(IBeatmap beatmap, Mod[] mods);
|
||||
|
||||
protected virtual double GetAccuracy(IBeatmap beatmap, Dictionary<HitResult, int> statistics) => 0;
|
||||
protected virtual double GetAccuracy(IBeatmap beatmap, Dictionary<HitResult, int> statistics, Mod[] mods) => 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,7 +60,7 @@ namespace PerformanceCalculator.Simulate
|
|||
};
|
||||
}
|
||||
|
||||
protected override double GetAccuracy(IBeatmap beatmap, Dictionary<HitResult, int> statistics)
|
||||
protected override double GetAccuracy(IBeatmap beatmap, Dictionary<HitResult, int> statistics, Mod[] mods)
|
||||
{
|
||||
int countGreat = statistics[HitResult.Great];
|
||||
int countGood = statistics[HitResult.Ok];
|
||||
|
|
|
@ -10,6 +10,7 @@ using osu.Game.Rulesets.Catch;
|
|||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Mania;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
|
@ -61,14 +62,14 @@ namespace PerformanceCalculatorGUI
|
|||
return (int)Math.Round(1000000 * scoreMultiplier);
|
||||
}
|
||||
|
||||
public static Dictionary<HitResult, int> GenerateHitResultsForRuleset(RulesetInfo ruleset, double accuracy, IBeatmap beatmap, int countMiss, int? countMeh, int? countGood, int? countLargeTickMisses, int? countSliderTailMisses)
|
||||
public static Dictionary<HitResult, int> GenerateHitResultsForRuleset(RulesetInfo ruleset, double accuracy, IBeatmap beatmap, Mod[] mods, int countMiss, int? countMeh, int? countGood, int? countLargeTickMisses, int? countSliderTailMisses)
|
||||
{
|
||||
return ruleset.OnlineID switch
|
||||
{
|
||||
0 => generateOsuHitResults(accuracy, beatmap, countMiss, countMeh, countGood, countLargeTickMisses, countSliderTailMisses),
|
||||
1 => generateTaikoHitResults(accuracy, beatmap, countMiss, countGood),
|
||||
2 => generateCatchHitResults(accuracy, beatmap, countMiss, countMeh, countGood),
|
||||
3 => generateManiaHitResults(accuracy, beatmap, countMiss),
|
||||
3 => generateManiaHitResults(accuracy, beatmap, mods, countMiss),
|
||||
_ => throw new ArgumentException("Invalid ruleset ID provided.")
|
||||
};
|
||||
}
|
||||
|
@ -225,43 +226,63 @@ namespace PerformanceCalculatorGUI
|
|||
};
|
||||
}
|
||||
|
||||
private static Dictionary<HitResult, int> generateManiaHitResults(double accuracy, IBeatmap beatmap, int countMiss)
|
||||
private static Dictionary<HitResult, int> generateManiaHitResults(double accuracy, IBeatmap beatmap, Mod[] mods, int countMiss)
|
||||
{
|
||||
int totalResultCount = beatmap.HitObjects.Count;
|
||||
int totalHits = beatmap.HitObjects.Count;
|
||||
if (!mods.Any(m => m is ModClassic))
|
||||
totalHits += beatmap.HitObjects.Count(ho => ho is HoldNote);
|
||||
|
||||
// Let Great=6, Good=2, Meh=1, Miss=0. The total should be this.
|
||||
int targetTotal = (int)Math.Round(accuracy * totalResultCount * 6);
|
||||
int perfectValue = mods.Any(m => m is ModClassic) ? 60 : 61;
|
||||
|
||||
// Let Great = 60, Good = 40, Ok = 20, Meh = 10, Miss = 0, Perfect = 61 or 60 depending on CL. The total should be this.
|
||||
int targetTotal = (int)Math.Round(accuracy * totalHits * perfectValue);
|
||||
|
||||
// Start by assuming every non miss is a meh
|
||||
// This is how much increase is needed by greats and goods
|
||||
int delta = targetTotal - (totalResultCount - countMiss);
|
||||
// This is how much increase is needed by the rest
|
||||
int remainingHits = totalHits - countMiss;
|
||||
int delta = Math.Max(targetTotal - (10 * remainingHits), 0);
|
||||
|
||||
// Each great increases total by 5 (great-meh=5)
|
||||
int countGreat = delta / 5;
|
||||
// Each good increases total by 1 (good-meh=1). Covers remaining difference.
|
||||
int countGood = delta % 5;
|
||||
// Mehs are left over. Could be negative if impossible value of amountMiss chosen
|
||||
int countMeh = totalResultCount - countGreat - countGood - countMiss;
|
||||
// Each perfect increases total by 50 (CL) or 51 (no CL) (perfect - meh = 50 or 51)
|
||||
int perfects = Math.Min(delta / (perfectValue - 10), remainingHits);
|
||||
delta -= perfects * (perfectValue - 10);
|
||||
remainingHits -= perfects;
|
||||
|
||||
// Each great increases total by 50 (great - meh = 50)
|
||||
int greats = Math.Min(delta / 50, remainingHits);
|
||||
delta -= greats * 50;
|
||||
remainingHits -= greats;
|
||||
|
||||
// Each good increases total by 30 (good - meh = 30)
|
||||
int goods = Math.Min(delta / 30, remainingHits);
|
||||
delta -= goods * 30;
|
||||
remainingHits -= goods;
|
||||
|
||||
// Each ok increases total by 10 (ok - meh = 10)
|
||||
int oks = Math.Min(delta / 10, remainingHits);
|
||||
remainingHits -= oks;
|
||||
|
||||
// Everything else is a meh, as initially assumed
|
||||
int mehs = remainingHits;
|
||||
|
||||
return new Dictionary<HitResult, int>
|
||||
{
|
||||
{ HitResult.Perfect, countGreat },
|
||||
{ HitResult.Great, 0 },
|
||||
{ HitResult.Good, countGood },
|
||||
{ HitResult.Ok, 0 },
|
||||
{ HitResult.Meh, countMeh },
|
||||
{ HitResult.Perfect, perfects },
|
||||
{ HitResult.Great, greats },
|
||||
{ HitResult.Ok, oks },
|
||||
{ HitResult.Good, goods },
|
||||
{ HitResult.Meh, mehs },
|
||||
{ HitResult.Miss, countMiss }
|
||||
};
|
||||
}
|
||||
|
||||
public static double GetAccuracyForRuleset(RulesetInfo ruleset, IBeatmap beatmap, Dictionary<HitResult, int> statistics)
|
||||
public static double GetAccuracyForRuleset(RulesetInfo ruleset, IBeatmap beatmap, Dictionary<HitResult, int> statistics, Mod[] mods)
|
||||
{
|
||||
return ruleset.OnlineID switch
|
||||
{
|
||||
0 => getOsuAccuracy(beatmap, statistics),
|
||||
1 => getTaikoAccuracy(statistics),
|
||||
2 => getCatchAccuracy(statistics),
|
||||
3 => getManiaAccuracy(statistics),
|
||||
3 => getManiaAccuracy(statistics, mods),
|
||||
_ => 0.0
|
||||
};
|
||||
}
|
||||
|
@ -314,7 +335,7 @@ namespace PerformanceCalculatorGUI
|
|||
return hits / total;
|
||||
}
|
||||
|
||||
private static double getManiaAccuracy(Dictionary<HitResult, int> statistics)
|
||||
private static double getManiaAccuracy(Dictionary<HitResult, int> statistics, Mod[] mods)
|
||||
{
|
||||
int countPerfect = statistics[HitResult.Perfect];
|
||||
int countGreat = statistics[HitResult.Great];
|
||||
|
@ -322,11 +343,13 @@ namespace PerformanceCalculatorGUI
|
|||
int countOk = statistics[HitResult.Ok];
|
||||
int countMeh = statistics[HitResult.Meh];
|
||||
int countMiss = statistics[HitResult.Miss];
|
||||
int total = countPerfect + countGreat + countGood + countOk + countMeh + countMiss;
|
||||
|
||||
return (double)
|
||||
((6 * (countPerfect + countGreat)) + (4 * countGood) + (2 * countOk) + countMeh) /
|
||||
(6 * total);
|
||||
int perfectWeight = mods.Any(m => m is ModClassic) ? 300 : 305;
|
||||
|
||||
double total = (perfectWeight * countPerfect) + (300 * countGreat) + (200 * countGood) + (100 * countOk) + (50 * countMeh);
|
||||
double max = perfectWeight * (countPerfect + countGreat + countGood + countOk + countMeh + countMiss);
|
||||
|
||||
return total / max;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -714,16 +714,16 @@ namespace PerformanceCalculatorGUI.Screens
|
|||
// official rulesets can generate more precise hits from accuracy
|
||||
if (appliedMods.Value.OfType<OsuModClassic>().Any(m => m.NoSliderHeadAccuracy.Value))
|
||||
{
|
||||
statistics = RulesetHelper.GenerateHitResultsForRuleset(ruleset.Value, accuracyTextBox.Value.Value / 100.0, beatmap, missesTextBox.Value.Value, countMeh, countGood,
|
||||
statistics = RulesetHelper.GenerateHitResultsForRuleset(ruleset.Value, accuracyTextBox.Value.Value / 100.0, beatmap, appliedMods.Value.ToArray(), missesTextBox.Value.Value, countMeh, countGood,
|
||||
null, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
statistics = RulesetHelper.GenerateHitResultsForRuleset(ruleset.Value, accuracyTextBox.Value.Value / 100.0, beatmap, missesTextBox.Value.Value, countMeh, countGood,
|
||||
statistics = RulesetHelper.GenerateHitResultsForRuleset(ruleset.Value, accuracyTextBox.Value.Value / 100.0, beatmap, appliedMods.Value.ToArray(), missesTextBox.Value.Value, countMeh, countGood,
|
||||
largeTickMissesTextBox.Value.Value, sliderTailMissesTextBox.Value.Value);
|
||||
}
|
||||
|
||||
accuracy = RulesetHelper.GetAccuracyForRuleset(ruleset.Value, beatmap, statistics);
|
||||
accuracy = RulesetHelper.GetAccuracyForRuleset(ruleset.Value, beatmap, statistics, appliedMods.Value.ToArray());
|
||||
}
|
||||
|
||||
var ppAttributes = performanceCalculator?.Calculate(new ScoreInfo(beatmap.BeatmapInfo, ruleset.Value)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue