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

Merge branch 'master' into fix_simulate_mania

This commit is contained in:
StanR 2025-06-06 11:59:20 +03:00 committed by GitHub
commit 70d5baa375
Signed by: github
GPG key ID: B5690EEEBB952194
13 changed files with 529 additions and 133 deletions

View file

@ -47,6 +47,7 @@ namespace PerformanceCalculator.Difficulty
mod.RequiresConfiguration, mod.RequiresConfiguration,
mod.UserPlayable, mod.UserPlayable,
mod.ValidForMultiplayer, mod.ValidForMultiplayer,
mod.ValidForFreestyleAsRequiredMod,
mod.ValidForMultiplayerAsFreeMod, mod.ValidForMultiplayerAsFreeMod,
mod.AlwaysValidForSubmission, mod.AlwaysValidForSubmission,
}); });

View file

@ -8,10 +8,10 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Alba.CsConsoleFormat" Version="1.0.0" /> <PackageReference Include="Alba.CsConsoleFormat" Version="1.0.0" />
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="4.1.1" /> <PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="4.1.1" />
<PackageReference Include="ppy.osu.Game" Version="2025.227.0" /> <PackageReference Include="ppy.osu.Game" Version="2025.530.0" />
<PackageReference Include="ppy.osu.Game.Rulesets.Osu" Version="2025.227.0" /> <PackageReference Include="ppy.osu.Game.Rulesets.Osu" Version="2025.530.0" />
<PackageReference Include="ppy.osu.Game.Rulesets.Taiko" Version="2025.227.0" /> <PackageReference Include="ppy.osu.Game.Rulesets.Taiko" Version="2025.530.0" />
<PackageReference Include="ppy.osu.Game.Rulesets.Catch" Version="2025.227.0" /> <PackageReference Include="ppy.osu.Game.Rulesets.Catch" Version="2025.530.0" />
<PackageReference Include="ppy.osu.Game.Rulesets.Mania" Version="2025.227.0" /> <PackageReference Include="ppy.osu.Game.Rulesets.Mania" Version="2025.530.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View file

