1
0
Fork 0
mirror of https://github.com/ppy/osu-tools.git synced 2025-06-07 23:07:01 +09:00

Merge branch 'master' into freestyle-mods

This commit is contained in:
Dean Herbert 2025-05-30 21:54:52 +09:00
commit 8aa6173b90
No known key found for this signature in database
8 changed files with 190 additions and 48 deletions

View file

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Alba.CsConsoleFormat;
using JetBrains.Annotations;
using McMaster.Extensions.CommandLineUtils;
@ -27,6 +28,9 @@ namespace PerformanceCalculator.Profile
[AllowedValues("0", "1", "2", "3")]
public int? Ruleset { get; }
private const int max_api_scores = 200;
private const int max_api_scores_in_one_query = 100;
public override void Execute()
{
var displayPlays = new List<UserPlayInfo>();
@ -39,22 +43,28 @@ namespace PerformanceCalculator.Profile
Console.WriteLine("Getting user top scores...");
foreach (var play in GetJsonFromApi<List<SoloScoreInfo>>($"users/{userData.Id}/scores/best?mode={rulesetApiName}&limit=100"))
var apiScores = new List<SoloScoreInfo>();
for (int i = 0; i < max_api_scores; i += max_api_scores_in_one_query)
{
apiScores.AddRange(GetJsonFromApi<List<SoloScoreInfo>>($"users/{userData.Id}/scores/best?mode={rulesetApiName}&limit={max_api_scores_in_one_query}&offset={i}"));
Thread.Sleep(200);
}
foreach (var play in apiScores)
{
var working = ProcessorWorkingBeatmap.FromFileOrId(play.BeatmapID.ToString());
Mod[] mods = play.Mods.Select(x => x.ToMod(ruleset)).ToArray();
var scoreInfo = play.ToScoreInfo(mods);
var scoreInfo = play.ToScoreInfo(mods, working.BeatmapInfo);
scoreInfo.Ruleset = ruleset.RulesetInfo;
var score = new ProcessorScoreDecoder(working).Parse(scoreInfo);
var difficultyCalculator = ruleset.CreateDifficultyCalculator(working);
var difficultyAttributes = difficultyCalculator.Calculate(scoreInfo.Mods);
var performanceCalculator = ruleset.CreatePerformanceCalculator();
var ppAttributes = performanceCalculator?.Calculate(score.ScoreInfo, difficultyAttributes);
var ppAttributes = performanceCalculator?.Calculate(scoreInfo, difficultyAttributes);
var thisPlay = new UserPlayInfo
{
Beatmap = working.BeatmapInfo,

View file

@ -40,6 +40,10 @@ namespace PerformanceCalculator.Simulate
[Option(Template = "-X|--misses <misses>", Description = "Number of misses. Defaults to 0.")]
public int Misses { get; }
[UsedImplicitly]
[Option(Template = "-l|--legacy-total-score <score>", Description = "Amount of legacy total score.")]
public long? LegacyTotalScore { get; }
//
// Options implemented in the ruleset-specific commands
// -> Catch renames Mehs/Goods to (tiny-)droplets
@ -73,6 +77,7 @@ namespace PerformanceCalculator.Simulate
Accuracy = GetAccuracy(beatmap, statistics),
MaxCombo = Combo ?? (int)Math.Round(PercentCombo / 100 * beatmapMaxCombo),
Statistics = statistics,
LegacyTotalScore = LegacyTotalScore,
Mods = mods
};

View file

@ -0,0 +1,42 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Extensions.Color4Extensions;
using osu.Game.Graphics.UserInterface;
namespace PerformanceCalculatorGUI.Components.TextBoxes
{
public partial class ReadonlyOsuTextBox : OsuTextBox
{
private readonly string text;
private readonly bool hasBackground;
public ReadonlyOsuTextBox(string text, bool hasBackground = true)
{
this.text = text;
this.hasBackground = hasBackground;
Text = text;
}
protected override void LoadComplete()
{
if (!hasBackground)
BackgroundUnfocused = BackgroundUnfocused.Opacity(0);
base.LoadComplete();
}
protected override void OnUserTextAdded(string added)
{
NotifyInputError();
Text = text;
}
protected override void OnUserTextRemoved(string removed)
{
NotifyInputError();
Text = text;
}
}
}

View file

@ -24,7 +24,7 @@ namespace PerformanceCalculatorGUI
public Score Parse(ScoreInfo scoreInfo)
{
var score = new Score { ScoreInfo = scoreInfo };
score.ScoreInfo.LegacyTotalScore = score.ScoreInfo.TotalScore;
score.ScoreInfo.LegacyTotalScore ??= score.ScoreInfo.TotalScore;
PopulateMaximumStatistics(score.ScoreInfo, beatmap);
StandardisedScoreMigrationTools.UpdateFromLegacy(score.ScoreInfo, beatmap);
return score;

View file

@ -26,6 +26,7 @@ using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Components;
using osu.Game.Screens.Edit.Components.Timelines.Summary;
using osu.Game.Screens.Edit.Compose.Components.Timeline;
using osu.Game.Skinning;
using osuTK.Input;
namespace PerformanceCalculatorGUI.Screens.ObjectInspection
@ -133,7 +134,7 @@ namespace PerformanceCalculatorGUI.Screens.ObjectInspection
timeline = new Timeline(new TimelineBlueprintContainer())
}
},
rulesetContainer = new Container
rulesetContainer = new RulesetSkinProvidingContainer(rulesetInstance, playableBeatmap, null)
{
Origin = Anchor.TopRight,
Anchor = Anchor.TopRight,

View file

@ -74,6 +74,8 @@ namespace PerformanceCalculatorGUI.Screens
public override bool ShouldShowConfirmationDialogOnSwitch => false;
private const float username_container_height = 40;
private const int max_api_scores = 200;
private const int max_api_scores_in_one_query = 100;
public ProfileScreen()
{
@ -289,11 +291,17 @@ namespace PerformanceCalculatorGUI.Screens
Schedule(() => loadingLayer.Text.Value = $"Calculating {player.Username} top scores...");
var apiScores = await apiManager.GetJsonFromApi<List<SoloScoreInfo>>($"users/{player.OnlineID}/scores/best?mode={ruleset.Value.ShortName}&limit=100").ConfigureAwait(false);
var apiScores = new List<SoloScoreInfo>();
for (int i = 0; i < max_api_scores; i += max_api_scores_in_one_query)
{
apiScores.AddRange(await apiManager.GetJsonFromApi<List<SoloScoreInfo>>($"users/{player.OnlineID}/scores/best?mode={ruleset.Value.ShortName}&limit={max_api_scores_in_one_query}&offset={i}").ConfigureAwait(false));
await Task.Delay(200, token).ConfigureAwait(false);
}
if (includePinnedCheckbox.Current.Value)
{
var pinnedScores = await apiManager.GetJsonFromApi<List<SoloScoreInfo>>($"users/{player.OnlineID}/scores/pinned?mode={ruleset.Value.ShortName}&limit=100")
var pinnedScores = await apiManager.GetJsonFromApi<List<SoloScoreInfo>>($"users/{player.OnlineID}/scores/pinned?mode={ruleset.Value.ShortName}&limit={max_api_scores_in_one_query}")
.ConfigureAwait(false);
apiScores = apiScores.Concat(pinnedScores.Where(p => !apiScores.Any(b => b.ID == p.ID)).ToArray()).ToList();
}

View file

@ -0,0 +1,96 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using Humanizer;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Overlays;
using PerformanceCalculatorGUI.Components.TextBoxes;
namespace PerformanceCalculatorGUI.Screens.Simulate
{
public partial class AttributesTable : Container
{
public readonly Bindable<Dictionary<string, object>> Attributes = new Bindable<Dictionary<string, object>>();
private const float row_height = 35;
private FillFlowContainer backgroundFlow;
private GridContainer grid;
[Resolved]
private OverlayColourProvider colourProvider { get; set; }
[BackgroundDependencyLoader]
private void load()
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
CornerRadius = ExtendedLabelledTextBox.CORNER_RADIUS;
Masking = true;
AddRangeInternal(new Drawable[]
{
backgroundFlow = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical
},
grid = new GridContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize), new Dimension() }
}
});
Attributes.BindValueChanged(onAttributesChanged);
}
private void onAttributesChanged(ValueChangedEvent<Dictionary<string, object>> changedEvent)
{
grid.RowDimensions = Enumerable.Repeat(new Dimension(GridSizeMode.Absolute, row_height), changedEvent.NewValue.Count).ToArray();
grid.Content = changedEvent.NewValue.Select(s => createRowContent(s.Key, s.Value)).ToArray();
backgroundFlow.Children = changedEvent.NewValue.Select((_, i) => new Box
{
RelativeSizeAxes = Axes.X,
Height = row_height,
Colour = colourProvider.Background4.Opacity(i % 2 == 0 ? 0.7f : 0.9f),
}).ToArray();
}
private Drawable[] createRowContent(string label, object value) => new Drawable[]
{
new OsuSpriteText
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Font = OsuFont.GetFont(size: 16, weight: FontWeight.Bold),
Text = label.Humanize().ToLowerInvariant(),
Margin = new MarginPadding { Left = 15, Right = 10 },
UseFullGlyphHeight = true
},
new ReadonlyOsuTextBox(FormattableString.Invariant($"{value:N2}"), false)
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Height = 1,
RelativeSizeAxes = Axes.Both,
SelectAllOnFocus = true,
FontSize = 18,
CornerRadius = ExtendedLabelledTextBox.CORNER_RADIUS
},
}.ToArray();
}
}

View file

@ -5,7 +5,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Humanizer;
using osu.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
@ -40,6 +39,7 @@ using PerformanceCalculatorGUI.Components;
using PerformanceCalculatorGUI.Components.TextBoxes;
using PerformanceCalculatorGUI.Configuration;
using PerformanceCalculatorGUI.Screens.ObjectInspection;
using PerformanceCalculatorGUI.Screens.Simulate;
namespace PerformanceCalculatorGUI.Screens
{
@ -71,10 +71,10 @@ namespace PerformanceCalculatorGUI.Screens
private SwitchButton fullScoreDataSwitch;
private DifficultyAttributes difficultyAttributes;
private FillFlowContainer difficultyAttributesContainer;
private FillFlowContainer performanceAttributesContainer;
private AttributesTable difficultyAttributesContainer;
private PerformanceCalculator performanceCalculator;
private AttributesTable performanceAttributesContainer;
[Cached]
private Bindable<DifficultyCalculator> difficultyCalculator = new Bindable<DifficultyCalculator>();
@ -418,37 +418,23 @@ namespace PerformanceCalculatorGUI.Screens
{
new OsuSpriteText
{
Margin = new MarginPadding { Left = 10f, Top = 5f, Bottom = 10.0f },
Margin = new MarginPadding { Left = 10f, Vertical = 5f },
Origin = Anchor.TopLeft,
Height = 20,
Text = "Difficulty Attributes"
},
difficultyAttributesContainer = new FillFlowContainer
{
Direction = FillDirection.Vertical,
RelativeSizeAxes = Axes.X,
Anchor = Anchor.TopLeft,
AutoSizeAxes = Axes.Y,
Spacing = new Vector2(0, 2f)
},
difficultyAttributesContainer = new AttributesTable(),
new OsuSpriteText
{
Margin = new MarginPadding(10.0f),
Margin = new MarginPadding { Left = 10f, Vertical = 5f },
Origin = Anchor.TopLeft,
Height = 20,
Text = "Performance Attributes"
},
performanceAttributesContainer = new FillFlowContainer
{
Direction = FillDirection.Vertical,
RelativeSizeAxes = Axes.X,
Anchor = Anchor.TopLeft,
AutoSizeAxes = Axes.Y,
Spacing = new Vector2(0, 2f)
},
performanceAttributesContainer = new AttributesTable(),
new OsuSpriteText
{
Margin = new MarginPadding(10.0f),
Margin = new MarginPadding { Left = 10f, Vertical = 5f },
Origin = Anchor.TopLeft,
Height = 20,
Text = "Strain graph (alt+scroll to zoom)"
@ -674,14 +660,7 @@ namespace PerformanceCalculatorGUI.Screens
try
{
difficultyAttributes = difficultyCalculator.Value.Calculate(appliedMods.Value);
difficultyAttributesContainer.Children = AttributeConversion.ToDictionary(difficultyAttributes).Select(x =>
new ExtendedLabelledTextBox
{
ReadOnly = true,
Label = x.Key.Humanize().ToLowerInvariant(),
Text = FormattableString.Invariant($"{x.Value:N2}")
}
).ToArray();
difficultyAttributesContainer.Attributes.Value = AttributeConversion.ToDictionary(difficultyAttributes);
}
catch (Exception e)
{
@ -754,17 +733,11 @@ namespace PerformanceCalculatorGUI.Screens
Statistics = statistics,
Mods = appliedMods.Value.ToArray(),
TotalScore = score,
Ruleset = ruleset.Value
Ruleset = ruleset.Value,
LegacyTotalScore = legacyTotalScore,
}, difficultyAttributes);
performanceAttributesContainer.Children = AttributeConversion.ToDictionary(ppAttributes).Select(x =>
new ExtendedLabelledTextBox
{
ReadOnly = true,
Label = x.Key.Humanize().ToLowerInvariant(),
Text = FormattableString.Invariant($"{x.Value:N2}")
}
).ToArray();
performanceAttributesContainer.Attributes.Value = AttributeConversion.ToDictionary(ppAttributes);
}
catch (Exception e)
{
@ -922,7 +895,10 @@ namespace PerformanceCalculatorGUI.Screens
private void resetCalculations()
{
createCalculators();
resetMods();
legacyTotalScore = null;
calculateDifficulty();
calculatePerformance();
populateScoreParams();
@ -1013,6 +989,8 @@ namespace PerformanceCalculatorGUI.Screens
notificationDisplay.Display(new Notification(message));
}
private long? legacyTotalScore;
private void populateSettingsFromScore(long scoreId)
{
if (scoreIdPopulateButton.State.Value == ButtonState.Loading)
@ -1037,6 +1015,8 @@ namespace PerformanceCalculatorGUI.Screens
ruleset.Value = rulesets.GetRuleset(scoreInfo.RulesetID);
appliedMods.Value = scoreInfo.Mods.Select(x => x.ToMod(ruleset.Value.CreateInstance())).ToList();
legacyTotalScore = scoreInfo.LegacyTotalScore;
fullScoreDataSwitch.Current.Value = true;
// TODO: this shouldn't be done in 2 lines