diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerRotationTracker.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerRotationTracker.cs index f1a782cbb5..e949017ccf 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerRotationTracker.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerRotationTracker.cs @@ -2,11 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Framework.Utils; +using osu.Game.Screens.Play; using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces @@ -77,6 +79,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces private bool rotationTransferred; + [Resolved] + private GameplayClock gameplayClock { get; set; } + protected override void Update() { base.Update(); @@ -126,7 +131,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces currentRotation += angle; // rate has to be applied each frame, because it's not guaranteed to be constant throughout playback // (see: ModTimeRamp) - RateAdjustedRotation += (float)(Math.Abs(angle) * Clock.Rate); + RateAdjustedRotation += (float)(Math.Abs(angle) * gameplayClock.TrueGameplayRate); } } } diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index d574991fa0..b585a78f42 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -2,7 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; @@ -59,7 +61,7 @@ namespace osu.Game.Rulesets.UI { if (clock != null) { - stabilityGameplayClock.ParentGameplayClock = parentGameplayClock = clock; + parentGameplayClock = stabilityGameplayClock.ParentGameplayClock = clock; GameplayClock.IsPaused.BindTo(clock.IsPaused); } } @@ -191,7 +193,9 @@ namespace osu.Game.Rulesets.UI private class StabilityGameplayClock : GameplayClock { - public IFrameBasedClock ParentGameplayClock; + public GameplayClock ParentGameplayClock; + + public override IEnumerable> NonGameplayAdjustments => ParentGameplayClock.NonGameplayAdjustments; public StabilityGameplayClock(FramedClock underlyingClock) : base(underlyingClock) diff --git a/osu.Game/Screens/Play/GameplayClock.cs b/osu.Game/Screens/Play/GameplayClock.cs index 4f2cf5005c..45da8816d6 100644 --- a/osu.Game/Screens/Play/GameplayClock.cs +++ b/osu.Game/Screens/Play/GameplayClock.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; +using System.Linq; using osu.Framework.Bindables; using osu.Framework.Timing; @@ -20,6 +22,11 @@ namespace osu.Game.Screens.Play public readonly BindableBool IsPaused = new BindableBool(); + /// + /// All adjustments applied to this clock which don't come from gameplay or mods. + /// + public virtual IEnumerable> NonGameplayAdjustments => Enumerable.Empty>(); + public GameplayClock(IFrameBasedClock underlyingClock) { this.underlyingClock = underlyingClock; @@ -29,6 +36,23 @@ namespace osu.Game.Screens.Play public double Rate => underlyingClock.Rate; + /// + /// The rate of gameplay when playback is at 100%. + /// This excludes any seeking / user adjustments. + /// + public double TrueGameplayRate + { + get + { + double baseRate = Rate; + + foreach (var adjustment in NonGameplayAdjustments) + baseRate /= adjustment.Value; + + return baseRate; + } + } + public bool IsRunning => underlyingClock.IsRunning; /// diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 7a9cb3dddd..d5c3a7232f 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading.Tasks; @@ -50,8 +51,10 @@ namespace osu.Game.Screens.Play /// /// The final clock which is exposed to underlying components. /// - [Cached] - public readonly GameplayClock GameplayClock; + public GameplayClock GameplayClock => localGameplayClock; + + [Cached(typeof(GameplayClock))] + private readonly LocalGameplayClock localGameplayClock; private Bindable userAudioOffset; @@ -79,7 +82,7 @@ namespace osu.Game.Screens.Play userOffsetClock = new HardwareCorrectionOffsetClock(platformOffsetClock); // the clock to be exposed via DI to children. - GameplayClock = new GameplayClock(userOffsetClock); + localGameplayClock = new LocalGameplayClock(userOffsetClock); GameplayClock.IsPaused.BindTo(IsPaused); } @@ -200,11 +203,26 @@ namespace osu.Game.Screens.Play protected override void Update() { if (!IsPaused.Value) + { userOffsetClock.ProcessFrame(); + } base.Update(); } + private double getTrueGameplayRate() + { + double baseRate = track.Rate; + + if (speedAdjustmentsApplied) + { + baseRate /= UserPlaybackRate.Value; + baseRate /= pauseFreqAdjust.Value; + } + + return baseRate; + } + private bool speedAdjustmentsApplied; private void updateRate() @@ -215,6 +233,9 @@ namespace osu.Game.Screens.Play track.AddAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); track.AddAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); + localGameplayClock.MutableNonGameplayAdjustments.Add(pauseFreqAdjust); + localGameplayClock.MutableNonGameplayAdjustments.Add(UserPlaybackRate); + speedAdjustmentsApplied = true; } @@ -231,9 +252,24 @@ namespace osu.Game.Screens.Play track.RemoveAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); track.RemoveAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); + localGameplayClock.MutableNonGameplayAdjustments.Remove(pauseFreqAdjust); + localGameplayClock.MutableNonGameplayAdjustments.Remove(UserPlaybackRate); + speedAdjustmentsApplied = false; } + public class LocalGameplayClock : GameplayClock + { + public readonly List> MutableNonGameplayAdjustments = new List>(); + + public override IEnumerable> NonGameplayAdjustments => MutableNonGameplayAdjustments; + + public LocalGameplayClock(FramedOffsetClock underlyingClock) + : base(underlyingClock) + { + } + } + private class HardwareCorrectionOffsetClock : FramedOffsetClock { // we always want to apply the same real-time offset, so it should be adjusted by the difference in playback rate (from realtime) to achieve this.