@ -4,6 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading;
using Alba.CsConsoleFormat; using Alba.CsConsoleFormat;
using JetBrains.Annotations; using JetBrains.Annotations;
using McMaster.Extensions.CommandLineUtils; using McMaster.Extensions.CommandLineUtils;
@ -27,6 +28,9 @@ namespace PerformanceCalculator.Profile
[AllowedValues("0", "1", "2", "3")] [AllowedValues("0", "1", "2", "3")]
public int? Ruleset { get; } 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() public override void Execute()
{ {
var displayPlays = new List<UserPlayInfo>(); var displayPlays = new List<UserPlayInfo>();
@ -39,22 +43,28 @@ namespace PerformanceCalculator.Profile
Console.WriteLine("Getting user top scores..."); 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()); var working = ProcessorWorkingBeatmap.FromFileOrId(play.BeatmapID.ToString());
Mod[] mods = play.Mods.Select(x => x.ToMod(ruleset)).ToArray(); 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; scoreInfo.Ruleset = ruleset.RulesetInfo;
var score = new ProcessorScoreDecoder(working).Parse(scoreInfo);
var difficultyCalculator = ruleset.CreateDifficultyCalculator(working); var difficultyCalculator = ruleset.CreateDifficultyCalculator(working);
var difficultyAttributes = difficultyCalculator.Calculate(scoreInfo.Mods); var difficultyAttributes = difficultyCalculator.Calculate(scoreInfo.Mods);
var performanceCalculator = ruleset.CreatePerformanceCalculator(); var performanceCalculator = ruleset.CreatePerformanceCalculator();
var ppAttributes = performanceCalculator?.Calculate(score.ScoreInfo, difficultyAttributes); var ppAttributes = performanceCalculator?.Calculate(scoreInfo, difficultyAttributes);
var thisPlay = new UserPlayInfo var thisPlay = new UserPlayInfo
{ {
Beatmap = working.BeatmapInfo, Beatmap = working.BeatmapInfo,

View file

@ -40,6 +40,10 @@ namespace PerformanceCalculator.Simulate
[Option(Template = "-X|--misses <misses>", Description = "Number of misses. Defaults to 0.")] [Option(Template = "-X|--misses <misses>", Description = "Number of misses. Defaults to 0.")]
public int Misses { get; } 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 // Options implemented in the ruleset-specific commands
// -> Catch renames Mehs/Goods to (tiny-)droplets // -> Catch renames Mehs/Goods to (tiny-)droplets
@ -73,6 +77,7 @@ namespace PerformanceCalculator.Simulate
Accuracy = GetAccuracy(beatmap, statistics, mods), 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,
Mods = mods Mods = mods
}; };

View file

@ -2,21 +2,26 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Overlays.Mods;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Utils; using osu.Game.Utils;
using osuTK; using osuTK;
@ -24,7 +29,7 @@ using PerformanceCalculatorGUI.Components.TextBoxes;
namespace PerformanceCalculatorGUI.Components namespace PerformanceCalculatorGUI.Components
{ {
public partial class BeatmapCard : OsuClickableContainer public partial class BeatmapCard : OsuClickableContainer, IHasCustomTooltip<ProcessorWorkingBeatmap>
{ {
private readonly ProcessorWorkingBeatmap beatmap; private readonly ProcessorWorkingBeatmap beatmap;
@ -40,6 +45,10 @@ namespace PerformanceCalculatorGUI.Components
[Resolved] [Resolved]
private Bindable<IReadOnlyList<Mod>> mods { get; set; } private Bindable<IReadOnlyList<Mod>> mods { get; set; }
public ITooltip<ProcessorWorkingBeatmap> GetCustomTooltip() => new BeatmapCardTooltip(colourProvider);
public ProcessorWorkingBeatmap TooltipContent { get; }
private ModSettingChangeTracker modSettingChangeTracker;
private OsuSpriteText bpmText = null!; private OsuSpriteText bpmText = null!;
public BeatmapCard(ProcessorWorkingBeatmap beatmap) public BeatmapCard(ProcessorWorkingBeatmap beatmap)
@ -49,6 +58,7 @@ namespace PerformanceCalculatorGUI.Components
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
Height = 40; Height = 40;
CornerRadius = ExtendedLabelledTextBox.CORNER_RADIUS; CornerRadius = ExtendedLabelledTextBox.CORNER_RADIUS;
TooltipContent = beatmap;
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -115,7 +125,13 @@ namespace PerformanceCalculatorGUI.Components
Action = () => { host.OpenUrlExternally($"https://osu.ppy.sh/beatmaps/{beatmap.BeatmapInfo.OnlineID}"); }; Action = () => { host.OpenUrlExternally($"https://osu.ppy.sh/beatmaps/{beatmap.BeatmapInfo.OnlineID}"); };
mods.BindValueChanged(_ => updateBpm()); mods.BindValueChanged(_ =>
{
modSettingChangeTracker?.Dispose();
modSettingChangeTracker = new ModSettingChangeTracker(mods.Value);
modSettingChangeTracker.SettingChanged += _ => updateBpm();
updateBpm();
}, true);
updateBpm(); updateBpm();
} }
@ -146,5 +162,146 @@ namespace PerformanceCalculatorGUI.Components
bpmText.Text = labelText; bpmText.Text = labelText;
} }
public partial class BeatmapCardTooltip : VisibilityContainer, ITooltip<ProcessorWorkingBeatmap>
{
public BeatmapCardTooltip(OverlayColourProvider colourProvider)
{
this.colourProvider = colourProvider;
AutoSizeAxes = Axes.Both;
Masking = true;
CornerRadius = 8;
}
protected override void PopIn() => this.FadeIn(150, Easing.OutQuint);
protected override void PopOut() => this.Delay(150).FadeOut(500, Easing.OutQuint);
public void Move(Vector2 pos) => Position = pos;
private ProcessorWorkingBeatmap beatmap;
private VerticalAttributeDisplay keyCountDisplay = null!;
private VerticalAttributeDisplay circleSizeDisplay = null!;
private VerticalAttributeDisplay drainRateDisplay = null!;
private VerticalAttributeDisplay approachRateDisplay = null!;
private VerticalAttributeDisplay overallDifficultyDisplay = null!;
[Resolved]
private Bindable<IReadOnlyList<Mod>> mods { get; set; }
private readonly OverlayColourProvider colourProvider;
private ModSettingChangeTracker modSettingChangeTracker;
[Resolved]
private IBindable<RulesetInfo> ruleset { get; set; }
protected override void LoadComplete()
{
base.LoadComplete();
mods.BindValueChanged(_ =>
{
modSettingChangeTracker?.Dispose();
modSettingChangeTracker = new ModSettingChangeTracker(mods.Value);
modSettingChangeTracker.SettingChanged += _ => updateValues();
updateValues();
}, true);
ruleset.BindValueChanged(_ => updateValues());
}
protected override bool OnMouseDown(MouseDownEvent e) => true;
protected override bool OnClick(ClickEvent e) => true;
private void updateValues() => Scheduler.AddOnce(() =>
{
if (beatmap?.BeatmapInfo == null)
return;
double rate = ModUtils.CalculateRateWithMods(mods.Value);
BeatmapDifficulty originalDifficulty = new BeatmapDifficulty(beatmap.BeatmapInfo.Difficulty);
BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(originalDifficulty);
foreach (var mod in mods.Value.OfType<IApplicableToDifficulty>())
mod.ApplyToDifficulty(adjustedDifficulty);
Ruleset rulesetInstance = ruleset.Value.CreateInstance();
adjustedDifficulty = rulesetInstance.GetRateAdjustedDisplayDifficulty(adjustedDifficulty, rate);
if (ruleset.Value.OnlineID >= 0)
{
if (ruleset.Value.ShortName is "osu" or "fruits")
{
circleSizeDisplay.Show();
circleSizeDisplay.AdjustType.Value = VerticalAttributeDisplay.CalculateEffect(originalDifficulty.CircleSize, adjustedDifficulty.CircleSize);
circleSizeDisplay.Current.Value = adjustedDifficulty.CircleSize;
approachRateDisplay.Show();
approachRateDisplay.AdjustType.Value = VerticalAttributeDisplay.CalculateEffect(originalDifficulty.ApproachRate, adjustedDifficulty.ApproachRate);
approachRateDisplay.Current.Value = adjustedDifficulty.ApproachRate;
}
else
{
circleSizeDisplay.Hide();
approachRateDisplay.Hide();
}
if (ruleset.Value.ShortName == "mania")
{
ILegacyRuleset legacyRuleset = (ILegacyRuleset)ruleset.Value.CreateInstance();
int keyCount = legacyRuleset.GetKeyCount(beatmap.BeatmapInfo, mods.Value);
int keyCountOriginal = legacyRuleset.GetKeyCount(beatmap.BeatmapInfo, []);
keyCountDisplay.Show();
keyCountDisplay.AdjustType.Value = VerticalAttributeDisplay.CalculateEffect(keyCountOriginal, keyCount);
keyCountDisplay.Current.Value = keyCount;
}
else
{
keyCountDisplay.Hide();
}
}
drainRateDisplay.AdjustType.Value = VerticalAttributeDisplay.CalculateEffect(originalDifficulty.DrainRate, adjustedDifficulty.DrainRate);
overallDifficultyDisplay.AdjustType.Value = VerticalAttributeDisplay.CalculateEffect(originalDifficulty.OverallDifficulty, adjustedDifficulty.OverallDifficulty);
drainRateDisplay.Current.Value = adjustedDifficulty.DrainRate;
overallDifficultyDisplay.Current.Value = adjustedDifficulty.OverallDifficulty;
});
public void SetContent(ProcessorWorkingBeatmap content)
{
if (content == beatmap && Children.Any())
return;
beatmap = content;
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colourProvider.Background6
},
new FillFlowContainer
{
Padding = new MarginPadding(8),
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{
keyCountDisplay = new VerticalAttributeDisplay("Keys") { AutoSizeAxes = Axes.Both, Alpha = 0 },
circleSizeDisplay = new VerticalAttributeDisplay("CS") { AutoSizeAxes = Axes.Both, Alpha = 0 },
drainRateDisplay = new VerticalAttributeDisplay("HP") { AutoSizeAxes = Axes.Both },
overallDifficultyDisplay = new VerticalAttributeDisplay("OD") { AutoSizeAxes = Axes.Both },
approachRateDisplay = new VerticalAttributeDisplay("AR") { AutoSizeAxes = Axes.Both, Alpha = 0 },
}
}
};
}
}
} }
} }

