Merge pull request #27429 from bdach/rewind-slider-tracking

Fix slider tracking state not restoring correctly in all cases on rewind
This commit is contained in:
Dean Herbert 2024-03-07 22:41:12 +08:00 committed by GitHub
commit 646edb239a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 92 additions and 0 deletions

View File

@ -457,6 +457,33 @@ namespace osu.Game.Rulesets.Osu.Tests
assertMidSliderJudgementFail();
}
[Test]
public void TestRewindHandling()
{
performTest(new List<ReplayFrame>
{
new OsuReplayFrame { Position = new Vector2(0), Actions = { OsuAction.LeftButton }, Time = time_slider_start },
new OsuReplayFrame { Position = new Vector2(175, 0), Actions = { OsuAction.LeftButton }, Time = 3250 },
new OsuReplayFrame { Position = new Vector2(175, 0), Actions = { OsuAction.LeftButton }, Time = time_slider_end },
}, new Slider
{
StartTime = time_slider_start,
Position = new Vector2(0, 0),
Path = new SliderPath(PathType.PERFECT_CURVE, new[]
{
Vector2.Zero,
new Vector2(250, 0),
}, 250),
});
AddUntilStep("wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value);
AddAssert("no miss judgements recorded", () => judgementResults.All(r => r.Type.IsHit()));
AddStep("rewind to middle of slider", () => currentPlayer.Seek(time_during_slide_4));
AddUntilStep("wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value);
AddAssert("no miss judgements recorded", () => judgementResults.All(r => r.Type.IsHit()));
}
private void assertAllMaxJudgements()
{
AddAssert("All judgements max", () =>

View File

@ -0,0 +1,20 @@
// 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.Collections.Generic;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Osu.Judgements
{
public class OsuSliderJudgementResult : OsuJudgementResult
{
public readonly Stack<(double time, bool tracking)> TrackingHistory = new Stack<(double, bool)>();
public OsuSliderJudgementResult(HitObject hitObject, Judgement judgement)
: base(hitObject, judgement)
{
TrackingHistory.Push((double.NegativeInfinity, false));
}
}
}

View File

@ -14,8 +14,10 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Layout;
using osu.Game.Audio;
using osu.Game.Graphics.Containers;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Rulesets.Osu.Skinning.Default;
using osu.Game.Rulesets.Scoring;
using osu.Game.Skinning;
@ -27,6 +29,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
public new Slider HitObject => (Slider)base.HitObject;
public new OsuSliderJudgementResult Result => (OsuSliderJudgementResult)base.Result;
public DrawableSliderHead HeadCircle => headContainer.Child;
public DrawableSliderTail TailCircle => tailContainer.Child;
@ -134,6 +138,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
}, true);
}
protected override JudgementResult CreateResult(Judgement judgement) => new OsuSliderJudgementResult(HitObject, judgement);
protected override void OnApply()
{
base.OnApply();

View File

@ -5,11 +5,14 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Input;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Screens.Play;
using osuTK;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
@ -21,6 +24,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
/// </summary>
public bool Tracking { get; private set; }
[Resolved]
private IGameplayClock? gameplayClock { get; set; }
/// <summary>
/// The point in time after which we can accept any key for tracking. Before this time, we may need to restrict tracking to the key used to hit the head circle.
///
@ -49,6 +55,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public SliderInputManager(DrawableSlider slider)
{
this.slider = slider;
this.slider.HitObjectApplied += resetState;
}
/// <summary>
@ -208,6 +215,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
/// <param name="isValidTrackingPosition">Whether the current mouse position is valid to begin tracking.</param>
private void updateTracking(bool isValidTrackingPosition)
{
if (gameplayClock?.IsRewinding == true)
{
var trackingHistory = slider.Result.TrackingHistory;
while (trackingHistory.TryPeek(out var historyEntry) && Time.Current < historyEntry.time)
trackingHistory.Pop();
Debug.Assert(trackingHistory.Count > 0);
Tracking = trackingHistory.Peek().tracking;
return;
}
bool wasTracking = Tracking;
// from the point at which the head circle is hit, this will be non-null.
// it may be null if the head circle was missed.
OsuAction? headCircleHitAction = getInitialHitAction();
@ -247,6 +268,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
&& isValidTrackingPosition
// valid action
&& validTrackingAction;
if (wasTracking != Tracking)
slider.Result.TrackingHistory.Push((Time.Current, Tracking));
}
private OsuAction? getInitialHitAction() => slider.HeadCircle?.HitAction;
@ -264,5 +288,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
return action == OsuAction.LeftButton || action == OsuAction.RightButton;
}
private void resetState(DrawableHitObject obj)
{
Tracking = false;
timeToAcceptAnyKeyAfter = null;
lastPressedActions.Clear();
screenSpaceMousePosition = null;
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
slider.HitObjectApplied -= resetState;
}
}
}