Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option for SongProgress to use strain in difficulty graph #30125

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
2 changes: 2 additions & 0 deletions osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,7 @@ protected override double StrainValueAt(DifficultyHitObject current)

return currentStrain;
}

public override bool IsRelevant => withSliders;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ private void displayRandomValues()
for (double i = 0; i < 5000; i += RNG.NextDouble() * 10 + i / 1000)
objects.Add(new HitObject { StartTime = i });

graph.Objects = objects;
graph.SetFromObjects(objects);
}

private partial class TestSongProgressGraph : DefaultSongProgressGraph
Expand Down
4 changes: 2 additions & 2 deletions osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@ public void TestBasic()

AddToggleStep("toggle graph", b =>
{
applyToDefaultProgress(s => s.ShowGraph.Value = b);
applyToArgonProgress(s => s.ShowGraph.Value = b);
applyToDefaultProgress(s => s.GraphType.Value = b ? DifficultyGraphType.ObjectDensity : DifficultyGraphType.None);
applyToArgonProgress(s => s.GraphType.Value = b ? DifficultyGraphType.ObjectDensity : DifficultyGraphType.None);
});

AddStep("set white background", () => background.FadeColour(Color4.White, 200, Easing.OutQuint));
Expand Down
8 changes: 8 additions & 0 deletions osu.Game/Beatmaps/BeatmapDifficultyCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,14 @@ public Task<List<TimedDifficultyAttributes>> GetTimedDifficultyAttributesAsync(I
updateScheduler);
}

public Task<List<double[]>> GetSectionDifficultiesAsync(IWorkingBeatmap beatmap, Ruleset ruleset, Mod[] mods, CancellationToken cancellationToken = default)
{
return Task.Factory.StartNew(() => ruleset.CreateDifficultyCalculator(beatmap).CalculateSectionDifficulties(mods, cancellationToken).ToList(),
cancellationToken,
TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously,
updateScheduler);
}

/// <summary>
/// Updates all tracked <see cref="BindableStarDifficulty"/> using the current ruleset and mods.
/// </summary>
Expand Down
8 changes: 4 additions & 4 deletions osu.Game/Localisation/HUD/SongProgressStrings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ public static class SongProgressStrings
private const string prefix = @"osu.Game.Resources.Localisation.HUD.SongProgress";

/// <summary>
/// "Show difficulty graph"
/// "Difficulty graph type"
/// </summary>
public static LocalisableString ShowGraph => new TranslatableString(getKey(@"show_graph"), "Show difficulty graph");
public static LocalisableString GraphType => new TranslatableString(getKey(@"graph_type"), "Difficulty graph type");

/// <summary>
/// "Whether a graph displaying difficulty throughout the beatmap should be shown"
/// "Type of a graph displaying difficulty throughout the beatmap"
/// </summary>
public static LocalisableString ShowGraphDescription => new TranslatableString(getKey(@"show_graph_description"), "Whether a graph displaying difficulty throughout the beatmap should be shown");
public static LocalisableString GraphTypeDescription => new TranslatableString(getKey(@"graph_type_description"), "Type of a graph displaying difficulty throughout the beatmap");

/// <summary>
/// "Show time"
Expand Down
49 changes: 49 additions & 0 deletions osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,55 @@ public List<TimedDifficultyAttributes> CalculateTimed([NotNull] IEnumerable<Mod>
return attribs;
}

/// <summary>
/// Calculates the difficulty of section of the beatmap with no mods applied.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Per-skill array where each value represents difficulty of this section in certain skill.</returns>
public IEnumerable<double[]> CalculateSectionDifficulties(CancellationToken cancellationToken = default)
=> CalculateSectionDifficulties(Array.Empty<Mod>(), cancellationToken);

/// <summary>
/// Calculates the difficulty of section of the beatmap using a specific mod combination.
/// </summary>
/// <param name="mods">The mods that should be applied to the beatmap.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Per-skill array where each value represents difficulty of this section in certain skill.</returns>
public IEnumerable<double[]> CalculateSectionDifficulties([NotNull] IEnumerable<Mod> mods, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
preProcess(mods, cancellationToken);

var skills = CreateSkills(Beatmap, playableMods, clockRate);
StrainSkill[] relevantSkills = skills.OfType<StrainSkill>().Where(s => s.IsRelevant).ToArray();
var hitObjects = getDifficultyHitObjects();

if (!hitObjects.Any())
return Enumerable.Empty<double[]>();

// Add sections before first object to preserve correct bounds
foreach (var skill in relevantSkills)
skill.AddEmptySections(hitObjects.First().StartTime, Beatmap.HitObjects.First().StartTime / clockRate);

foreach (var hitObject in hitObjects)
{
foreach (var skill in relevantSkills)
{
cancellationToken.ThrowIfCancellationRequested();
skill.Process(hitObject);
}
}

// Add sections up to the end time of last object to preserve correct bounds
foreach (var skill in relevantSkills)
skill.AddEmptySections(Beatmap.HitObjects.Last().GetEndTime() / clockRate);

var strainsForSkills = relevantSkills
.Select(skill => skill.GetCurrentStrainPeaks().ToArray());

return strainsForSkills;
}