View file

@ -25,6 +25,7 @@ using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osu.Game.Utils; using osu.Game.Utils;
using osu.Game.Users.Drawables;
using osuTK; using osuTK;
using PerformanceCalculatorGUI.Components.TextBoxes; using PerformanceCalculatorGUI.Components.TextBoxes;
@ -74,6 +75,7 @@ namespace PerformanceCalculatorGUI.Components
public partial class ExtendedProfileScore : CompositeDrawable public partial class ExtendedProfileScore : CompositeDrawable
{ {
private const int height = 35; private const int height = 35;
private const int avatar_size = 35;
private const int performance_width = 100; private const int performance_width = 100;
private const int rank_difference_width = 35; private const int rank_difference_width = 35;
private const int small_text_font_size = 11; private const int small_text_font_size = 11;
@ -82,6 +84,8 @@ namespace PerformanceCalculatorGUI.Components
public readonly ExtendedScore Score; public readonly ExtendedScore Score;
public readonly bool ShowAvatar;
[Resolved] [Resolved]
private OsuColour colours { get; set; } private OsuColour colours { get; set; }
@ -90,17 +94,20 @@ namespace PerformanceCalculatorGUI.Components
private OsuSpriteText positionChangeText; private OsuSpriteText positionChangeText;
public ExtendedProfileScore(ExtendedScore score) public ExtendedProfileScore(ExtendedScore score, bool showAvatar = false)
{ {
Score = score; Score = score;
ShowAvatar = showAvatar;
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
Height = height; Height = height;
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(RulesetStore rulesets) private void load(GameHost host, RulesetStore rulesets)
{ {
int avatarPadding = ShowAvatar ? avatar_size : 0;
AddInternal(new ExtendedProfileItemContainer AddInternal(new ExtendedProfileItemContainer
{ {
OnHoverAction = () => OnHoverAction = () =>
@ -111,8 +118,17 @@ namespace PerformanceCalculatorGUI.Components
{ {
positionChangeText.Text = $"{Score.PositionChange.Value:+0;-0;-}"; positionChangeText.Text = $"{Score.PositionChange.Value:+0;-0;-}";
}, },
Children = new Drawable[] Children = new[]
{ {
ShowAvatar
? new ClickableAvatar(Score.SoloScore.User, true)
{
Masking = true,
CornerRadius = ExtendedLabelledTextBox.CORNER_RADIUS,
Size = new Vector2(avatar_size),
Action = () => { host.OpenUrlExternally($"https://osu.ppy.sh/users/{Score.SoloScore.User?.Id}"); }
}
: Empty(),
new Container new Container
{ {
Name = "Rank difference", Name = "Rank difference",
@ -120,6 +136,7 @@ namespace PerformanceCalculatorGUI.Components
Anchor = Anchor.CentreLeft, Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft, Origin = Anchor.CentreLeft,
Width = rank_difference_width, Width = rank_difference_width,
Margin = new MarginPadding { Left = avatarPadding },
Child = positionChangeText = new OsuSpriteText Child = positionChangeText = new OsuSpriteText
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
@ -132,7 +149,7 @@ namespace PerformanceCalculatorGUI.Components
{ {
Name = "Score info", Name = "Score info",
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Left = rank_difference_width, Right = performance_width }, Padding = new MarginPadding { Left = rank_difference_width + avatarPadding, Right = performance_width },
Children = new Drawable[] Children = new Drawable[]
{ {
new FillFlowContainer new FillFlowContainer

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

@ -6,10 +6,10 @@
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Game" Version="2025.227.0" /> <PackageReference Include="ppy.osu.Game" Version="2025.530.0" />
<PackageReference Include="ppy.osu.Game.Rulesets.Osu" Version="2025.227.0" /> <PackageReference Include="ppy.osu.Game.Rulesets.Osu" Version="2025.530.0" />
<PackageReference Include="ppy.osu.Game.Rulesets.Taiko" Version="2025.227.0" /> <PackageReference Include="ppy.osu.Game.Rulesets.Taiko" Version="2025.530.0" />
<PackageReference Include="ppy.osu.Game.Rulesets.Catch" Version="2025.227.0" /> <PackageReference Include="ppy.osu.Game.Rulesets.Catch" Version="2025.530.0" />
<PackageReference Include="ppy.osu.Game.Rulesets.Mania" Version="2025.227.0" /> <PackageReference Include="ppy.osu.Game.Rulesets.Mania" Version="2025.530.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View file

@ -24,7 +24,7 @@ namespace PerformanceCalculatorGUI
public Score Parse(ScoreInfo scoreInfo) public Score Parse(ScoreInfo scoreInfo)
{ {
var score = new Score { ScoreInfo = scoreInfo }; var score = new Score { ScoreInfo = scoreInfo };
score.ScoreInfo.LegacyTotalScore = score.ScoreInfo.TotalScore; score.ScoreInfo.LegacyTotalScore ??= score.ScoreInfo.TotalScore;
PopulateMaximumStatistics(score.ScoreInfo, beatmap); PopulateMaximumStatistics(score.ScoreInfo, beatmap);
StandardisedScoreMigrationTools.UpdateFromLegacy(score.ScoreInfo, beatmap); StandardisedScoreMigrationTools.UpdateFromLegacy(score.ScoreInfo, beatmap);
return score; 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;
using osu.Game.Screens.Edit.Components.Timelines.Summary; using osu.Game.Screens.Edit.Components.Timelines.Summary;
using osu.Game.Screens.Edit.Compose.Components.Timeline; using osu.Game.Screens.Edit.Compose.Components.Timeline;
using osu.Game.Skinning;
using osuTK.Input; using osuTK.Input;
namespace PerformanceCalculatorGUI.Screens.ObjectInspection namespace PerformanceCalculatorGUI.Screens.ObjectInspection
@ -133,7 +134,7 @@ namespace PerformanceCalculatorGUI.Screens.ObjectInspection
timeline = new Timeline(new TimelineBlueprintContainer()) timeline = new Timeline(new TimelineBlueprintContainer())
} }
}, },
rulesetContainer = new Container rulesetContainer = new RulesetSkinProvidingContainer(rulesetInstance, playableBeatmap, null)
{ {
Origin = Anchor.TopRight, Origin = Anchor.TopRight,
Anchor = Anchor.TopRight, Anchor = Anchor.TopRight,

View file

@ -38,6 +38,7 @@ namespace PerformanceCalculatorGUI.Screens
private StatefulButton calculationButton; private StatefulButton calculationButton;
private SwitchButton includePinnedCheckbox; private SwitchButton includePinnedCheckbox;
private SwitchButton onlyDisplayBestCheckbox;
private VerboseLoadingLayer loadingLayer; private VerboseLoadingLayer loadingLayer;
private GridContainer layout; private GridContainer layout;
@ -48,7 +49,7 @@ namespace PerformanceCalculatorGUI.Screens
private Container userPanelContainer; private Container userPanelContainer;
private UserCard userPanel; private UserCard userPanel;
private string currentUser; private string[] currentUsers = Array.Empty<string>();
private CancellationTokenSource calculationCancellatonToken; private CancellationTokenSource calculationCancellatonToken;
@ -73,6 +74,8 @@ namespace PerformanceCalculatorGUI.Screens
public override bool ShouldShowConfirmationDialogOnSwitch => false; public override bool ShouldShowConfirmationDialogOnSwitch => false;
private const float username_container_height = 40; 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() public ProfileScreen()
{ {
@ -121,15 +124,15 @@ namespace PerformanceCalculatorGUI.Screens
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
Anchor = Anchor.TopLeft, Anchor = Anchor.TopLeft,
Label = "Username", Label = "Username(s)",
PlaceholderText = "peppy", PlaceholderText = "peppy, rloseise, peppy2",
CommitOnFocusLoss = false CommitOnFocusLoss = false
}, },
calculationButton = new StatefulButton("Start calculation") calculationButton = new StatefulButton("Start calculation")
{ {
Width = 150, Width = 150,
Height = username_container_height, Height = username_container_height,
Action = () => { calculateProfile(usernameTextBox.Current.Value); } Action = () => { calculateProfiles(usernameTextBox.Current.Value.Split(", ", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)); }
} }
} }
} }
@ -172,6 +175,20 @@ namespace PerformanceCalculatorGUI.Screens
Font = OsuFont.Torus.With(weight: FontWeight.SemiBold, size: 14), Font = OsuFont.Torus.With(weight: FontWeight.SemiBold, size: 14),
UseFullGlyphHeight = false, UseFullGlyphHeight = false,
Text = "Include pinned scores" Text = "Include pinned scores"
},
onlyDisplayBestCheckbox = new SwitchButton
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Current = { Value = true },
},
new OsuSpriteText
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Font = OsuFont.Torus.With(weight: FontWeight.SemiBold, size: 14),
UseFullGlyphHeight = false,
Text = "Only display best score on each beatmap"
} }
} }
}, },
@ -207,17 +224,20 @@ namespace PerformanceCalculatorGUI.Screens
} }
}; };
usernameTextBox.OnCommit += (_, _) => { calculateProfile(usernameTextBox.Current.Value); }; usernameTextBox.OnCommit += (_, _) => { calculateProfiles(usernameTextBox.Current.Value.Split(",", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)); };
sorting.ValueChanged += e => { updateSorting(e.NewValue); }; sorting.ValueChanged += e => { updateSorting(e.NewValue); };
includePinnedCheckbox.Current.ValueChanged += e => { calculateProfile(currentUser); }; includePinnedCheckbox.Current.ValueChanged += e => { calculateProfiles(currentUsers); };
onlyDisplayBestCheckbox.Current.ValueChanged += e => { calculateProfiles(currentUsers); };
if (RuntimeInfo.IsDesktop) if (RuntimeInfo.IsDesktop)
HotReloadCallbackReceiver.CompilationFinished += _ => Schedule(() => { calculateProfile(currentUser); }); HotReloadCallbackReceiver.CompilationFinished += _ => Schedule(() => { calculateProfiles(currentUsers); });
} }
private void calculateProfile(string username) private void calculateProfiles(string[] usernames)
{ {
if (string.IsNullOrEmpty(username)) currentUsers = usernames.Distinct().ToArray();
if (usernames.Length < 1)
{ {
usernameTextBox.FlashColour(Color4.Red, 1); usernameTextBox.FlashColour(Color4.Red, 1);
return; return;
@ -236,22 +256,11 @@ namespace PerformanceCalculatorGUI.Screens
Task.Run(async () => Task.Run(async () =>
{ {
Schedule(() => loadingLayer.Text.Value = "Getting user data...");
var player = await apiManager.GetJsonFromApi<APIUser>($"users/{username}/{ruleset.Value.ShortName}").ConfigureAwait(false);
currentUser = player.Username;
Schedule(() => Schedule(() =>
{ {
if (userPanel != null) if (userPanel != null)
userPanelContainer.Remove(userPanel, true); userPanelContainer.Remove(userPanel, true);
userPanelContainer.Add(userPanel = new UserCard(player)
{
RelativeSizeAxes = Axes.X
});
sortingTabControl.Alpha = 1.0f; sortingTabControl.Alpha = 1.0f;
sortingTabControl.Current.Value = ProfileSortCriteria.Local; sortingTabControl.Current.Value = ProfileSortCriteria.Local;
@ -268,53 +277,107 @@ namespace PerformanceCalculatorGUI.Screens
return; return;
var plays = new List<ExtendedScore>(); var plays = new List<ExtendedScore>();
var players = new List<APIUser>();
var rulesetInstance = ruleset.Value.CreateInstance(); var rulesetInstance = ruleset.Value.CreateInstance();
Schedule(() => loadingLayer.Text.Value = $"Calculating {player.Username} top scores..."); foreach (string username in currentUsers)
var apiScores = await apiManager.GetJsonFromApi<List<SoloScoreInfo>>($"users/{player.OnlineID}/scores/best?mode={ruleset.Value.ShortName}&limit=100").ConfigureAwait(false);
if (includePinnedCheckbox.Current.Value)
{ {
var pinnedScores = await apiManager.GetJsonFromApi<List<SoloScoreInfo>>($"users/{player.OnlineID}/scores/pinned?mode={ruleset.Value.ShortName}&limit=100").ConfigureAwait(false); try
apiScores = apiScores.Concat(pinnedScores.Where(p => !apiScores.Any(b => b.ID == p.ID)).ToArray()).ToList(); {
} Schedule(() => loadingLayer.Text.Value = $"Getting {username} user data...");
foreach (var score in apiScores) var player = await apiManager.GetJsonFromApi<APIUser>($"users/{username}/{ruleset.Value.ShortName}").ConfigureAwait(false);
{ players.Add(player);
if (token.IsCancellationRequested)
return;
var working = ProcessorWorkingBeatmap.FromFileOrId(score.BeatmapID.ToString(), cachePath: configManager.GetBindable<string>(Settings.CachePath).Value); Schedule(() => loadingLayer.Text.Value = $"Calculating {player.Username} top scores...");
Schedule(() => loadingLayer.Text.Value = $"Calculating {working.Metadata}"); var apiScores = new List<SoloScoreInfo>();
Mod[] mods = score.Mods.Select(x => x.ToMod(rulesetInstance)).ToArray(); 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);
}
var scoreInfo = score.ToScoreInfo(rulesets, working.BeatmapInfo); if (includePinnedCheckbox.Current.Value)
{
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();
}
var parsedScore = new ProcessorScoreDecoder(working).Parse(scoreInfo); foreach (var score in apiScores)
{
if (token.IsCancellationRequested)
return;
var difficultyCalculator = rulesetInstance.CreateDifficultyCalculator(working); var working = ProcessorWorkingBeatmap.FromFileOrId(score.BeatmapID.ToString(), cachePath: configManager.GetBindable<string>(Settings.CachePath).Value);
var difficultyAttributes = difficultyCalculator.Calculate(mods);
var performanceCalculator = rulesetInstance.CreatePerformanceCalculator();
if (performanceCalculator == null)
continue;
double? livePp = score.PP; Schedule(() => loadingLayer.Text.Value = $"Calculating {working.Metadata}");
var perfAttributes = await performanceCalculator.CalculateAsync(parsedScore.ScoreInfo, difficultyAttributes, token).ConfigureAwait(false);
score.PP = perfAttributes.Total;
var extendedScore = new ExtendedScore(score, livePp, perfAttributes); Mod[] mods = score.Mods.Select(x => x.ToMod(rulesetInstance)).ToArray();
plays.Add(extendedScore);
Schedule(() => scores.Add(new ExtendedProfileScore(extendedScore))); var scoreInfo = score.ToScoreInfo(rulesets, working.BeatmapInfo);
var parsedScore = new ProcessorScoreDecoder(working).Parse(scoreInfo);
var difficultyCalculator = rulesetInstance.CreateDifficultyCalculator(working);
var difficultyAttributes = difficultyCalculator.Calculate(mods);
var performanceCalculator = rulesetInstance.CreatePerformanceCalculator();
if (performanceCalculator == null)
continue;
double? livePp = score.PP;
var perfAttributes = await performanceCalculator.CalculateAsync(parsedScore.ScoreInfo, difficultyAttributes, token).ConfigureAwait(false);
score.PP = perfAttributes.Total;
var extendedScore = new ExtendedScore(score, livePp, perfAttributes);
plays.Add(extendedScore);
}
}
catch (Exception ex)
{
Logger.Log(ex.ToString(), level: LogLevel.Error);
notificationDisplay.Display(new Notification($"Failed to calculate {username}: {ex.Message}"));
}
} }
if (token.IsCancellationRequested) if (token.IsCancellationRequested)
return; return;
bool calculatingSingleProfile = players.Count == 1;
// Add user card if only calculating single profile
if (calculatingSingleProfile)
{
Schedule(() =>
{
userPanelContainer.Add(userPanel = new UserCard(players[0])
{
RelativeSizeAxes = Axes.X
});
});
}
// Filter plays if only displaying best score on each beatmap
if (onlyDisplayBestCheckbox.Current.Value)
{
Schedule(() => loadingLayer.Text.Value = "Filtering plays");
var filteredPlays = new List<ExtendedScore>();
// List of all beatmap IDs in plays without duplicates
var beatmapIDs = plays.Select(x => x.SoloScore.BeatmapID).Distinct().ToList();
foreach (int id in beatmapIDs)
{
var bestPlayOnBeatmap = plays.Where(x => x.SoloScore.BeatmapID == id).OrderByDescending(x => x.SoloScore.PP).First();
filteredPlays.Add(bestPlayOnBeatmap);
}
plays = filteredPlays;
}
var localOrdered = plays.OrderByDescending(x => x.SoloScore.PP).ToList(); var localOrdered = plays.OrderByDescending(x => x.SoloScore.PP).ToList();
var liveOrdered = plays.OrderByDescending(x => x.LivePP ?? 0).ToList(); var liveOrdered = plays.OrderByDescending(x => x.LivePP ?? 0).ToList();
@ -322,6 +385,8 @@ namespace PerformanceCalculatorGUI.Screens
{ {
foreach (var play in plays) foreach (var play in plays)
{ {
scores.Add(new ExtendedProfileScore(play, !calculatingSingleProfile));
if (play.LivePP != null) if (play.LivePP != null)
{ {
play.Position.Value = localOrdered.IndexOf(play) + 1; play.Position.Value = localOrdered.IndexOf(play) + 1;
@ -330,29 +395,34 @@ namespace PerformanceCalculatorGUI.Screens
} }
}); });
decimal totalLocalPP = 0; if (calculatingSingleProfile)
for (int i = 0; i < localOrdered.Count; i++)
totalLocalPP += (decimal)(Math.Pow(0.95, i) * (localOrdered[i].SoloScore.PP ?? 0));
decimal totalLivePP = player.Statistics.PP ?? (decimal)0.0;
decimal nonBonusLivePP = 0;
for (int i = 0; i < liveOrdered.Count; i++)
nonBonusLivePP += (decimal)(Math.Pow(0.95, i) * liveOrdered[i].LivePP ?? 0);
//todo: implement properly. this is pretty damn wrong.
decimal playcountBonusPP = (totalLivePP - nonBonusLivePP);
totalLocalPP += playcountBonusPP;
Schedule(() =>
{ {
userPanel.Data.Value = new UserCardData var player = players.First();
decimal totalLocalPP = 0;
for (int i = 0; i < localOrdered.Count; i++)
totalLocalPP += (decimal)(Math.Pow(0.95, i) * (localOrdered[i].SoloScore.PP ?? 0));
decimal totalLivePP = player.Statistics.PP ?? (decimal)0.0;
decimal nonBonusLivePP = 0;
for (int i = 0; i < liveOrdered.Count; i++)
nonBonusLivePP += (decimal)(Math.Pow(0.95, i) * liveOrdered[i].LivePP ?? 0);
//todo: implement properly. this is pretty damn wrong.
decimal playcountBonusPP = (totalLivePP - nonBonusLivePP);
totalLocalPP += playcountBonusPP;
Schedule(() =>
{ {
LivePP = totalLivePP, userPanel.Data.Value = new UserCardData
LocalPP = totalLocalPP, {
PlaycountPP = playcountBonusPP LivePP = totalLivePP,
}; LocalPP = totalLocalPP,
}); PlaycountPP = playcountBonusPP
};
});
}
}, token).ContinueWith(t => }, token).ContinueWith(t =>
{ {
Logger.Log(t.Exception?.ToString(), level: LogLevel.Error); Logger.Log(t.Exception?.ToString(), level: LogLevel.Error);

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.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Humanizer;
using osu.Framework; using osu.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Audio;
@ -40,6 +39,7 @@ using PerformanceCalculatorGUI.Components;
using PerformanceCalculatorGUI.Components.TextBoxes; using PerformanceCalculatorGUI.Components.TextBoxes;
using PerformanceCalculatorGUI.Configuration; using PerformanceCalculatorGUI.Configuration;
using PerformanceCalculatorGUI.Screens.ObjectInspection; using PerformanceCalculatorGUI.Screens.ObjectInspection;
using PerformanceCalculatorGUI.Screens.Simulate;
namespace PerformanceCalculatorGUI.Screens namespace PerformanceCalculatorGUI.Screens
{ {
@ -71,10 +71,10 @@ namespace PerformanceCalculatorGUI.Screens
private SwitchButton fullScoreDataSwitch; private SwitchButton fullScoreDataSwitch;
private DifficultyAttributes difficultyAttributes; private DifficultyAttributes difficultyAttributes;
private FillFlowContainer difficultyAttributesContainer; private AttributesTable difficultyAttributesContainer;
private FillFlowContainer performanceAttributesContainer;
private PerformanceCalculator performanceCalculator; private PerformanceCalculator performanceCalculator;
private AttributesTable performanceAttributesContainer;
[Cached] [Cached]
private Bindable<DifficultyCalculator> difficultyCalculator = new Bindable<DifficultyCalculator>(); private Bindable<DifficultyCalculator> difficultyCalculator = new Bindable<DifficultyCalculator>();
@ -418,37 +418,23 @@ namespace PerformanceCalculatorGUI.Screens
{ {
new OsuSpriteText new OsuSpriteText
{ {
Margin = new MarginPadding { Left = 10f, Top = 5f, Bottom = 10.0f }, Margin = new MarginPadding { Left = 10f, Vertical = 5f },
Origin = Anchor.TopLeft, Origin = Anchor.TopLeft,
Height = 20, Height = 20,
Text = "Difficulty Attributes" Text = "Difficulty Attributes"
}, },
difficultyAttributesContainer = new FillFlowContainer difficultyAttributesContainer = new AttributesTable(),
{
Direction = FillDirection.Vertical,
RelativeSizeAxes = Axes.X,
Anchor = Anchor.TopLeft,
AutoSizeAxes = Axes.Y,
Spacing = new Vector2(0, 2f)
},
new OsuSpriteText new OsuSpriteText
{ {
Margin = new MarginPadding(10.0f), Margin = new MarginPadding { Left = 10f, Vertical = 5f },
Origin = Anchor.TopLeft, Origin = Anchor.TopLeft,
Height = 20, Height = 20,
Text = "Performance Attributes" Text = "Performance Attributes"
}, },
performanceAttributesContainer = new FillFlowContainer performanceAttributesContainer = new AttributesTable(),
{
Direction = FillDirection.Vertical,
RelativeSizeAxes = Axes.X,
Anchor = Anchor.TopLeft,
AutoSizeAxes = Axes.Y,
Spacing = new Vector2(0, 2f)
},
new OsuSpriteText new OsuSpriteText
{ {
Margin = new MarginPadding(10.0f), Margin = new MarginPadding { Left = 10f, Vertical = 5f },
Origin = Anchor.TopLeft, Origin = Anchor.TopLeft,
Height = 20, Height = 20,
Text = "Strain graph (alt+scroll to zoom)" Text = "Strain graph (alt+scroll to zoom)"
@ -582,6 +568,7 @@ namespace PerformanceCalculatorGUI.Screens
// recreate calculators to update DHOs // recreate calculators to update DHOs
createCalculators(); createCalculators();
modSettingChangeTracker?.Dispose();
modSettingChangeTracker = new ModSettingChangeTracker(mods.NewValue); modSettingChangeTracker = new ModSettingChangeTracker(mods.NewValue);
modSettingChangeTracker.SettingChanged += m => modSettingChangeTracker.SettingChanged += m =>
{ {
@ -592,7 +579,7 @@ namespace PerformanceCalculatorGUI.Screens
updateMissesTextboxes(); updateMissesTextboxes();
calculateDifficulty(); calculateDifficulty();
calculatePerformance(); calculatePerformance();
}, 100); }, 300);
}; };
calculateDifficulty(); calculateDifficulty();
@ -647,6 +634,7 @@ namespace PerformanceCalculatorGUI.Screens
resetCalculations(); resetCalculations();
} }
beatmapTitle.Clear();
beatmapTitle.Add(new BeatmapCard(working)); beatmapTitle.Add(new BeatmapCard(working));
loadBackground(); loadBackground();
@ -672,14 +660,7 @@ namespace PerformanceCalculatorGUI.Screens
try try
{ {
difficultyAttributes = difficultyCalculator.Value.Calculate(appliedMods.Value); difficultyAttributes = difficultyCalculator.Value.Calculate(appliedMods.Value);
difficultyAttributesContainer.Children = AttributeConversion.ToDictionary(difficultyAttributes).Select(x => difficultyAttributesContainer.Attributes.Value = AttributeConversion.ToDictionary(difficultyAttributes);
new ExtendedLabelledTextBox
{
ReadOnly = true,
Label = x.Key.Humanize().ToLowerInvariant(),
Text = FormattableString.Invariant($"{x.Value:N2}")
}
).ToArray();
} }
catch (Exception e) catch (Exception e)
{ {
@ -752,17 +733,11 @@ namespace PerformanceCalculatorGUI.Screens
Statistics = statistics, Statistics = statistics,
Mods = appliedMods.Value.ToArray(), Mods = appliedMods.Value.ToArray(),
TotalScore = score, TotalScore = score,
Ruleset = ruleset.Value Ruleset = ruleset.Value,
LegacyTotalScore = legacyTotalScore,
}, difficultyAttributes); }, difficultyAttributes);
performanceAttributesContainer.Children = AttributeConversion.ToDictionary(ppAttributes).Select(x => performanceAttributesContainer.Attributes.Value = AttributeConversion.ToDictionary(ppAttributes);
new ExtendedLabelledTextBox
{
ReadOnly = true,
Label = x.Key.Humanize().ToLowerInvariant(),
Text = FormattableString.Invariant($"{x.Value:N2}")
}
).ToArray();
} }
catch (Exception e) catch (Exception e)
{ {
@ -920,7 +895,10 @@ namespace PerformanceCalculatorGUI.Screens
private void resetCalculations() private void resetCalculations()
{ {
createCalculators(); createCalculators();
resetMods(); resetMods();
legacyTotalScore = null;
calculateDifficulty(); calculateDifficulty();
calculatePerformance(); calculatePerformance();
populateScoreParams(); populateScoreParams();
@ -1011,6 +989,8 @@ namespace PerformanceCalculatorGUI.Screens
notificationDisplay.Display(new Notification(message)); notificationDisplay.Display(new Notification(message));
} }
private long? legacyTotalScore;
private void populateSettingsFromScore(long scoreId) private void populateSettingsFromScore(long scoreId)
{ {
if (scoreIdPopulateButton.State.Value == ButtonState.Loading) if (scoreIdPopulateButton.State.Value == ButtonState.Loading)
@ -1035,6 +1015,8 @@ namespace PerformanceCalculatorGUI.Screens
ruleset.Value = rulesets.GetRuleset(scoreInfo.RulesetID); ruleset.Value = rulesets.GetRuleset(scoreInfo.RulesetID);
appliedMods.Value = scoreInfo.Mods.Select(x => x.ToMod(ruleset.Value.CreateInstance())).ToList(); appliedMods.Value = scoreInfo.Mods.Select(x => x.ToMod(ruleset.Value.CreateInstance())).ToList();
legacyTotalScore = scoreInfo.LegacyTotalScore;
fullScoreDataSwitch.Current.Value = true; fullScoreDataSwitch.Current.Value = true;
// TODO: this shouldn't be done in 2 lines // TODO: this shouldn't be done in 2 lines
@ -1062,6 +1044,21 @@ namespace PerformanceCalculatorGUI.Screens
mehsTextBox.Text = mehs.ToString(); mehsTextBox.Text = mehs.ToString();
} }
if (ruleset.Value?.ShortName == "fruits")
{
if (scoreInfo.Statistics.TryGetValue(HitResult.LargeTickHit, out int largeTickHits))
{
goodsTextBox.Value.Value = largeTickHits;
goodsTextBox.Text = largeTickHits.ToString();
}
if (scoreInfo.Statistics.TryGetValue(HitResult.SmallTickHit, out int smallTickHits))
{
mehsTextBox.Value.Value = smallTickHits;
mehsTextBox.Text = smallTickHits.ToString();
}
}
if (scoreInfo.Statistics.TryGetValue(HitResult.LargeTickMiss, out int largeTickMisses)) if (scoreInfo.Statistics.TryGetValue(HitResult.LargeTickMiss, out int largeTickMisses))
{ {
largeTickMissesTextBox.Value.Value = largeTickMisses; largeTickMissesTextBox.Value.Value = largeTickMisses;