diff --git a/PerformanceCalculator/Simulate/ManiaSimulateCommand.cs b/PerformanceCalculator/Simulate/ManiaSimulateCommand.cs index 10e44867e..632ae81ce 100644 --- a/PerformanceCalculator/Simulate/ManiaSimulateCommand.cs +++ b/PerformanceCalculator/Simulate/ManiaSimulateCommand.cs @@ -3,13 +3,13 @@ using System; using System.Collections.Generic; -using System.Diagnostics; +using System.Linq; using JetBrains.Annotations; using McMaster.Extensions.CommandLineUtils; using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Mania; -using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Scoring; namespace PerformanceCalculator.Simulate @@ -17,18 +17,30 @@ namespace PerformanceCalculator.Simulate [Command(Name = "mania", Description = "Computes the performance (pp) of a simulated osu!mania play.")] public class ManiaSimulateCommand : SimulateCommand { - public override int Score - { - get - { - Debug.Assert(score != null); - return score.Value; - } - } + [UsedImplicitly] + [Option(Template = "-a|--accuracy ", Description = "Accuracy. Enter as decimal 0-100. Defaults to 100." + + " Scales hit results as well and is rounded to the nearest possible value for the beatmap.")] + public override double Accuracy { get; } = 100; + + [UsedImplicitly] + [Option(Template = "-X|--misses ", Description = "Number of misses. Defaults to 0.")] + public override int Misses { get; } + + [UsedImplicitly] + [Option(Template = "-M|--mehs ", Description = "Number of mehs. Will override accuracy if used. Otherwise is automatically calculated.")] + public override int? Mehs { get; } + + [UsedImplicitly] + [Option(Template = "-O|--oks ", Description = "Number of oks. Will override accuracy if used. Otherwise is automatically calculated.")] + private int? oks { get; set; } + + [UsedImplicitly] + [Option(Template = "-G|--goods ", Description = "Number of goods. Will override accuracy if used. Otherwise is automatically calculated.")] + public override int? Goods { get; } [UsedImplicitly] - [Option(Template = "-s|--score ", Description = "Score. An integer 0-1000000.")] - private int? score { get; set; } + [Option(Template = "-T|--greats ", Description = "Number of greats. Will override accuracy if used. Otherwise is automatically calculated.")] + private int? greats { get; set; } [UsedImplicitly] [Option(CommandOptionType.MultipleValue, Template = "-m|--mod ", Description = "One for each mod. The mods to compute the performance with." @@ -37,40 +49,62 @@ public override int Score public override Ruleset Ruleset => new ManiaRuleset(); - public override void Execute() + protected override int GetMaxCombo(IBeatmap beatmap) => 0; + + protected override Dictionary GenerateHitResults(double accuracy, IBeatmap beatmap, int countMiss, int? countMeh, int? countGood) { - if (score == null) + // One judgement per normal note. Two judgements per hold note (head + tail). + var totalHits = beatmap.HitObjects.Count + beatmap.HitObjects.Count(ho => ho is HoldNote); + + if (countMeh != null || oks != null || countGood != null || greats != null) { - double scoreMultiplier = 1; + int countPerfect = totalHits - (countMiss + (countMeh ?? 0) + (oks ?? 0) + (countGood ?? 0) + (greats ?? 0)); - // Cap score depending on difficulty adjustment mods (matters for mania). - foreach (var mod in GetMods(Ruleset)) + return new Dictionary { - if (mod.Type == ModType.DifficultyReduction) - scoreMultiplier *= mod.ScoreMultiplier; - } - - score = (int)Math.Round(1000000 * scoreMultiplier); + [HitResult.Perfect] = countPerfect, + [HitResult.Great] = greats ?? 0, + [HitResult.Good] = countGood ?? 0, + [HitResult.Ok] = oks ?? 0, + [HitResult.Meh] = countMeh ?? 0, + [HitResult.Miss] = countMiss + }; } - base.Execute(); - } + // Let Great=Perfect=6, Good=4, Ok=2, Meh=1, Miss=0. The total should be this. + var targetTotal = (int)Math.Round(accuracy * totalHits * 6); - protected override int GetMaxCombo(IBeatmap beatmap) => 0; + // 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; - protected override Dictionary GenerateHitResults(double accuracy, IBeatmap beatmap, int countMiss, int? countMeh, int? countGood) - { - var totalHits = beatmap.HitObjects.Count; + // Each great and perfect increases total by 5 (great-meh=5) + // There is no difference in accuracy between them, so just halve arbitrarily. + greats = Math.Min(delta / 5, remainingHits) / 2; + int perfects = greats.Value; + delta -= (greats.Value + perfects) * 5; + remainingHits -= (greats.Value + perfects); + + // Each good increases total by 3 (good-meh=3). + countGood = Math.Min(delta / 3, remainingHits); + delta -= countGood.Value * 3; + remainingHits -= countGood.Value; + + // Each ok increases total by 1 (ok-meh=1). + oks = delta; + + // Everything else is a meh, as initially assumed. + countMeh = remainingHits; - // Only total number of hits is considered currently, so specifics don't matter return new Dictionary { - { HitResult.Perfect, totalHits }, - { HitResult.Great, 0 }, - { HitResult.Ok, 0 }, - { HitResult.Good, 0 }, - { HitResult.Meh, 0 }, - { HitResult.Miss, 0 } + { HitResult.Perfect, perfects }, + { HitResult.Great, greats.Value }, + { HitResult.Ok, oks.Value }, + { HitResult.Good, countGood.Value }, + { HitResult.Meh, countMeh.Value }, + { HitResult.Miss, countMiss } }; } }