/// <summary>
/// Calculates the difficulty of the beatmap using all mod combinations applicable to the beatmap.
/// </summary>
Expand Down
5 changes: 5 additions & 0 deletions osu.Game/Rulesets/Difficulty/Skills/Skill.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,10 @@ protected Skill(Mod[] mods)
/// Returns the calculated difficulty value representing all <see cref="DifficultyHitObject"/>s that have been processed up to this point.
/// </summary>
public abstract double DifficultyValue();

/// <summary>
/// Returns true if this Skill is relevant to star rating calculation.
/// </summary>
public virtual bool IsRelevant => true;
}
}
16 changes: 16 additions & 0 deletions osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,22 @@ public sealed override void Process(DifficultyHitObject current)
currentSectionPeak = Math.Max(StrainValueAt(current), currentSectionPeak);
}

/// <summary>
/// Adds empty strain sections
/// </summary>
public void AddEmptySections(double time, double? currentSectionOverride = null)
{
if (currentSectionOverride.HasValue)
currentSectionEnd = Math.Ceiling(currentSectionOverride.Value / SectionLength) * SectionLength;

while (time > currentSectionEnd)
{
saveCurrentPeak();
currentSectionPeak = 0; // This is wrong, but there's no way get decay from this class
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can't just say "this is wrong" and then do it anyway

Copy link
Contributor Author

@Givikap120 Givikap120 Oct 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct way to do this is applying strain decay to currentSectionPeak
but there's no way to do this under current structure of StrainSkill
this is possible in #29290, as strain decay logic is moved back to the StrainSkill class

But this is rather minor inconvenience, what wouldn't be noticeable for average player

currentSectionEnd += SectionLength;
}
}

/// <summary>
/// Saves the current peak strain level to the list of strain peaks, which will be used to calculate an overall difficulty.
/// </summary>
Expand Down
19 changes: 12 additions & 7 deletions osu.Game/Screens/Play/HUD/ArgonSongProgress.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ public partial class ArgonSongProgress : SongProgress

private const float bar_height = 10;

[SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.ShowGraph), nameof(SongProgressStrings.ShowGraphDescription))]
public Bindable<bool> ShowGraph { get; } = new BindableBool(true);
[SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.GraphType), nameof(SongProgressStrings.GraphTypeDescription))]
public Bindable<DifficultyGraphType> GraphType { get; } = new Bindable<DifficultyGraphType>(DifficultyGraphType.ObjectDensity);

[SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.ShowTime), nameof(SongProgressStrings.ShowTimeDescription))]
public Bindable<bool> ShowTime { get; } = new BindableBool(true);
Expand Down Expand Up @@ -98,8 +98,11 @@ protected override void LoadComplete()
{
base.LoadComplete();

GraphTypeInternal.ValueChanged += _ => updateGraphVisibility();
GraphTypeInternal.Value = GraphType.Value;
GraphTypeInternal.BindTo(GraphType);

Interactive.BindValueChanged(_ => bar.Interactive = Interactive.Value, true);
ShowGraph.BindValueChanged(_ => updateGraphVisibility(), true);
ShowTime.BindValueChanged(_ => info.FadeTo(ShowTime.Value ? 1 : 0, 200, Easing.In), true);
AccentColour.BindValueChanged(_ => Colour = AccentColour.Value, true);

Expand All @@ -109,17 +112,19 @@ protected override void LoadComplete()
Width = previousWidth;
}

protected override void UpdateObjects(IEnumerable<HitObject> objects)
protected override void UpdateTimeBounds()
{
graph.Objects = objects;

info.StartTime = bar.StartTime = FirstHitTime;
info.EndTime = bar.EndTime = LastHitTime;
}

protected override void UpdateFromObjects(IEnumerable<HitObject> objects) => graph.SetFromObjects(objects);

protected override void UpdateFromStrains(double[] sectionStrains) => graph.SetFromStrains(sectionStrains);

private void updateGraphVisibility()
{
graph.FadeTo(ShowGraph.Value ? 1 : 0, 200, Easing.In);
graph.FadeTo(GraphTypeInternal.Value != DifficultyGraphType.None ? 1 : 0, 200, Easing.In);
}

protected override void Update()
Expand Down
49 changes: 24 additions & 25 deletions osu.Game/Screens/Play/HUD/ArgonSongProgressGraph.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,49 +10,48 @@
using osu.Game.Graphics;
using osu.Game.Rulesets.Objects;
using osu.Game.Graphics.UserInterface;
using osu.Game.Utils;

