mirror of
https://github.com/ppy/osu-tools.git
synced 2025-06-07 23:07:01 +09:00
Combine commands and processors
This commit is contained in:
parent
c827fad168
commit
4070c06998
20 changed files with 506 additions and 653 deletions
|
@ -1,12 +0,0 @@
|
|||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-tools/master/LICENCE
|
||||
|
||||
using McMaster.Extensions.CommandLineUtils;
|
||||
|
||||
namespace PerformanceCalculator
|
||||
{
|
||||
[HelpOption("-?|-h|--help")]
|
||||
public abstract class CommandBase
|
||||
{
|
||||
}
|
||||
}
|
|
@ -1,9 +1,21 @@
|
|||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-tools/master/LICENCE
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using McMaster.Extensions.CommandLineUtils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Catch.Difficulty;
|
||||
using osu.Game.Rulesets.Mania.Difficulty;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Difficulty;
|
||||
using osu.Game.Rulesets.Taiko.Difficulty;
|
||||
|
||||
namespace PerformanceCalculator.Difficulty
|
||||
{
|
||||
|
@ -26,6 +38,74 @@ namespace PerformanceCalculator.Difficulty
|
|||
+ "Values: hr, dt, hd, fl, ez, 4k, 5k, etc...")]
|
||||
public string[] Mods { get; }
|
||||
|
||||
protected override IProcessor CreateProcessor() => new DifficultyProcessor(this);
|
||||
public override void Execute()
|
||||
{
|
||||
if (Directory.Exists(Path))
|
||||
{
|
||||
foreach (string file in Directory.GetFiles(Path, "*.osu", SearchOption.AllDirectories))
|
||||
{
|
||||
var beatmap = new ProcessorWorkingBeatmap(file);
|
||||
Console.WriteLine(beatmap.BeatmapInfo.ToString());
|
||||
|
||||
processBeatmap(beatmap);
|
||||
}
|
||||
}
|
||||
else
|
||||
processBeatmap(new ProcessorWorkingBeatmap(Path));
|
||||
}
|
||||
|
||||
private void processBeatmap(WorkingBeatmap beatmap)
|
||||
{
|
||||
// Get the ruleset
|
||||
var ruleset = LegacyHelper.GetRulesetFromLegacyID(Ruleset ?? beatmap.BeatmapInfo.RulesetID);
|
||||
var attributes = ruleset.CreateDifficultyCalculator(beatmap).Calculate(getMods(ruleset).ToArray());
|
||||
|
||||
writeAttribute("Ruleset", ruleset.ShortName);
|
||||
writeAttribute("Stars", attributes.StarRating.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
switch (attributes)
|
||||
{
|
||||
case OsuDifficultyAttributes osu:
|
||||
writeAttribute("Aim", osu.AimStrain.ToString(CultureInfo.InvariantCulture));
|
||||
writeAttribute("Speed", osu.SpeedStrain.ToString(CultureInfo.InvariantCulture));
|
||||
writeAttribute("MaxCombo", osu.MaxCombo.ToString(CultureInfo.InvariantCulture));
|
||||
writeAttribute("AR", osu.ApproachRate.ToString(CultureInfo.InvariantCulture));
|
||||
writeAttribute("OD", osu.OverallDifficulty.ToString(CultureInfo.InvariantCulture));
|
||||
break;
|
||||
case TaikoDifficultyAttributes taiko:
|
||||
writeAttribute("HitWindow", taiko.GreatHitWindow.ToString(CultureInfo.InvariantCulture));
|
||||
writeAttribute("MaxCombo", taiko.MaxCombo.ToString(CultureInfo.InvariantCulture));
|
||||
break;
|
||||
case CatchDifficultyAttributes c:
|
||||
writeAttribute("MaxCombo", c.MaxCombo.ToString(CultureInfo.InvariantCulture));
|
||||
writeAttribute("AR", c.ApproachRate.ToString(CultureInfo.InvariantCulture));
|
||||
break;
|
||||
case ManiaDifficultyAttributes mania:
|
||||
writeAttribute("HitWindow", mania.GreatHitWindow.ToString(CultureInfo.InvariantCulture));
|
||||
break;
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
private void writeAttribute(string name, string value) => Console.WriteLine($"{name.PadRight(15)}: {value}");
|
||||
|
||||
private List<Mod> getMods(Ruleset ruleset)
|
||||
{
|
||||
var mods = new List<Mod>();
|
||||
if (Mods == null)
|
||||
return mods;
|
||||
|
||||
var availableMods = ruleset.GetAllMods().ToList();
|
||||
foreach (var modString in Mods)
|
||||
{
|
||||
Mod newMod = availableMods.FirstOrDefault(m => string.Equals(m.Acronym, modString, StringComparison.CurrentCultureIgnoreCase));
|
||||
if (newMod == null)
|
||||
throw new ArgumentException($"Invalid mod provided: {modString}");
|
||||
mods.Add(newMod);
|
||||
}
|
||||
|
||||
return mods;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,99 +0,0 @@
|
|||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-tools/master/LICENCE
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using McMaster.Extensions.CommandLineUtils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Catch.Difficulty;
|
||||
using osu.Game.Rulesets.Mania.Difficulty;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Difficulty;
|
||||
using osu.Game.Rulesets.Taiko.Difficulty;
|
||||
|
||||
namespace PerformanceCalculator.Difficulty
|
||||
{
|
||||
public class DifficultyProcessor : IProcessor
|
||||
{
|
||||
private readonly DifficultyCommand command;
|
||||
|
||||
public DifficultyProcessor(DifficultyCommand command)
|
||||
{
|
||||
this.command = command;
|
||||
}
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
if (Directory.Exists(command.Path))
|
||||
{
|
||||
foreach (string file in Directory.GetFiles(command.Path, "*.osu", SearchOption.AllDirectories))
|
||||
{
|
||||
var beatmap = new ProcessorWorkingBeatmap(file);
|
||||
command.Console.WriteLine(beatmap.BeatmapInfo.ToString());
|
||||
|
||||
processBeatmap(beatmap);
|
||||
}
|
||||
}
|
||||
else
|
||||
processBeatmap(new ProcessorWorkingBeatmap(command.Path));
|
||||
}
|
||||
|
||||
private void processBeatmap(WorkingBeatmap beatmap)
|
||||
{
|
||||
// Get the ruleset
|
||||
var ruleset = LegacyHelper.GetRulesetFromLegacyID(command.Ruleset ?? beatmap.BeatmapInfo.RulesetID);
|
||||
var attributes = ruleset.CreateDifficultyCalculator(beatmap).Calculate(getMods(ruleset).ToArray());
|
||||
|
||||
writeAttribute("Ruleset", ruleset.ShortName);
|
||||
writeAttribute("Stars", attributes.StarRating.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
switch (attributes)
|
||||
{
|
||||
case OsuDifficultyAttributes osu:
|
||||
writeAttribute("Aim", osu.AimStrain.ToString(CultureInfo.InvariantCulture));
|
||||
writeAttribute("Speed", osu.SpeedStrain.ToString(CultureInfo.InvariantCulture));
|
||||
writeAttribute("MaxCombo", osu.MaxCombo.ToString(CultureInfo.InvariantCulture));
|
||||
writeAttribute("AR", osu.ApproachRate.ToString(CultureInfo.InvariantCulture));
|
||||
writeAttribute("OD", osu.OverallDifficulty.ToString(CultureInfo.InvariantCulture));
|
||||
break;
|
||||
case TaikoDifficultyAttributes taiko:
|
||||
writeAttribute("HitWindow", taiko.GreatHitWindow.ToString(CultureInfo.InvariantCulture));
|
||||
writeAttribute("MaxCombo", taiko.MaxCombo.ToString(CultureInfo.InvariantCulture));
|
||||
break;
|
||||
case CatchDifficultyAttributes c:
|
||||
writeAttribute("MaxCombo", c.MaxCombo.ToString(CultureInfo.InvariantCulture));
|
||||
writeAttribute("AR", c.ApproachRate.ToString(CultureInfo.InvariantCulture));
|
||||
break;
|
||||
case ManiaDifficultyAttributes mania:
|
||||
writeAttribute("HitWindow", mania.GreatHitWindow.ToString(CultureInfo.InvariantCulture));
|
||||
break;
|
||||
}
|
||||
|
||||
command.Console.WriteLine();
|
||||
}
|
||||
|
||||
private void writeAttribute(string name, string value) => command.Console.WriteLine($"{name.PadRight(15)}: {value}");
|
||||
|
||||
private List<Mod> getMods(Ruleset ruleset)
|
||||
{
|
||||
var mods = new List<Mod>();
|
||||
if (command.Mods == null)
|
||||
return mods;
|
||||
|
||||
var availableMods = ruleset.GetAllMods().ToList();
|
||||
foreach (var modString in command.Mods)
|
||||
{
|
||||
Mod newMod = availableMods.FirstOrDefault(m => string.Equals(m.Acronym, modString, StringComparison.CurrentCultureIgnoreCase));
|
||||
if (newMod == null)
|
||||
throw new ArgumentException($"Invalid mod provided: {modString}");
|
||||
mods.Add(newMod);
|
||||
}
|
||||
|
||||
return mods;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-tools/master/LICENCE
|
||||
|
||||
namespace PerformanceCalculator
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for the processors of all <see cref="ProcessorCommand"/>.
|
||||
/// </summary>
|
||||
public interface IProcessor
|
||||
{
|
||||
/// <summary>
|
||||
/// Processes the command.
|
||||
/// </summary>
|
||||
void Execute();
|
||||
}
|
||||
}
|
|
@ -1,9 +1,14 @@
|
|||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-tools/master/LICENCE
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using McMaster.Extensions.CommandLineUtils;
|
||||
using osu.Game.Scoring;
|
||||
|
||||
namespace PerformanceCalculator.Performance
|
||||
{
|
||||
|
@ -19,7 +24,38 @@ namespace PerformanceCalculator.Performance
|
|||
[FileExists]
|
||||
[Option(Template = "-r|--replay <file>", Description = "One for each replay. The replay file.")]
|
||||
public string[] Replays { get; }
|
||||
|
||||
public override void Execute()
|
||||
{
|
||||
var workingBeatmap = new ProcessorWorkingBeatmap(Beatmap);
|
||||
var scoreParser = new ProcessorScoreParser(workingBeatmap);
|
||||
|
||||
protected override IProcessor CreateProcessor() => new PerformanceProcessor(this);
|
||||
foreach (var f in Replays)
|
||||
{
|
||||
Score score;
|
||||
using (var stream = File.OpenRead(f))
|
||||
score = scoreParser.Parse(stream);
|
||||
|
||||
workingBeatmap.Mods.Value = score.ScoreInfo.Mods;
|
||||
|
||||
// Convert + process beatmap
|
||||
var categoryAttribs = new Dictionary<string, double>();
|
||||
double pp = score.ScoreInfo.Ruleset.CreateInstance().CreatePerformanceCalculator(workingBeatmap, score.ScoreInfo).Calculate(categoryAttribs);
|
||||
|
||||
Console.WriteLine(f);
|
||||
writeAttribute("Player", score.ScoreInfo.User.Username);
|
||||
writeAttribute("Mods", score.ScoreInfo.Mods.Length > 0
|
||||
? score.ScoreInfo.Mods.Select(m => m.Acronym).Aggregate((c, n) => $"{c}, {n}")
|
||||
: "None");
|
||||
|
||||
foreach (var kvp in categoryAttribs)
|
||||
writeAttribute(kvp.Key, kvp.Value.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
writeAttribute("pp", pp.ToString(CultureInfo.InvariantCulture));
|
||||
Console.WriteLine();
|
||||
}
|
||||
}
|
||||
|
||||
private void writeAttribute(string name, string value) => Console.WriteLine($"{name.PadRight(15)}: {value}");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,55 +0,0 @@
|
|||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-tools/master/LICENCE
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using McMaster.Extensions.CommandLineUtils;
|
||||
using osu.Game.Scoring;
|
||||
|
||||
namespace PerformanceCalculator.Performance
|
||||
{
|
||||
public class PerformanceProcessor : IProcessor
|
||||
{
|
||||
private readonly PerformanceCommand command;
|
||||
|
||||
public PerformanceProcessor(PerformanceCommand command)
|
||||
{
|
||||
this.command = command;
|
||||
}
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
var workingBeatmap = new ProcessorWorkingBeatmap(command.Beatmap);
|
||||
var scoreParser = new ProcessorScoreParser(workingBeatmap);
|
||||
|
||||
foreach (var f in command.Replays)
|
||||
{
|
||||
Score score;
|
||||
using (var stream = File.OpenRead(f))
|
||||
score = scoreParser.Parse(stream);
|
||||
|
||||
workingBeatmap.Mods.Value = score.ScoreInfo.Mods;
|
||||
|
||||
// Convert + process beatmap
|
||||
var categoryAttribs = new Dictionary<string, double>();
|
||||
double pp = score.ScoreInfo.Ruleset.CreateInstance().CreatePerformanceCalculator(workingBeatmap, score.ScoreInfo).Calculate(categoryAttribs);
|
||||
|
||||
command.Console.WriteLine(f);
|
||||
writeAttribute("Player", score.ScoreInfo.User.Username);
|
||||
writeAttribute("Mods", score.ScoreInfo.Mods.Length > 0
|
||||
? score.ScoreInfo.Mods.Select(m => m.Acronym).Aggregate((c, n) => $"{c}, {n}")
|
||||
: "None");
|
||||
|
||||
foreach (var kvp in categoryAttribs)
|
||||
writeAttribute(kvp.Key, kvp.Value.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
writeAttribute("pp", pp.ToString(CultureInfo.InvariantCulture));
|
||||
command.Console.WriteLine();
|
||||
}
|
||||
}
|
||||
|
||||
private void writeAttribute(string name, string value) => command.Console.WriteLine($"{name.PadRight(15)}: {value}");
|
||||
}
|
||||
}
|
|
@ -1,12 +1,15 @@
|
|||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-tools/master/LICENCE
|
||||
|
||||
using System.IO;
|
||||
using Alba.CsConsoleFormat;
|
||||
using JetBrains.Annotations;
|
||||
using McMaster.Extensions.CommandLineUtils;
|
||||
|
||||
namespace PerformanceCalculator
|
||||
{
|
||||
public abstract class ProcessorCommand : CommandBase
|
||||
[HelpOption("-?|-h|--help")]
|
||||
public abstract class ProcessorCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// The console.
|
||||
|
@ -20,13 +23,32 @@ namespace PerformanceCalculator
|
|||
public void OnExecute(CommandLineApplication app, IConsole console)
|
||||
{
|
||||
Console = console;
|
||||
CreateProcessor().Execute();
|
||||
Execute();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the <see cref="IProcessor"/> to process this <see cref="ProcessorCommand"/>.
|
||||
/// </summary>
|
||||
/// <returns>The <see cref="IProcessor"/>.</returns>
|
||||
protected abstract IProcessor CreateProcessor();
|
||||
public void OutputDocument(Document document)
|
||||
{
|
||||
// todo: make usable by other command
|
||||
using (var writer = new StringWriter())
|
||||
{
|
||||
ConsoleRenderer.RenderDocumentToText(document, new TextRenderTarget(writer));
|
||||
|
||||
var str = writer.GetStringBuilder().ToString();
|
||||
|
||||
var lines = str.Split('\n');
|
||||
for (int i = 0; i < lines.Length; i++)
|
||||
lines[i] = lines[i].TrimEnd();
|
||||
str = string.Join('\n', lines);
|
||||
|
||||
Console.Write(str);
|
||||
if (OutputFile != null)
|
||||
File.WriteAllText(OutputFile, str);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public virtual void Execute()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,19 @@
|
|||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-tools/master/LICENCE
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Alba.CsConsoleFormat;
|
||||
using JetBrains.Annotations;
|
||||
using McMaster.Extensions.CommandLineUtils;
|
||||
using osu.Framework.IO.Network;
|
||||
using osu.Game.Beatmaps.Legacy;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
|
||||
namespace PerformanceCalculator.Profile
|
||||
{
|
||||
|
@ -25,6 +35,103 @@ namespace PerformanceCalculator.Profile
|
|||
[AllowedValues("0", "1", "2", "3")]
|
||||
public int? Ruleset { get; }
|
||||
|
||||
protected override IProcessor CreateProcessor() => new ProfileProcessor(this);
|
||||
private const string base_url = "https://osu.ppy.sh";
|
||||
|
||||
public override void Execute()
|
||||
{
|
||||
//initializing pp-information-holding sorted list
|
||||
var displayPlays = new List<UserPlayInfo>();
|
||||
//initialize the information from the top 100 plays, held in a dynamic
|
||||
|
||||
var ruleset = LegacyHelper.GetRulesetFromLegacyID(Ruleset ?? 0);
|
||||
|
||||
foreach (var play in getJsonFromApi($"get_user_best?k={Key}&u={ProfileName}&m={Ruleset}&limit=100&type=username"))
|
||||
{
|
||||
string beatmapID = play.beatmap_id;
|
||||
|
||||
string cachePath = Path.Combine("cache", $"{beatmapID}.osu");
|
||||
if (!File.Exists(cachePath))
|
||||
new FileWebRequest(cachePath, $"{base_url}/osu/{beatmapID}").Perform();
|
||||
|
||||
Mod[] mods = ruleset.ConvertLegacyMods((LegacyMods)play.enabled_mods).ToArray();
|
||||
|
||||
var working = new ProcessorWorkingBeatmap(cachePath, (int)play.beatmap_id) { Mods = { Value = mods } };
|
||||
|
||||
var score = new ProcessorScoreParser(working).Parse(new ScoreInfo
|
||||
{
|
||||
Ruleset = ruleset.RulesetInfo,
|
||||
MaxCombo = play.maxcombo,
|
||||
Mods = mods,
|
||||
Statistics = new Dictionary<HitResult, int>
|
||||
{
|
||||
{ HitResult.Perfect, 0 },
|
||||
{ HitResult.Great, (int)play.count300 },
|
||||
{ HitResult.Good, (int)play.count100 },
|
||||
{ HitResult.Ok, 0 },
|
||||
{ HitResult.Meh, (int)play.count50 },
|
||||
{ HitResult.Miss, (int)play.countmiss }
|
||||
}
|
||||
});
|
||||
|
||||
var thisPlay = new UserPlayInfo
|
||||
{
|
||||
Beatmap = working.BeatmapInfo,
|
||||
LocalPP = ruleset.CreatePerformanceCalculator(working, score.ScoreInfo).Calculate(),
|
||||
LivePP = play.pp,
|
||||
Mods = mods.Length > 0 ? mods.Select(m => m.Acronym).Aggregate((c, n) => $"{c}, {n}") : "None"
|
||||
};
|
||||
|
||||
displayPlays.Add(thisPlay);
|
||||
}
|
||||
|
||||
dynamic userData = getJsonFromApi($"get_user?k={Key}&u={ProfileName}&m={Ruleset}&type=username")[0];
|
||||
|
||||
var localOrdered = displayPlays.OrderByDescending(p => p.LocalPP).ToList();
|
||||
var liveOrdered = displayPlays.OrderByDescending(p => p.LivePP).ToList();
|
||||
|
||||
int index = 0;
|
||||
double totalLocalPP = localOrdered.Sum(play => Math.Pow(0.95, index++) * play.LocalPP);
|
||||
double totalLivePP = userData.pp_raw;
|
||||
|
||||
index = 0;
|
||||
double nonBonusLivePP = liveOrdered.Sum(play => Math.Pow(0.95, index++) * play.LivePP);
|
||||
|
||||
//todo: implement properly. this is pretty damn wrong.
|
||||
var playcountBonusPP = (totalLivePP - nonBonusLivePP);
|
||||
totalLocalPP += playcountBonusPP;
|
||||
|
||||
OutputDocument(new Document(
|
||||
new Span($"User: {userData.username}"), "\n",
|
||||
new Span($"Live PP: {totalLivePP:F1} (including {playcountBonusPP:F1}pp from playcount)"), "\n",
|
||||
new Span($"Local PP: {totalLocalPP:F1}"), "\n",
|
||||
new Grid
|
||||
{
|
||||
Columns = { GridLength.Auto, GridLength.Auto, GridLength.Auto, GridLength.Auto, GridLength.Auto },
|
||||
Children =
|
||||
{
|
||||
new Cell("beatmap"),
|
||||
new Cell("live pp"),
|
||||
new Cell("local pp"),
|
||||
new Cell("pp change"),
|
||||
new Cell("position change"),
|
||||
localOrdered.Select(item => new[]
|
||||
{
|
||||
new Cell($"{item.Beatmap.OnlineBeatmapID} - {item.Beatmap}"),
|
||||
new Cell($"{item.LivePP:F1}") { Align = Align.Right },
|
||||
new Cell($"{item.LocalPP:F1}") { Align = Align.Right },
|
||||
new Cell($"{item.LocalPP - item.LivePP:F1}") { Align = Align.Right },
|
||||
new Cell($"{liveOrdered.IndexOf(item) - localOrdered.IndexOf(item):+0;-0;-}") { Align = Align.Center },
|
||||
})
|
||||
}
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
private dynamic getJsonFromApi(string request)
|
||||
{
|
||||
var req = new JsonWebRequest<dynamic>($"{base_url}/api/{request}");
|
||||
req.Perform();
|
||||
return req.ResponseObject;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,145 +0,0 @@
|
|||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-tools/master/LICENCE
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Alba.CsConsoleFormat;
|
||||
using osu.Framework.IO.Network;
|
||||
using osu.Game.Beatmaps.Legacy;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
|
||||
namespace PerformanceCalculator.Profile
|
||||
{
|
||||
public class ProfileProcessor : IProcessor
|
||||
{
|
||||
private readonly ProfileCommand command;
|
||||
|
||||
private const string base_url = "https://osu.ppy.sh";
|
||||
|
||||
public ProfileProcessor(ProfileCommand command)
|
||||
{
|
||||
this.command = command;
|
||||
}
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
//initializing pp-information-holding sorted list
|
||||
var displayPlays = new List<UserPlayInfo>();
|
||||
//initialize the information from the top 100 plays, held in a dynamic
|
||||
|
||||
var ruleset = LegacyHelper.GetRulesetFromLegacyID(command.Ruleset ?? 0);
|
||||
|
||||
foreach (var play in getJsonFromApi($"get_user_best?k={command.Key}&u={command.ProfileName}&m={command.Ruleset}&limit=100&type=username"))
|
||||
{
|
||||
string beatmapID = play.beatmap_id;
|
||||
|
||||
string cachePath = Path.Combine("cache", $"{beatmapID}.osu");
|
||||
if (!File.Exists(cachePath))
|
||||
new FileWebRequest(cachePath, $"{base_url}/osu/{beatmapID}").Perform();
|
||||
|
||||
Mod[] mods = ruleset.ConvertLegacyMods((LegacyMods)play.enabled_mods).ToArray();
|
||||
|
||||
var working = new ProcessorWorkingBeatmap(cachePath, (int)play.beatmap_id) { Mods = { Value = mods } };
|
||||
|
||||
var score = new ProcessorScoreParser(working).Parse(new ScoreInfo
|
||||
{
|
||||
Ruleset = ruleset.RulesetInfo,
|
||||
MaxCombo = play.maxcombo,
|
||||
Mods = mods,
|
||||
Statistics = new Dictionary<HitResult, int>
|
||||
{
|
||||
{ HitResult.Perfect, 0 },
|
||||
{ HitResult.Great, (int)play.count300 },
|
||||
{ HitResult.Good, (int)play.count100 },
|
||||
{ HitResult.Ok, 0 },
|
||||
{ HitResult.Meh, (int)play.count50 },
|
||||
{ HitResult.Miss, (int)play.countmiss }
|
||||
}
|
||||
});
|
||||
|
||||
var thisPlay = new UserPlayInfo
|
||||
{
|
||||
Beatmap = working.BeatmapInfo,
|
||||
LocalPP = ruleset.CreatePerformanceCalculator(working, score.ScoreInfo).Calculate(),
|
||||
LivePP = play.pp,
|
||||
Mods = mods.Length > 0 ? mods.Select(m => m.Acronym).Aggregate((c, n) => $"{c}, {n}") : "None"
|
||||
};
|
||||
|
||||
displayPlays.Add(thisPlay);
|
||||
}
|
||||
|
||||
dynamic userData = getJsonFromApi($"get_user?k={command.Key}&u={command.ProfileName}&m={command.Ruleset}&type=username")[0];
|
||||
|
||||
var localOrdered = displayPlays.OrderByDescending(p => p.LocalPP).ToList();
|
||||
var liveOrdered = displayPlays.OrderByDescending(p => p.LivePP).ToList();
|
||||
|
||||
int index = 0;
|
||||
double totalLocalPP = localOrdered.Sum(play => Math.Pow(0.95, index++) * play.LocalPP);
|
||||
double totalLivePP = userData.pp_raw;
|
||||
|
||||
index = 0;
|
||||
double nonBonusLivePP = liveOrdered.Sum(play => Math.Pow(0.95, index++) * play.LivePP);
|
||||
|
||||
//todo: implement properly. this is pretty damn wrong.
|
||||
var playcountBonusPP = (totalLivePP - nonBonusLivePP);
|
||||
totalLocalPP += playcountBonusPP;
|
||||
|
||||
outputDocument(new Document(
|
||||
new Span($"User: {userData.username}"), "\n",
|
||||
new Span($"Live PP: {totalLivePP:F1} (including {playcountBonusPP:F1}pp from playcount)"), "\n",
|
||||
new Span($"Local PP: {totalLocalPP:F1}"), "\n",
|
||||
new Grid
|
||||
{
|
||||
Columns = { GridLength.Auto, GridLength.Auto, GridLength.Auto, GridLength.Auto, GridLength.Auto },
|
||||
Children =
|
||||
{
|
||||
new Cell("beatmap"),
|
||||
new Cell("live pp"),
|
||||
new Cell("local pp"),
|
||||
new Cell("pp change"),
|
||||
new Cell("position change"),
|
||||
localOrdered.Select(item => new[]
|
||||
{
|
||||
new Cell($"{item.Beatmap.OnlineBeatmapID} - {item.Beatmap}"),
|
||||
new Cell($"{item.LivePP:F1}") { Align = Align.Right },
|
||||
new Cell($"{item.LocalPP:F1}") { Align = Align.Right },
|
||||
new Cell($"{item.LocalPP - item.LivePP:F1}") { Align = Align.Right },
|
||||
new Cell($"{liveOrdered.IndexOf(item) - localOrdered.IndexOf(item):+0;-0;-}") { Align = Align.Center },
|
||||
})
|
||||
}
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
private void outputDocument(Document document)
|
||||
{
|
||||
// todo: make usable by other command
|
||||
using (var writer = new StringWriter())
|
||||
{
|
||||
ConsoleRenderer.RenderDocumentToText(document, new TextRenderTarget(writer));
|
||||
|
||||
var str = writer.GetStringBuilder().ToString();
|
||||
|
||||
var lines = str.Split('\n');
|
||||
for (int i = 0; i < lines.Length; i++)
|
||||
lines[i] = lines[i].TrimEnd();
|
||||
str = string.Join('\n', lines);
|
||||
|
||||
Console.Write(str);
|
||||
if (command.OutputFile != null)
|
||||
File.WriteAllText(command.OutputFile, str);
|
||||
}
|
||||
}
|
||||
|
||||
private dynamic getJsonFromApi(string request)
|
||||
{
|
||||
var req = new JsonWebRequest<dynamic>($"{base_url}/api/{request}");
|
||||
req.Perform();
|
||||
return req.ResponseObject;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,8 +15,9 @@ namespace PerformanceCalculator
|
|||
[Subcommand("difficulty", typeof(DifficultyCommand))]
|
||||
[Subcommand("performance", typeof(PerformanceCommand))]
|
||||
[Subcommand("profile", typeof(ProfileCommand))]
|
||||
[Subcommand("simulate", typeof(SimulateCommand))]
|
||||
public class Program : CommandBase
|
||||
[Subcommand("simulate", typeof(SimulateListing))]
|
||||
[HelpOption("-?|-h|--help")]
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-tools/master/LICENCE
|
||||
|
||||
using JetBrains.Annotations;
|
||||
using osu.Game.Rulesets;
|
||||
|
||||
namespace PerformanceCalculator.Simulate
|
||||
{
|
||||
public abstract class BaseSimulateCommand : ProcessorCommand
|
||||
{
|
||||
public abstract string Beatmap { get; }
|
||||
|
||||
public abstract Ruleset Ruleset { get; }
|
||||
|
||||
[UsedImplicitly]
|
||||
public virtual double Accuracy { get; }
|
||||
|
||||
[UsedImplicitly]
|
||||
public virtual int? Combo { get; }
|
||||
|
||||
[UsedImplicitly]
|
||||
public virtual double PercentCombo { get; }
|
||||
|
||||
[UsedImplicitly]
|
||||
public virtual int Score { get; }
|
||||
|
||||
[UsedImplicitly]
|
||||
public virtual string[] Mods { get; }
|
||||
|
||||
[UsedImplicitly]
|
||||
public virtual int Misses { get; }
|
||||
|
||||
[UsedImplicitly]
|
||||
public virtual int? Mehs { get; }
|
||||
|
||||
[UsedImplicitly]
|
||||
public virtual int? Goods { get; }
|
||||
}
|
||||
}
|
|
@ -1,97 +0,0 @@
|
|||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-tools/master/LICENCE
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using McMaster.Extensions.CommandLineUtils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
|
||||
namespace PerformanceCalculator.Simulate
|
||||
{
|
||||
public abstract class BaseSimulateProcessor : IProcessor
|
||||
{
|
||||
private readonly BaseSimulateCommand command;
|
||||
|
||||
protected BaseSimulateProcessor(BaseSimulateCommand command)
|
||||
{
|
||||
this.command = command;
|
||||
}
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
var ruleset = command.Ruleset;
|
||||
|
||||
var mods = getMods(ruleset).ToArray();
|
||||
|
||||
var workingBeatmap = new ProcessorWorkingBeatmap(command.Beatmap);
|
||||
workingBeatmap.Mods.Value = mods;
|
||||
|
||||
var beatmap = workingBeatmap.GetPlayableBeatmap(ruleset.RulesetInfo);
|
||||
|
||||
var beatmapMaxCombo = GetMaxCombo(beatmap);
|
||||
var maxCombo = command.Combo ?? (int)Math.Round(command.PercentCombo / 100 * beatmapMaxCombo);
|
||||
var statistics = GenerateHitResults(command.Accuracy / 100, beatmap, command.Misses, command.Mehs, command.Goods);
|
||||
var score = command.Score;
|
||||
var accuracy = GetAccuracy(statistics);
|
||||
|
||||
var scoreInfo = new ScoreInfo
|
||||
{
|
||||
Accuracy = accuracy,
|
||||
MaxCombo = maxCombo,
|
||||
Statistics = statistics,
|
||||
Mods = mods,
|
||||
TotalScore = score
|
||||
};
|
||||
|
||||
var categoryAttribs = new Dictionary<string, double>();
|
||||
double pp = ruleset.CreatePerformanceCalculator(workingBeatmap, scoreInfo).Calculate(categoryAttribs);
|
||||
|
||||
command.Console.WriteLine(workingBeatmap.BeatmapInfo.ToString());
|
||||
|
||||
WritePlayInfo(scoreInfo, beatmap);
|
||||
|
||||
WriteAttribute("Mods", mods.Length > 0
|
||||
? mods.Select(m => m.Acronym).Aggregate((c, n) => $"{c}, {n}")
|
||||
: "None");
|
||||
|
||||
foreach (var kvp in categoryAttribs)
|
||||
WriteAttribute(kvp.Key, kvp.Value.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
WriteAttribute("pp", pp.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
private List<Mod> getMods(Ruleset ruleset)
|
||||
{
|
||||
var mods = new List<Mod>();
|
||||
if (command.Mods == null)
|
||||
return mods;
|
||||
|
||||
var availableMods = ruleset.GetAllMods().ToList();
|
||||
foreach (var modString in command.Mods)
|
||||
{
|
||||
Mod newMod = availableMods.FirstOrDefault(m => string.Equals(m.Acronym, modString, StringComparison.CurrentCultureIgnoreCase));
|
||||
if (newMod == null)
|
||||
throw new ArgumentException($"Invalid mod provided: {modString}");
|
||||
mods.Add(newMod);
|
||||
}
|
||||
|
||||
return mods;
|
||||
}
|
||||
|
||||
protected abstract void WritePlayInfo(ScoreInfo scoreInfo, IBeatmap beatmap);
|
||||
|
||||
protected abstract int GetMaxCombo(IBeatmap beatmap);
|
||||
|
||||
protected abstract Dictionary<HitResult, int> GenerateHitResults(double accuracy, IBeatmap beatmap, int countMiss, int? countMeh, int? countGood);
|
||||
|
||||
protected virtual double GetAccuracy(Dictionary<HitResult, int> statistics) => 0;
|
||||
|
||||
protected void WriteAttribute(string name, string value) => command.Console.WriteLine($"{name.PadRight(15)}: {value}");
|
||||
}
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-tools/master/LICENCE
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
|
||||
namespace PerformanceCalculator.Simulate.Mania
|
||||
{
|
||||
public class ManiaSimulateProcessor : BaseSimulateProcessor
|
||||
{
|
||||
protected override int GetMaxCombo(IBeatmap beatmap) => 0;
|
||||
|
||||
protected override Dictionary<HitResult, int> GenerateHitResults(double accuracy, IBeatmap beatmap, int countMiss, int? countMeh, int? countGood)
|
||||
{
|
||||
var totalHits = beatmap.HitObjects.Count;
|
||||
|
||||
// Only total number of hits is considered currently, so specifics don't matter
|
||||
return new Dictionary<HitResult, int>
|
||||
{
|
||||
{ HitResult.Perfect, totalHits },
|
||||
{ HitResult.Great, 0 },
|
||||
{ HitResult.Ok, 0 },
|
||||
{ HitResult.Good, 0 },
|
||||
{ HitResult.Meh, 0 },
|
||||
{ HitResult.Miss, 0 }
|
||||
};
|
||||
}
|
||||
|
||||
protected override void WritePlayInfo(ScoreInfo scoreInfo, IBeatmap beatmap)
|
||||
{
|
||||
WriteAttribute("Score", scoreInfo.TotalScore.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
public ManiaSimulateProcessor(BaseSimulateCommand command)
|
||||
: base(command)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,16 +1,21 @@
|
|||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-tools/master/LICENCE
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Globalization;
|
||||
using JetBrains.Annotations;
|
||||
using McMaster.Extensions.CommandLineUtils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mania;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
|
||||
namespace PerformanceCalculator.Simulate.Mania
|
||||
namespace PerformanceCalculator.Simulate
|
||||
{
|
||||
[Command(Name = "simulate mania", Description = "Computes the performance (pp) of a simulated osu!mania play.")]
|
||||
public class ManiaSimulateCommand : BaseSimulateCommand
|
||||
public class ManiaSimulateCommand : SimulateCommand
|
||||
{
|
||||
[UsedImplicitly]
|
||||
[Required, FileExists]
|
||||
|
@ -28,6 +33,27 @@ namespace PerformanceCalculator.Simulate.Mania
|
|||
|
||||
public override Ruleset Ruleset => new ManiaRuleset();
|
||||
|
||||
protected override IProcessor CreateProcessor() => new ManiaSimulateProcessor(this);
|
||||
protected override int GetMaxCombo(IBeatmap beatmap) => 0;
|
||||
|
||||
protected override Dictionary<HitResult, int> GenerateHitResults(double accuracy, IBeatmap beatmap, int countMiss, int? countMeh, int? countGood)
|
||||
{
|
||||
var totalHits = beatmap.HitObjects.Count;
|
||||
|
||||
// Only total number of hits is considered currently, so specifics don't matter
|
||||
return new Dictionary<HitResult, int>
|
||||
{
|
||||
{ HitResult.Perfect, totalHits },
|
||||
{ HitResult.Great, 0 },
|
||||
{ HitResult.Ok, 0 },
|
||||
{ HitResult.Good, 0 },
|
||||
{ HitResult.Meh, 0 },
|
||||
{ HitResult.Miss, 0 }
|
||||
};
|
||||
}
|
||||
|
||||
protected override void WritePlayInfo(ScoreInfo scoreInfo, IBeatmap beatmap)
|
||||
{
|
||||
WriteAttribute("Score", scoreInfo.TotalScore.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-tools/master/LICENCE
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using JetBrains.Annotations;
|
||||
using McMaster.Extensions.CommandLineUtils;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
|
||||
namespace PerformanceCalculator.Simulate.Osu
|
||||
{
|
||||
[Command(Name = "simulate osu", Description = "Computes the performance (pp) of a simulated osu! play.")]
|
||||
public class OsuSimulateCommand : BaseSimulateCommand
|
||||
{
|
||||
[UsedImplicitly]
|
||||
[Required, FileExists]
|
||||
[Argument(0, Name = "beatmap", Description = "Required. The beatmap file (.osu).")]
|
||||
public override string Beatmap { get; }
|
||||
|
||||
[UsedImplicitly]
|
||||
[Option(Template = "-a|--accuracy <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 = "-c|--combo <combo>", Description = "Maximum combo during play. Defaults to beatmap maximum.")]
|
||||
public override int? Combo { get; }
|
||||
|
||||
[UsedImplicitly]
|
||||
[Option(Template = "-C|--percent-combo <combo>", Description = "Percentage of beatmap maximum combo achieved. Alternative to combo option."
|
||||
+ " Enter as decimal 0-100.")]
|
||||
public override double PercentCombo { get; } = 100;
|
||||
|
||||
[UsedImplicitly]
|
||||
[Option(CommandOptionType.MultipleValue, Template = "-m|--mod <mod>", Description = "One for each mod. The mods to compute the performance with."
|
||||
+ " Values: hr, dt, hd, fl, ez, etc...")]
|
||||
public override string[] Mods { get; }
|
||||
|
||||
[UsedImplicitly]
|
||||
[Option(Template = "-X|--misses <misses>", Description = "Number of misses. Defaults to 0.")]
|
||||
public override int Misses { get; }
|
||||
|
||||
[UsedImplicitly]
|
||||
[Option(Template = "-M|--mehs <mehs>", Description = "Number of mehs. Will override accuracy if used. Otherwise is automatically calculated.")]
|
||||
public override int? Mehs { get; }
|
||||
|
||||
[UsedImplicitly]
|
||||
[Option(Template = "-G|--goods <goods>", Description = "Number of goods. Will override accuracy if used. Otherwise is automatically calculated.")]
|
||||
public override int? Goods { get; }
|
||||
|
||||
public override Ruleset Ruleset => new OsuRuleset();
|
||||
|
||||
protected override IProcessor CreateProcessor() => new OsuSimulateProcessor(this);
|
||||
}
|
||||
}
|
|
@ -3,17 +3,61 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using McMaster.Extensions.CommandLineUtils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
|
||||
namespace PerformanceCalculator.Simulate.Osu
|
||||
namespace PerformanceCalculator.Simulate
|
||||
{
|
||||
public class OsuSimulateProcessor : BaseSimulateProcessor
|
||||
[Command(Name = "simulate osu", Description = "Computes the performance (pp) of a simulated osu! play.")]
|
||||
public class OsuSimulateCommand : SimulateCommand
|
||||
{
|
||||
[UsedImplicitly]
|
||||
[Required, FileExists]
|
||||
[Argument(0, Name = "beatmap", Description = "Required. The beatmap file (.osu).")]
|
||||
public override string Beatmap { get; }
|
||||
|
||||
[UsedImplicitly]
|
||||
[Option(Template = "-a|--accuracy <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 = "-c|--combo <combo>", Description = "Maximum combo during play. Defaults to beatmap maximum.")]
|
||||
public override int? Combo { get; }
|
||||
|
||||
[UsedImplicitly]
|
||||
[Option(Template = "-C|--percent-combo <combo>", Description = "Percentage of beatmap maximum combo achieved. Alternative to combo option."
|
||||
+ " Enter as decimal 0-100.")]
|
||||
public override double PercentCombo { get; } = 100;
|
||||
|
||||
[UsedImplicitly]
|
||||
[Option(CommandOptionType.MultipleValue, Template = "-m|--mod <mod>", Description = "One for each mod. The mods to compute the performance with."
|
||||
+ " Values: hr, dt, hd, fl, ez, etc...")]
|
||||
public override string[] Mods { get; }
|
||||
|
||||
[UsedImplicitly]
|
||||
[Option(Template = "-X|--misses <misses>", Description = "Number of misses. Defaults to 0.")]
|
||||
public override int Misses { get; }
|
||||
|
||||
[UsedImplicitly]
|
||||
[Option(Template = "-M|--mehs <mehs>", Description = "Number of mehs. Will override accuracy if used. Otherwise is automatically calculated.")]
|
||||
public override int? Mehs { get; }
|
||||
|
||||
[UsedImplicitly]
|
||||
[Option(Template = "-G|--goods <goods>", Description = "Number of goods. Will override accuracy if used. Otherwise is automatically calculated.")]
|
||||
public override int? Goods { get; }
|
||||
|
||||
public override Ruleset Ruleset => new OsuRuleset();
|
||||
|
||||
protected override int GetMaxCombo(IBeatmap beatmap) => beatmap.HitObjects.Count + beatmap.HitObjects.OfType<Slider>().Sum(s => s.NestedHitObjects.Count - 1);
|
||||
|
||||
protected override Dictionary<HitResult, int> GenerateHitResults(double accuracy, IBeatmap beatmap, int countMiss, int? countMeh, int? countGood)
|
||||
|
@ -72,10 +116,5 @@ namespace PerformanceCalculator.Simulate.Osu
|
|||
WriteAttribute(Enum.GetName(typeof(HitResult), statistic.Key), statistic.Value.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
}
|
||||
|
||||
public OsuSimulateProcessor(BaseSimulateCommand command)
|
||||
: base(command)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,24 +1,119 @@
|
|||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-tools/master/LICENCE
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using McMaster.Extensions.CommandLineUtils;
|
||||
using PerformanceCalculator.Simulate.Mania;
|
||||
using PerformanceCalculator.Simulate.Osu;
|
||||
using PerformanceCalculator.Simulate.Taiko;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
|
||||
namespace PerformanceCalculator.Simulate
|
||||
{
|
||||
[Command(Name = "performance", Description = "Computes the performance (pp) of a simulated play.")]
|
||||
[Subcommand("osu", typeof(OsuSimulateCommand))]
|
||||
[Subcommand("taiko", typeof(TaikoSimulateCommand))]
|
||||
[Subcommand("mania", typeof(ManiaSimulateCommand))]
|
||||
public class SimulateCommand : CommandBase
|
||||
public abstract class SimulateCommand : ProcessorCommand
|
||||
{
|
||||
public int OnExecute(CommandLineApplication app, IConsole console)
|
||||
public abstract string Beatmap { get; }
|
||||
|
||||
public abstract Ruleset Ruleset { get; }
|
||||
|
||||
[UsedImplicitly]
|
||||
public virtual double Accuracy { get; }
|
||||
|
||||
[UsedImplicitly]
|
||||
public virtual int? Combo { get; }
|
||||
|
||||
[UsedImplicitly]
|
||||
public virtual double PercentCombo { get; }
|
||||
|
||||
[UsedImplicitly]
|
||||
public virtual int Score { get; }
|
||||
|
||||
[UsedImplicitly]
|
||||
public virtual string[] Mods { get; }
|
||||
|
||||
[UsedImplicitly]
|
||||
public virtual int Misses { get; }
|
||||
|
||||
[UsedImplicitly]
|
||||
public virtual int? Mehs { get; }
|
||||
|
||||
[UsedImplicitly]
|
||||
public virtual int? Goods { get; }
|
||||
|
||||
public override void Execute()
|
||||
{
|
||||
console.WriteLine("You must specify a subcommand.");
|
||||
app.ShowHelp();
|
||||
return 1;
|
||||
var ruleset = Ruleset;
|
||||
|
||||
var mods = getMods(ruleset).ToArray();
|
||||
|
||||
var workingBeatmap = new ProcessorWorkingBeatmap(Beatmap);
|
||||
workingBeatmap.Mods.Value = mods;
|
||||
|
||||
var beatmap = workingBeatmap.GetPlayableBeatmap(ruleset.RulesetInfo);
|
||||
|
||||
var beatmapMaxCombo = GetMaxCombo(beatmap);
|
||||
var maxCombo = Combo ?? (int)Math.Round(PercentCombo / 100 * beatmapMaxCombo);
|
||||
var statistics = GenerateHitResults(Accuracy / 100, beatmap, Misses, Mehs, Goods);
|
||||
var score = Score;
|
||||
var accuracy = GetAccuracy(statistics);
|
||||
|
||||
var scoreInfo = new ScoreInfo
|
||||
{
|
||||
Accuracy = accuracy,
|
||||
MaxCombo = maxCombo,
|
||||
Statistics = statistics,
|
||||
Mods = mods,
|
||||
TotalScore = score
|
||||
};
|
||||
|
||||
var categoryAttribs = new Dictionary<string, double>();
|
||||
double pp = ruleset.CreatePerformanceCalculator(workingBeatmap, scoreInfo).Calculate(categoryAttribs);
|
||||
|
||||
Console.WriteLine(workingBeatmap.BeatmapInfo.ToString());
|
||||
|
||||
WritePlayInfo(scoreInfo, beatmap);
|
||||
|
||||
WriteAttribute("Mods", mods.Length > 0
|
||||
? mods.Select(m => m.Acronym).Aggregate((c, n) => $"{c}, {n}")
|
||||
: "None");
|
||||
|
||||
foreach (var kvp in categoryAttribs)
|
||||
WriteAttribute(kvp.Key, kvp.Value.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
WriteAttribute("pp", pp.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
private List<Mod> getMods(Ruleset ruleset)
|
||||
{
|
||||
var mods = new List<Mod>();
|
||||
if (Mods == null)
|
||||
return mods;
|
||||
|
||||
var availableMods = ruleset.GetAllMods().ToList();
|
||||
foreach (var modString in Mods)
|
||||
{
|
||||
Mod newMod = availableMods.FirstOrDefault(m => string.Equals(m.Acronym, modString, StringComparison.CurrentCultureIgnoreCase));
|
||||
if (newMod == null)
|
||||
throw new ArgumentException($"Invalid mod provided: {modString}");
|
||||
mods.Add(newMod);
|
||||
}
|
||||
|
||||
return mods;
|
||||
}
|
||||
|
||||
protected abstract void WritePlayInfo(ScoreInfo scoreInfo, IBeatmap beatmap);
|
||||
|
||||
protected abstract int GetMaxCombo(IBeatmap beatmap);
|
||||
|
||||
protected abstract Dictionary<HitResult, int> GenerateHitResults(double accuracy, IBeatmap beatmap, int countMiss, int? countMeh, int? countGood);
|
||||
|
||||
protected virtual double GetAccuracy(Dictionary<HitResult, int> statistics) => 0;
|
||||
|
||||
protected void WriteAttribute(string name, string value) => Console.WriteLine($"{name.PadRight(15)}: {value}");
|
||||
}
|
||||
}
|
||||
|
|
23
PerformanceCalculator/Simulate/SimulateListingCommand.cs
Normal file
23
PerformanceCalculator/Simulate/SimulateListingCommand.cs
Normal file
|
@ -0,0 +1,23 @@
|
|||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-tools/master/LICENCE
|
||||
|
||||
using JetBrains.Annotations;
|
||||
using McMaster.Extensions.CommandLineUtils;
|
||||
|
||||
namespace PerformanceCalculator.Simulate
|
||||
{
|
||||
[Command(Name = "performance", Description = "Computes the performance (pp) of a simulated play.")]
|
||||
[Subcommand("osu", typeof(OsuSimulateCommand))]
|
||||
[Subcommand("taiko", typeof(TaikoSimulateCommand))]
|
||||
[Subcommand("mania", typeof(ManiaSimulateCommand))]
|
||||
public class SimulateListing
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public int OnExecute(CommandLineApplication app, IConsole console)
|
||||
{
|
||||
console.WriteLine("You must specify a subcommand.");
|
||||
app.ShowHelp();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-tools/master/LICENCE
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using JetBrains.Annotations;
|
||||
using McMaster.Extensions.CommandLineUtils;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Taiko;
|
||||
|
||||
namespace PerformanceCalculator.Simulate.Taiko
|
||||
{
|
||||
[Command(Name = "simulate taiko", Description = "Computes the performance (pp) of a simulated osu!taiko play.")]
|
||||
public class TaikoSimulateCommand : BaseSimulateCommand
|
||||
{
|
||||
[UsedImplicitly]
|
||||
[Required, FileExists]
|
||||
[Argument(0, Name = "beatmap", Description = "Required. The beatmap file (.osu).")]
|
||||
public override string Beatmap { get; }
|
||||
|
||||
[UsedImplicitly]
|
||||
[Option(Template = "-a|--accuracy <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 = "-c|--combo <combo>", Description = "Maximum combo during play. Defaults to beatmap maximum.")]
|
||||
public override int? Combo { get; }
|
||||
|
||||
[UsedImplicitly]
|
||||
[Option(Template = "-C|--percent-combo <combo>", Description = "Percentage of beatmap maximum combo achieved. Alternative to combo option."
|
||||
+ " Enter as decimal 0-100.")]
|
||||
public override double PercentCombo { get; } = 100;
|
||||
|
||||
[UsedImplicitly]
|
||||
[Option(CommandOptionType.MultipleValue, Template = "-m|--mod <mod>", Description = "One for each mod. The mods to compute the performance with."
|
||||
+ " Values: hr, dt, hd, fl, ez, etc...")]
|
||||
public override string[] Mods { get; }
|
||||
|
||||
[UsedImplicitly]
|
||||
[Option(Template = "-X|--misses <misses>", Description = "Number of misses. Defaults to 0.")]
|
||||
public override int Misses { get; }
|
||||
|
||||
[UsedImplicitly]
|
||||
[Option(Template = "-G|--goods <goods>", Description = "Number of goods. Will override accuracy if used. Otherwise is automatically calculated.")]
|
||||
public override int? Goods { get; }
|
||||
|
||||
public override Ruleset Ruleset => new TaikoRuleset();
|
||||
|
||||
protected override IProcessor CreateProcessor() => new TaikoSimulateProcessor(this);
|
||||
}
|
||||
}
|
|
@ -3,17 +3,57 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using McMaster.Extensions.CommandLineUtils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Taiko;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Scoring;
|
||||
|
||||
namespace PerformanceCalculator.Simulate.Taiko
|
||||
namespace PerformanceCalculator.Simulate
|
||||
{
|
||||
public class TaikoSimulateProcessor : BaseSimulateProcessor
|
||||
[Command(Name = "simulate taiko", Description = "Computes the performance (pp) of a simulated osu!taiko play.")]
|
||||
public class TaikoSimulateCommand : SimulateCommand
|
||||
{
|
||||
[UsedImplicitly]
|
||||
[Required, FileExists]
|
||||
[Argument(0, Name = "beatmap", Description = "Required. The beatmap file (.osu).")]
|
||||
public override string Beatmap { get; }
|
||||
|
||||
[UsedImplicitly]
|
||||
[Option(Template = "-a|--accuracy <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 = "-c|--combo <combo>", Description = "Maximum combo during play. Defaults to beatmap maximum.")]
|
||||
public override int? Combo { get; }
|
||||
|
||||
[UsedImplicitly]
|
||||
[Option(Template = "-C|--percent-combo <combo>", Description = "Percentage of beatmap maximum combo achieved. Alternative to combo option."
|
||||
+ " Enter as decimal 0-100.")]
|
||||
public override double PercentCombo { get; } = 100;
|
||||
|
||||
[UsedImplicitly]
|
||||
[Option(CommandOptionType.MultipleValue, Template = "-m|--mod <mod>", Description = "One for each mod. The mods to compute the performance with."
|
||||
+ " Values: hr, dt, hd, fl, ez, etc...")]
|
||||
public override string[] Mods { get; }
|
||||
|
||||
[UsedImplicitly]
|
||||
[Option(Template = "-X|--misses <misses>", Description = "Number of misses. Defaults to 0.")]
|
||||
public override int Misses { get; }
|
||||
|
||||
[UsedImplicitly]
|
||||
[Option(Template = "-G|--goods <goods>", Description = "Number of goods. Will override accuracy if used. Otherwise is automatically calculated.")]
|
||||
public override int? Goods { get; }
|
||||
|
||||
public override Ruleset Ruleset => new TaikoRuleset();
|
||||
|
||||
protected override int GetMaxCombo(IBeatmap beatmap) => beatmap.HitObjects.OfType<Hit>().Count();
|
||||
|
||||
protected override Dictionary<HitResult, int> GenerateHitResults(double accuracy, IBeatmap beatmap, int countMiss, int? countMeh, int? countGood)
|
||||
|
@ -62,10 +102,5 @@ namespace PerformanceCalculator.Simulate.Taiko
|
|||
WriteAttribute("Goods", scoreInfo.Statistics[HitResult.Good].ToString(CultureInfo.InvariantCulture));
|
||||
WriteAttribute("Greats", scoreInfo.Statistics[HitResult.Great].ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
public TaikoSimulateProcessor(BaseSimulateCommand command)
|
||||
: base(command)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue