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

Allow a JudgementProcessor to provide a rate-scaled time offset of a JudgementResult #30842

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ public override ScoreRank RankFromScore(double accuracy, IReadOnlyDictionary<Hit
return anyImperfect ? rank : ScoreRank.X;
}

protected override double? GetTimeScaleForResult(JudgementResult result) => 1.0;

private class JudgementOrderComparer : IComparer<HitObject>
{
public static readonly JudgementOrderComparer DEFAULT = new JudgementOrderComparer();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,15 @@ public void TestVariousTypesOfHitResult()
: offset > 16 ? HitResult.Good
: offset > 8 ? HitResult.Great
: HitResult.Perfect;
return new HitEvent(h.TimeOffset, 1.0, result, placeholder_object, placeholder_object, null);
return new HitEvent(h.TimeOffset, h.TimeScale, result, placeholder_object, placeholder_object, null);
}).ToList());
}

[Test]
public void TestNonBasicHitResultsAreIgnored()
{
createTest(CreateDistributedHitEvents(0, 50)
.Select(h => new HitEvent(h.TimeOffset, 1.0, h.TimeOffset > 0 ? HitResult.Ok : HitResult.LargeTickHit, placeholder_object, placeholder_object, null))
.Select(h => new HitEvent(h.TimeOffset, h.TimeScale, h.TimeOffset > 0 ? HitResult.Ok : HitResult.LargeTickHit, placeholder_object, placeholder_object, null))
.ToList());
}

Expand All @@ -110,7 +110,7 @@ public void TestMultipleWindowsOfHitResult()
: offset > 8 ? HitResult.Great
: HitResult.Perfect;

return new HitEvent(h.TimeOffset, 1.0, result, placeholder_object, placeholder_object, null);
return new HitEvent(h.TimeOffset, h.TimeScale, result, placeholder_object, placeholder_object, null);
});
var narrow = CreateDistributedHitEvents(0, 50).Select(h =>
{
Expand All @@ -121,7 +121,7 @@ public void TestMultipleWindowsOfHitResult()
: offset > 10 ? HitResult.Good
: offset > 5 ? HitResult.Great
: HitResult.Perfect;
return new HitEvent(h.TimeOffset, 1.0, result, placeholder_object, placeholder_object, null);
return new HitEvent(h.TimeOffset, h.TimeScale, result, placeholder_object, placeholder_object, null);
});
createTest(wide.Concat(narrow).ToList());
}
Expand Down
12 changes: 6 additions & 6 deletions osu.Game/Rulesets/Scoring/HitEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ public readonly struct HitEvent
public readonly double TimeOffset;

/// <summary>
/// The true gameplay rate at the time of the event.
/// The scale to apply to the <see cref="TimeOffset"/> to account for potential rate adjustments.
/// </summary>
public readonly double? GameplayRate;
public readonly double? TimeScale;

/// <summary>
/// The hit result.
Expand Down Expand Up @@ -50,15 +50,15 @@ public readonly struct HitEvent
/// Creates a new <see cref="HitEvent"/>.
/// </summary>
/// <param name="timeOffset">The time offset from the end of <paramref name="hitObject"/> at which the event occurs.</param>
/// <param name="timeScale">The rate-scaled <paramref name="timeOffset"/></param>
/// <param name="result">The <see cref="HitResult"/>.</param>
/// <param name="gameplayRate">The true gameplay rate at the time of the event.</param>
/// <param name="hitObject">The <see cref="HitObject"/> that triggered the event.</param>
/// <param name="lastHitObject">The previous <see cref="HitObject"/>.</param>
/// <param name="position">A position corresponding to the event.</param>
public HitEvent(double timeOffset, double? gameplayRate, HitResult result, HitObject hitObject, [CanBeNull] HitObject lastHitObject, [CanBeNull] Vector2? position)
public HitEvent(double timeOffset, double? timeScale, HitResult result, HitObject hitObject, [CanBeNull] HitObject lastHitObject, [CanBeNull] Vector2? position)
{
TimeOffset = timeOffset;
GameplayRate = gameplayRate;
TimeScale = timeScale;
Result = result;
HitObject = hitObject;
LastHitObject = lastHitObject;
Expand All @@ -70,6 +70,6 @@ public HitEvent(double timeOffset, double? gameplayRate, HitResult result, HitOb
/// </summary>
/// <param name="positionOffset">The positional offset.</param>
/// <returns>The new <see cref="HitEvent"/>.</returns>
public HitEvent With(Vector2? positionOffset) => new HitEvent(TimeOffset, GameplayRate, Result, HitObject, LastHitObject, positionOffset);
public HitEvent With(Vector2? positionOffset) => new HitEvent(TimeOffset, TimeScale, Result, HitObject, LastHitObject, positionOffset);
}
}
6 changes: 3 additions & 3 deletions osu.Game/Rulesets/Scoring/HitEventExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public static class HitEventExtensions
/// </returns>
public static double? CalculateUnstableRate(this IEnumerable<HitEvent> hitEvents)
{
Debug.Assert(hitEvents.All(ev => ev.GameplayRate != null));
Debug.Assert(hitEvents.All(ev => ev.TimeScale != null));

int count = 0;
double mean = 0;
Expand All @@ -35,8 +35,8 @@ public static class HitEventExtensions

count++;

// Division by gameplay rate is to account for TimeOffset scaling with gameplay rate.
double currentValue = e.TimeOffset / e.GameplayRate!.Value;
// Division by TimeScale is used to account for TimeOffset _usually_ scaling with gameplay rate.
double currentValue = e.TimeOffset / e.TimeScale!.Value;
double nextMean = mean + (currentValue - mean) / count;
sumOfSquares += (currentValue - mean) * (currentValue - nextMean);
mean = nextMean;
Expand Down
7 changes: 7 additions & 0 deletions osu.Game/Rulesets/Scoring/JudgementProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,13 @@ private IEnumerable<HitObject> enumerateRecursively(IEnumerable<HitObject> hitOb
/// <returns>The simulated <see cref="HitResult"/> for the judgement.</returns>
protected virtual HitResult GetSimulatedHitResult(Judgement judgement) => judgement.MaxResult;

/// <summary>
/// Gets the time scale for a <see cref="JudgementResult"/>.
/// </summary>
/// <param name="judgementResult">The judgement result.</param>
/// <returns>The time scale.</returns>
protected virtual double? GetTimeScaleForResult(JudgementResult judgementResult) => judgementResult.GameplayRate;

protected override void Update()
{
base.Update();
Expand Down
2 changes: 1 addition & 1 deletion osu.Game/Rulesets/Scoring/ScoreProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ protected sealed override void ApplyResultInternal(JudgementResult result)
/// <param name="result">The <see cref="JudgementResult"/> to describe.</param>
/// <returns>The <see cref="HitEvent"/>.</returns>
protected virtual HitEvent CreateHitEvent(JudgementResult result)
=> new HitEvent(result.TimeOffset, result.GameplayRate, result.Type, result.HitObject, lastHitObject, null);
=> new HitEvent(result.TimeOffset, GetTimeScaleForResult(result), result.Type, result.HitObject, lastHitObject, null);

protected sealed override void RevertResultInternal(JudgementResult result)
{
Expand Down
Loading