namespace osu.Game.Screens.Play.HUD
{
public partial class ArgonSongProgressGraph : SegmentedGraph<int>
public partial class ArgonSongProgressGraph : SegmentedGraph<float>
{
private const int tier_count = 5;

private const int display_granularity = 200;

private IEnumerable<HitObject>? objects;

public IEnumerable<HitObject> Objects
public void SetFromObjects(IEnumerable<HitObject> objects)
{
set
{
objects = value;
float[] values = new float[display_granularity];

int[] values = new int[display_granularity];
if (!objects.Any())
return;

if (!objects.Any())
return;
(double firstHit, double lastHit) = BeatmapExtensions.CalculatePlayableBounds(objects);

(double firstHit, double lastHit) = BeatmapExtensions.CalculatePlayableBounds(objects);
if (lastHit == 0)
lastHit = objects.Last().StartTime;

if (lastHit == 0)
lastHit = objects.Last().StartTime;
double interval = (lastHit - firstHit + 1) / display_granularity;

double interval = (lastHit - firstHit + 1) / display_granularity;
foreach (var h in objects)
{
double endTime = h.GetEndTime();

foreach (var h in objects)
{
double endTime = h.GetEndTime();
Debug.Assert(endTime >= h.StartTime);

Debug.Assert(endTime >= h.StartTime);
int startRange = (int)((h.StartTime - firstHit) / interval);
int endRange = (int)((endTime - firstHit) / interval);
for (int i = startRange; i <= endRange; i++)
values[i]++;
}

int startRange = (int)((h.StartTime - firstHit) / interval);
int endRange = (int)((endTime - firstHit) / interval);
for (int i = startRange; i <= endRange; i++)
values[i]++;
}
Values = values;
}

Values = values;
}
public void SetFromStrains(double[] strains)
{
Values = FormatUtils.ResampleStrains(strains, display_granularity).Select(value => (float)value).ToArray();
}

public ArgonSongProgressGraph()
Expand Down
25 changes: 16 additions & 9 deletions osu.Game/Screens/Play/HUD/DefaultSongProgress.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ public partial class DefaultSongProgress : SongProgress
private readonly SongProgressInfo info;
private readonly Container content;

[SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.ShowGraph), nameof(SongProgressStrings.ShowGraphDescription))]
public Bindable<bool> ShowGraph { get; } = new BindableBool(true);
[SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.GraphType), nameof(SongProgressStrings.GraphTypeDescription))]
public Bindable<DifficultyGraphType> GraphType { get; } = new Bindable<DifficultyGraphType>(DifficultyGraphType.ObjectDensity);

[SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.ShowTime), nameof(SongProgressStrings.ShowTimeDescription))]
public Bindable<bool> ShowTime { get; } = new BindableBool(true);
Expand Down Expand Up @@ -95,24 +95,31 @@ private void load(OsuColour colours)

protected override void LoadComplete()
{
GraphTypeInternal.ValueChanged += _ => updateGraphVisibility();
GraphTypeInternal.Value = GraphType.Value;
GraphTypeInternal.BindTo(GraphType);

Interactive.BindValueChanged(_ => updateBarVisibility(), true);
ShowGraph.BindValueChanged(_ => updateGraphVisibility(), true);
ShowTime.BindValueChanged(_ => updateTimeVisibility(), true);
AccentColour.BindValueChanged(_ => Colour = AccentColour.Value, true);

updateGraphVisibility();

base.LoadComplete();
}

protected override void UpdateObjects(IEnumerable<HitObject> objects)
protected override void UpdateTimeBounds()
{
graph.Objects = objects;

info.StartTime = FirstHitTime;
info.EndTime = LastHitTime;
bar.StartTime = FirstHitTime;
bar.EndTime = LastHitTime;
}

protected override void UpdateFromObjects(IEnumerable<HitObject> objects) => graph.SetFromObjects(objects);

protected override void UpdateFromStrains(double[] sectionStrains) => graph.SetFromStrains(sectionStrains);

protected override void UpdateProgress(double progress, bool isIntro)
{
graph.Progress = isIntro ? 0 : (int)(graph.ColumnCount * progress);
Expand Down Expand Up @@ -140,8 +147,8 @@ private void updateGraphVisibility()
{
float barHeight = bottom_bar_height + handle_size.Y;

bar.ResizeHeightTo(ShowGraph.Value ? barHeight + graph_height : barHeight, transition_duration, Easing.In);
graph.FadeTo(ShowGraph.Value ? 1 : 0, transition_duration, Easing.In);
bar.ResizeHeightTo(GraphTypeInternal.Value != DifficultyGraphType.None ? barHeight + graph_height : barHeight, transition_duration, Easing.In);
graph.FadeTo(GraphTypeInternal.Value != DifficultyGraphType.None ? 1 : 0, transition_duration, Easing.In);

updateInfoMargin();
}
Expand All @@ -155,7 +162,7 @@ private void updateTimeVisibility()

private void updateInfoMargin()
{
float finalMargin = bottom_bar_height + (Interactive.Value ? handle_size.Y : 0) + (ShowGraph.Value ? graph_height : 0);
float finalMargin = bottom_bar_height + (Interactive.Value ? handle_size.Y : 0) + (GraphTypeInternal.Value != DifficultyGraphType.None ? graph_height : 0);
info.TransformTo(nameof(info.Margin), new MarginPadding { Bottom = finalMargin }, transition_duration, Easing.In);
}
}
Expand Down
Loading
Loading