Skip to content

Commit

Permalink
Merge pull request ppy#30322 from smoogipoo/bat-max-performance
Browse files Browse the repository at this point in the history
Implement "max pp" beatmap difficulty attribute text
  • Loading branch information
peppy authored Nov 14, 2024
2 parents 85d292a + d37c1bb commit 5cc1cbe
Show file tree
Hide file tree
Showing 9 changed files with 157 additions and 150 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,23 @@ public void TestStarRating()
AddUntilStep("check star rating is 2", getText, () => Is.EqualTo("Star Rating: 2.00"));
}

[Test]
public void TestMaxPp()
{
AddStep("set test ruleset", () => Ruleset.Value = new TestRuleset().RulesetInfo);
AddStep("set max pp attribute", () => text.Attribute.Value = BeatmapAttribute.MaxPP);
AddAssert("check max pp is 0", getText, () => Is.EqualTo("Max PP: 0"));

// Adding mod
TestMod mod = null!;
AddStep("add mod with pp 1", () => SelectedMods.Value = new[] { mod = new TestMod { Performance = { Value = 1 } } });
AddUntilStep("check max pp is 1", getText, () => Is.EqualTo("Max PP: 1"));

// Changing mod setting
AddStep("change mod pp to 2", () => mod.Performance.Value = 2);
AddUntilStep("check max pp is 2", getText, () => Is.EqualTo("Max PP: 2"));
}

private string getText() => text.ChildrenOfType<SpriteText>().Single().Text.ToString();

private class TestRuleset : Ruleset
Expand Down
78 changes: 74 additions & 4 deletions osu.Game/Beatmaps/BeatmapDifficultyCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Audio.Track;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Graphics.Textures;
using osu.Framework.Lists;
using osu.Framework.Logging;
using osu.Framework.Threading;
Expand All @@ -18,7 +21,11 @@
using osu.Game.Rulesets;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Scoring;
using osu.Game.Skinning;
using osu.Game.Storyboards;

namespace osu.Game.Beatmaps
{
Expand Down Expand Up @@ -237,10 +244,37 @@ private void updateBindable(BindableStarDifficulty bindable, IRulesetInfo? rules
var ruleset = rulesetInfo.CreateInstance();
Debug.Assert(ruleset != null);

var calculator = ruleset.CreateDifficultyCalculator(beatmapManager.GetWorkingBeatmap(key.BeatmapInfo));
var attributes = calculator.Calculate(key.OrderedMods, cancellationToken);
PlayableCachedWorkingBeatmap workingBeatmap = new PlayableCachedWorkingBeatmap(beatmapManager.GetWorkingBeatmap(key.BeatmapInfo));
IBeatmap playableBeatmap = workingBeatmap.GetPlayableBeatmap(ruleset.RulesetInfo, key.OrderedMods, cancellationToken);

return new StarDifficulty(attributes);
var difficulty = ruleset.CreateDifficultyCalculator(workingBeatmap).Calculate(key.OrderedMods, cancellationToken);
cancellationToken.ThrowIfCancellationRequested();

var performanceCalculator = ruleset.CreatePerformanceCalculator();
if (performanceCalculator == null)
return new StarDifficulty(difficulty, new PerformanceAttributes());

ScoreProcessor scoreProcessor = ruleset.CreateScoreProcessor();
scoreProcessor.Mods.Value = key.OrderedMods;
scoreProcessor.ApplyBeatmap(playableBeatmap);
cancellationToken.ThrowIfCancellationRequested();

ScoreInfo perfectScore = new ScoreInfo(key.BeatmapInfo, ruleset.RulesetInfo)
{
Passed = true,
Accuracy = 1,
Mods = key.OrderedMods,
MaxCombo = scoreProcessor.MaximumCombo,
Combo = scoreProcessor.MaximumCombo,
TotalScore = scoreProcessor.MaximumTotalScore,
Statistics = scoreProcessor.MaximumStatistics,
MaximumStatistics = scoreProcessor.MaximumStatistics
};

var performance = performanceCalculator.Calculate(perfectScore, difficulty);
cancellationToken.ThrowIfCancellationRequested();

return new StarDifficulty(difficulty, performance);
}
catch (OperationCanceledException)
{
Expand Down Expand Up @@ -276,7 +310,6 @@ protected override void Dispose(bool isDisposing)
{
public readonly BeatmapInfo BeatmapInfo;
public readonly RulesetInfo Ruleset;

public readonly Mod[] OrderedMods;

public DifficultyCacheLookup(BeatmapInfo beatmapInfo, RulesetInfo? ruleset, IEnumerable<Mod>? mods)
Expand Down Expand Up @@ -317,5 +350,42 @@ public BindableStarDifficulty(IBeatmapInfo beatmapInfo, CancellationToken cancel
CancellationToken = cancellationToken;
}
}

/// <summary>
/// A working beatmap that caches its playable representation.
/// This is intended as single-use for when it is guaranteed that the playable beatmap can be reused.
/// </summary>
private class PlayableCachedWorkingBeatmap : IWorkingBeatmap
{
private readonly IWorkingBeatmap working;
private IBeatmap? playable;

public PlayableCachedWorkingBeatmap(IWorkingBeatmap working)
{
this.working = working;
}

public IBeatmap GetPlayableBeatmap(IRulesetInfo ruleset, IReadOnlyList<Mod> mods)
=> playable ??= working.GetPlayableBeatmap(ruleset, mods);

public IBeatmap GetPlayableBeatmap(IRulesetInfo ruleset, IReadOnlyList<Mod> mods, CancellationToken cancellationToken)
=> playable ??= working.GetPlayableBeatmap(ruleset, mods, cancellationToken);

IBeatmapInfo IWorkingBeatmap.BeatmapInfo => working.BeatmapInfo;
bool IWorkingBeatmap.BeatmapLoaded => working.BeatmapLoaded;
bool IWorkingBeatmap.TrackLoaded => working.TrackLoaded;
IBeatmap IWorkingBeatmap.Beatmap => working.Beatmap;
Texture IWorkingBeatmap.GetBackground() => working.GetBackground();
Texture IWorkingBeatmap.GetPanelBackground() => working.GetPanelBackground();
Waveform IWorkingBeatmap.Waveform => working.Waveform;
Storyboard IWorkingBeatmap.Storyboard => working.Storyboard;
ISkin IWorkingBeatmap.Skin => working.Skin;
Track IWorkingBeatmap.Track => working.Track;
Track IWorkingBeatmap.LoadTrack() => working.LoadTrack();
Stream IWorkingBeatmap.GetStream(string storagePath) => working.GetStream(storagePath);
void IWorkingBeatmap.BeginAsyncLoad() => working.BeginAsyncLoad();
void IWorkingBeatmap.CancelAsyncLoad() => working.CancelAsyncLoad();
void IWorkingBeatmap.PrepareTrackForPreview(bool looping, double offsetFromPreviewPoint) => working.PrepareTrackForPreview(looping, offsetFromPreviewPoint);
}
}
}
27 changes: 14 additions & 13 deletions osu.Game/Beatmaps/StarDifficulty.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

#nullable disable

using JetBrains.Annotations;
using osu.Framework.Utils;
using osu.Game.Rulesets.Difficulty;

Expand All @@ -25,30 +22,34 @@ public readonly struct StarDifficulty
/// The difficulty attributes computed for the given beatmap.
/// Might not be available if the star difficulty is associated with a beatmap that's not locally available.
/// </summary>
[CanBeNull]
public readonly DifficultyAttributes Attributes;
public readonly DifficultyAttributes? DifficultyAttributes;

/// <summary>
/// The performance attributes computed for a perfect score on the given beatmap.
/// Might not be available if the star difficulty is associated with a beatmap that's not locally available.
/// </summary>
public readonly PerformanceAttributes? PerformanceAttributes;

/// <summary>
/// Creates a <see cref="StarDifficulty"/> structure based on <see cref="DifficultyAttributes"/> computed
/// by a <see cref="DifficultyCalculator"/>.
/// Creates a <see cref="StarDifficulty"/> structure.
/// </summary>
public StarDifficulty([NotNull] DifficultyAttributes attributes)
public StarDifficulty(DifficultyAttributes difficulty, PerformanceAttributes performance)
{
Stars = double.IsFinite(attributes.StarRating) ? attributes.StarRating : 0;
MaxCombo = attributes.MaxCombo;
Attributes = attributes;
Stars = double.IsFinite(difficulty.StarRating) ? difficulty.StarRating : 0;
MaxCombo = difficulty.MaxCombo;
DifficultyAttributes = difficulty;
PerformanceAttributes = performance;
// Todo: Add more members (BeatmapInfo.DifficultyRating? Attributes? Etc...)
}

/// <summary>
/// Creates a <see cref="StarDifficulty"/> structure with a pre-populated star difficulty and max combo
/// in scenarios where computing <see cref="DifficultyAttributes"/> is not feasible (i.e. when working with online sources).
/// in scenarios where computing <see cref="Rulesets.Difficulty.DifficultyAttributes"/> is not feasible (i.e. when working with online sources).
/// </summary>
public StarDifficulty(double starDifficulty, int maxCombo)
{
Stars = double.IsFinite(starDifficulty) ? starDifficulty : 0;
MaxCombo = maxCombo;
Attributes = null;
}

public DifficultyRating DifficultyRating => GetDifficultyRating(Stars);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,28 @@ public static class BeatmapAttributeTextStrings
/// <summary>
/// "Attribute"
/// </summary>
public static LocalisableString Attribute => new TranslatableString(getKey(@"attribute"), "Attribute");
public static LocalisableString Attribute => new TranslatableString(getKey(@"attribute"), @"Attribute");

/// <summary>
/// "The attribute to be displayed."
/// </summary>
public static LocalisableString AttributeDescription => new TranslatableString(getKey(@"attribute_description"), "The attribute to be displayed.");
public static LocalisableString AttributeDescription => new TranslatableString(getKey(@"attribute_description"), @"The attribute to be displayed.");

/// <summary>
/// "Template"
/// </summary>
public static LocalisableString Template => new TranslatableString(getKey(@"template"), "Template");
public static LocalisableString Template => new TranslatableString(getKey(@"template"), @"Template");

/// <summary>
/// "Supports {{Label}} and {{Value}}, but also including arbitrary attributes like {{StarRating}} (see attribute list for supported values)."
/// </summary>
public static LocalisableString TemplateDescription => new TranslatableString(getKey(@"template_description"), @"Supports {{Label}} and {{Value}}, but also including arbitrary attributes like {{StarRating}} (see attribute list for supported values).");

private static string getKey(string key) => $"{prefix}:{key}";
/// <summary>
/// "Max PP"
/// </summary>
public static LocalisableString MaxPP => new TranslatableString(getKey(@"max_pp"), @"Max PP");

private static string getKey(string key) => $@"{prefix}:{key}";
}
}
121 changes: 0 additions & 121 deletions osu.Game/Rulesets/Difficulty/PerformanceBreakdownCalculator.cs

This file was deleted.

6 changes: 6 additions & 0 deletions osu.Game/Rulesets/Scoring/ScoreProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,11 @@ public partial class ScoreProcessor : JudgementProcessor
/// </summary>
public long MaximumTotalScore { get; private set; }

/// <summary>
/// The maximum achievable combo.
/// </summary>
public int MaximumCombo { get; private set; }

/// <summary>
/// The maximum sum of accuracy-affecting judgements at the current point in time.
/// </summary>
Expand Down Expand Up @@ -423,6 +428,7 @@ protected override void Reset(bool storeResults)
MaximumResultCounts.AddRange(ScoreResultCounts);

MaximumTotalScore = TotalScore.Value;
MaximumCombo = HighestCombo.Value;
}

ScoreResultCounts.Clear();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,10 @@ private void load(BeatmapDifficultyCache difficultyCache, CancellationToken? can
var performanceCalculator = score.Ruleset.CreateInstance().CreatePerformanceCalculator();
// Performance calculation requires the beatmap and ruleset to be locally available. If not, return a default value.
if (attributes?.Attributes == null || performanceCalculator == null)
if (attributes?.DifficultyAttributes == null || performanceCalculator == null)
return;
var result = await performanceCalculator.CalculateAsync(score, attributes.Value.Attributes, cancellationToken ?? default).ConfigureAwait(false);
var result = await performanceCalculator.CalculateAsync(score, attributes.Value.DifficultyAttributes, cancellationToken ?? default).ConfigureAwait(false);
Schedule(() => setPerformanceValue(score, result.Total));
}, cancellationToken ?? default);
Expand Down
Loading

0 comments on commit 5cc1cbe

Please sign in to comment.