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 hits = statistics[HitResult.Great] + statistics[HitResult.LargeTickHit] + statistics[HitResult.SmallTickHit];
|
||||||
double total = hits + statistics[HitResult.Miss] + statistics[HitResult.SmallTickMiss];
|
double total = hits + statistics[HitResult.Miss] + statistics[HitResult.SmallTickMiss];
|
||||||
|
|
|
@ -36,12 +36,14 @@ namespace PerformanceCalculator.Simulate
|
||||||
|
|
||||||
public override Ruleset Ruleset => new ManiaRuleset();
|
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).
|
// 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)
|
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 perfectValue = mods.Any(m => m is ModClassic) ? 60 : 61;
|
||||||
int targetTotal = (int)Math.Round(accuracy * totalHits * 6);
|
|
||||||
|
// 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
|
// Start by assuming every non miss is a meh
|
||||||
// This is how much increase is needed by the rest
|
// This is how much increase is needed by the rest
|
||||||
int remainingHits = totalHits - countMiss;
|
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)
|
// Each perfect increases total by 50 (CL) or 51 (no CL) (perfect - meh = 50 or 51)
|
||||||
// There is no difference in accuracy between them, so just halve arbitrarily (favouring perfects for an odd number).
|
int perfects = Math.Min(delta / (perfectValue - 10), remainingHits);
|
||||||
int greatsAndPerfects = Math.Min(delta / 5, remainingHits);
|
delta -= perfects * (perfectValue - 10);
|
||||||
int greats = greatsAndPerfects / 2;
|
remainingHits -= perfects;
|
||||||
int perfects = greatsAndPerfects - greats;
|
|
||||||
delta -= (greats + perfects) * 5;
|
|
||||||
remainingHits -= greats + perfects;
|
|
||||||
|
|
||||||
// Each good increases total by 3 (good-meh=3).
|
// Each great increases total by 50 (great - meh = 50)
|
||||||
countGood = Math.Min(delta / 3, remainingHits);
|
int greats = Math.Min(delta / 50, remainingHits);
|
||||||
delta -= countGood.Value * 3;
|
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;
|
remainingHits -= countGood.Value;
|
||||||
|
|
||||||
// Each ok increases total by 1 (ok-meh=1).
|
// Each ok increases total by 10 (ok - meh = 10)
|
||||||
int oks = delta;
|
int oks = Math.Min(delta / 10, remainingHits);
|
||||||
remainingHits -= oks;
|
remainingHits -= oks;
|
||||||
|
|
||||||
// Everything else is a meh, as initially assumed.
|
// Everything else is a meh, as initially assumed
|
||||||
countMeh = remainingHits;
|
countMeh = remainingHits;
|
||||||
|
|
||||||
return new Dictionary<HitResult, int>
|
return new Dictionary<HitResult, int>
|
||||||
|
@ -96,5 +102,22 @@ namespace PerformanceCalculator.Simulate
|
||||||
{ HitResult.Miss, countMiss }
|
{ 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;
|
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 countGreat = statistics[HitResult.Great];
|
||||||
int countGood = statistics[HitResult.Ok];
|
int countGood = statistics[HitResult.Ok];
|
||||||
|
|
|
@ -74,7 +74,7 @@ namespace PerformanceCalculator.Simulate
|
||||||
var statistics = GenerateHitResults(beatmap, mods);
|
var statistics = GenerateHitResults(beatmap, mods);
|
||||||
var scoreInfo = new ScoreInfo(beatmap.BeatmapInfo, ruleset.RulesetInfo)
|
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),
|
MaxCombo = Combo ?? (int)Math.Round(PercentCombo / 100 * beatmapMaxCombo),
|
||||||
Statistics = statistics,
|
Statistics = statistics,
|
||||||
LegacyTotalScore = LegacyTotalScore,
|
LegacyTotalScore = LegacyTotalScore,
|
||||||
|
@ -91,6 +91,6 @@ namespace PerformanceCalculator.Simulate
|
||||||
|
|
||||||
protected abstract Dictionary<HitResult, int> GenerateHitResults(IBeatmap beatmap, Mod[] mods);
|
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 countGreat = statistics[HitResult.Great];
|
||||||
int countGood = statistics[HitResult.Ok];
|
int countGood = statistics[HitResult.Ok];
|
||||||
|
|
|
@ -10,6 +10,7 @@ using osu.Game.Rulesets.Catch;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
using osu.Game.Rulesets.Difficulty;
|
using osu.Game.Rulesets.Difficulty;
|
||||||
using osu.Game.Rulesets.Mania;
|
using osu.Game.Rulesets.Mania;
|
||||||
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
@ -61,14 +62,14 @@ namespace PerformanceCalculatorGUI
|
||||||
return (int)Math.Round(1000000 * scoreMultiplier);
|
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
|
return ruleset.OnlineID switch
|
||||||
{
|
{
|
||||||
0 => generateOsuHitResults(accuracy, beatmap, countMiss, countMeh, countGood, countLargeTickMisses, countSliderTailMisses),
|
0 => generateOsuHitResults(accuracy, beatmap, countMiss, countMeh, countGood, countLargeTickMisses, countSliderTailMisses),
|
||||||
1 => generateTaikoHitResults(accuracy, beatmap, countMiss, countGood),
|
1 => generateTaikoHitResults(accuracy, beatmap, countMiss, countGood),
|
||||||
2 => generateCatchHitResults(accuracy, beatmap, countMiss, countMeh, 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.")
|
_ => 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 perfectValue = mods.Any(m => m is ModClassic) ? 60 : 61;
|
||||||
int targetTotal = (int)Math.Round(accuracy * totalResultCount * 6);
|
|
||||||
|
// 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
|
// Start by assuming every non miss is a meh
|
||||||
// This is how much increase is needed by greats and goods
|
// This is how much increase is needed by the rest
|
||||||
int delta = targetTotal - (totalResultCount - countMiss);
|
int remainingHits = totalHits - countMiss;
|
||||||
|
int delta = Math.Max(targetTotal - (10 * remainingHits), 0);
|
||||||
|
|
||||||
// Each great increases total by 5 (great-meh=5)
|
// Each perfect increases total by 50 (CL) or 51 (no CL) (perfect - meh = 50 or 51)
|
||||||
int countGreat = delta / 5;
|
int perfects = Math.Min(delta / (perfectValue - 10), remainingHits);
|
||||||
// Each good increases total by 1 (good-meh=1). Covers remaining difference.
|
delta -= perfects * (perfectValue - 10);
|
||||||
int countGood = delta % 5;
|
remainingHits -= perfects;
|
||||||
// Mehs are left over. Could be negative if impossible value of amountMiss chosen
|
|
||||||
int countMeh = totalResultCount - countGreat - countGood - countMiss;
|
// 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>
|
return new Dictionary<HitResult, int>
|
||||||
{
|
{
|
||||||
{ HitResult.Perfect, countGreat },
|
{ HitResult.Perfect, perfects },
|
||||||
{ HitResult.Great, 0 },
|
{ HitResult.Great, greats },
|
||||||
{ HitResult.Good, countGood },
|
{ HitResult.Ok, oks },
|
||||||
{ HitResult.Ok, 0 },
|
{ HitResult.Good, goods },
|
||||||
{ HitResult.Meh, countMeh },
|
{ HitResult.Meh, mehs },
|
||||||
{ HitResult.Miss, countMiss }
|
{ 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
|
return ruleset.OnlineID switch
|
||||||
{
|
{
|
||||||
0 => getOsuAccuracy(beatmap, statistics),
|
0 => getOsuAccuracy(beatmap, statistics),
|
||||||
1 => getTaikoAccuracy(statistics),
|
1 => getTaikoAccuracy(statistics),
|
||||||
2 => getCatchAccuracy(statistics),
|
2 => getCatchAccuracy(statistics),
|
||||||
3 => getManiaAccuracy(statistics),
|
3 => getManiaAccuracy(statistics, mods),
|
||||||
_ => 0.0
|
_ => 0.0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -314,7 +335,7 @@ namespace PerformanceCalculatorGUI
|
||||||
return hits / total;
|
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 countPerfect = statistics[HitResult.Perfect];
|
||||||
int countGreat = statistics[HitResult.Great];
|
int countGreat = statistics[HitResult.Great];
|
||||||
|
@ -322,11 +343,13 @@ namespace PerformanceCalculatorGUI
|
||||||
int countOk = statistics[HitResult.Ok];
|
int countOk = statistics[HitResult.Ok];
|
||||||
int countMeh = statistics[HitResult.Meh];
|
int countMeh = statistics[HitResult.Meh];
|
||||||
int countMiss = statistics[HitResult.Miss];
|
int countMiss = statistics[HitResult.Miss];
|
||||||
int total = countPerfect + countGreat + countGood + countOk + countMeh + countMiss;
|
|
||||||
|
|
||||||
return (double)
|
int perfectWeight = mods.Any(m => m is ModClassic) ? 300 : 305;
|
||||||
((6 * (countPerfect + countGreat)) + (4 * countGood) + (2 * countOk) + countMeh) /
|
|
||||||
(6 * total);
|
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
|
// official rulesets can generate more precise hits from accuracy
|
||||||
if (appliedMods.Value.OfType<OsuModClassic>().Any(m => m.NoSliderHeadAccuracy.Value))
|
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);
|
null, null);
|
||||||
}
|
}
|
||||||
else
|
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);
|
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)
|
var ppAttributes = performanceCalculator?.Calculate(new ScoreInfo(beatmap.BeatmapInfo, ruleset.Value)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue